每日一Go-48、Go语言利用Gitlab CI/CD自动化构建流水线来提高代码质量和交付效率

0 阅读5分钟

一个标准的Go自动化流水线通常包含有:依赖下载、代码检查(Lint)、单元测试、编译构建、以及工件(Artifacts)保存。

一、核心流程设计

典型的Go流水线分为三个主要阶段:

1. Test:运行静态检查、代码格式化检查和单元测试。

2. Build:编译生成容器镜像,并把镜像push等到镜像服务器。

3. Deploy/Release:从镜像服务器拉取镜像,部署到目标服务器上。

二、编写.gitlab-ci.yml配置文件

在项目根目录下创建 .gitlab-ci.yml文件。以下是我们经常使用的示例:

# GitLab CI/CD 配置文件
# 定义整个CI/CD流水线的配置,包含安全扫描、构建、测试、部署和发布等完整流程

# 缓存配置
cache:
  # 指定需要缓存的路径列表
  paths:
    # 缓存Go模块的下载包,加速后续构建
    - .go/pkg/mod
    # 缓存Go构建缓存
    - .go/cache

# 定义流水线的阶段顺序
stages:
  - gosec        # 静态代码安全扫描阶段
  - test         # 测试阶段
  - build        # 构建阶段
  - post-build   # 构建后阶段(主要用于镜像扫描)
  - deploy-test  # 部署测试阶段(主要用于动态应用安全测试)
  - deploy       # 部署阶段
  # - coverage   # 代码覆盖率阶段(已注释掉,暂不启用)
  - release      # 发布阶段

# 静态应用程序安全测试任务
sast_gosec:
  # 使用最新的gosec安全扫描镜像
  image: securego/gosec:latest
  # 指定任务所属的阶段
  stage: gosec
  # 任务执行的脚本命令
  script:
    # 使用gosec对当前目录下所有Go代码进行高严重级别的安全扫描
    - gosec -severity high ./...
  # 指定运行此任务的GitLab Runner标签
  tags:
    - runner-from-186
  # 指定触发条件:只有在合并请求时运行
  only:
    - merge_requests

# 测试任务定义
test:
  # 使用指定的Golang Docker镜像
  image: "172.16.2.100:5000/golang:v1.21.0"
  # 指定任务所属的阶段
  stage: test
  # 任务执行的脚本命令
  script:
    # 设置Go模块模式为开启
    - go env -w GO111MODULE=on
    # 设置Go代理,加速依赖下载
    - go env -w GOPROXY=http://172.16.2.100:8080,direct
    # 安装swag工具,用于生成API文档
    - go install github.com/swaggo/swag/cmd/swag@v1.8.10
    # 格式化所有Go代码
    - go fmt $(go list ./... )
    # 生成API文档
    - swag init
    # 检查代码中的常见错误
    - go vet $(go list ./... )
    # 进入tests目录并运行简短测试
    - cd tests && go test -short `go list ./..`
    - go test -race 
  # 指定运行此任务的GitLab Runner标签
  tags:
    - runner-from-186
  # 指定触发条件:只有在合并请求时运行
  only:
    - merge_requests

# 构建任务定义
build:
  # 使用Docker构建镜像
  image: "172.16.2.100:5000/docker:20.10.16"
  # 指定任务所属的阶段
  stage: build
  # 任务执行的脚本命令
  script:
    # 登录到私有Docker仓库
    - docker login 172.16.2.100:7000 -u gitlab+deploy-token-10 -p Yyk8uTN9ijNvzMWVzFjy
    # 构建Docker镜像,传递构建参数BUILDTAG为"swag",限制CPU使用权重
    - docker build --build-arg BUILDTAG="swag" -t 172.16.2.100:7000/codee_jun/api-server --cpu-shares 2 .
    # 推送镜像到仓库
    - docker push 172.16.2.100:7000/codee_jun/api-server
    # 退出Docker登录
    - docker logout 172.16.2.100:7000
  # 指定运行此任务的GitLab Runner标签
  tags:
    - runner-from-186
  # 指定触发条件:只有在dev分支变更时运行
  only:
    - dev

# 容器镜像安全扫描任务
container_scan:
  # 指定任务所属的阶段
  stage: post-build
  # 使用trivy镜像扫描工具的最新版本
  image:
    name: aquasec/trivy:latest
    entrypoint: [""]  # 清空镜像的默认入口点
  # 任务执行的脚本命令
  script:
    # 使用trivy扫描镜像,只检查CRITICAL级别的漏洞,发现漏洞则退出码为1
    - trivy image --severity CRITICAL --exit-code 1 $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  # 指定运行此任务的GitLab Runner标签
  tags:
    - runner-from-186
  # 指定触发条件:只有在dev分支变更时运行
  only:
    - dev

# 动态应用程序安全测试任务
dast_scan:
  # 指定任务所属的阶段
  stage: deploy-test
  # 使用OWASP ZAP动态应用安全测试工具镜像
  image: owasp/zap2docker-stable
  # 任务执行的脚本命令
  script:
    # 创建工作目录
    - mkdir /zap/wrk
    # 使用ZAP API扫描工具,基于OpenAPI规范扫描API
    # -t 指定扫描目标(swagger.json文件)
    # -f 指定文件格式为openapi
    # -r 生成HTML格式的报告
    - zap-api-scan.py -t ./docs/swagger.json -f openapi -r report.html
  # 定义任务产物
  artifacts:
    # 产物存储路径
    paths:
      - report.html  # 保存安全扫描报告
  # 指定运行此任务的GitLab Runner标签
  tags:
    - runner-from-186
  # 指定触发条件:只有在dev分支变更时运行
  only:
    - dev

# 部署任务定义
deploy:
  # 使用SSH工具镜像
  image: 172.16.2.100:5000/ssh_image:v1
  # 指定任务所属的阶段
  stage: deploy
  # 定义任务变量
  variables:
    # SSH连接命令,通过端口1022连接到publisher用户
    SSH_COMMAND: ssh -p 1022 publisher@172.16.2.100
  # 任务执行的脚本命令
  script:
    # 通过SSH连接到远程服务器执行部署命令
    - $SSH_COMMAND 'docker login 172.16.2.100:7000 -u gitlab+deploy-token-17 -p Yyk8uTN9ijNvzMWVzFjy
      && docker pull 172.16.2.100:7000/codee_jun/api-server
      && docker rm -f api-server || true
      && docker run -d -p 8081:80 -v /data/golang-project/api-server/runtime/logs/:/runtime/logs -v /data/golang-projects/api-server/assets:/assets --name api-server --restart=always 172.16.2.100:7000/codee_jun/api-server
      && docker logout 172.16.2.100:7000'
  # 指定运行此任务的GitLab Runner标签
  tags:
    - runner-from-186
  # 指定触发条件:只有在dev分支变更时运行
  only:
    - dev

# 发布任务定义
release:
  # 指定任务所属的阶段
  stage: release
  # 使用GoReleaser镜像
  image: 
    # 镜像地址
    name: 172.16.2.100:5000/goreleaser:v1
    # 清空镜像的默认入口点
    entrypoint: ['']
  # 定义任务变量
  variables:
    # 设置Git克隆深度,0表示克隆完整历史
    GIT_DEPTH: 0
  # 定义任务产物
  artifacts:
    # 产物存储路径
    paths:
      - dist/  # 发布产物目录
    # 产物过期时间
    expire_in: "1 week"
  # 任务执行的脚本命令
  script: 
    # 准备SSH密钥
    - mkdir -p /root/.ssh/  && cp -r ./ssh/* /root/.ssh/ 
    - chmod -R 0600 /root/.ssh 
    # 克隆公共库
    - rm -rf ../go-common
    - git clone ssh://git@172.16.2.101:1022/codee_jun/go-common.git ../go-common
    # 更新公共库到最新
    - cd ../go-common && git fetch --all && git reset --hard origin/main && git pull --force
    # 返回项目目录
    - cd ../api-server
    # 使用GoReleaser发布(快照模式),发布后清理
    - goreleaser release --snapshot  --clean
    # 执行发布脚本,传递Git标签作为参数
    - sh release.sh $CI_COMMIT_TAG
  # 指定运行此任务的GitLab Runner标签
  tags:
    - runner-from-186
  # 指定触发条件:只有在打标签时运行
  only:
    - tags

三、关键优化点说明

缓存:Go 项目构建最耗时的是下载依赖包(go mod download)。通过设置 GOMODCACHE 并将其包含在 GitLab 的 cache 路径中,流水线在后续运行时可以直接读取已下载的包,减少 50% 以上的构建时间。

产物:在 build 阶段使用 artifacts 关键字。这样编译出的二进制文件会上传到 GitLab 服务器,你可以直接从 GitLab UI 下载,或者在后续的部署阶段(如推送到生产服务器)直接使用这些文件。

四、常见问题:Gitlab Runner配置

Gitlab配置Runner的位置在项目->设置->CI/CD->Runner.

图片

要运行上述流水线,需要配置一个正确的Gitlab Runner:

Executor:建议使用docker模式,因为它可以隔离环境并轻松指定不同的Go版本。

Privileged:如果你的流水线最后需要构建Docker镜像(Docker-in-Docker),Runner需要开启Privileged=true模式。

在多个项目里,尽量给每个项目配置不同的Runner,流水线相当耗CPU,如果同时有多个流水线并行跑,会频繁切换流水线上下文,让整体编译特别慢,最好是一个项目一个流水线,一个流水线一台单独的机器。

人生版 GitLab CI/CD

人生就像一条 CI/CD 流水线:每一次选择都是一次提交,习惯是 lint,挫折是 test,坚持是 build,而真正上线的,永远是那些经得起自动校验的人。

*源码地址*

1、公众号“Codee君”回复“每日一Go”获取源码

2、pan.baidu.com/s/1B6pgLWfS…


如果您喜欢这篇文章,请您(点赞、分享、亮爱心),万分感谢!