Gitlab CI/CD 实践四:Golang 项目 CI/CD 流水线配置

概要

CD :: George's Blog

通过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'

定义流水线执行步骤: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的这个选项:

    image-20220531140614848

  • 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全局,项目群组,项目。

    image-20220531153438532

部署到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

完整的流水线脚本:.gitlab-ci.yml

可参考.gitlab-ci.yml

作者:Yuyy
博客:https://yuyy.info

评论

  1. ysh
    置顶
    Windows Chrome
    8月前
    2024-3-22 13:51:18

    想知道,项目中一般会配置几个runner呢

    • Yuyy
      博主
      ysh
      Macintosh Chrome
      8月前
      2024-3-25 14:20:47

      规模不大,流水线速度要求不高,可以根据构建环境来,例如 x86 一个,arm 一个。

  2. Windows Chrome
    2年前
    2022-6-10 10:36:53

    大佬,由于没找到您的关于或者留言板的评论界面,所以我在这里评论一下。主要是感觉您的博客写的不错,互链一波,以后多多学习!我已加你。
    站点名称:浮云翩迁之间
    站点地址:https://blognas.hwb0307.com
    站点描述:百代繁华一朝都,谁非过客;千秋明月吹角寒,花是主人。
    站点图标:https://blognas.hwb0307.com/logo.jpg

    • Yuyy
      博主
      Bensz
      Linux Chrome
      2年前
      2022-6-10 10:46:35

      好的,已添加。麻烦修改下你友链中我博客的描述:Golang、后端、Leetcode、程序员。谢谢!

      • Yuyy
        Windows Chrome
        2年前
        2022-6-10 10:50:23

        已经修改完成 回复这么快(~ ̄▽ ̄)~

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇