从过程式、命令式到声明式:编程与运维的一次“权力转移”

Posted on Tue 27 January 2026 in Method

Abstract 从过程式、命令式到声明式编程和运维的转变
Authors Walter Fan
Category 方法论 / 工程实践
Status v1.0
Updated 2026-01-27
License CC-BY-NC-ND 4.0

开篇:你以为你在“部署”,其实你在“追自己留下的脚印”

我见过最经典的一类线上事故,不是代码写错了,也不是机器挂了,而是四个字:“跟上次不一样”

你上周用一串命令把服务推到线上:

kubectl set image deploy/api api=...:v12
kubectl scale deploy/api --replicas=6
kubectl patch deploy/api -p '...'

这周你想复现“上周那个稳定状态”,但你突然发现:

  • 你记不清当时到底 patch 了哪些字段
  • 同事在你不知情的情况下又改了几处(还挺合理)
  • 线上对象已经长得跟你的记忆完全不一样

于是你开始在群里问:“谁动了我的 deployment?”
然后大家开始一起演一出“没有监控也能复盘”的职场魔幻剧。

这就是为什么我说:从过程式/命令式到声明式的转变,本质是一次“权力转移”——把“怎么做”的权力从人手里交给系统,让人只对“想要什么”负责。


一、三种范式,三种对“控制”的态度

先把概念说清楚,别上来就被术语劝退。

1) 过程式(Procedural):我亲自下场,一步一步来

过程式最像“写菜谱”:先切葱再下锅,火候几分钟,步骤是核心。

  • 你关心的是 How:每一步怎么执行
  • 你写的是流程:if/for/while、函数调用顺序、状态如何变化

优点:清晰、可控、性能调优空间大
缺点:当系统变复杂、并发变多,“步骤”容易变成灾难现场

2) 命令式(Imperative):你照我说的做

命令式不一定写在代码里,也可能写在命令行、脚本、控制台里。

  • 你关心的是 做什么动作:create / update / delete / scale
  • 你通常在操作“现在的对象”(live state)

它的最大问题不是“不好用”,而是不可回放

  • 同样一串命令在不同初始状态下,结果可能不一样
  • 命令执行的副作用(顺序、失败重试、部分成功)很难完全复盘

3) 声明式(Declarative):我只说我想要什么

声明式最像“点外卖”:你说要一份麻辣香锅,不必告诉商家先洗菜还是先开火。

  • 你表达的是 What:目标状态/约束条件/不变量
  • 系统负责 How:规划、调度、重试、收敛(reconcile)

这里有个关键提醒:

声明式不是“没有过程”,而是“过程不由你显式写出来”。

过程仍然存在,只是被下沉到框架/控制器/引擎里了。


二、为什么声明式越来越火:因为我们在管理“复杂系统”,不是单机程序

声明式流行,不是因为它更“高级”,而是因为它更适合几个现实约束。

1) 规模化:人类不擅长记住 200 个细节

当资源少、环境简单时,命令式挺爽:改一行、重启一下就好了。

但当你要管的是:

  • 多集群、多环境(dev/staging/prod)
  • 多租户、多团队协作
  • 还要合规、要审计、要可追溯

命令式就会把人逼成“记忆型动物”:靠脑子记、靠聊天记录找。

2) 幂等与可重复:声明式天然更接近“可重放”

声明式通常要求你描述一个收敛目标,系统可以反复执行直到达成。

这带来两件工程上的好处:

  • 幂等:同一个声明反复 apply,结果应该一致
  • 可复制:你可以把目标状态放进版本库,让环境可重建

3) 协作与审计:Git 比微信群靠谱

现实里最可靠的“变更记录”,不是谁在群里说了一句“我改了下”,而是:

  • 有 diff
  • 有 commit message
  • 有 reviewer
  • 有回滚点

声明式天然适合和 Git 绑定,这也是 GitOps 能跑起来的原因。


三、运维世界的转变:从“我手动敲命令”到“系统帮我收敛”

运维的演进其实很像软件工程的演进:从“个人英雄主义”走向“系统化协作”。

1) 命令式运维:手工命令 + 脚本(最常见,也最容易背锅)

早期的典型画面:

  • SSH 上去改配置、重启服务
  • 写个脚本跑一遍,希望别炸
  • 炸了就再 SSH 上去修

这套方式的问题在于:操作和结果绑定得太紧,而环境差异、顺序差异会把你拖进泥潭。

2) IaC(基础设施即代码):把“基础设施”变成可版本化的声明

Terraform 是一个典型代表:你写配置描述目标基础设施,工具负责 plan/apply,并通过状态(state)管理“现实长什么样”。官方文档里对 Terraform 的定位就是“infrastructure as code”。
你把它看成一句话就行:

把基础设施从“手工操作对象”,升级成“可版本化的目标状态”。

Terraform 文档入口(可扩展阅读):https://developer.hashicorp.com/terraform/docs

3) GitOps:把“部署动作”变成“状态同步”

当你把目标状态放在 Git 里时,“部署”就不再是 CI 里跑一堆命令,而更像:

  • Git 是事实来源(source of truth)
  • 集群里有个控制器持续对比:现实 vs 目标
  • 不一致就收敛(同步),一致就躺平(保持)

Argo CD 的官方文档里就明确把它定义为“declarative, GitOps continuous delivery”。
文档入口:https://argo-cd.readthedocs.io/en/stable/

这类系统最关键的变化是:你不再“推送”变更到集群,而是让集群“拉取并对齐”到 Git 的目标状态。


四、Kubernetes 是声明式运维的“教科书”

K8s 的核心设计就是“声明式 + 控制器收敛”。你写一个 Deployment,真正有用的不是 YAML 的格式,而是这句话:

你告诉它你想要的副本数、镜像版本、策略;它负责把系统状态收敛到你想要的样子。

Kubernetes 官方文档把对象管理分成三类:命令式、命令式配置、声明式配置,其中声明式推荐用 kubectl apply 管理目录级配置。
参考:https://kubernetes.io/docs/tasks/manage-kubernetes-objects/declarative-config/

声明式的“进阶版”:Server-Side Apply(SSA)

当多个系统/人都在“声明式地”改一个对象时,就会出现一个现实问题:谁说了算?

SSA 的思路是:让 apiserver 记录“字段归属”(谁管理哪个 field),冲突就显式报出来,而不是默默互相覆盖。
参考:https://kubernetes.io/docs/reference/using-api/server-side-apply/

这背后其实是在解决一个更大的问题:声明式协作的边界


五、从编程视角看:声明式不是“新语言”,而是一种抽象层级

声明式并不只存在于运维。

1) SQL:你说“要什么数据”,优化器决定“怎么查”

SQL 经常被拿来当声明式的例子:你写 SELECT ... WHERE ...,并不指定索引怎么用、join 顺序怎么排。
PostgreSQL 文档入口:https://www.postgresql.org/docs/15/sql.html

2) UI 世界:你描述“界面应该长什么样”,框架负责更新

React 这类框架的心智模型也是声明式:你描述 UI = f(state)。
你不需要手动做 DOM diff(那是框架的事),你只负责“目标长相”。

3) 但别神化:声明式的代价是真实存在的

声明式把“怎么做”下沉给系统,代价就是:

  • 你要理解系统的收敛机制(reconcile loop)
  • 你要接受一定的“黑盒感”
  • 排错时得学会观察“目标/现实/差异”三件套

六、怎么落地:把声明式用成“省心”,而不是用成“更大的 YAML 地狱”

我自己总结的落地顺序是:先定边界,再定事实来源,再定收敛方式。

1) 先问一句:哪些东西适合声明式?

适合声明式的通常是:

  • 资源类:机器、网络、权限、部署对象(可描述的目标状态)
  • 可收敛:系统能通过重试/调度把它对齐

不太适合直接声明式化的通常是:

  • 需要复杂流程编排且强顺序依赖的(当然也能做,但成本更高)
  • 本质是一次性动作/事务的(比如“把这批数据迁移完”)

2) 让 Git 成为“事实来源”,别让 Slack/微信群当数据库

  • 目标状态进 Git
  • 变更走 PR
  • 重要环境的 apply/sync 尽量自动化

3) 给“收敛”留出口:观察、回滚、保护阈值

声明式系统最怕两件事:

  • 漂移(drift):现实被手工改了,目标没更新
  • 自动收敛误伤:目标写错了,系统很勤奋地把你拉进坑里

所以你需要配套:

  • diff(变更预览)
  • rollback(回滚路径)
  • guardrail(例如审批、保护分支、变更窗口、限速)

七、常见坑:声明式不是银弹,它只会把坑换个形状

  • 把命令式当声明式:明明每次都 kubectl patch,还以为自己在 GitOps
  • 一半声明式一半手工改:最后一定会遇到“到底谁是事实来源”的战争
  • 状态文件/字段归属没管好:Terraform state、K8s managedFields 这类东西,都是“系统记忆”,丢了就会失忆
  • 把一切都 YAML 化:复杂逻辑硬塞 YAML,往往是“用错抽象层级”

TL;DR(给只想快速抓重点的人)

  • 过程式:我写步骤(How)
  • 命令式:我发命令(Do)
  • 声明式:我描述目标(What),系统负责收敛(Reconcile)
  • 声明式流行不是审美,而是复杂系统时代对 幂等、协作、审计、可重复 的刚需

可执行清单(Checklist)

  • [ ] 先划清边界:哪些资源/配置要声明式管理,哪些保留流程化
  • [ ] 选定事实来源:Git 是唯一目标状态(至少对 prod 如此)
  • [ ] 设计收敛机制:谁负责对齐(人、CI、控制器、GitOps 工具)
  • [ ] 给变更加护栏:diff、审批、回滚、限速
  • [ ] 规定“禁止手工改”的范围(或明确“手工改必回填”)
  • [ ] 监控漂移:现实与目标不一致要能被发现、被告警

互动提问

你现在团队里最“命令式”的一块运维工作是什么?是 Kubernetes 的 kubectl patch/scale,还是云资源的控制台点点点?我可以基于你的场景,给一条更具体的迁移路线(先收敛哪几类资源最划算、怎么避免一上来就掉进 YAML 地狱)。


Reference

  • Kubernetes: Declarative object configuration: https://kubernetes.io/docs/tasks/manage-kubernetes-objects/declarative-config/
  • Kubernetes: Server-Side Apply: https://kubernetes.io/docs/reference/using-api/server-side-apply/
  • Terraform docs: https://developer.hashicorp.com/terraform/docs
  • Argo CD docs: https://argo-cd.readthedocs.io/en/stable/
  • PostgreSQL: The SQL Language: https://www.postgresql.org/docs/15/sql.html

本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。