|
|
# Gitlab CI Best Practice
|
|
|
|
|
|
本文总结 Gitlab CI/CD 使用中的常见问题和解决方法, 请大家集思广益, 持续更新
|
|
|
|
|
|
## Build Images
|
|
|
|
|
|
Gitlab CI Job 默认以 docker 容器形式运行, 由 kubernetes runner 调度执行. 我们整理了部分常用编译环境供大家使用, 存放在 [hub/ci](https://git.supremind.info/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 等 |
|
|
|
|
|
|
### 参见
|
|
|
- [gitlab ci pipeline reference](https://docs.gitlab.com/ee/ci/yaml/)
|
|
|
- [hub/ci registry](https://git.supremind.info/hub/ci/container_registry)
|
|
|
|
|
|
|
|
|
## Docker in Docker
|
|
|
|
|
|
很多项目需要在 CI job 中执行 `docker build`, `docker run` 等命令, 可以用 docker-in-docker 的方式, 即 build job 多启动一个 docker:dind sidecar 容器运行 docker daemon, build job 中的 docker client 访问该 daemon, 基本配置如下:
|
|
|
|
|
|
```yaml
|
|
|
build xxx:
|
|
|
stage: build
|
|
|
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 的[问题](https://about.gitlab.com/releases/2019/07/31/docker-in-docker-with-docker-19-dot-03/).
|
|
|
|
|
|
docker service command 中 `--mtu=1450` 必填, 解决 docker in docker in kubernetes 中 MTU 错误的[问题](https://github.com/gliderlabs/docker-alpine/issues/307#issuecomment-427256504).
|
|
|
|
|
|
如果一个项目中所有 job 都需要用 docker-in-docker, 可以将 `services` 和 `variables` 定义移动到该 yaml 的 root 层级.
|
|
|
如果不是所有 job 都要用 docker-in-docker, 多个 job 定义重复的 `services` 又过于冗余, 可以使用 yaml anchor, 如 `products/atom/apiserver` 定义了两个 service, job 各取所需, 详见文末完整示例.
|
|
|
|
|
|
### 参见
|
|
|
- [Docker in Docker](https://www.docker.com/blog/docker-can-now-run-within-docker/)
|
|
|
|
|
|
|
|
|
## 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 不使用代理:
|
|
|
|
|
|
```yaml
|
|
|
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 至容器中:
|
|
|
|
|
|
```Dockerfile
|
|
|
COPY . /src/apiserver
|
|
|
RUN --mount=type=secret,id=netrc,dst=/root/.netrc go mod download
|
|
|
```
|
|
|
|
|
|
```sh
|
|
|
DOCKER_BUILDKIT=1 docker build --secret id=netrc,src=${HOME}/.netrc .
|
|
|
```
|
|
|
|
|
|
CI Job 中使用 ci token 做密码:
|
|
|
|
|
|
```yaml
|
|
|
before_script:
|
|
|
- echo -e "machine git.supremind.info\nlogin gitlab-ci-token\npassword ${CI_JOB_TOKEN}" > /root/.netrc
|
|
|
```
|
|
|
|
|
|
job 中执行的 docker build 命令参数与本地一致
|
|
|
|
|
|
### 参见
|
|
|
|
|
|
- [use secret in docker build](https://docs.docker.com/develop/develop-images/build_enhancements/#new-docker-build-secret-information)
|
|
|
|
|
|
## 一个完整示例
|
|
|
|
|
|
以上经验主要总结于 [`products/atom/apiserver`](https://git.supremind.info/products/atom/apiserver/) 项目 CI 配置的多次迭代, 其完整配置如下, 供参考
|
|
|
|
|
|
```yaml
|
|
|
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
|
|
|
``` |