Gitlab CI Best Practice
本文总结 Gitlab CI/CD 使用中的常见问题和解决方法, 请大家集思广益, 持续更新
Build Images
Gitlab CI Job 默认以 docker 容器形式运行, 由 kubernetes runner 调度执行. 我们整理了部分常用编译环境供大家使用, 存放在 hub/ci项目中:
image | description |
---|---|
"reg.supremind.info/hub/ci/golang-guilder" | go 编译环境, 添加基础工具 git, ssh, make, bash 等 |
"reg.supremind.info/hub/ci/docker-runner" | docker client |
"reg.supremind.info/hub/ci/alpine-base" | 最小 linux 环境 alpine, 添加基础工具 git, ssh, make, bash 等 |
参见
Docker in Docker
很多项目需要在 CI job 中执行 docker build
, docker run
等命令, 可以用 docker-in-docker 的方式, 即 build job 多启动一个 docker:dind sidecar 容器运行 docker daemon, build job 中的 docker client 访问该 daemon, 基本配置如下:
build xxx:
stage: build
# required, 该镜像含 docker client, 及 bash, make, git 等工具
image: reg.supremind.info/hub/ci/docker-runner:latest
variables:
# required, 指定使用 sidecar 运行的 docker daemon
DOCKER_HOST: tcp://localhost:2375
# optional, 镜像推送至该项目所属 registry, 建议在 Makefile 等处将该变量包含进镜像完整路径, 形如:
# "reg.supremind.info/<GROUP>/<PROJECT>:<TAG>, 或
# "reg.supremind.info/<GROUP>/<PROJECT>/<IMAGE>:<TAG>
DOCKER_REG: $CI_REGISTRY_IMAGE
# required, 见后文
DOCKER_TLS_CERTDIR: ''
services:
- name: docker:19.03-dind
command:
# required, 见后文
- --mtu=1450
# optional, 代理 docker hub 镜像
- --registry-mirror=https://docker.mirrors.ustc.edu.cn
script:
# required
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build ...
- docker push ...
上述变量中, DOCKER_HOST
与 DOCKER_TLS_CERTDIR
必填, 且值固定, 可以写在 yaml 文件 root 层级. 其中 DOCKER_TLS_CERTDIR
解决 docker 19.03+ 版本引入默认启用 TLS 认证导致无法连接 docker daemon 的问题.
docker service command 中 --mtu=1450
必填, 解决 docker in docker in kubernetes 中 MTU 错误的问题.
如果一个项目中所有 job 都需要用 docker-in-docker, 可以将 services
和 variables
定义移动到该 yaml 的 root 层级.
如果不是所有 job 都要用 docker-in-docker, 多个 job 定义重复的 services
又过于冗余, 可以使用 yaml anchor, 如 products/atom/apiserver
定义了两个 service, job 各取所需, 详见文末完整示例.
参见
Go Module
使用 go module 后, 在 CI job 中执行 go get
, go test
, go build
等命令需要下载所有依赖 module, 即便使用 goproxy.cn
等代理, 由于并发请求过多, 滴滴云公网环境较差, 常常出现 TLS handshake timeout
的问题, 对于依赖较多的项目尤为严重. 因此我们在滴滴云 kubernetes 中搭建了 goproxy 服务, 缓存上游 goproxy.io
内容, 集群内外均可访问, 地址为 "https://goproxy.sl.supremind.info", 使用时可配置多个 goproxy 服务, 并指定私有 module 不使用代理:
variables:
GOPROXY: https://goproxy.sl.supremind.info,https://goproxy.cn,https://gorpoxy.io,direct
GOPRIVATE: git.supremind.info
本地开发时建议也配置这两个环境变量
Private Dependencies
无论使用哪种语言, 都有可能依赖内部项目, 而我们的内部项目多数不能公开访问. 对于 go, go get
就不能直接获取依赖. 解决方法是配置 ~/.netrc
文件, 该文件是供 curl, git 等工具使用的环境配置, 本地可手动添加:
machine git.supremind.info
login <USERNAME>
password <PASSWORD>
Dockerfile 中需使用 mount 方法避免密码泄露, 执行 docker build
时需启用 DOCKER_BUILDKIT
, 将 host 机器的 netrc 文件 copy 至容器中:
COPY . /src/apiserver
RUN --mount=type=secret,id=netrc,dst=/root/.netrc go mod download
DOCKER_BUILDKIT=1 docker build --secret id=netrc,src=${HOME}/.netrc .
CI Job 中使用 ci token 做密码:
before_script:
- echo -e "machine git.supremind.info\nlogin gitlab-ci-token\npassword ${CI_JOB_TOKEN}" > /root/.netrc
job 中执行的 docker build 命令参数与本地一致
参见
一个完整示例
以上经验主要总结于 products/atom/apiserver
项目 CI 配置的多次迭代, 其完整配置如下, 供参考
stages:
- test
- build
variables:
DOCKER_HOST: tcp://localhost:2375
DOCKER_REG: $CI_REGISTRY_IMAGE
DOCKER_TLS_CERTDIR: ''
GOPROXY: https://goproxy.sl.supremind.info,https://goproxy.cn,https://gorpoxy.io,direct
GOPRIVATE: git.supremind.info
.dind-service: &dind
name: docker:19.03-dind
command:
- --mtu=1450
- --registry-mirror=https://docker.mirrors.ustc.edu.cn
.mongo-service: &mongo
name: cbsnihp1.mirror.aliyuncs.com/library/mongo:3.6
before_script:
- echo -e "machine git.supremind.info\nlogin gitlab-ci-token\npassword ${CI_JOB_TOKEN}" > /root/.netrc
test:
stage: test
image: reg.supremind.info/hub/ci/golang-builder:latest
services:
- *mongo
variables:
TEST_MONGO: mongodb://localhost:27017
script:
- make test
build apiserver:
stage: build
image: reg.supremind.info/hub/ci/docker-runner:latest
services:
- *dind
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- make apiserver-push
build syncer:
stage: build
image: reg.supremind.info/hub/ci/docker-runner:latest
services:
- *dind
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- make syncer-push
build sshproxy:
stage: build
image: reg.supremind.info/hub/ci/docker-runner:latest
services:
- *dind
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- make sshproxy-push