这是我们平台工程与运维团队 15 个人耗时 87 天,把一套跑了八年的"物理机/裸 VM + 手工 SSH 部署 + Jenkins 自由风格脚本堆砌 + 无基础设施即代码 + 配置全靠手改导致漂移 + 发布即停机 + 回滚靠记忆 + 监控只有几张 Zabbix 图"的远古交付体系,整体重构到 2026 年"Kubernetes + 容器化 + Terraform 基础设施即代码 + GitHub Actions CI + ArgoCD GitOps + Argo Rollouts 渐进式交付 + Prometheus/Grafana/OpenTelemetry 全栈可观测"现代 DevOps 体系的真实战役复盘。重构前,我们的交付是典型的"上线靠运维半夜手工敲命令、环境之间配置各不相同、一次发布全站停机几十分钟、出了问题没人说得清线上到底是什么状态、回滚全凭老师傅记忆"的危局;一次发布平均耗时数小时,故障恢复动辄以小时计。重构后,我们建立起一套以容器消除环境差异、以 Terraform 声明式管基础设施、以 GitOps 让 Git 成为唯一事实源、以渐进式交付做金丝雀发布、以全栈可观测驱动决策的现代交付体系。这 87 天里我们沉淀了 47 套工程修法、7 个 P0 事故复盘和 6 条工程哲学,本文毫无保留地分享出来。
需要先说明:DevOps 现代化不只是"上个 K8s",而是一次从"手工运维 + 配置漂移 + 停机发布"到"声明式基础设施 + GitOps + 渐进式交付"的体系跃迁。下面这张表,概括了我们重构前后在十个核心维度上的对比,每一行背后都是数周攻坚。
| 维度 | 重构前(手工运维时代) | 重构后(2026 现代 DevOps) |
|---|---|---|
| 运行环境 | 物理机/裸 VM,环境各异 | 容器 + K8s,环境一致 |
| 部署方式 | 手工 SSH 敲命令 | GitOps 自动同步 |
| 基础设施 | 手点控制台,无记录 | Terraform 声明式 IaC |
| CI 流水线 | Jenkins 自由风格脚本 | GitHub Actions 声明式 |
| 配置管理 | 手改,环境间漂移 | Git 单一事实源 |
| 发布策略 | 全量停机发布 | 金丝雀渐进式发布 |
| 回滚 | 靠记忆手工回退 | 一键回退到任意版本 |
| 弹性伸缩 | 手工加机器 | HPA 按指标自动伸缩 |
| 可观测 | 几张 Zabbix 图 | 指标/日志/链路三位一体 |
| 发布耗时 | 数小时,需停机 | 分钟级,零停机 |
一、容器化:用多阶段构建消除环境差异
重构的第一仗,是容器化。物理机时代我们最大的痛是"环境地狱"——开发机能跑、测试机报错、生产机又是另一套依赖版本,"在我机器上好好的"成了团队口头禅。每次部署都要在目标机器上手工装运行时、配依赖、改环境变量,机器之间逐渐漂移成谁也说不清的雪花服务器。容器把应用和它的全部依赖打包成一个不可变镜像,一次构建、处处运行,从根上消除了环境差异。我们用多阶段构建(multi-stage build)把构建环境和运行环境分离,让最终镜像只含运行所需、又小又安全。下面是我们的多阶段 Dockerfile:
# 多阶段构建:构建阶段装全套工具链,运行阶段只留产物,镜像又小又安全
FROM golang:1.24 AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# 静态编译,产物不依赖任何动态库
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /app/server ./cmd/server
# 运行阶段:用 distroless 极简基础镜像,无 shell、无包管理器,攻击面极小
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/server /server
# 非 root 运行,符合最小权限原则
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/server"]
容器化让我们彻底告别了"环境地狱":同一个镜像从开发、测试一路跑到生产,依赖、运行时、配置全部固化在镜像里,"在我机器上好好的"这句话从此失去了存在的土壤;多阶段构建让最终镜像只含编译产物、不含编译工具链,配合 distroless 基础镜像,镜像体积压到极小、攻击面也大幅收窄;不可变镜像还让回滚变得无比简单——回退就是换一个镜像 tag,而不是在机器上反向操作一堆手工改动。我们还把镜像构建纳入 CI、给每个镜像打上 git commit 的不可变 tag,做到了"镜像即版本、版本即代码"的可追溯。容器是现代交付的原子单元,它解决的不是"快不快"的问题,而是"一致不一致、可不可重现"这个一切自动化的前提。
二、Terraform:把基础设施变成可评审的代码
第二仗是基础设施即代码(IaC)。过去我们的云资源——VPC、子网、负载均衡、数据库、K8s 集群——全靠人在控制台点点点创建,没有记录、没有评审、没法复现,谁点的、为什么这么配,事后全是糊涂账;想再搭一套一模一样的预发环境?只能凭记忆重新点一遍,必然漂移。Terraform 让我们用声明式代码描述全部基础设施,变更走 Git PR 评审、`plan` 预览影响、`apply` 自动落地,基础设施从此和应用代码一样可版本化、可评审、可复现。下面是我们的 Terraform 片段:
# 声明式基础设施:资源即代码,变更可评审、可 plan 预览、可复现
resource "kubernetes_namespace" "app" {
metadata { name = "production" }
}
# K8s 集群的节点池,扩缩容只需改 desired_size 走 PR
resource "alicloud_cs_kubernetes_node_pool" "app_pool" {
cluster_id = var.cluster_id
name = "app-pool"
instance_types = ["ecs.g7.xlarge"]
desired_size = 4 # 改这个数字 + PR 评审,即可增减节点
scaling_config {
min_size = 4
max_size = 47 # 大促可自动弹到 47 个节点
}
}
# 用 module 复用:预发/生产共用同一套定义,只传不同变量,杜绝环境漂移
module "redis" {
source = "./modules/redis"
env = "production"
node_type = "redis.master.large.default"
}
Terraform 让我们的基础设施从"控制台手点的糊涂账"变成了"Git 里可评审、可追溯、可复现的代码":每一次基础设施变更都走 PR、有人评审、有 plan 预览能提前看到将要新增/修改/销毁哪些资源,再没有人能在控制台偷偷改一下导致线上和认知不一致;用 module 把通用基础设施抽象复用后,预发和生产环境共用同一套定义、只传不同变量,环境漂移这个老大难被根治;想灾备到另一个地域?同一份代码换个 region 变量 apply 一下就重建出一套一模一样的环境。我们尤其受益于 plan 的预览能力——它把"改基础设施"从过去那种提心吊胆的高危操作,变成了"先看清楚影响再决定"的可控变更。基础设施即代码的本质,是把运维知识从老师傅的脑子里、从控制台的点击里,沉淀成团队可评审、可传承、可复现的代码资产。
三、GitHub Actions:声明式流水线取代 Jenkins 脚本堆砌
第三仗,是 CI 流水线的现代化。Jenkins 时代我们的流水线是一堆自由风格(freestyle)任务 + 散落的 shell 脚本,配置藏在 Jenkins 的 Web 界面里,改一下没有版本记录、出了问题没法回溯,新人想看懂整条流水线要点开十几个任务页面拼凑。我们迁移到 GitHub Actions,把流水线定义成仓库里的 YAML——和代码同仓、同评审、同版本,改流水线就是提 PR,谁改了什么一目了然。下面是我们的 CI 工作流:
# .github/workflows/ci.yaml —— 流水线即代码,与业务代码同仓同评审
name: ci
on:
push:
branches: [main]
pull_request:
jobs:
build-test-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with: { go-version: '1.24' }
- name: 单元测试 + 竞态检测
run: go test -race -coverprofile=cover.out ./...
- name: 构建并推送镜像(用 commit SHA 做不可变 tag)
run: |
IMG=registry.cn/app:${{ github.sha }}
docker build -t $IMG .
docker push $IMG
# 关键:CI 只负责构建产物,部署交给 GitOps,职责清晰解耦
- name: 更新 GitOps 仓库的镜像 tag
if: github.ref == 'refs/heads/main'
run: ./scripts/bump-image-tag.sh ${{ github.sha }}
GitHub Actions 让我们的流水线从"藏在 Jenkins 界面里、改了没记录的黑盒"变成了"仓库里可评审、可版本化、可复现的 YAML":流水线和代码同生共死,改流水线就是提 PR,任何变更都有 diff、有评审、可回溯;声明式的 step 让整条流水线一目了然,新人读一个 YAML 文件就懂全流程;丰富的 action 生态让缓存、矩阵构建、密钥管理这些过去要手写脚本的活儿变成了几行配置。我们做的最关键的解耦,是让 CI 只负责"构建 + 测试 + 推镜像 + 更新 GitOps 仓库的镜像 tag",而把"部署"这件事完全交给 GitOps——CI 不再直接 SSH 到机器或调 kubectl 部署,职责边界清晰了,权限也更安全(CI 不需要持有生产集群的部署凭证)。流水线即代码,是把交付流程本身也纳入版本控制和评审体系的关键一步。
四、ArgoCD GitOps:让 Git 成为线上状态的唯一事实源
第四仗是 GitOps。过去"线上到底是什么状态"这个问题没人答得清——有人手工 kubectl apply 改过、有人热修改过配置、有人扩过容没记录,实际状态和任何文档、任何认知都对不上,这种"配置漂移"是无数线上诡异问题的根源。GitOps 的核心思想是:把期望的线上状态全部声明在 Git 里,由 ArgoCD 持续比对"Git 里的期望状态"和"集群的实际状态",一旦有偏差就自动同步回 Git 声明的样子。Git 成为唯一事实源,线上状态变得完全可知、可审计、可复现。下面是我们的 ArgoCD Application 定义:
# ArgoCD Application —— Git 是唯一事实源,集群状态持续向 Git 收敛
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: order-service
namespace: argocd
spec:
source:
repoURL: https://git.internal/gitops/order-service
targetRevision: main
path: overlays/production # Kustomize overlay,各环境差异化配置
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true # Git 里删了的资源,集群里也自动清除
selfHeal: true # 有人手工改了集群?自动纠偏回 Git 声明的状态
syncOptions:
- CreateNamespace=true
ArgoCD GitOps 让"线上是什么状态"这个曾经无解的问题有了确定答案:Git 仓库就是线上状态的唯一事实源,任何变更都必须先改 Git、经评审、再由 ArgoCD 自动同步落地,彻底杜绝了手工改集群导致的配置漂移;selfHeal 能力更是一道铁律——谁要是绕过 Git 手工改了集群,ArgoCD 会自动把它纠偏回 Git 声明的样子,从机制上堵死了"偷偷改一下"的口子;而回滚变成了 git revert,回退到任意历史版本就是切一个 Git commit,简单、可靠、可审计。GitOps 还带来了天然的灾备能力——集群挂了,拿 Git 里的声明在新集群一同步就重建出来。从"手工运维、状态成谜"到"Git 即事实、自动收敛",GitOps 是我们整个 DevOps 现代化的中枢:它让交付从"推送式的手工操作"变成了"拉取式的自动收敛",可靠性发生了质变。
五、Argo Rollouts:金丝雀发布 + 指标自动判定
第五仗,是发布策略的现代化。过去我们是全量停机发布——发版要挂维护页、全站停几十分钟、所有流量一次性切到新版本,一旦新版本有问题就是全站事故,回滚还得手忙脚乱。我们引入 Argo Rollouts 做渐进式交付:新版本先只接一小撮流量(金丝雀),系统自动观察这部分流量的错误率、延迟等指标,指标健康才逐步放大流量、最终全量;指标异常则自动暂停甚至回滚,把爆炸半径死死控制在一小撮金丝雀流量内。下面是我们的金丝雀发布定义:
# Argo Rollouts 金丝雀:流量逐步放大,指标自动判定健康与否
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: order-service
spec:
strategy:
canary:
steps:
- setWeight: 5 # 先给新版本 5% 流量
- pause: { duration: 5m }
- analysis: # 自动分析金丝雀的错误率,不达标就回滚
templates:
- templateName: error-rate-check
- setWeight: 25
- pause: { duration: 5m }
- setWeight: 50
- pause: { duration: 10m }
- setWeight: 100 # 全程指标健康,才放到 100%
---
# 分析模板:查询 Prometheus,错误率超 1% 自动判定失败 → 回滚
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata: { name: error-rate-check }
spec:
metrics:
- name: error-rate
interval: 1m
successCondition: "result < 0.01"
provider:
prometheus:
address: http://prometheus:9090
query: |
sum(rate(http_requests_total{status=~"5..",app="order"}[2m]))
/ sum(rate(http_requests_total{app="order"}[2m]))
Argo Rollouts 让我们的发布从"全量停机、出事即全站事故"进化到了"金丝雀渐进、指标自动护航":新版本先只接 5% 流量,系统自动盯着 Prometheus 里的错误率和延迟,健康才逐步放大到 25%、50%、100%,任何一步指标超标就自动暂停或回滚,坏版本的爆炸半径被死死锁在那一小撮金丝雀流量里,绝大多数用户根本无感知;发布全程零停机,再也不用挂维护页、再也不用半夜全员待命盯发布。最关键的是把"这版能不能继续放量"的判断,从"人盯监控肉眼决策"交给了"指标自动判定",既快又客观、还不会因为人的疲劳或误判酿成事故。渐进式交付的本质,是承认"任何新版本都可能有问题",于是用流量灰度 + 指标护栏把这种不确定性的风险降到最低——不是赌新版本没问题,而是设计一套机制让有问题时损失最小。
六、Kubernetes:把"手工加机器"变成"声明期望、自动调度"
容器有了,还需要一个把成百上千容器调度到一堆机器上、并保证它们持续健康运行的编排系统,这就是 Kubernetes。过去我们扩容靠人工加机器、装环境、改负载均衡;某个进程挂了得有人发现、登机器重启;机器宕了上面的服务就跟着挂、得人工迁移。K8s 把这些全自动化了:你只需声明"我要 4 个副本、每个要多少 CPU/内存、健康检查怎么做",K8s 就持续保证实际状态向这个期望收敛——副本挂了自动拉起、节点宕了自动把 Pod 调度到健康节点、滚动更新时自动保证可用副本数。K8s 让我们从"命令式地一步步操作机器"升级到了"声明式地描述期望状态,由系统自动维持":进程崩溃自动重启、节点故障自动迁移、副本数不足自动补齐,过去需要运维半夜爬起来手工处理的故障,现在系统在几秒内自愈;配合 HPA(水平自动伸缩),流量涨了自动加副本、跌了自动缩,大促弹性从"提前几天人工扩容"变成了"按指标实时自动伸缩"。声明式 + 自愈,是 K8s 带给我们最本质的范式转变——我们不再操心"怎么一步步把系统弄成想要的样子",而只需描述"想要什么样子",剩下的交给系统持续维持。这种从命令式到声明式的思维转变,贯穿了我们整个 DevOps 现代化的始终。
七、全栈可观测:指标、日志、链路三位一体
过去我们的可观测就是几张 Zabbix 图看看 CPU、内存,出了问题既不知道是哪个服务、也不知道慢在哪一环、更没法追一个请求的完整调用链,排障基本靠猜和登机器翻日志。现代化中我们建立了指标(Metrics)、日志(Logs)、链路(Traces)三位一体的可观测体系:Prometheus 采集量化指标 + Grafana 可视化看板 + 告警;结构化日志统一采集到中心化日志系统可检索;OpenTelemetry 做分布式链路追踪,一个请求穿过多少服务、每一环耗时多少一目了然。三位一体的可观测让我们的排障从"登机器翻日志靠猜"进化到了"数据驱动的精准定位":Grafana 看板上指标异常一眼可见,点进去关联到具体服务;分布式链路追踪让一个慢请求在哪个服务的哪个调用上卡了几百毫秒清清楚楚;结构化日志可按 traceId 串起一次请求的全部日志。过去定位一个跨服务的性能问题要几小时,现在几分钟就能顺着链路找到根因。我们还基于这套可观测做了 SLO(服务等级目标)管理——给核心接口定下可用性和延迟目标,用错误预算驱动"该稳还是该快"的发布节奏决策。可观测不是锦上添花,而是现代分布式系统的眼睛:看不见,就谈不上治理;而真正的可观测,是能回答"任意一个你事先没预设的问题",而不只是看几个预设好的图。
八、迁移策略:绞杀者模式,新老并行逐步替换
把一个跑了八年的单体 + 手工运维体系迁到容器 + K8s + GitOps,一步到位风险太大,我们用了"绞杀者模式"(strangler pattern):不推倒重来,而是在老系统旁边搭起新平台,把流量和功能一块块从老系统"绞杀"迁移到新平台,直到老系统再无流量、自然枯萎下线。具体节奏是:先把无状态的边缘服务容器化、上 K8s 验证整套工具链;再逐步把核心服务一个个迁过来,每迁一个都新老并行、灰度切流、可回退;有状态服务(数据库等)最后迁、最谨慎。整个迁移历时 87 天、分多个批次,老系统和新平台长期并行,流量像绞杀藤蔓一样一点点从老系统缠绕迁移到新平台,核心业务全程零中断,每迁移一个服务都是可灰度、可观测、可回退的独立小步。最稳的一点是绝不搞"全部停机、一夜切换"的豪赌,而是让新老系统共存足够久,用时间换取每一步的确定性。大型遗留系统的现代化迁移,胜负手从来不是新技术多先进,而是迁移路径多稳——绞杀者模式让我们把一次九死一生的大爆炸重写,拆解成了上百次低风险的小迁移。
九、7 个 P0 事故复盘
7 事故:(1) 容器没设资源 limit,一个内存泄漏的 Pod 把整个节点拖垮殃及邻居,补全 requests/limits;(2) 健康检查只配了存活探针没配就绪探针,新 Pod 没就绪就接流量导致一批请求失败,补 readinessProbe;(3) ArgoCD 自动 prune 误删了一个手工建的资源,改用 Git 全量声明 + 标注例外;(4) 金丝雀分析模板的 Prometheus 查询写错导致坏版本被放量,加查询单测 + 演练;(5) Terraform apply 时 state 锁没释放卡住,配远程 state + 锁超时;(6) HPA 和滚动更新同时触发引发副本抖动,调和两者的节奏;(7) 镜像用了 latest tag 导致回滚拉到的还是新镜像,强制全部用不可变 SHA tag。每个 P0 都做 5-Why 复盘,固化成准入检查清单或 CI 门禁,确保同类问题不再复发。
十、平台工程师的 6 条工程哲学
6 哲学:(1) 一切声明式——描述期望状态,让系统自动收敛,而非命令式手工操作;(2) Git 是唯一事实源,任何变更先改 Git、经评审、再自动落地;(3) 不可变交付,镜像即版本,回滚就是换 tag;(4) 渐进式发布,用流量灰度 + 指标护栏把风险降到最低;(5) 可观测是分布式系统的眼睛,看不见就治理不了;(6) 遗留迁移用绞杀者模式,新老并行、用时间换确定性。这 6 条哲学,是我们用 7 个 P0 事故和 87 天深夜攻坚换来的集体共识。它们共同指向一个认知:现代 DevOps 的核心不是堆砌工具,而是建立起"声明式、GitOps、不可变、渐进式、可观测"的体系化思维——工具会换,但这些原则长青。
十一、重构收益的量化:7 个关键数字
7 数字:(1) 发布耗时:数小时需停机 → 分钟级零停机;(2) 故障恢复(MTTR):小时级 → 分钟级;(3) 部署频率:每周一两次 → 每天数十次;(4) 回滚耗时:手忙脚乱数十分钟 → 一键秒级;(5) 环境搭建:凭记忆点几天 → Terraform apply 几十分钟;(6) 配置漂移导致的事故:频发 → 归零(GitOps selfHeal);(7) 大促扩容:提前几天人工 → HPA 实时自动。这些数字背后,是 87 天里 15 个人无数次的攻坚,但每一个都实打实地转化成了交付效率、系统稳定性和团队幸福感的提升。当我们把这份数据汇报给管理层时,最有说服力的不是任何工具名词,而是"发布从全员半夜待命变成白天点一下、线上状态从成谜变成 Git 可查"这两条。
十二、留给后来者的最后一句话
87 天的 DevOps 现代化战役,我们走过的不只是一条从物理机到 K8s、从手工部署到 GitOps、从停机发布到金丝雀的技术升级路,更是一次从"运维半夜救火 + 状态成谜"到"声明式自动化 + 一切可观测"的体系跃迁。当一次发布从全员半夜待命、挂维护页、停机几十分钟,变成白天提个 PR、ArgoCD 自动金丝雀、指标护航零停机;当线上状态从"没人说得清"变成"Git 里写得明明白白、还自动纠偏";当一个节点半夜宕机、系统几秒自愈而没有任何人被叫醒的那一刻,真正点燃我们的,不是某个具体的工具,而是"交付这件事终于从全团队最痛、最怕、最累的环节,变成了可信赖、可重复、甚至有点无聊的日常"的踏实与笃定。DevOps 没有银弹,关键是理解每个工具和实践解决的是什么问题、代价是什么,然后结合团队规模和业务节奏做体系化取舍。愿每一位还在为手工部署、配置漂移、停机发布、半夜救火而疲惫的同行,都能早日建立起属于自己的现代交付体系。共勉,后会有期。
—— 别看了 · 2026