4. GitLab CI/CD核心机制回顾

在进入发布编排之前,需要把 GitLab CI/CD 的运行时骨架重新梳理清楚。发布编排并不是另起一套系统,而是把已有的 pipeline、stage、job、runner、变量、缓存和制品这些机制组合成可控、可追溯、可回滚的交付链路。理解它们各自的职责边界,才能判断一个发布流程为什么卡住、为什么产物丢失、为什么同一份代码在不同环境表现不一致。

pipeline 与 stage:从提交到执行的骨架

pipeline 是一次流水线运行的完整实例,由 .gitlab-ci.yml 定义。它由若干 stage 组成,stage 之间默认串行,同一 stage 内的 job 默认并行。这个“串行—并行”结构决定了发布节奏:编译、测试、构建镜像、部署到预发、人工确认、部署生产,通常被拆成不同 stage,以便在前一阶段失败时立即阻断后续动作。

stage 的顺序由 stages 关键字声明,例如:

stages:
  - build
  - test
  - package
  - deploy-staging
  - deploy-prod

如果某个 stage 中所有 job 都失败或被跳过,后续 stage 默认不会执行。发布编排中常利用这一点做“质量门禁”:只有测试和镜像构建都通过,才允许进入部署阶段。需要注意的是,stage 只是逻辑分组,真正执行 job 的是 runner。

job 与 runner:任务定义与执行环境

job 是 pipeline 中最小的可执行单元,包含脚本、镜像、变量、依赖关系、缓存和制品声明。job 本身不执行,它由 runner 领取并运行。runner 是安装在物理机、虚拟机、容器或 Kubernetes 上的代理进程,按标签(tag)匹配 job。发布场景中,构建 job 可能跑在带 Docker 的 runner 上,部署 job 则跑在能访问目标集群的 runner 上。

job 的关键字段包括:

  • script:实际执行的命令序列。
  • image:job 运行所用的容器镜像,决定工具链版本。
  • tags:选择具备相应能力的 runner。
  • needs:声明跨 stage 的依赖,实现有向无环图式执行,缩短等待时间。
  • rules / only / except:控制 job 何时被创建。
  • artifacts:声明要上传并传递给后续 job 的文件。
  • cache:声明跨 pipeline 复用的目录,如依赖包缓存。

runner 与 job 的匹配关系是发布排障的高频点。若 job 指定了 tags: [deploy-prod],但集群中没有对应标签的 runner,job 会一直处于 pending,而不是失败。此时应检查 runner 注册标签、并发限制和是否被暂停。

变量:配置注入与作用域

变量决定 job 在不同环境中的行为。GitLab 变量分为预定义变量(如 CI_COMMIT_SHA、CI_ENVIRONMENT_NAME)、项目/组级变量、pipeline 变量和 job 内变量。优先级从高到低大致为:job 内定义、pipeline 触发时传入、项目/组级、预定义。发布编排中,镜像标签、目标命名空间、部署策略通常通过变量注入,而不是硬编码在脚本里。

受保护变量(protected)只会在受保护分支或标签上暴露,适合存放生产部署凭据。掩码变量(masked)会在日志中隐藏值,但不能替代最小权限原则。一个常见错误是把生产 kubeconfig 放在普通变量中,导致任意分支的 job 都能读取。

缓存与制品:两种不同的传递机制

缓存(cache)和制品(artifacts)经常被混用,但目标不同。缓存用于加速,跨 pipeline 复用依赖目录,例如 Maven 的 .m2、npm 的 node_modules、Go 的模块缓存。缓存不保证存在,可能被清理,也不适合作为交付物。制品用于传递和留存,由上游 job 上传,下游 job 通过 dependencies 或 needs 下载,并可在 GitLab 界面下载或保留过期时间。

发布链路中,构建 job 产出镜像 tar 包、Helm chart、版本清单或二进制文件,应使用 artifacts 而不是 cache。例如:

build-image:
  stage: package
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
    - docker save $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA -o image.tar
  artifacts:
    paths:
      - image.tar
    expire_in: 1 day

deploy-staging:
  stage: deploy-staging
  needs: ["build-image"]
  script:
    - docker load -i image.tar
    - kubectl set image deploy/app app=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA -n staging

这里 needs 让部署 job 直接依赖构建 job,而不必等待整个 package stage 的其他 job。制品 image.tar 被下游下载后加载,保证部署的是同一份构建产物,而不是重新构建。若把镜像 tar 放进 cache,可能因缓存未命中或过期导致部署失败,且无法追溯。

GitLab CI/CD 核心机制:pipeline → stage → job → runner pipeline(一次运行实例) build test package deploy-staging deploy-prod job(最小执行单元) script / image / tags / needs rules / artifacts / cache build-image unit-test deploy runner(执行代理) docker-runner(build) k8s-runner(deploy) 按 tags 匹配 job;pending 常因标签不匹配 变量(配置注入) CI_COMMIT_SHA CI_ENVIRONMENT_NAME 受保护变量 / 掩码变量 cache(加速复用) .m2 / node_modules / go mod 不保证存在,不作为交付物 artifacts(传递留存) image.tar / chart / 版本清单 下游 needs/dependencies 下载

发布编排中必须明确的几个判断点

第一,产物一致性。部署 job 应使用构建 job 上传的制品或镜像摘要,而不是重新执行构建。若使用 needs 加 artifacts,可以保证同一 pipeline 内传递;若使用镜像仓库,应使用不可变标签(如 commit SHA)而非 latest。

第二,变量作用域与安全。生产部署凭据应放在受保护分支可用的受保护变量中,并限制 runner 标签,避免开发分支的 job 意外获得生产权限。环境变量 CI_ENVIRONMENT_NAME 可用于在脚本中区分 staging 与 production,但不应作为唯一安全边界。

第三,缓存与制品的取舍。依赖下载慢时用 cache;需要跨 job 传递构建结果、测试报告、部署清单时用 artifacts。cache 的 key 通常按分支或锁文件哈希生成,避免不同分支互相污染;artifacts 应设置 expire_in,防止存储无限增长。

第四,runner 容量与并发。发布窗口内多个 pipeline 同时触发时,runner 并发不足会导致 job 排队。可通过 resource_group 限制同一环境的部署串行化,避免两个 pipeline 同时部署生产。

在发布编排中,建议把“构建产物”和“部署动作”拆成不同 job,并通过 artifacts 或镜像仓库传递。这样即使部署失败,也能用同一份产物重试,而不必重新构建。

一个可复用的最小发布骨架

下面给出一个简化但完整的示例,展示 stage、job、runner 标签、变量、制品和部署串行化的组合方式。示例参数为教学假设,实际使用时需替换镜像地址、命名空间和凭据变量。

stages:
  - build
  - test
  - package
  - deploy

variables:
  IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA

build:
  stage: build
  tags: [docker]
  script:
    - docker build -t $IMAGE_TAG .
    - docker push $IMAGE_TAG
  rules:
    - if: $CI_COMMIT_BRANCH

test:
  stage: test
  tags: [docker]
  script:
    - docker run --rm $IMAGE_TAG npm test

package:
  stage: package
  tags: [docker]
  needs: ["build"]
  script:
    - docker pull $IMAGE_TAG
    - docker save $IMAGE_TAG -o image.tar
  artifacts:
    paths: [image.tar]
    expire_in: 1 day

deploy-staging:
  stage: deploy
  tags: [k8s]
  needs: ["package"]
  environment:
    name: staging
  resource_group: staging
  script:
    - docker load -i image.tar
    - kubectl set image deploy/app app=$IMAGE_TAG -n staging

deploy-prod:
  stage: deploy
  tags: [k8s]
  needs: ["package"]
  environment:
    name: production
  resource_group: production
  when: manual
  script:
    - docker load -i image.tar
    - kubectl set image deploy/app app=$IMAGE_TAG -n production

这个骨架体现了几个关键点:needs 让 package 和 deploy 不必等待无关 job;artifacts 把镜像 tar 从 package 传到 deploy;resource_group 保证同一环境不会并发部署;when: manual 为生产部署提供人工确认点。理解这些机制后,后续的发布编排、版本治理和回滚策略才有稳定的执行基础。