概要
通过gitlab CI/CD,可实现提交代码时触发golang应用的单元测试、代码检查、构建镜像、推送镜像到镜像私仓、部署应用到k8s,下面说下具体实现。
创建流水线脚本文件
在项目根目录创建.gitlab-ci.yml
文件。
引入公共脚本库
include:
- project: 'devops/gitlab-cicd-template'
ref: main
file: '/golang/golangci-lint/1.52.2/.gitlab-ci.yml'
- project: 'devops/gitlab-cicd-template'
ref: main
file: '/golang/unit-test/.gitlab-ci.yml'
- project: 'devops/gitlab-cicd-template'
ref: main
file: '/golang/build-image/.gitlab-ci.yml'
- project: 'devops/gitlab-cicd-template'
ref: main
file: '/common/push-image/.gitlab-ci.yml'
- project: 'devops/gitlab-cicd-template'
ref: main
file: '/common/deploy-to-k8s/.gitlab-ci.yml'
- project: 'devops/gitlab-cicd-template'
ref: main
file: '/common/clean-image/.gitlab-ci.yml'
- 公共脚本库介绍:Gitlab CI/CD 实践七:公共脚本仓库
定义流水线执行步骤:stages
stages:
- test
- lint
- build
- push
- deploy
- clean
-
流水线以job为单位运行,每个job就是自己想通过流水线做的事情,例如单元测试的job,核心是运行脚本:
go test -short
go list ./...
-
每个阶段下可以定义多个job。
-
同一阶段的job会并行执行。
-
阶段是串行执行,可通过
needs
关键字优化,下文有提到。
定义脚本用到的公共变量
variables: &global-variables
MODULE_PREFIX: user-manage
IMAGE_GROUP: user-manage
-
自定义一些变量,在流水线执行过程中以环境变量的形式存在,可被job里定义的变量覆盖。
-
MODULE_PREFIX
:用于拼接镜像名,例如构建user-manage项目里的admin服务,最终构建的镜像名就是user-manage-admin。 -
IMAGE_GROUX
:镜像私仓harbor里的项目,例如172.1.1.1/common/kubectl:1.25.12里的common
代码检测job
-
前面引入了公共脚本库里的代码检测job,项目里的
.gitlab-ci.yml
不需要修改。 -
下面讲解下公共脚本库里的代码检测job:
/golang/golangci-lint/1.52.2/base/.gitlab-ci.yml
include: '/golang/golangci-lint/lint-rule.yml'
.golangci_lint_base:
stage: lint
image: 172.1.1.1/common/golangci-lint:1.52.2
tags:
- x86
interruptible: true
only:
changes:
- "**/*.go"
- "go.mod"
- "go.sum"
- ".gitlab-ci.yml"
extends:
- .lint_rule
variables:
GOLANGCI_LINT_CACHE: /cache/${CI_PROJECT_PATH}/golangci-lint-cache
script:
- echo "lint 规则参考 kratos"
- echo "注意:当前使用的代码检查规则是gitlab-cicd-template仓库里的/golang/golangci-lint/lint-rule.yml,并不是当前项目里的.golangci.yml"
- mkdir -p $GOLANGCI_LINT_CACHE
- go mod tidy # 必须先下载依赖,否则golangci-lint会报:could not load export data: no export data for
- golangci-lint run -v --timeout=5m
-
stage
:指定此job属于lint阶段 -
172.1.1.1
:公司内部镜像私仓地址 -
172.1.1.1/common/golangci-lint:1.52.2
:用来执行此job的容器镜像,封装了一些工具,具体定义请看dockerfile仓库 -
tags
:指定运行流水线的gitlab runner。 -
x86
:gitlab runner 的 tag -
interruptible
:支持可中断,例如上一次流水线还没跑完,又触发了一次,这种情况下会取消上一次流水线。还需要勾选gitlab的这个选项: -
only.changes
:触发方式为go代码和CI/CD脚本变更。 -
lint_rule
:/golang/golangci-lint/lint-rule.yml里的golangci-lint规则,覆盖各个项目里配置的.golangci.yml,目的是团队内部统一使用一套lint规则。 -
GOLANGCI_LINT_CACHE
:golangci-lint运行时会产生缓存,一定要使用缓存,否则代码检测job运行时间比本地运行要长很多。/cache
目录是每个运行流水线的容器都会挂载的宿主机目录,在Gitlab CI/CD 实践三:Docker 安装 Gitlab Runner里设置的。CI_PROJECT_PATH
:这是gitlab设置的环境变量,值为项目名称,用来拼接缓存目录,区分不同项目。
-
script
:job核心内容,指定此job运行的脚本。 -
.golangci_lint_base
:gitlab流水线会认为.
开头的脚本不是一个job,就不会去执行。这里只是作为脚本模板,后面真正运行的job(golang/golangci-lint/1.52.2/.gitlab-ci.yml)会去extends此模板,同时可以覆盖模板里的值,达到定制的效果。
单元测试job
- 前面引入了公共脚本库里的单元测试job,项目里的
.gitlab-ci.yml
不需要修改。 - 下面讲解下公共脚本库里的单元测试job:
/golang/unit-test/base/.gitlab-ci.yml
.unit_test_base:
stage: test
image: 172.1.1.1/common/golang:1.20.3
tags:
- x86
interruptible: true
only:
changes:
- "**/*.go"
- "go.mod"
- "go.sum"
- ".gitlab-ci.yml"
script:
- go test -short `go list ./...`
-
核心内容是
go test -short
go list ./...
构建镜像job
- 需要在项目里的
.gitlab-ci.yml
添加以下job
build_admin_image:
extends:
- .golang_build_image_base
variables:
<<: *global-variables
SUB_MODULE: admin
-
SUB_MODULE
:需要构建的模块,如果项目是多模块,就需要为每个模块新增一个这样的job。 -
构建镜像的Dockerfile可参考user-manage里的Dockerfile
-
这里解释下公共脚本库里的
golang_build_image_base
.build_app: &build_app mkdir -p bin; cd cmd/$SUB_MODULE; CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-X main.Version=$VERSION" -o ../../bin/; cd - > /dev/null; .build_image: &build_image docker build --build-arg SUB_MODULE=$SUB_MODULE -t $MODULE_PREFIX-$SUB_MODULE:$VERSION -f $DOCKER_FILE .; .golang_build_image_base: stage: build image: 172.1.1.1/common/golang:1.20.3 tags: - x86 interruptible: true variables: SUB_MODULE: default MODULE_PREFIX: default IMAGE_GROUP: default DOCKER_FILE: Dockerfile script: - | if [ -z ${CI_COMMIT_TAG} ];then VERSION=${CI_COMMIT_SHORT_SHA}; else VERSION=${CI_COMMIT_TAG}; fi; - echo "VERSION:$VERSION" - *build_app - *build_image
- 如果当前触发流水线是打tag,那么镜像版本号就是git tag。否则是git commit hash的前6位
- 版本号还会赋值给go工程main包里的Version变量,用于输出到日志,可参考user-manage。
- go build会用到go mod依赖的库,这里也需要使用缓存来提高效率,默认依赖库会放到
/go/pkg/mod
,该目录是每个运行流水线容器都会挂载的宿主机目录,在Gitlab CI/CD 实践三:Docker 安装 Gitlab Runner里设置的。
推送镜像job
push_admin_image:
extends:
- .push_image_base
variables:
<<: *global-variables
SUB_MODULE: admin
needs: [ build_admin_image ]
-
needs
:指定依赖关系,流水线默认是一个阶段一个阶段的运行,这里推送admin镜像,只需要admin镜像制作好就运行,而不是等待整个build阶段运行完才执行,针对同一阶段多job的情况能提高效率。 -
下面讲解下公共脚本库里的代码检测job:
/common/push-image/.gitlab-ci.yml
.push_image: &push_image set -e; if [ -z ${CI_COMMIT_TAG} ];then VERSION=${CI_COMMIT_SHORT_SHA}; else VERSION=${CI_COMMIT_TAG}; fi; echo "APP VERSION:$VERSION"; if [ -z ${CUSTOM_IMAGE_NAME} ];then TARGET_IMAGE_NAME=$MODULE_PREFIX-$SUB_MODULE; else TARGET_IMAGE_NAME=${CUSTOM_IMAGE_NAME}; fi; echo "镜像名称(可通过'CUSTOM_IMAGE_NAME'变量指定):${TARGET_IMAGE_NAME}"; docker login "${DOCKER_REGISTRY_SERVER}" --username "${DOCKER_REGISTRY_USER}" --password "${DOCKER_REGISTRY_PASSWORD}"; docker tag "$MODULE_PREFIX-$SUB_MODULE:$VERSION" "${DOCKER_REGISTRY_ADDR}/${IMAGE_GROUP}/${TARGET_IMAGE_NAME}:$VERSION"; docker tag "$MODULE_PREFIX-$SUB_MODULE:$VERSION" "${DOCKER_REGISTRY_ADDR}/${IMAGE_GROUP}/${TARGET_IMAGE_NAME}:latest"; docker push "${DOCKER_REGISTRY_ADDR}/${IMAGE_GROUP}/${TARGET_IMAGE_NAME}:$VERSION"; docker push "${DOCKER_REGISTRY_ADDR}/${IMAGE_GROUP}/${TARGET_IMAGE_NAME}:latest"; timeout 10 docker rmi -f "${DOCKER_REGISTRY_ADDR}/${IMAGE_GROUP}/${TARGET_IMAGE_NAME}:$VERSION"; timeout 10 docker rmi -f "${DOCKER_REGISTRY_ADDR}/${IMAGE_GROUP}/${TARGET_IMAGE_NAME}:latest"; .push_image_base: stage: push image: 172.1.1.1/common/golang:1.20.3 tags: - x86 interruptible: true variables: SUB_MODULE: default MODULE_PREFIX: default IMAGE_GROUP: default script: - *push_image
-
CUSTOM_IMAGE_NAME
:可以在job中声明此变量,用于自定义镜像名称,默认为$MODULE_PREFIX-$SUB_MODULE -
DOCKER_REGISTRY_SERVER
:在gitlab配置的变量,可根据实际情况,放到不同级别下。例如gitlab全局,项目群组,项目。
-
部署到K8s环境job
deploy_admin_to_test_k8s:
extends:
- .deploy_to_k8s_base
before_script:
- K8S_CONFIG=$K8S_CONFIG_192
- NAME_SPACE=default
variables:
<<: *global-variables
SUB_MODULE: admin
ENV: test
needs: [ push_admin_image ]
deploy_admin_to_prod_k8s:
extends:
- .deploy_to_k8s_base
before_script:
- K8S_CONFIG=$K8S_CONFIG_193
- NAME_SPACE=default
variables:
<<: *global-variables
SUB_MODULE: admin
ENV: prod
needs: [ push_admin_image ]
only:
- tags
-
所有git提交都会部署到test环境,但只有git tag才会部署到prod环境,通过only控制。还可以限制特定分支,支持正则。
-
K8S_CONFIG_192
:在gitlab里配置的变量,需要进行base64编码。 -
ENV
:部署环境,决定使用项目里定义的哪个环境的部署文件。- 当前项目的k8s manifest资源定义文件需要放到deploy目录下,如下所示,具体可参考user-manage。
deploy/ ├── admin │ ├── prod │ │ ├── configmap.yaml │ │ ├── deployment.yaml │ │ └── service.yaml │ └── test │ ├── configmap.yaml │ ├── deployment.yaml │ └── service.yaml ├── interface │ ├── prod │ │ ├── configmap.yaml │ │ ├── deployment.yaml │ │ └── service.yaml │ └── test │ ├── configmap.yaml │ ├── deployment.yaml │ └── service.yaml ├── service │ ├── prod │ │ ├── configmap.yaml │ │ ├── deployment.yaml │ │ └── service.yaml │ └── test │ ├── configmap.yaml │ ├── deployment.yaml │ └── service.yaml └── task ├── prod │ ├── configmap.yaml │ └── deployment.yaml └── test ├── configmap.yaml └── deployment.yaml
- admin、interface、service、task是user-manage项目下的几个微服务。
-
下面讲解下公共脚本库里的代码检测job:
/common/push-image/.gitlab-ci.yml
.deploy_to_k8s: &deploy_to_k8s
if [ -z ${CI_COMMIT_TAG} ];then
VERSION=${CI_COMMIT_SHORT_SHA};
else
VERSION=${CI_COMMIT_TAG};
fi;
echo "VERSION:$VERSION";
mkdir -p /.kube;
mkdir -p /root/.kube;
for ((i=0;i<${#K8S_CONFIG[*]};i++)); do
echo "index:$i";
cp -r deploy /tmp;
cd /tmp;
sed -i "s/IMAGE_VERSION/$VERSION/g" deploy/$SUB_MODULE/$ENV/*.yaml;
sed -i "s/NAME_SPACE/${NAME_SPACE[i]}/g" deploy/$SUB_MODULE/$ENV/*.yaml;
sed -i "s/IMAGE_NAME/$MODULE_PREFIX-$SUB_MODULE/g" deploy/$SUB_MODULE/$ENV/*.yaml;
sed -i "s/DOCKER_REGISTRY_ADDR/${DOCKER_REGISTRY_ADDR}/g" deploy/$SUB_MODULE/$ENV/*.yaml;
sed -i "s/IMAGE_GROUP/${IMAGE_GROUP}/g" deploy/$SUB_MODULE/$ENV/*.yaml;
echo ${K8S_CONFIG[i]} | base64 -id > /.kube/config;
echo ${K8S_CONFIG[i]} | base64 -id > /root/.kube/config;
kubectl apply -f deploy/$SUB_MODULE/$ENV;
cd - > /dev/null;
rm -rf /tmp/deploy;
done;
.deploy_to_k8s_base:
stage: deploy
image:
name: 172.1.1.1/common/kubectl:1.25.12
entrypoint: [ "" ]
tags:
- x86
interruptible: true
variables:
SUB_MODULE: default
MODULE_PREFIX: default
IMAGE_GROUP: default
ENV: default
script:
- *deploy_to_k8s
- 部署用到的k8s manifest文件,可参考deploy。
想知道,项目中一般会配置几个runner呢
规模不大,流水线速度要求不高,可以根据构建环境来,例如 x86 一个,arm 一个。
大佬,由于没找到您的关于或者留言板的评论界面,所以我在这里评论一下。主要是感觉您的博客写的不错,互链一波,以后多多学习!我已加你。
站点名称:浮云翩迁之间
站点地址:https://blognas.hwb0307.com
站点描述:百代繁华一朝都,谁非过客;千秋明月吹角寒,花是主人。
站点图标:https://blognas.hwb0307.com/logo.jpg
好的,已添加。麻烦修改下你友链中我博客的描述:Golang、后端、Leetcode、程序员。谢谢!
已经修改完成 回复这么快(~ ̄▽ ̄)~