前言
手工部署的痛苦: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 篇。