写点什么

从代码到部署微服务实战(一)

用户头像
Kevin Wan
关注
发布于: 2021 年 01 月 10 日

当前微服务已经成为服务端开发的主流架构,而 Go 语言因其简单易学、内置高并发、快速编译、占用内存小等特点也越来越受到开发者的青睐,微服务实战系列文章将从实战的角度和大家一起学习微服务相关的知识。本系列文章将以一个“博客系统”由浅入深的和大家一起一步步搭建起一个完整的微服务系统


该篇文章为微服务实战系列的第一篇文章,我们将基于 go-zero+gitlab+jenkins+k8s 构建微服务持续集成和自动构建发布系统,先对以上模块做一个简单介绍:


  • go-zero是一个集成了各种工程实践的 web 和 rpc 框架。通过弹性设计保障了大并发服务端的稳定性,经受了充分的实战检验

  • gitlab是一款基于 Git 的完全集成的软件开发平台,另外,GitLab 且具有 wiki 以及在线编辑、issue 跟踪功能、CI/CD 等功能

  • jenkins 是基于 Java 开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件的持续集成变成可能

  • kubernetes 常简称为 K8s,是用于自动部署、扩展和管理容器化应用程序”的开源系统。该系统由 Google 设计并捐赠给 Cloud Native Computing Foundation(今属 Linux 基金会)来使用。它旨在提供“跨主机集群的自动部署、扩展以及运行应用程序容器的平台



实战主要分为五个步骤,下面针对以下的五个步骤分别进行详细的讲解

  1. 第一步环境搭建,这里我采用了两台 ubuntu16.04 服务器分别安装了 gitlab 和 jenkins,采用 xxx 云弹性 k8s 集群

  2. 第二步生成项目,这里我采用 go-zero 提供的 goctl 工具快速生成项目,并对项目做简单的修改以便测试

  3. 第三部生成 Dockerfile 和 k8s 部署文件,k8s 部署文件编写复杂而且容易出错,goctl 工具提供了生成 Dockerfile 和 k8s 部署文件的功能非常的方便

  4. Jenkins Pipeline 采用声明式语法构建,创建 Jenkinsfile 文件并使用 gitlab 进行版本管理

  5. 最后进行项目测试验证服务是否正常

环境搭建

首先我们搭建实验环境,这里我采用了两台 ubuntu16.04 服务器,分别安装了 gitlab 和 jenkins。gtilab 使用 apt-get 直接安装,安装好后启动服务并查看服务状态,各组件为 run 状态说明服务已经启动,默认端口为 9090 直接访问即可

gitlab-ctl start  // 启动服务
gitlab-ctl status // 查看服务状态
run: alertmanager: (pid 1591) 15442s; run: log: (pid 2087) 439266srun: gitaly: (pid 1615) 15442s; run: log: (pid 2076) 439266srun: gitlab-exporter: (pid 1645) 15442s; run: log: (pid 2084) 439266srun: gitlab-workhorse: (pid 1657) 15441s; run: log: (pid 2083) 439266srun: grafana: (pid 1670) 15441s; run: log: (pid 2082) 439266srun: logrotate: (pid 5873) 1040s; run: log: (pid 2081) 439266srun: nginx: (pid 1694) 15440s; run: log: (pid 2080) 439266srun: node-exporter: (pid 1701) 15439s; run: log: (pid 2088) 439266srun: postgres-exporter: (pid 1708) 15439s; run: log: (pid 2079) 439266srun: postgresql: (pid 1791) 15439s; run: log: (pid 2075) 439266srun: prometheus: (pid 10763) 12s; run: log: (pid 2077) 439266srun: puma: (pid 1816) 15438s; run: log: (pid 2078) 439266srun: redis: (pid 1821) 15437s; run: log: (pid 2086) 439266srun: redis-exporter: (pid 1826) 15437s; run: log: (pid 2089) 439266srun: sidekiq: (pid 1835) 15436s; run: log: (pid 2104) 439266s
复制代码

jenkins 也是用 apt-get 直接安装,需要注意的是安装 jenkins 前需要先安装 java,过程比较简单这里就不再演示,jenkins 默认端口为 8080,默认账号为 admin,初始密码路径为/var/lib/jenkins/secrets/initialAdminPassword,初始化安装推荐的插件即可,后面可以根据自己的需要再安装其它插件

k8s 集群搭建过程比较复杂,虽然可以使用 kubeadm 等工具快速搭建,但距离真正的生产级集群还是有一定差距,因为我们的服务最终是要上生产的,所以这里我选择了 xxx 云的弹性 k8s 集群版本为 1.16.9,弹性集群的好处是按需收费没有额外的费用,当我们实验完成后通过 kubectl delete 立马释放资源只会产生很少的费用,而且 xxx 云的 k8s 集群给我们提供了友好的监控页面,可以通过这些界面查看各种统计信息,集群创建好后需要创建集群访问凭证才能访问集群

  • 若当前访问客户端尚未配置任何集群的访问凭证,即 ~/.kube/config 内容为空,可直接将访问凭证内容并粘贴入 ~/.kube/config 中

  • 若当前访问客户端已配置了其他集群的访问凭证,需要通过如下命令合并凭证

配置好访问权限后通过如下命令可查看当前集群

kubectl config current-context
复制代码

查看集群版本,输出内容如下

kubectl version
Client Version: version.Info{Major:"1", Minor:"16", GitVersion:"v1.16.9", GitCommit:"a17149e1a189050796ced469dbd78d380f2ed5ef", GitTreeState:"clean", BuildDate:"2020-04-16T11:44:51Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}Server Version: version.Info{Major:"1", Minor:"16+", GitVersion:"v1.16.9-eks.2", GitCommit:"f999b99a13f40233fc5f875f0607448a759fc613", GitTreeState:"clean", BuildDate:"2020-10-09T12:54:13Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}
复制代码

到这里我们的试验已经搭建完成了,这里版本管理也可以使用 github

生成项目

整个项目采用大仓的方式,目录结构如下,最外层项目命名为 blog,app 目录下为按照业务拆分成的不同的微服务,比如 user 服务下面又分为 api 服务和 rpc 服务,其中 api 服务为聚合网关对外提供 restful 接口,而 rpc 服务为内部通信提供高性能的数据缓存等操作

    ├── blog    │   ├── app    │   │   ├── user    │   │   │   ├── api    │   │   │   └── rpc    │   │   ├── article    │   │   │   ├── api    │   │   │   └── rpc
复制代码

项目目录创建好之后我们进入 api 目录创建 user.api 文件,文件内容如下,定义服务端口为 2233,同时定义了一个/user/info 接口

type UserInfoRequest struct {    Uid int64 `form:"uid"`}
type UserInfoResponse struct { Uid int64 `json:"uid"` Name string `json:"name"` Level int `json:"level"`}
@server( port: 2233)service user-api { @doc( summary: 获取用户信息 ) @server( handler: UserInfo ) get /user/info(UserInfoRequest) returns(UserInfoResponse)}
复制代码

定义好 api 文件之后我们执行如下命令生成 api 服务代码,一键生成真是能大大提升我们的生产力呢

goctl api go -api user.api -dir .
复制代码

代码生成后我们对代码稍作改造以便后面部署后方便进行测试,改造后的代码为返回本机的 ip 地址

func (ul *UserInfoLogic) UserInfo(req types.UserInfoRequest) (*types.UserInfoResponse, error) {    addrs, err := net.InterfaceAddrs()    if err != nil {        return nil, err    }    var name string    for _, addr := range addrs {        if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() && ipnet.IP.To4() != nil {            name = ipnet.IP.String()        }    }
return &types.UserInfoResponse{ Uid: req.Uid, Name: name, Level: 666, }, nil}
复制代码

到这里服务生成部分就完成了,因为本节为基础框架的搭建所以只是添加一些测试的代码,后面会继续丰富项目代码

生成镜像和部署文件

一般的常用镜像比如 mysql、memcache 等我们可以直接从镜像仓库拉取,但是我们的服务镜像需要我们自定义,自定义镜像有多重方式而使用 Dockerfile 则是使用最多的一种方式,使用 Dockerfile 定义镜像虽然不难但是也很容易出错,所以这里我们也借助工具来自动生成,这里就不得不再夸一下 goctl 这个工具真的是棒棒的,还能帮助我们一键生成 Dockerfile 呢,在 api 目录下执行如下命令

goctl docker -go user.go
复制代码

生成后的文件稍作改动以符合我们的目录结构,文件内容如下,采用了两阶段构建,第一阶段构建可执行文件确保构建独立于宿主机,第二阶段会引用第一阶段构建的结果,最终构建出极简镜像

FROM golang:alpine AS builder
LABEL stage=gobuilder
ENV CGO_ENABLED 0ENV GOOS linuxENV GOPROXY https://goproxy.cn,direct
WORKDIR /build/zero
RUN go mod init blog/app/user/apiRUN go mod downloadCOPY . .COPY /etc /app/etcRUN go build -ldflags="-s -w" -o /app/user user.go

FROM alpine
RUN apk update --no-cache && apk add --no-cache ca-certificates tzdataENV TZ Asia/Shanghai
WORKDIR /appCOPY --from=builder /app/user /app/userCOPY --from=builder /app/etc /app/etc
CMD ["./user", "-f", "etc/user-api.yaml"]
复制代码

然后执行如下命令创建镜像

docker build -t user:v1 app/user/api/
复制代码

这个时候我们使用 docker images 命令查看镜像会发现 user 镜像已经创建,版本为 v1

REPOSITORY                            TAG                 IMAGE ID            CREATED             SIZEuser                                  v1                  1c1f64579b40        4 days ago          17.2MB
复制代码

同样,k8s 的部署文件编写也比较复杂很容易出错,所以我们也使用 goctl 自动来生成,在 api 目录下执行如下命令

goctl kube deploy -name user-api -namespace blog -image user:v1 -o user.yaml -port 2233
复制代码

生成的 ymal 文件如下

apiVersion: apps/v1kind: Deploymentmetadata:  name: user-api  namespace: blog  labels:    app: user-apispec:  replicas: 2  revisionHistoryLimit: 2  selector:    matchLabels:      app: user-api  template:    metadata:      labels:        app: user-api    spec:      containers:      - name: user-api        image: user:v1        lifecycle:          preStop:            exec:              command: ["sh","-c","sleep 5"]        ports:        - containerPort: 2233        readinessProbe:          tcpSocket:            port: 2233          initialDelaySeconds: 5          periodSeconds: 10        livenessProbe:          tcpSocket:            port: 2233          initialDelaySeconds: 15          periodSeconds: 10        resources:          requests:            cpu: 500m            memory: 512Mi          limits:            cpu: 1000m            memory: 1024Mi
复制代码

到此生成镜像和 k8s 部署文件步骤已经结束了,上面主要是为了演示,真正的生产环境中都是通过持续集成工具自动创建镜像的

Jenkins Pipeline

jenkins 是常用的继续集成工具,其提供了多种构建方式,而 pipeline 是最常用的构建方式之一,pipeline 支持声名式和脚本式两种方式,脚本式语法灵活、可扩展,但也意味着更复杂,而且需要学习 Grovvy 语言,增加了学习成本,所以才有了声明式语法,声名式语法是一种更简单,更结构化的语法,我们后面也都会使用声名式语法

这里再介绍下 Jenkinsfile,其实 Jenkinsfile 就是一个纯文本文件,也就是部署流水线概念在 Jenkins 中的表现形式,就像 Dockerfile 之于 Docker,所有的部署流水线逻辑都可在 Jenkinsfile 文件中定义,需要注意,Jenkins 默认是不支持 Jenkinsfile 的,我们需要安装 Pipeline 插件,安装插件的流程为 Manage Jenkins -> Manager Plugins 然后搜索安装即可,之后便可构建 pipeline 了

我们可以直接在 pipeline 的界面中输入构建脚本,但是这样没法做到版本化,所以如果不是临时测试的话是不推荐这种方式的,更通用的方式是让 jenkins 从 git 仓库中拉取 Jenkinsfile 并执行

首先需要安装 Git 插件,然后使用 ssh clone 方式拉取代码,所以,需要将 git 私钥放到 jenkins 中,这样 jenkins 才有权限从 git 仓库拉取代码

将 git 私钥放到 jenkins 中的步骤是:Manage Jenkins -> Manage credentials -> 添加凭据,类型选择为 SSH Username with private key,接下来按照提示进行设置就可以了,如下图所示

然后在我们的 gitlab 中新建一个项目,只需要一个 Jenkinsfile 文件



在 user-api 项目中流水线定义选择 Pipeline script from SCM,添加 gitlab ssh 地址和对应的 token,如下图所示

接着我们就可以按照上面的实战步骤进行 Jenkinsfile 文件的编写了

  • 从 gitlab 拉取代码,从我们的 gitlab 仓库中拉取代码,并使用 commit_id 用来区分不同版本

  • 构建 docker 镜像,使用 goctl 生成的 Dockerfile 文件构建镜像

  • 上传镜像到镜像仓库,把生产的镜像 push 到镜像仓库

  • 部署到 k8s,把部署文件中的版本号替换,从远程拉取镜,使用 kubectl apply 命令进行部署


构建详细输出如下,pipeline 对应的每一个 stage 都有详细的输出

Started by user adminObtained Jenkinsfile from git git@xxx.xxx.xxx.xxx:gitlab-instance-1ac0cea5/pipelinefiles.gitRunning in Durability level: MAX_SURVIVABILITY[Pipeline] Start of Pipeline[Pipeline] nodeRunning on Jenkins in /var/lib/jenkins/workspace/user-api[Pipeline] {[Pipeline] stage[Pipeline] { (Declarative: Checkout SCM)[Pipeline] checkoutSelected Git installation does not exist. Using DefaultThe recommended git tool is: NONEusing credential gitlab_token > git rev-parse --is-inside-work-tree # timeout=10Fetching changes from the remote Git repository > git config remote.origin.url git@xxx.xxx.xxx.xxx:gitlab-instance-1ac0cea5/pipelinefiles.git # timeout=10Fetching upstream changes from git@xxx.xxx.xxx.xxx:gitlab-instance-1ac0cea5/pipelinefiles.git > git --version # timeout=10 > git --version # 'git version 2.7.4'using GIT_SSH to set credentials  > git fetch --tags --progress git@xxx.xxx.xxx.xxx:gitlab-instance-1ac0cea5/pipelinefiles.git +refs/heads/*:refs/remotes/origin/* # timeout=10 > git rev-parse refs/remotes/origin/master^{commit} # timeout=10Checking out Revision 77eac3a4ca1a5b6aea705159ce26523ddd179bdf (refs/remotes/origin/master) > git config core.sparsecheckout # timeout=10 > git checkout -f 77eac3a4ca1a5b6aea705159ce26523ddd179bdf # timeout=10Commit message: "add" > git rev-list --no-walk 77eac3a4ca1a5b6aea705159ce26523ddd179bdf # timeout=10[Pipeline] }[Pipeline] // stage[Pipeline] withEnv[Pipeline] {[Pipeline] stage[Pipeline] { (从gitlab拉取服务代码)[Pipeline] echo从gitlab拉取服务代码[Pipeline] gitThe recommended git tool is: NONEusing credential gitlab_user_pwd > git rev-parse --is-inside-work-tree # timeout=10Fetching changes from the remote Git repository > git config remote.origin.url http://xxx.xxx.xxx.xxx:9090/blog/blog.git # timeout=10Fetching upstream changes from http://xxx.xxx.xxx.xxx:9090/blog/blog.git > git --version # timeout=10 > git --version # 'git version 2.7.4'using GIT_ASKPASS to set credentials  > git fetch --tags --progress http://xxx.xxx.xxx.xxx:9090/blog/blog.git +refs/heads/*:refs/remotes/origin/* # timeout=10 > git rev-parse refs/remotes/origin/master^{commit} # timeout=10Checking out Revision b757e9eef0f34206414bdaa4debdefec5974c3f5 (refs/remotes/origin/master) > git config core.sparsecheckout # timeout=10 > git checkout -f b757e9eef0f34206414bdaa4debdefec5974c3f5 # timeout=10 > git branch -a -v --no-abbrev # timeout=10 > git branch -D master # timeout=10 > git checkout -b master b757e9eef0f34206414bdaa4debdefec5974c3f5 # timeout=10Commit message: "Merge branch 'blog/dev' into 'master'" > git rev-list --no-walk b757e9eef0f34206414bdaa4debdefec5974c3f5 # timeout=10[Pipeline] script[Pipeline] {[Pipeline] sh+ git rev-parse --short HEAD[Pipeline] }[Pipeline] // script[Pipeline] }[Pipeline] // stage[Pipeline] stage[Pipeline] { (构建镜像)[Pipeline] echo构建镜像[Pipeline] sh+ docker build -t user:b757e9e app/user/api/Sending build context to Docker daemon  28.16kB
Step 1/18 : FROM golang:alpine AS builderalpine: Pulling from library/golang801bfaa63ef2: Pulling fs layeree0a1ba97153: Pulling fs layer1db7f31c0ee6: Pulling fs layerecebeec079cf: Pulling fs layer63b48972323a: Pulling fs layerecebeec079cf: Waiting63b48972323a: Waiting1db7f31c0ee6: Verifying Checksum1db7f31c0ee6: Download completeee0a1ba97153: Verifying Checksumee0a1ba97153: Download complete63b48972323a: Verifying Checksum63b48972323a: Download complete801bfaa63ef2: Verifying Checksum801bfaa63ef2: Download complete801bfaa63ef2: Pull completeee0a1ba97153: Pull complete1db7f31c0ee6: Pull completeecebeec079cf: Verifying Checksumecebeec079cf: Download completeecebeec079cf: Pull complete63b48972323a: Pull completeDigest: sha256:49b4eac11640066bc72c74b70202478b7d431c7d8918e0973d6e4aeb8b3129d2Status: Downloaded newer image for golang:alpine ---> 1463476d8605Step 2/18 : LABEL stage=gobuilder ---> Running in c4f4dea39a32Removing intermediate container c4f4dea39a32 ---> c04bee317ea1Step 3/18 : ENV CGO_ENABLED 0 ---> Running in e8e848d64f71Removing intermediate container e8e848d64f71 ---> ff82ee26966dStep 4/18 : ENV GOOS linux ---> Running in 58eb095128acRemoving intermediate container 58eb095128ac ---> 825ab47146f5Step 5/18 : ENV GOPROXY https://goproxy.cn,direct ---> Running in df2add4e39d5Removing intermediate container df2add4e39d5 ---> c31c1aebe5faStep 6/18 : WORKDIR /build/zero ---> Running in f2a1da3ca048Removing intermediate container f2a1da3ca048 ---> 5363d05f25f0Step 7/18 : RUN go mod init blog/app/user/api ---> Running in 11d0adfa9d53go: creating new go.mod: module blog/app/user/apiRemoving intermediate container 11d0adfa9d53 ---> 3314852f00feStep 8/18 : RUN go mod download ---> Running in aa9e9d9eb850Removing intermediate container aa9e9d9eb850 ---> a0f2a7ffe392Step 9/18 : COPY . . ---> a807f60ed250Step 10/18 : COPY /etc /app/etc ---> c4c5d9f15dc0Step 11/18 : RUN go build -ldflags="-s -w" -o /app/user user.go ---> Running in a4321c3aa6e2go: finding module for package github.com/tal-tech/go-zero/core/confgo: finding module for package github.com/tal-tech/go-zero/rest/httpxgo: finding module for package github.com/tal-tech/go-zero/restgo: finding module for package github.com/tal-tech/go-zero/core/logxgo: downloading github.com/tal-tech/go-zero v1.1.1go: found github.com/tal-tech/go-zero/core/conf in github.com/tal-tech/go-zero v1.1.1go: found github.com/tal-tech/go-zero/rest in github.com/tal-tech/go-zero v1.1.1go: found github.com/tal-tech/go-zero/rest/httpx in github.com/tal-tech/go-zero v1.1.1go: found github.com/tal-tech/go-zero/core/logx in github.com/tal-tech/go-zero v1.1.1go: downloading gopkg.in/yaml.v2 v2.4.0go: downloading github.com/justinas/alice v1.2.0go: downloading github.com/dgrijalva/jwt-go v3.2.0+incompatiblego: downloading go.uber.org/automaxprocs v1.3.0go: downloading github.com/spaolacci/murmur3 v1.1.0go: downloading github.com/google/uuid v1.1.1go: downloading google.golang.org/grpc v1.29.1go: downloading github.com/prometheus/client_golang v1.5.1go: downloading github.com/beorn7/perks v1.0.1go: downloading github.com/golang/protobuf v1.4.2go: downloading github.com/prometheus/common v0.9.1go: downloading github.com/cespare/xxhash/v2 v2.1.1go: downloading github.com/prometheus/client_model v0.2.0go: downloading github.com/prometheus/procfs v0.0.8go: downloading github.com/matttproud/golang_protobuf_extensions v1.0.1go: downloading google.golang.org/protobuf v1.25.0Removing intermediate container a4321c3aa6e2 ---> 99ac2cd5fa39Step 12/18 : FROM alpinelatest: Pulling from library/alpine801bfaa63ef2: Already existsDigest: sha256:3c7497bf0c7af93428242d6176e8f7905f2201d8fc5861f45be7a346b5f23436Status: Downloaded newer image for alpine:latest ---> 389fef711851Step 13/18 : RUN apk update --no-cache && apk add --no-cache ca-certificates tzdata ---> Running in 51694dcb96b6fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/main/x86_64/APKINDEX.tar.gzfetch http://dl-cdn.alpinelinux.org/alpine/v3.12/community/x86_64/APKINDEX.tar.gzv3.12.3-38-g9ff116e4f0 [http://dl-cdn.alpinelinux.org/alpine/v3.12/main]v3.12.3-39-ge9195171b7 [http://dl-cdn.alpinelinux.org/alpine/v3.12/community]OK: 12746 distinct packages availablefetch http://dl-cdn.alpinelinux.org/alpine/v3.12/main/x86_64/APKINDEX.tar.gzfetch http://dl-cdn.alpinelinux.org/alpine/v3.12/community/x86_64/APKINDEX.tar.gz(1/2) Installing ca-certificates (20191127-r4)(2/2) Installing tzdata (2020f-r0)Executing busybox-1.31.1-r19.triggerExecuting ca-certificates-20191127-r4.triggerOK: 10 MiB in 16 packagesRemoving intermediate container 51694dcb96b6 ---> e5fb2e4d5eeaStep 14/18 : ENV TZ Asia/Shanghai ---> Running in 332fd0df28b5Removing intermediate container 332fd0df28b5 ---> 11c0e2e49e46Step 15/18 : WORKDIR /app ---> Running in 26e22103c8b7Removing intermediate container 26e22103c8b7 ---> 11d11c5ea040Step 16/18 : COPY --from=builder /app/user /app/user ---> f69f19ffc225Step 17/18 : COPY --from=builder /app/etc /app/etc ---> b8e69b663683Step 18/18 : CMD ["./user", "-f", "etc/user-api.yaml"] ---> Running in 9062b0ed752fRemoving intermediate container 9062b0ed752f ---> 4867b4994e43Successfully built 4867b4994e43Successfully tagged user:b757e9e[Pipeline] }[Pipeline] // stage[Pipeline] stage[Pipeline] { (上传镜像到镜像仓库)[Pipeline] echo上传镜像到镜像仓库[Pipeline] sh+ docker login -u xxx -p xxxxxxxxWARNING! Using --password via the CLI is insecure. Use --password-stdin.WARNING! Your password will be stored unencrypted in /var/lib/jenkins/.docker/config.json.Configure a credential helper to remove this warning. Seehttps://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded[Pipeline] sh+ docker tag user:b757e9e xxx/user:b757e9e[Pipeline] sh+ docker push xxx/user:b757e9eThe push refers to repository [docker.io/xxx/user]b19a970f64b9: Preparingf695b957e209: Preparingee27c5ca36b5: Preparing7da914ecb8b0: Preparing777b2c648970: Preparing777b2c648970: Layer already existsee27c5ca36b5: Pushedb19a970f64b9: Pushed7da914ecb8b0: Pushedf695b957e209: Pushedb757e9e: digest: sha256:6ce02f8a56fb19030bb7a1a6a78c1a7c68ad43929ffa2d4accef9c7437ebc197 size: 1362[Pipeline] }[Pipeline] // stage[Pipeline] stage[Pipeline] { (部署到k8s)[Pipeline] echo部署到k8s[Pipeline] sh+ sed -i s/<COMMIT_ID_TAG>/b757e9e/ app/user/api/user.yaml[Pipeline] sh+ kubectl apply -f app/user/api/user.yamldeployment.apps/user-api created[Pipeline] }[Pipeline] // stage[Pipeline] }[Pipeline] // withEnv[Pipeline] }[Pipeline] // node[Pipeline] End of PipelineFinished: SUCCESS
复制代码

可以看到最后输出了 SUCCESS 说明我们的 pipeline 已经成了,这个时候我们可以通过 kubectl 工具查看一下,-n 参数为指定 namespace

kubectl get pods -n blog
NAME READY STATUS RESTARTS AGEuser-api-84ffd5b7b-c8c5w 1/1 Running 0 10muser-api-84ffd5b7b-pmh92 1/1 Running 0 10m
复制代码

我们在 k8s 部署文件中制定了命名空间为 blog,所以在执行 pipeline 之前我们需要先创建这个 namespance

kubectl create namespace blog
复制代码

服务已经部署好了,那么接下来怎么从外部访问服务呢?这里使用 LoadBalancer 方式,Service 部署文件定义如下,80 端口映射到容器的 2233 端口上,selector 用来匹配 Deployment 中定义的 label

apiVersion: v1kind: Servicemetadata:  name: user-api-service  namespace: blogspec:  selector:    app: user-api  type: LoadBalancer  ports:    - protocol: TCP      port: 80      targetPort: 2233
复制代码

执行创建 service,创建完后查看 service 输出如下,注意一定要加上-n 参数指定 namespace

kubectl apply -f user-service.yaml
复制代码


kubectl get services -n blog
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEuser-api-service LoadBalancer <none> xxx.xxx.xxx.xx 80:32470/TCP 79m
复制代码

这里的 EXTERNAL-IP 即为提供给外网访问的 ip,端口为 80

到这里我们的所有的部署任务都完成了,大家最好也能亲自动手来实践一下

测试

最后我们来测试下部署的服务是否正常,使用 EXTERNAL-IP 来进行访问

curl "http://xxx.xxx.xxx.xxx:80/user/info?uid=1"
{"uid":1,"name":"172.17.0.5","level":666}
curl http://xxx.xxx.xxx.xxx:80/user/info\?uid\=1
{"uid":1,"name":"172.17.0.8","level":666}
复制代码

curl 访问了两次/user/info 接口,都能正常返回,说明我们的服务没有问题,name 字段分别输出了两个不同 ip,可以看出 LoadBalancer 默认采用了 Round Robin 的负载均衡策略

总结

以上我们实现了从代码开发到版本管理再到构建部署的 DevOps 全流程,完成了基础架构的搭建,当然这个架构现在依然很简陋。在本系列后续中,我们将以这个博客系统为基础逐渐的完善整个架构,比如逐渐的完善 CI、CD 流程、增加监控、博客系统功能的完善、高可用最佳实践和其原理等等

工欲善其事必先利其器,好的工具能大大提升我们的工作效率而且能降低出错的可能,上面我们大量使用了 goctl 工具简直有点爱不释手了哈哈哈,下次见

由于个人能力有限难免有表达有误的地方,欢迎广大观众姥爷批评指正!

项目地址

https://github.com/tal-tech/go-zero

欢迎使用并 star 支持我们!👏

发布于: 2021 年 01 月 10 日阅读数: 70
用户头像

Kevin Wan

关注

保持简单 2017.10.24 加入

go-zero作者

评论

发布
暂无评论
从代码到部署微服务实战(一)