实战示例与排障

示例 1:测试 + 构建(通用骨架)

stages: [test, build]
 
default:
  image: golang:1.24
 
test:
  script:
    - go vet ./...
    - go test -race ./...
  cache:
    key: go-mod-$CI_COMMIT_REF_SLUG
    paths: [.go-mod-cache/]
  variables:
    GOMODCACHE: $CI_PROJECT_DIR/.go-mod-cache
 
build:
  needs: [test]
  script: go build -o app ./cmd/app
  artifacts:
    paths: [app]
    expire_in: 1 week

Python 项目把 cache paths 换成 .venv/,Node 项目换成 node_modules/ 或 npm/yarn 缓存目录,key 思路相同。

示例 2:构建并推送 Docker 镜像

方式 A:kaniko(推荐,无需 privileged)

build-image:
  stage: build
  image:
    name: gcr.io/kaniko-project/executor:debug
    entrypoint: [""]
  script:
    - mkdir -p /kaniko/.docker
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
    - /kaniko/executor
      --context "$CI_PROJECT_DIR"
      --dockerfile "$CI_PROJECT_DIR/Dockerfile"
      --destination "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"
      --destination "$CI_REGISTRY_IMAGE:latest"

方式 B:docker-in-docker(需要 Runner privileged)

build-image:
  stage: build
  image: docker:27
  services:
    - docker:27-dind
  variables:
    DOCKER_HOST: tcp://docker:2375
    DOCKER_TLS_CERTDIR: ""
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA

示例 3:部署到 Kubernetes

kubeconfig 存为 File 类型变量 KUBE_CONFIG(Settings → CI/CD → Variables):

deploy-prod:
  stage: deploy
  image: bitnami/kubectl:latest
  needs: [build-image]
  environment:
    name: production
    url: https://app.example.com
  rules:
    - if: $CI_COMMIT_TAG
  before_script:
    - mkdir -p ~/.kube
    - cp $KUBE_CONFIG ~/.kube/config
  script:
    - kubectl set image deployment/app app=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA -n prod
    - kubectl rollout status deployment/app -n prod --timeout=180s

要点:

  • environment 让部署记录出现在 Operate → Environments,支持回滚与 stop action
  • 多 Job 并发部署同一环境时加 resource_group: production 互斥
  • 更安全的替代:GitLab Agent for Kubernetes(集群内拉模式,CI 里不存 kubeconfig)

示例 4:Job 间传变量(dotenv)

build:
  script:
    - export BUILD_VERSION=1.2.3-$CI_COMMIT_SHORT_SHA
    - echo "BUILD_VERSION=$BUILD_VERSION" >> build.env
  artifacts:
    reports:
      dotenv: build.env
 
deploy:
  needs: [build]
  script: echo "deploying $BUILD_VERSION"   # 自动注入为环境变量

示例 5:多项目流水线

trigger-backend:
  stage: deploy
  trigger:
    project: mygroup/backend
    branch: main
    strategy: depend          # 等待下游结束并继承其状态
    forward:
      yaml_variables: true    # 把上游变量传给下游

artifacts vs cache

artifactscache
用途Stage/Job 间传递产物、对外下载加速依赖下载(可丢失)
可靠性上传到 GitLab,保证可用尽力而为,miss 属正常
典型内容二进制、测试报告、dotenvnode_modules、go mod、pip 缓存

cache key 建议绑定锁文件或分支(key: files: [package-lock.json]、prefix: $CI_COMMIT_REF_SLUG);多 Runner 共享缓存需 S3/MinIO 后端。

排障工具箱

症状排查
Job 一直 pending(stuck)项目无可用 Runner,或 tags 不匹配;Settings → CI/CD → Runners 看状态
某 Job 根本没出现rules 没命中;Pipeline editor 逐条验证
变量值为空Protected 变量 + 非 protected 分支;或 Mask 格式不合法没保存成功
sh: xxx: command not foundimage 缺工具,换镜像或在 before_script 安装
DinD 连不上 daemon确认 DOCKER_HOST=tcp://docker:2375 与 DOCKER_TLS_CERTDIR="" 配套;Runner 需 privileged
缓存不命中key 变了(锁文件变化)、换了 Runner 机器且缓存非共享
同一次提交跑了多条流水线缺 workflow:rules(见 cicd-yaml-syntax)
需要现场调试Job 运行中点页面右侧 Terminal,进入交互式 Web Terminal(docker/k8s executor)

其他技巧:

  • Pipeline editor 自带 CI Lint,改 yml 先 Validate 再提交
  • 失败 Job 直接 Retry;manual job 支持 Run with variables 临时注入变量
  • CI_DEBUG_TRACE: "true" 打印变量展开过程(用完删掉)
  • artifacts 一律加 expire_in,避免存储配额告警

最佳实践清单

  1. 用 rules 代替 only/except(后者已是 legacy)
  2. 用 workflow:rules 防止分支 + MR 双流水线
  3. 镜像固定版本 tag(golang:1.24 而非 latest),关键构建 pin 到 digest
  4. interruptible: true 让过期 Job 可被自动取消,节省 Runner
  5. Job 粒度小、单一职责;用 needs 尽早并行
  6. 敏感信息一律走 CI/CD Variables(Mask + 最小权限),不进 yml
  7. 部署 Job 加 environment + resource_group,保留审计与互斥

相关