前言

手工部署的痛苦:SSH → 拉代码 → 编译 → 覆盖文件 → 重启 → 祈祷。CI/CD 把这一切变成"git push 后自动完成"。这篇讲概念、工具链和一条可落地的流水线。

一、核心概念

CI(持续集成)                        CD(持续交付/部署)
代码提交自动:                         通过流水线自动:
 └─► 拉取代码                          └─► 制品投递到环境
 └─► 编译构建                               ├─ 持续交付: 到生产"门前",手动点一下发布
 └─► 单元测试                               └─ 持续部署: 全自动到生产
 └─► 静态检查/安全扫描
 └─► 打包制品(镜像/包)→ 推仓库

一切皆制品(Artifact):流水线的产物是不可变的镜像/tar 包——部署不是"到服务器上编译",而是"把指定版本的制品放到指定环境"。同一个镜像走完全程是环境一致性的根基。

二、工具版图

环节 工具 说明
代码托管 GitLab / Gitea / GitHub GitLab 一站式最常见
CI 引擎 GitLab CI / Jenkins / Drone .gitlab-ci.yml 声明式最省心
制品仓库 Harbor / Nexus / Registry 镜像与包的版本化仓库
部署执行 K8s(GitOps: ArgoCD) 声明式发布
质量门禁 SonarQube / Trivy 代码质量与镜像漏洞扫描

选型建议:小团队 GitLab + Harbor + K8s 原生滚动;上规模后加 ArgoCD 玩 GitOps。

三、一条典型流水线

git push → [lint] → [test] → [build] → [scan] → [deploy-staging] → [deploy-prod]
   │         │        │         │          │            │                │
   │      语法检查   单测+覆盖率  镜像构建    Trivy       自动            手动审批
   │      gofmt     -race      多阶段     镜像扫描      部署            (保护生产)
   └─ 触发:main 分支或 MR

四、GitLab CI 实战

项目根目录 .gitlab-ci.yml:

stages: [lint, test, build, deploy]

variables:
  IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA    # 每次提交一个镜像 tag

lint:
  stage: lint
  image: golang:1.22
  script:
    - gofmt -l . | tee /dev/stderr | (! read)        # 有未格式化文件则失败
    - go vet ./...

test:
  stage: test
  image: golang:1.22
  script:
    - go test ./... -race -coverprofile=cover.out
    - go tool cover -func=cover.out | tail -1

build:
  stage: build
  image: docker:26
  services: [docker:26-dind]
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker build -t $IMAGE .
    - docker push $IMAGE
  rules:
    - if: $CI_COMMIT_BRANCH == "main"                # 只有 main 才构建

deploy-staging:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - kubectl set image deploy/app app=$IMAGE -n staging
    - kubectl rollout status deploy/app -n staging
  environment: staging
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

deploy-prod:
  stage: deploy
  extends: deploy-staging
  script:
    - kubectl set image deploy/app app=$IMAGE -n prod
    - kubectl rollout status deploy/app -n prod
  environment: production
  when: manual                # 手动点击确认才发生产
  rules:
    - if: $CI_COMMIT_TAG                       # 打 tag 才允许发生产

要点:

  • 镜像 tag 用 commit SHA:版本可追溯,不用 latest
  • stages 分层:失败早停(lint 不过不浪费 test 资源)
  • when: manual + tag:生产发布有人工闸门
  • environment:GitLab 里能看每次部署历史,支持一键回滚

五、发布策略

策略 做法 特点
滚动 逐批替换实例 K8s 默认;简单,但新旧共存一段时间
蓝绿 新版本整套并行部署,验证后切流量 回滚秒级(切回蓝),资源双份
金丝雀 1% → 10% → 50% → 100% 渐进放量 风险最小,需要指标观测支撑
# K8s 金丝雀简版:两个 Deployment 手动调比例
kubectl scale deploy/app-v1 --replicas=9
kubectl scale deploy/app-v2 --replicas=1     # 观察 v2 的错误率/延迟
# 指标 OK 再继续倾斜…(成熟做法: Argo Rollouts 自动化)

六、质量与安全门禁

# 流水线里加扫描段
scan:
  stage: build
  image: aquasec/trivy
  script:
    - trivy image --exit-code 1 --severity HIGH,CRITICAL $IMAGE
    # 有高危漏洞 → 流水线失败 → 挡在发布前

再加:单元测试覆盖率阈值、gosec 代码安全扫描、镜像签名(cosign)——门禁越早,修复越便宜。

七、从零起步路线

第1周: 代码进 Git(哪怕先不 CI)
第2周: 加 test + lint 段 → 提交即检查
第3周: 构建镜像推 Harbor, 打 SHA tag
第4周: 自动部署 staging; 生产留手动
第5周+: 金丝雀、回滚演练、ArgoCD…

不要一上来追求全自动到生产——先把"每次提交都被验证"这个习惯建立起来。

八、常见坑

现象 原因
CI 里拉依赖超时 Runner 没配国内代理(GOPROXY/pip 源)
镜像构建巨慢 没利用层缓存;依赖层与代码层没分离
staging 好的生产挂 环境配置漂移 → 配置全走 ConfigMap/环境变量注入
密钥泄漏进仓库 用 CI Variables(masked)+ .gitignore
发布后才知道挂了 没有发布后自动验证(smoke test)

小结

  • CI 解决"合进去的是好的",CD 解决"发出去的是对的"
  • 制品不可变 + 同一镜像走全程是一致性根基
  • 流水线分段、生产留闸门、发布有回滚
  • 从小做起:先让每次 push 都跑测试

本文是「云原生」系列第 9 篇。