从零开始:生产级 GitLab CI 流水线搭建
本文以 Go 语言为案例,从「只做编译和测试」的最小流水线起步,逐步加上构建镜像、镜像漏洞扫描、推送 Harbor,最终形成五阶段完整生产级 GitLab CI 流水线。 下面是本文的实践路线: 最小流水线,只做 build 和 test 阶段,让流水线先简单跑通。 增加 build-image 阶段,用 Kaniko 按 Dockerfile 构建镜像。 增加 scan-image 阶段,用 Trivy 扫描镜像,有高危漏洞直接退出。 增加 push 阶段,扫描通过后把镜像推送并打上标签。 汇总:给出完整五阶段配置与生产最佳实践。 为什么从头开始一点点增加步骤? 直接给出五十行的完整配置的话,出了问题根本不知道是编译、测试、构建、扫描还是推送哪一个阶段的问题。先跑通最小闭环,每次增加一个阶段,有问题能立即验证,排查范围小,人也更有把握。 日常编写构建生产级别的流水线也建议这样一步步验证。 1. 最小流水线验证 先只配置两个 job: build 阶段:负责编译校验,确认代码能否正常打包、是否存在语法错误。 test 阶段:运行单元测试。 这两个 job 是整个 CI 的基础,也是触发最频繁、最需要优先跑稳的部分。 1.1 为什么先只做这两步 反馈最快:编译和单元测试在同一个代码环境中运行,几秒内就能拿到结果,不依赖镜像仓库或外部服务。 成本最低:不构建镜像、不推送,也不做扫描,任何分支的提交都可以安全触发,不会产生多余产物。 优先保障代码质量:如果编译或测试失败,就不应继续执行后续的构建、扫描和推送步骤。 1.3 写配置文件 在项目根目录新建 .gitlab-ci.yml,写入下面内容: stages: - build - test variables: GO_VERSION: "1.26" # 国内须覆盖 GOPROXY,否则 go mod download 走 proxy.golang.org 会超时 GOPROXY: "https://goproxy.cn,direct" GOSUMDB: "sum.golang.google.cn" # 镜像仓库地址 HARBOR_REGISTRY: example.harbor.com IMAGE_NAME: ${HARBOR_REGISTRY}/devops/web-demo # 缓存目录 GOMODCACHE: $CI_PROJECT_DIR/.cache/go-mod GOCACHE: $CI_PROJECT_DIR/.cache/go-build # ============================================================================ # Stage 1: 服务构建 # ============================================================================ build: stage: build image: ${HARBOR_REGISTRY}/devops/golang:1.26-trixie variables: GOFLAGS: "-mod=mod" cache: key: files: - go.sum paths: - .cache/go-mod/ - .cache/go-build/ untracked: true before_script: - mkdir -p ${GOMODCACHE} ${GOCACHE} script: - go mod download - go build -o /dev/null ./... # 只验证能否编译,不产出二进制 - go vet ./... # ============================================================================ # Stage 2: 单元测试 # ============================================================================ test: stage: test image: ${HARBOR_REGISTRY}/devops/golang:1.26-trixie cache: key: files: - go.sum paths: - .cache/go-mod/ - .cache/go-build/ untracked: true before_script: - mkdir -p ${GOMODCACHE} ${GOCACHE} script: - go mod download - go test -v -race -coverprofile=coverage.out ./... artifacts: when: always expire_in: 1 week reports: coverage_report: coverage_format: cobertura path: coverage.out paths: - coverage.out 配置说明: ...