在 Go CI/CD 中使用 ko
背景
Go 项目用的是 GitLab CI/CD + Kubernetes 执行器。之前一直有点慢,最近用 ko 优化了一下流程,记录一下。
CD 要打包容器镜像并且推送到镜像仓库,因为用的是 Kubernetes 执行器,没办法直接使用机器上的 docker,GitLab 官方给的方案有 dood,dind 和 kaniko。
- dood 简单理解就是暴露主机的 docker daemon 给容器内的 docker client,但这是非常具有风险的。
- dind 每次会额外启动一个容器运行 docker daemon,dind 容器通过容器网络 link 访问 docker daemon,但是 docker 需要运行在特权环境下。
- kaniko 解决了 dind 需要特权模式的问题,支持完整的 Dockerfile 命令,并且有丰富的定制选项,是比较推荐的方式之一。
- GitLab 里还提到了一个工具叫 buildah,buildah 类似 kaniko,主要用于构建 OCI 镜像,但也能支持 docker 镜像。
kaniko 是我项目之前在用的方案。我的使用场景里,先是做了一个构建环境镜像 builder,包含了 go 和大仓项目依赖的包,镜像大概是 800+MB,GitLab CI 时启动镜像是 gcr.io/kaniko-project/executor:debug
,再由 kaniko 执行 Dockerfile,拉取 builder 镜像,执行构建脚本,最后推送镜像。整个流程中最大的问题就是(故意)没有配置 Caching,每次启动都会重新拉取 builder,虽然是在 VPC 网络内拉取,整个过程还是会花费不少时间。还有一些额外的问题,比如我发现在用 multi-stage builds 的时候,filesystem 会 resolve 不止一次。
ko
无意中发现了 ko 这个项目,功能非常契合我的需求,所以决定用 ko 替换 kaniko。
我主要用到的是 ko build,这个命令的功能是:
- 支持部分 goreleaser 的配置选项,通过配置构建二进制文件
- 拉取一个镜像作为基础镜像,简单地将二进制文件和资源文件放到对应的目录,输出一个新镜像,
- 通过选项配置推送镜像到
KO_DOCKER_REPO
指定的镜像仓库
命令参数里需要特别注意的是 --sbom
选项,各个云平台的镜像仓库实现不同,如果无法正常推送,那你可能需要考虑把 --sbom
指定为 none
。
详细 build 命令文档参考传送门
ko 会从环境变量 KO_CONFIG_PATH
或工作目录下的 .ko.yaml
加载配置
.ko.yaml
例子:
1 | defaultBaseImage: gcr.io/distroless/static:nonroot |
由 ko 构建的镜像有两个点要注意:
- 二进制文件会被放在 /ko-app 下,例如 /ko-app/demo,/ko-app 目录已经被添加到 PATH
- 如果构建目录下有名为 kodata 的目录,那么 kodata 目录中的文件也会被复制到镜像里,容器内应用通过
KO_DATA_PATH
环境变量获得 kodata 路径(大概是 /var/run/ko)
GitLab CI/CD 集成
很重要的一个改变是配置 .gitlab-ci.yml 启动为 builder,但是在这之前我需要把 ko 二进制添加进 builder 镜像。制作 builder 镜像没有环境限制,可以轻松地在 docker build 环节把从二进制直接 ADD 到镜像或者在 builder 镜像构建时直接用 go install github.com/google/ko@latest
直接从源码构建(不要忘记 builder 有 go 环境),构建完成后需要 go clean -modcache
。启动镜像是 builder 的优势不仅仅是少了一层镜像的启动,大部分时候 Runner 拉取过的镜像都会保留在 host 上,所以进一步节省了拉取镜像的耗时。
最后在大仓里配置好 .ko.yaml,然后只要在构建脚本中执行 ko build <各种参数> <构建目录>
即可。
.gitlab-ci.yml
例子:
1 | build: |
Github Actions 集成
Github Actions 中集成更加简单,你可以用现成的 imjasonh/setup-ko
.github/workflows/ko-build.yml
例子:
1 | name: build |
这是结尾
在这次把 kaniko 改成 ko 后,平均构建耗时从 1m30s 降到了 22s,而且避免了不必要的网络流量和文件系统扫描,效果非常满意。