引言
通过前面几节的介绍,想必你已能够基本玩转GitLab流水线。接下来就来介绍下实际项目中一些流水线的高级设置。比如使用流水线模板来统一相同项目类型的流水线流程。
定义模板
通过上面流水线的案例,不难发现一个完整的从构建、测试、打包、发布的流水线配置,相对较长。对于一个解决方案中有多个发布项目的场景,如果都在一个.gitlab-ci.yml
文件中配置,那整个流水线的可读性将很差。如果能定义模板,那么不仅可以增加的流水线的可读性,而且可以在相同类型的项目中进行复用。GitLab的流水线就提供的有模板功能,允许用户按照规范定义自己的模板。下面就来定义一个可复用的模板,来简化流水线的配置。
创建模板仓库
在demos
组下创建一个新的项目,命名为:ci-cd templates
,并创建jobs
和templates
两个文件夹。其中jobs
文件夹用于创建各个job模板,方便复用;templates
文件夹用于定义流水线模板。
定义构建Job模板
在jobs
文件夹中添加build.yml
文件,文件中定义.build-job
如下,其中引用BUILD_IMAGE
和BUILD_SHELL
变量用来指定构建镜像和构建脚本。
.build-job:
image: $BUILD_IMAGE
stage: build
script:
- $BUILD_SHELL
- echo "build passed"
定义测试Job模板
在jobs
文件夹中添加test.yml
文件,文件中定义.test-job
如下,其中引用TEST_IMAGE
和TEST_SHELL
变量用来指定构建镜像和测试脚本。
.test-job:
image: $TEST_IMAGE
stage: test
script:
- $TEST_SHELL
- echo "test passed"
定义发布Job模板
在jobs
文件夹中添加publish.yml
文件,文件中定义.publish-job
如下,其中引用PUBLISH_IMAGE
、PUBLISH_SHELL
和PUBLISH_PATH
变量用来指定构建镜像、发布脚本和发布目录。
.publish-job:
image: $PUBLISH_IMAGE
stage: deploy
script:
- $PUBLISH_SHELL
- echo "publish passed"
artifacts:
paths:
- $PUBLISH_PATH
定义构建镜像Job模板
在jobs
文件夹中添加build-image.yml
文件,文件中定义.build-image-job
如下,其中引用BUILD_IMAGE_SHELL
和IMAGE_TAG
变量用来指定构建镜像脚本和镜像标签。
.build-image-job:
image: docker:19.03.13
variables:
DOCKER_HOST: tcp://docker:2376
DOCKER_TLS_CERTDIR: "/certs"
DOCKER_TLS_VERIFY: 1
DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client"
services:
- docker:19.03.13-dind
tags:
- docker
stage: deploy
before_script:
- until docker info; do sleep 1; done
- docker login -u $CI_DEPLOY_USER -p $CI_DEPLOY_PASSWORD $CI_REGISTRY
- echo $IMAGE_TAG
script:
- eval $BUILD_IMAGE_SHELL
- echo 'build docker image succeed'
- docker push $IMAGE_TAG
- docker image rm $IMAGE_TAG
定义Deployment Job模板
在jobs
文件夹中添加apply-deploy.yml
文件,文件中定义.apply-deployment-job
如下,主要用于创建通用Deployment
模板,并进行占位符替换。
.apply-deployment-job:
stage: deploy
image: bitnami/kubectl:1.19
environment:
name: $ENV
before_script:
- kubectl version
- kubectl create secret docker-registry gitlab-registry-secret
--docker-server="$CI_REGISTRY"
--docker-username="$CI_DEPLOY_USER"
--docker-password="$CI_DEPLOY_PASSWORD"
--docker-email="$GITLAB_USER_EMAIL"
-o yaml --dry-run=client | kubectl apply -f -
script:
- |
cat << EOF > app.deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: __APP_NAME__-deployment
labels:
app: __APP_NAME__
annotations:
app.gitlab.com/app: __APP_NAME__
app.gitlab.com/env: __ENV__
spec:
replicas: 3
selector:
matchLabels:
app: __APP_NAME__
template:
metadata:
labels:
app: __APP_NAME__
spec:
imagePullSecrets:
- name: gitlab-registry-secret
containers:
- name: __APP_NAME__
image: __IMAGE_TAG__
ports:
- containerPort: 80
EOF
- sed -i "s#__APP_NAME__#$APP_NAME#g" app.deployment.yaml
- sed -i "s#__IMAGE_TAG__#$IMAGE_TAG#g" app.deployment.yaml
- sed -i "s#__ENV__#$ENV#g" app.deployment.yaml
- cat app.deployment.yaml
- kubectl apply -f app.deployment.yaml
- echo 'create deployment succeed'
cache: []
定义Service Job模板
在jobs
文件夹中添加apply-service.yml
文件,文件中定义.apply-service-job
如下,主要用于创建通用Service
模板,并进行占位符替换。
.apply-service-job:
stage: deploy
environment:
name: $ENV
image: bitnami/kubectl:1.19
before_script: []
script:
- |
cat << EOF > app.service.yaml
apiVersion: v1
kind: Service
metadata:
name: __APP_NAME__-service
labels:
app: __APP_NAME__
spec:
selector:
app: __APP_NAME__
ports:
- protocol: TCP
port: 80
targetPort: 80
EOF
- sed -i "s#__APP_NAME__#$APP_NAME#g" app.service.yaml
- cat app.service.yaml
- kubectl apply -f app.service.yaml
- echo 'create service succeed'
cache: []
定义Ingress Job 模板
在jobs文件夹中添加apply-ingress.yml
文件,文件中定义.apply-ingress-job
如下,主要用于创建通用Ingress
模板,并进行占位符替换。
.apply-ingress-job:
stage: deploy
environment:
name: $ENV
image: bitnami/kubectl:1.19
before_script: []
script:
- |
cat << EOF > app.ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: __APP_NAME__-ingress
labels:
app: __APP_NAME__
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
acme.cert-manager.io/http01-edit-in-place: "true"
kubernetes.io/ingress.class: gitlab-nginx
kubernetes.io/ingress.provider: nginx
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
tls:
- hosts:
- __INGRESS_HOST__
secretName: __APP_NAME__-tls
rules:
- host: __INGRESS_HOST__
http:
paths:
- path: __INGRESS_PATH__
pathType: Prefix
backend:
service:
name: __APP_NAME__-service
port:
number: 80
EOF
- sed -i "s#__APP_NAME__#$APP_NAME#g" app.ingress.yaml
- sed -i "s#__INGRESS_HOST__#$INGRESS_HOST#g" app.ingress.yaml
- sed -i "s#__INGRESS_PATH__#$INGRESS_PATH#g" app.ingress.yaml
- cat app.ingress.yaml
- kubectl apply -f app.ingress.yaml
- echo 'create ingress succeed'
cache: []
定义ASP.NET Core 流水线模板
在templates
文件夹下创建aspnetcore-pipeline.yml
,通过GitLab定义的includes
和extends
完成流水线的预置,如下所示:
include:
- project: 'demos/cicd-templates'
ref: main
file:
- 'jobs/build.yml'
- 'jobs/test.yml'
- 'jobs/publish.yml'
- 'jobs/build-image.yml'
- 'jobs/apply-deploy.yml'
- 'jobs/apply-service.yml'
- 'jobs/apply-ingress.yml'
variables:
ENV: development
INGRESS_PATH: '/'
DOTNET_SDK_IMAGE: mcr.microsoft.com/dotnet/sdk:5.0
NUGET_CACHE_DIR: '.nuget'
SLN_PATH: './'
APP_NAME: $CI_PROJECT_PATH_SLUG
BUILD_PROJET_PATH: $SLN_PATH
TEST_PROJECT_PATH: $SLN_PATH
PUBLISH_PROJECT_PATH: $SLN_PATH
PUBLISH_PATH: publish/
BUILD_IMAGE: $DOTNET_SDK_IMAGE
TEST_IMAGE: $DOTNET_SDK_IMAGE
PUBLISH_IMAGE: $DOTNET_SDK_IMAGE
BUILD_SHELL: dotnet build $BUILD_PROJECT_PATH --no-restore
TEST_SHELL: dotnet test $TEST_PROJECT_PATH --no-restore
PUBLISH_SHELL: dotnet publish --no-restore $PUBLISH_PROJECT_PATH -o $PUBLISH_PATH
IMAGE_TAG: $CI_REGISTRY_IMAGE/$APP_NAME:$CI_COMMIT_SHORT_SHA
BUILD_IMAGE_SHELL: "docker build -t ${IMAGE_TAG} $PUBLISH_PATH -f $PUBLISH_PROJECT_PATH/Dockerfile"
cache: &global_cache
key: $CI_PROJECT_PATH_SLUG
paths:
- './*/obj/project.assets.json'
- $NUGET_CACHE_DIR
before_script:
- dotnet restore ${SLN_PATH} --packages $NUGET_CACHE_DIR
stages:
- build
- test
- deploy
build-job:
stage: build
extends: .build-job
test-job:
stage: test
extends: .test-job
cache:
<<: *global_cache
policy: pull
publish-job:
stage: deploy
when: manual
extends: .publish-job
cache:
<<: *global_cache
policy: pull
build-image-job:
stage: deploy
extends: .build-image-job
tags: ["docker"]
cache: []
needs:
- publish-job
apply-deployment-job:
stage: deploy
extends: .apply-deployment-job
cache: []
needs:
- publish-job
- build-image-job
apply-service-job:
stage: deploy
when: manual
extends: .apply-service-job
cache: []
apply-ingress-job:
stage: deploy
when: manual
extends: .apply-ingress-job
cache: []
完成后,该ASP.NET Core 流水线模板创建完毕,整个模块库的代码结构如下图所示:
使用流水线模板
回到我们的示例项目AspNetCore.CiCd.Demo
,修改.gitlab-ci.yml
如下所示:
include:
- project: 'demos/cicd-templates'
ref: main
file: 'templates/aspnetcore-pipeline.yml' # 直接引用上面定义的ASP.NET Core 流水线模板
variables: # 配置流水线预置遍历
APP_NAME: aspnetcore-cicd-web # 指定应用名称
TEST_PROJECT_PATH: './AspNetCore.CiCd.Web.Tests/' # 指定测试项目路径
PUBLISH_PROJECT_PATH: './AspNetCore.CiCd.Web/' # 指定发布项目路径
INGRESS_HOST: demo.shengjie.dev # 指定Ingress域名
INGRESS_PATH: /dotnet # 指定Ingress 路径
提交后,流水线会按预置的流水线模板进行运行,运行效果如下图所示:通过这种方式,可以发现,仅需管理员创建好通用的流水线模板,比如前端模板,后端模板,其他项目仅需简单按需引用即可,大大简化了项目流水线的配置难度。
限定分支
以上的流水线有个问题就是,任何的提交都会触发流水线,这样会相对浪费流水线资源。而按照标准Git Flow 流程,对合并到develop
、release
和main
分支的提交进行持续构建、测试即可。而对于发布应该限定在release
和main
分支。那如何进行设置呢?简单,可以通过使用rules
或only
关键字进行限定。比如修改构建Job和测试Job模板如下,即可限定持续构建和测试自动运行在main
、develop
和main
分支。
build-job:
stage: build
extends: .build-job
only: ["main","develop","/^release.*$/","merge_requests"]
test-job:
stage: test
extends: .test-job
cache:
<<: *global_cache
policy: pull
only: ["main","develop","/^release.*$/","merge_requests"]
通过修改发布Job模板如下,即可限定持续部署仅能运行在`main`和`release`分支。
publish-job:
stage: deploy
when: manual
extends: .publish-job
cache:
<<: *global_cache
policy: pull
only:
- main
- /^release.*$/