.NET DevOps 接入指南 | 使用GitLab流水线模板

引言

通过前面几节的介绍,想必你已能够基本玩转GitLab流水线。接下来就来介绍下实际项目中一些流水线的高级设置。比如使用流水线模板来统一相同项目类型的流水线流程。

定义模板

通过上面流水线的案例,不难发现一个完整的从构建、测试、打包、发布的流水线配置,相对较长。对于一个解决方案中有多个发布项目的场景,如果都在一个.gitlab-ci.yml文件中配置,那整个流水线的可读性将很差。如果能定义模板,那么不仅可以增加的流水线的可读性,而且可以在相同类型的项目中进行复用。GitLab的流水线就提供的有模板功能,允许用户按照规范定义自己的模板。下面就来定义一个可复用的模板,来简化流水线的配置。

创建模板仓库

demos组下创建一个新的项目,命名为:ci-cd templates,并创建jobstemplates两个文件夹。其中jobs文件夹用于创建各个job模板,方便复用;templates文件夹用于定义流水线模板。

定义构建Job模板

jobs文件夹中添加build.yml文件,文件中定义.build-job如下,其中引用BUILD_IMAGEBUILD_SHELL变量用来指定构建镜像和构建脚本。

.build-job:
    image: $BUILD_IMAGE
    stage: build
    script:
        - $BUILD_SHELL
        - echo "build passed"

定义测试Job模板

jobs文件夹中添加test.yml文件,文件中定义.test-job如下,其中引用TEST_IMAGETEST_SHELL变量用来指定构建镜像和测试脚本。

.test-job:   
  image: $TEST_IMAGE
  stage: test 
  script:    
    - $TEST_SHELL
    - echo "test passed"

定义发布Job模板

jobs文件夹中添加publish.yml文件,文件中定义.publish-job如下,其中引用PUBLISH_IMAGEPUBLISH_SHELLPUBLISH_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_SHELLIMAGE_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定义的includesextends完成流水线的预置,如下所示:

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 流水线模板创建完毕,整个模块库的代码结构如下图所示:


image

使用流水线模板

回到我们的示例项目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 路径

提交后,流水线会按预置的流水线模板进行运行,运行效果如下图所示:
image

通过这种方式,可以发现,仅需管理员创建好通用的流水线模板,比如前端模板,后端模板,其他项目仅需简单按需引用即可,大大简化了项目流水线的配置难度。

限定分支

以上的流水线有个问题就是,任何的提交都会触发流水线,这样会相对浪费流水线资源。而按照标准Git Flow 流程,对合并到developreleasemain分支的提交进行持续构建、测试即可。而对于发布应该限定在releasemain分支。那如何进行设置呢?简单,可以通过使用rulesonly关键字进行限定。比如修改构建Job和测试Job模板如下,即可限定持续构建和测试自动运行在maindevelopmain分支。

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.*$/
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容