当前位置:

首页 > 系统应用 > 从代码到部署微服务实战(一)

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

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

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

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

  • go-zero是一个集成了各种工程实践的 web 和 rpc 框架。通过弹性设计保障了大并发服务端的稳定性,经受了充分的实战检验
  • gitlab是一款基于 Git 的完全集成的软件开发平台,另外,GitLab 且具有wiki以及在线编辑、issue跟踪功能、CI/CD 等功能
  • jenkins 是基于Ja va开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件的持续集成变成可能
  • 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) 439266s
run: gitaly: (pid 1615) 15442s; run: log: (pid 2076) 439266s
run: gitlab-exporter: (pid 1645) 15442s; run: log: (pid 2084) 439266s
run: gitlab-workhorse: (pid 1657) 15441s; run: log: (pid 2083) 439266s
run: grafana: (pid 1670) 15441s; run: log: (pid 2082) 439266s
run: logrotate: (pid 5873) 1040s; run: log: (pid 2081) 439266s
run: nginx: (pid 1694) 15440s; run: log: (pid 2080) 439266s
run: node-exporter: (pid 1701) 15439s; run: log: (pid 2088) 439266s
run: postgres-exporter: (pid 1708) 15439s; run: log: (pid 2079) 439266s
run: postgresql: (pid 1791) 15439s; run: log: (pid 2075) 439266s
run: prometheus: (pid 10763) 12s; run: log: (pid 2077) 439266s
run: puma: (pid 1816) 15438s; run: log: (pid 2078) 439266s
run: redis: (pid 1821) 15437s; run: log: (pid 2086) 439266s
run: redis-exporter: (pid 1826) 15437s; run: log: (pid 2089) 439266s
run: sidekiq: (pid 1835) 15436s; run: log: (pid 2104) 439266s

jenkins同样可以通过apt-get直接安装,但要注意,在安装jenkins之前得先安装ja va,这个过程相对简单,就不在这儿详细演示了。jenkins的默认端口是8080,默认账号是admin,初始密码的路径是/var/lib/jenkins/secrets/initialAdminPassword。初始化安装时推荐安装的插件就足够了,后续可以根据自身需求再安装其他插件。

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

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

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

    KUBECONFIG=~/.kube/config:~/Downloads/k8s-cluster-config kubectl config view --merge --flatten > ~/.kube/config
    export KUBECONFIG=~/.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 0
ENV GOOS linux
ENV GOPROXY https://goproxy.cn,direct

WORKDIR /build/zero

RUN go mod init blog/app/user/api
RUN go mod download
COPY . .
COPY /etc /app/etc
RUN go build -ldflags="-s -w" -o /app/user user.go


FROM alpine

RUN apk update --no-cache && apk add --no-cache ca-certificates tzdata
ENV TZ Asia/Shanghai

WORKDIR /app
COPY --from=builder /app/user /app/user
COPY --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             SIZE
user 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/v1
kind: Deployment
metadata:
name: user-api
namespace: blog
labels:
app: user-api
spec:
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用来区分不同版本

    stage('从gitlab拉取服务代码') {
    steps {
    echo '从gitlab拉取服务代码'
    git credentialsId: 'xxxxxxxx', url: 'http://xxx.xxx.xxx.xxx:xxx/blog/blog.git'
    script {
    commit_id = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
    }
    }
    }
  • 构建docker镜像,使用goctl生成的Dockerfile文件构建镜像

    stage('构建镜像') {
    steps {
    echo '构建镜像'
    sh "docker build -t user:${commit_id} app/user/api/"
    }
    }
  • 上传镜像到镜像仓库,把生产的镜像push到镜像仓库

    stage('上传镜像到镜像仓库') {
    steps {
    echo "上传镜像到镜像仓库"
    sh "docker login -u xxx -p xxxxxxx"
    sh "docker tag user:${commit_id} xxx/user:${commit_id}"
    sh "docker push xxx/user:${commit_id}"
    }
    }
  • 部署到k8s,把部署文件中的版本号替换,从远程拉取镜,使用kubectl apply命令进行部署

    stage('部署到k8s') {
    steps {
    echo "部署到k8s"
    sh "sed -i 's//${commit_id}/' app/user/api/user.yaml"
    sh "cp app/user/api/user.yaml ."
    sh "kubectl apply -f user.yaml"
    }
    }

    完整的Jenkinsfile文件如下

    pipeline {
    agent any

    stages {
    stage('从gitlab拉取服务代码') {
    steps {
    echo '从gitlab拉取服务代码'
    git credentialsId: 'xxxxxx', url: 'http://xxx.xxx.xxx.xxx:9090/blog/blog.git'
    script {
    commit_id = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
    }
    }
    }
    stage('构建镜像') {
    steps {
    echo '构建镜像'
    sh "docker build -t user:${commit_id} app/user/api/"
    }
    }
    stage('上传镜像到镜像仓库') {
    steps {
    echo "上传镜像到镜像仓库"
    sh "docker login -u xxx -p xxxxxxxx"
    sh "docker tag user:${commit_id} xxx/user:${commit_id}"
    sh "docker push xxx/user:${commit_id}"
    }
    }
    stage('部署到k8s') {
    steps {
    echo "部署到k8s"
    sh "sed -i 's//${commit_id}/' app/user/api/user.yaml"
    sh "kubectl apply -f app/user/api/user.yaml"
    }
    }
    }
    }

    到这里所有的配置基本完毕,我们的基础框架也基本搭建完成,下面开始执行pipeline,点击左侧的立即构建在下面Build History中就回产生一个构建历史序列号,点击对应的序列号然后点击左侧的Console Output即可查看构建过程的详细信息,如果构建过程出现错误也会在这里输出

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

Started by user admin
Obtained Jenkinsfile from git git@xxx.xxx.xxx.xxx:gitlab-instance-1ac0cea5/pipelinefiles.git
Running in Durability level: MAX_SURVIVABILITY
[Pipeline] Start of Pipeline
[Pipeline] node
Running on Jenkins in /var/lib/jenkins/workspace/user-api
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Declarative: Checkout SCM)
[Pipeline] checkout
Selected Git installation does not exist. Using Default
The recommended git tool is: NONE
using credential gitlab_token
> git rev-parse --is-inside-work-tree # timeout=10
Fetching changes from the remote Git repository
> git config remote.origin.url git@xxx.xxx.xxx.xxx:gitlab-instance-1ac0cea5/pipelinefiles.git # timeout=10
Fetching 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=10
Checking out Revision 77eac3a4ca1a5b6aea705159ce26523ddd179bdf (refs/remotes/origin/master)
> git config core.sparsecheckout # timeout=10
> git checkout -f 77eac3a4ca1a5b6aea705159ce26523ddd179bdf # timeout=10
Commit message: "add"
> git rev-list --no-walk 77eac3a4ca1a5b6aea705159ce26523ddd179bdf # timeout=10
[Pipeline] }
[Pipeline] // stage
[Pipeline] withEnv
[Pipeline] {
[Pipeline] stage
[Pipeline] { (从gitlab拉取服务代码)
[Pipeline] echo
从gitlab拉取服务代码
[Pipeline] git
The recommended git tool is: NONE
using credential gitlab_user_pwd
> git rev-parse --is-inside-work-tree # timeout=10
Fetching changes from the remote Git repository
> git config remote.origin.url http://xxx.xxx.xxx.xxx:9090/blog/blog.git # timeout=10
Fetching 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=10
Checking 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=10
Commit 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 builder
alpine: Pulling from library/golang
801bfaa63ef2: Pulling fs layer
ee0a1ba97153: Pulling fs layer
1db7f31c0ee6: Pulling fs layer
ecebeec079cf: Pulling fs layer
63b48972323a: Pulling fs layer
ecebeec079cf: Waiting
63b48972323a: Waiting
1db7f31c0ee6: Verifying Checksum
1db7f31c0ee6: Download complete
ee0a1ba97153: Verifying Checksum
ee0a1ba97153: Download complete
63b48972323a: Verifying Checksum
63b48972323a: Download complete
801bfaa63ef2: Verifying Checksum
801bfaa63ef2: Download complete
801bfaa63ef2: Pull complete
ee0a1ba97153: Pull complete
1db7f31c0ee6: Pull complete
ecebeec079cf: Verifying Checksum
ecebeec079cf: Download complete
ecebeec079cf: Pull complete
63b48972323a: Pull complete
Digest: sha256:49b4eac11640066bc72c74b70202478b7d431c7d8918e0973d6e4aeb8b3129d2
Status: Downloaded newer image for golang:alpine
---> 1463476d8605
Step 2/18 : LABEL stage=gobuilder
---> Running in c4f4dea39a32
Removing intermediate container c4f4dea39a32
---> c04bee317ea1
Step 3/18 : ENV CGO_ENABLED 0
---> Running in e8e848d64f71
Removing intermediate container e8e848d64f71
---> ff82ee26966d
Step 4/18 : ENV GOOS linux
---> Running in 58eb095128ac
Removing intermediate container 58eb095128ac
---> 825ab47146f5
Step 5/18 : ENV GOPROXY https://goproxy.cn,direct
---> Running in df2add4e39d5
Removing intermediate container df2add4e39d5
---> c31c1aebe5fa
Step 6/18 : WORKDIR /build/zero
---> Running in f2a1da3ca048
Removing intermediate container f2a1da3ca048
---> 5363d05f25f0
Step 7/18 : RUN go mod init blog/app/user/api
---> Running in 11d0adfa9d53
[91mgo: creating new go.mod: module blog/app/user/api
[0mRemoving intermediate container 11d0adfa9d53
---> 3314852f00fe
Step 8/18 : RUN go mod download
---> Running in aa9e9d9eb850
Removing intermediate container aa9e9d9eb850
---> a0f2a7ffe392
Step 9/18 : COPY . .
---> a807f60ed250
Step 10/18 : COPY /etc /app/etc
---> c4c5d9f15dc0
Step 11/18 : RUN go build -ldflags="-s -w" -o /app/user user.go
---> Running in a4321c3aa6e2
[91mgo: finding module for package github.com/tal-tech/go-zero/core/conf
[0m [91mgo: finding module for package github.com/tal-tech/go-zero/rest/httpx
[0m [91mgo: finding module for package github.com/tal-tech/go-zero/rest
[0m [91mgo: finding module for package github.com/tal-tech/go-zero/core/logx
[0m [91mgo: downloading github.com/tal-tech/go-zero v1.1.1
[0m [91mgo: found github.com/tal-tech/go-zero/core/conf in github.com/tal-tech/go-zero v1.1.1
go: found github.com/tal-tech/go-zero/rest in github.com/tal-tech/go-zero v1.1.1
go: found github.com/tal-tech/go-zero/rest/httpx in github.com/tal-tech/go-zero v1.1.1
go: found github.com/tal-tech/go-zero/core/logx in github.com/tal-tech/go-zero v1.1.1
[0m [91mgo: downloading gopkg.in/yaml.v2 v2.4.0
[0m [91mgo: downloading github.com/justinas/alice v1.2.0
[0m [91mgo: downloading github.com/dgrijalva/jwt-go v3.2.0+incompatible
[0m [91mgo: downloading go.uber.org/automaxprocs v1.3.0
[0m [91mgo: downloading github.com/spaolacci/murmur3 v1.1.0
[0m [91mgo: downloading github.com/google/uuid v1.1.1
[0m [91mgo: downloading google.golang.org/grpc v1.29.1
[0m [91mgo: downloading github.com/prometheus/client_golang v1.5.1
[0m [91mgo: downloading github.com/beorn7/perks v1.0.1
[0m [91mgo: downloading github.com/golang/protobuf v1.4.2
[0m [91mgo: downloading github.com/prometheus/common v0.9.1
[0m [91mgo: downloading github.com/cespare/xxhash/v2 v2.1.1
[0m [91mgo: downloading github.com/prometheus/client_model v0.2.0
[0m [91mgo: downloading github.com/prometheus/procfs v0.0.8
[0m [91mgo: downloading github.com/matttproud/golang_protobuf_extensions v1.0.1
[0m [91mgo: downloading google.golang.org/protobuf v1.25.0
[0mRemoving intermediate container a4321c3aa6e2
---> 99ac2cd5fa39
Step 12/18 : FROM alpine
latest: Pulling from library/alpine
801bfaa63ef2: Already exists
Digest: sha256:3c7497bf0c7af93428242d6176e8f7905f2201d8fc5861f45be7a346b5f23436
Status: Downloaded newer image for alpine:latest
---> 389fef711851
Step 13/18 : RUN apk update --no-cache && apk add --no-cache ca-certificates tzdata
---> Running in 51694dcb96b6
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/community/x86_64/APKINDEX.tar.gz
v3.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 a vailable
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/main/x86_64/APKINDEX.tar.gz
fetch 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.trigger
Executing ca-certificates-20191127-r4.trigger
OK: 10 MiB in 16 packages
Removing intermediate container 51694dcb96b6
---> e5fb2e4d5eea
Step 14/18 : ENV TZ Asia/Shanghai
---> Running in 332fd0df28b5
Removing intermediate container 332fd0df28b5
---> 11c0e2e49e46
Step 15/18 : WORKDIR /app
---> Running in 26e22103c8b7
Removing intermediate container 26e22103c8b7
---> 11d11c5ea040
Step 16/18 : COPY --from=builder /app/user /app/user
---> f69f19ffc225
Step 17/18 : COPY --from=builder /app/etc /app/etc
---> b8e69b663683
Step 18/18 : CMD ["./user", "-f", "etc/user-api.yaml"]
---> Running in 9062b0ed752f
Removing intermediate container 9062b0ed752f
---> 4867b4994e43
Successfully built 4867b4994e43
Successfully tagged user:b757e9e
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (上传镜像到镜像仓库)
[Pipeline] echo
上传镜像到镜像仓库
[Pipeline] sh
+ docker login -u xxx -p xxxxxxxx
WARNING! 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. See
https://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:b757e9e
The push refers to repository [docker.io/xxx/user]
b19a970f64b9: Preparing
f695b957e209: Preparing
ee27c5ca36b5: Preparing
7da914ecb8b0: Preparing
777b2c648970: Preparing
777b2c648970: Layer already exists
ee27c5ca36b5: Pushed
b19a970f64b9: Pushed
7da914ecb8b0: Pushed
f695b957e209: Pushed
b757e9e: digest: sha256:6ce02f8a56fb19030bb7a1a6a78c1a7c68ad43929ffa2d4accef9c7437ebc197 size: 1362
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (部署到k8s)
[Pipeline] echo
部署到k8s
[Pipeline] sh
+ sed -i s//b757e9e/ app/user/api/user.yaml
[Pipeline] sh
+ kubectl apply -f app/user/api/user.yaml
deployment.apps/user-api created
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

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

kubectl get pods -n blog

NAME READY STATUS RESTARTS AGE
user-api-84ffd5b7b-c8c5w 1/1 Running 0 10m
user-api-84ffd5b7b-pmh92 1/1 Running 0 10m

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

kubectl create namespace blog

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

apiVersion: v1
kind: Service
metadata:
name: user-api-service
namespace: blog
spec:
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) AGE
user-api-service LoadBalancer 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

https://gitee.com/kevwan/go-zero

欢迎使用并 star 支持我们!

本文内容来源于网友投稿,如有侵权请联系删除。
作者最新文章
系统应用
相关文章 更多
Win11专业版和家庭版安装流程有什么区别
Win11专业版和家庭版安装流程有什么区别

Windows 11 家庭版和专业版在安装步骤上并无本质差异,主要区别在于产品密钥激活和功能解锁。本教程指导你如何检查硬件兼容性、使用官方媒体创建工具制作安装盘,以及在安装过程中正确选择版本。同时解析安装后如何验证激活状态,以及从家庭版升级到专业版的正规路径,确保系统稳定且合规。

除了界面,Windows11和Win10还有哪些实质差异
除了界面,Windows11和Win10还有哪些实质差异

除了开始菜单的变化,Windows 11在TPM 2.0安全要求、窗口贴靠布局、驱动兼容性以及系统更新策略上与Windows 10有显著不同。本文详解两者实质差异,助你判断是否值得升级。

win10专业版和家庭版关闭更新方法差别在哪
win10专业版和家庭版关闭更新方法差别在哪

详解Windows 10家庭版和专业版在关闭或暂停自动更新时的操作区别。涵盖通用的暂停更新、活动时间设置,以及专业版独有的组策略管理入口,帮助不同版本用户合理控制更新节奏,避免系统安全风险。

win10暂停更新最长可以设置多少天怎么操作
win10暂停更新最长可以设置多少天怎么操作

想知道Win10暂停更新最长能设多久?官方支持最长暂停35天。本文图文演示如何在设置中开启暂停、确认生效日期,以及到期后如何恢复更新或调整活动时间以避免打扰。

win10怎么屏蔽win10系统更新的弹窗提醒
win10怎么屏蔽win10系统更新的弹窗提醒

本教程介绍如何在Windows 10中通过暂停更新、设置活动时间及安排重启时间来减少更新弹窗提醒。包含通知隐藏技巧及更新失败排查步骤,帮助你在保持系统安全的同时减少工作打扰。

win10更新后台占用CPU过高怎么关闭自动更新
win10更新后台占用CPU过高怎么关闭自动更新

Windows 10 更新时 CPU 占用过高怎么办?本教程演示如何通过任务管理器确认更新进程,使用“暂停更新”功能临时停止后台活动,并设置“活动时间”防止自动重启干扰工作。提供安全的故障排查步骤,避免直接禁用系统服务带来的风险。

win10正在玩游戏弹出更新重启怎么禁止
win10正在玩游戏弹出更新重启怎么禁止

Win10玩游戏时突然弹出更新重启提示?不要强制关机。本文教你如何通过设置“活动时间”避免自动重启,利用“安排重启”规划空闲时间,以及合理使用“暂停更新”功能。区分不同状态下的应对策略,既保护游戏进度又维持系统安全。

win10自动更新抢占网络带宽该怎么处理
win10自动更新抢占网络带宽该怎么处理

Win10自动更新抢占带宽导致游戏卡顿或网页打不开?本教程教你通过任务管理器确认更新进程,利用暂停更新、按流量计费连接和传递优化带宽限制,精准控制Windows Update下载速度,解决网络拥堵问题。

win10家庭版有没有简单办法阻止强制更新
win10家庭版有没有简单办法阻止强制更新

Win10家庭版用户常受强制更新困扰。本文详解如何利用系统自带的暂停更新、活动时间和安排重启功能,在不破坏系统稳定性的前提下减少更新打扰,并分析注册表修改的风险及系统支持现状。

win10升级新版本后取消开机密码失效怎么修复
win10升级新版本后取消开机密码失效怎么修复

Windows 10更新后开机突然要求输入密码?本文解析自动登录失效的真实原因,提供通过netplwiz重新保存凭据、关闭Windows Hello干扰及排查账户策略的完整步骤,助你恢复免密进入桌面。

查看更多
精品专题 更多
装机必备
装机必备

正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

Windows
Windows

正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

macOS软件
macOS软件

正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

Mac软件 更多
photoshop
photoshop
Windows、macOS 、 iPad

Photoshop 2026 是 Adobe 推出的专业图像处理与视觉设计软件,支持 Windows、macOS 和 iPad 等平台,广泛应用于摄影修图、电商设计、平面海报、数字绘画及视觉合成等创作场景。

Blender
Blender
Windows、macOS 和 Linux

Blender 是一款免费开源、跨平台的专业 3D 创作软件,集建模、动画、渲染、视频编辑与视觉合成等功能于一体,广泛应用于影视动画、游戏设计和建筑可视化等领域。软件支持 Cycles 物理渲染器与 Eevee 实时渲染引擎,并提供多边形建模、骨骼绑定、物理模拟等专业工具。Blender 兼容 Windows、macOS 和 Linux 系统,安装包轻巧、运行流畅,依托活跃的全球开发者社区持续更新,是从初学者到专业创作者都值得选择的正版 3D 创作工具。

灵活计算器
灵活计算器
macOS/iOS/Android

灵活计算器是一款笔记式算数应用,支持实时计算、动态关联和云端同步功能。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

WINDOWS 更多
3dmax(3ds max)
3dmax(3ds max)
Windows

Autodesk 3ds Max 是一款专业的三维建模、动画与渲染软件,广泛应用于建筑可视化、游戏开发、影视动画、广告设计和产品展示等领域。

photoshop
photoshop
Windows、macOS 、 iPad

Photoshop 2026 是 Adobe 推出的专业图像处理与视觉设计软件,支持 Windows、macOS 和 iPad 等平台,广泛应用于摄影修图、电商设计、平面海报、数字绘画及视觉合成等创作场景。

Blender
Blender
Windows、macOS 和 Linux

Blender 是一款免费开源、跨平台的专业 3D 创作软件,集建模、动画、渲染、视频编辑与视觉合成等功能于一体,广泛应用于影视动画、游戏设计和建筑可视化等领域。软件支持 Cycles 物理渲染器与 Eevee 实时渲染引擎,并提供多边形建模、骨骼绑定、物理模拟等专业工具。Blender 兼容 Windows、macOS 和 Linux 系统,安装包轻巧、运行流畅,依托活跃的全球开发者社区持续更新,是从初学者到专业创作者都值得选择的正版 3D 创作工具。