gitlab runner需挂载docker socket或预装docker cli,否则docker build报错;禁用dind以避免go缓存失效;go构建必须指定goos=linux goarch=amd64及-ldflags="-s -w";依赖缓存key须基于go.sum哈希,且显式设置gomodcache路径。

GitLab Runner 必须挂载 Docker socket 或预装 Docker
直接在 gitlab-ci.yml 里写 docker build 会报 command not found: docker —— 因为默认的 golang 镜像不含 Docker CLI,Runner 容器本身也不带 Docker daemon。
两个可行路径:
- 挂载宿主机的
/var/run/docker.sock到 Runner 容器(推荐用于单机部署),启动 Runner 时加--volume /var/run/docker.sock:/var/run/docker.sock - 用自定义镜像(如
golang:1.22-slim)并在其中安装 Docker CLI,但需注意curl -fsSL https://get.docker.com | sh在 CI 环境中常因网络或权限失败
别用 docker:dind(Docker-in-Docker),它与 Go 构建阶段的文件系统隔离冲突大,go mod download 缓存无法跨阶段复用。
go build 不指定 GOOS/GOARCH 就等于白干
本地 macOS 开发,CI 默认跑出的是 darwin/amd64 二进制,扔到 Linux 服务器上执行直接报 cannot execute binary file: Exec format error —— 这是上线前最常卡住的第一步。
必须硬编码目标平台:
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o bin/app-linux-amd64 ./cmd/service- 若需支持 ARM64(如 AWS Graviton 或树莓派),补一行:
GOOS=linux GOARCH=arm64 go build -o bin/app-linux-arm64 ./cmd/service
-ldflags="-s -w" 去掉调试符号和 DWARF 信息,体积通常减小 30%+,直接影响容器镜像大小;不加的话,alpine 镜像里塞个 20MB 的二进制很常见。
依赖缓存不带 go.sum 哈希就等于没缓存
只写 key: go-mod-cache 这种静态 key,哪怕 go.mod 里只加一个空格,也会命中旧缓存,导致 go build 找不到新引入的包或版本冲突。
正确做法是让 key 绑定依赖真实状态:
- 用
hashFiles('**/go.sum')生成 key,例如:key: cache-go-mod-${{ hashFiles('**/go.sum') }} - 必须在
go build或go test之前执行go mod download,否则缓存不会写入 - 只缓存
$HOME/go/pkg/mod,别缓存整个$HOME/go—— 里面混着GOPATH下的构建产物,会污染后续 job
GitLab CI 中还需显式设 GOMODCACHE: $CI_PROJECT_DIR/.go/pkg/mod,因为 runner 默认不信任非 $HOME 路径下的模块缓存。
go test -race 在 CI 失败不是 bug,是它终于起作用了
本地 go test 过了,CI 加了 -race 却挂了?这不是环境问题,是竞态检测器在 CI 更受限的调度下暴露了真实竞争 —— 本地跑过不等于线程安全。
常见诱因和应对:
- 用
time.Sleep等 goroutine 结束?race detector 会忽略 sleep,但实际仍可能漏掉信号 —— 改用sync.WaitGroup或channel显式同步 -
sync.WaitGroup.Add()和Wait()不在同 goroutine?会导致 panic 或漏 wait,必须确保所有Add()在Wait()前完成 - 第三方库自带竞态(如老版本
golang.org/x/net/http2)?升到v0.18.0+,或临时在 CI 中跳过该包测试(不推荐长期这么做)
CI 中必须固定启用 -race,不能只在本地跑 —— 它不是可选项,是 Go 微服务上线前的必过门槛。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











