“告诉服务器’它应该是什么’,而不是’去做什么’。”
— 这一句话在过去十年中改变了API设计的格局。
>
>
无数曾经分道扬镳的API设计方式(河流)最终汇聚成一个巨大的标准(海洋)。
本文涵盖内容
- 传统REST与k8s风格API的根本区别
- apiVersion / kind / metadata / spec / status 这五个骨架的力量
- 声明式(Declarative)模型和协调循环(Reconciliation Loop)的原理
- Crossplane、ArgoCD、Istio、Knative等项目为何全部采用k8s风格
- 以这种风格设计API时必须注意的实战要点
引言 — REST设计者的长期困扰
任何设计过REST API的人都可能遇到过这样的困扰:
- 用户创建用POST /users很简洁,但激活用户应该放在哪里?
- POST /users/{id}/activate?这是一个动词,是否与REST哲学相悖?
- 添加角色时,是POST /users/{id}/roles,还是PUT /users/{id}?
这种困扰反复出现的原因很简单。传统REST始于“对资源的CRUD”,但实际的业务逻辑大多是“状态转换”的连续过程。 激活、批准、取消、重试——所有这些都被强行塞进了REST的语法中。
然而,自2015年以来,随着云原生生态系统的爆炸式增长,一场悄然但强大的变革发生了。Kubernetes所展示的API设计方式已成为事实上的标准。 Crossplane、ArgoCD、Istio、Knative、Tekton、KubeVirt——这些耳熟能详的项目都采用了相同的API模式。甚至连AWS Controllers for Kubernetes (ACK)、GCP Config Connector等云服务提供商也开始将自己的资源封装成k8s风格的API。
为什么会这样?

命令式 vs 声明式 — 最大的范式转变
传统REST是“命令式(Imperative)”的
POST /users → 생성해라
PUT /users/{id}/activate → 활성화해라
POST /users/{id}/roles → 역할을 추가해라
DELETE /users/{id} → 삭제해라
客户端指示服务器“去做什么”。每个请求都是一个动作,如果失败,客户端必须自己编写重试逻辑。两次调用可能会导致两次执行(幂等性问题)。
k8s风格是“声明式(Declarative)”的
apiVersion: v1
kind: User
metadata:
name: dohyeon
spec:
active: true
roles:
- admin
- developer
这个YAML通过PUT /apis/v1/users/dohyeon一次性发送。服务器会比较当前状态(status)和期望状态(spec),并自行弥补差异。这就是协调循环(Reconciliation Loop)。
客户端只需声明“它应该是什么”。激活、添加角色、删除——所有这些都集成到一个端点、一个资源文档中。
为什么这很重要?
分布式系统中最困难的问题是部分失败(partial failure)。网络中断、请求重复、顺序错乱。在命令式API中,要应对这些问题,客户端必须直接管理复杂的有限状态机。然而,声明式API是基于收敛(convergence)设计的,因此即使发送相同的请求多次,结果也是一致的。这就是GitOps、基础设施即代码与k8s风格API完美契合的原因。
k8s API的5个骨架
k8s风格的资源总是包含以下5个字段:
apiVersion: apps/v1 # (1) 哪个API组/版本 (GVK)
kind: Deployment # (2) 是什么类型的资源
metadata: # (3) 通用元数据
name: web-server
namespace: production
labels:
app: frontend
annotations:
team: platform
spec: # (4) 期望状态 (用户编写)
replicas: 3
selector:
matchLabels:
app: frontend
status: # (5) 实际状态 (控制器编写)
replicas: 3
availableReplicas: 3
conditions:
- type: Available
status: "True"
这个结构蕴含着深刻的哲学。
- apiVersion + kind — 通过Group/Version/Kind (GVK)唯一标识资源。即使版本升级,现有API也不会被破坏。
- metadata — 名称、命名空间、标签、注解等所有资源共享的元数据。可以统一进行搜索、过滤和所有权追踪。
- spec / status 分离 — 将用户编写的区域(spec)和系统编写的区域(status)物理分离。明确了谁对什么负责。
只要遵循这5个骨架,kubectl、Helm、ArgoCD、Crossplane等现有工具就能直接工作。这就是标准化的复利效应。
协调循环(Reconciliation Loop) — 简单却最强大的思想
k8s的核心是控制器(Controller)。控制器会不断地运行这样的循环:
- 观察当前状态(status)。
- 与期望状态(spec)进行比较。
- 如果存在差异,则采取行动来缩小差异。
- 返回步骤1。
由于这个循环,三个惊人的特性随之而来:
- 自我修复(Self-healing) — 如果Pod宕机,控制器会自动重新创建。客户端无需重试。
- 事件驱动 — 通过Watch API (GET /apis/…/pods?watch=true)实时接收变更流。摆脱了轮询地狱。
- 幂等性 — 即使发送相同的spec 100次,循环也只会收敛一次。
为何成为标准 — 5个决定性原因
1. CRD让任何人都能创建相同结构的API
自定义资源定义(Custom Resource Definition, CRD)通过一个YAML文件解决了“我们公司的资源也想像k8s资源一样使用”的问题。OpenAPI schema、验证、版本管理、Watch——所有这些都免费提供。
2. kubectl这一统一的客户端
kubectl get, kubectl apply, kubectl describe, kubectl logs。无论资源是什么,命令体系都是相同的。学习新工具的成本几乎趋近于零。
3. 与GitOps的完美契合
由于是声明式的,可以将YAML文件上传到Git仓库,ArgoCD/Flux会直接将“期望状态”同步到集群。Git成为单一事实来源(Single Source of Truth)。
4. 通过Operator模式将运维经验代码化
数据库备份、Kafka重新平衡、Redis集群故障转移等运维经验可以封装到控制器中。无需人工在凌晨3点起床进行SSH操作。
5. 生态系统的复利效应
当Crossplane以k8s风格抽象AWS资源后,ArgoCD也能通过GitOps管理AWS S3存储桶。工具调用工具的网络效应。这已是不可逆转的趋势。
亲手构建k8s风格API
即使没有Kubernetes,一般的后端系统也可以采用这种风格。让我们通过FastAPI看一个非常简单的例子。
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional, List
app = FastAPI()
class Metadata(BaseModel):
name: str
labels: Optional[dict] = {}
class UserSpec(BaseModel):
active: bool = True
roles: List[str] = []
class UserStatus(BaseModel):
phase: str = "Pending" # Pending | Ready | Failed
observed_generation: int = 0
class User(BaseModel):
apiVersion: str = "v1"
kind: str = "User"
metadata: Metadata
spec: UserSpec
status: Optional[UserStatus] = None
# 用户只发送spec。status由控制器填充。
@app.put("/apis/v1/users/{name}")
def apply_user(name: str, user: User):
# 1) 保存spec
# 2) 独立的worker(控制器)观察实际状态并更新status
return {"applied": user.metadata.name}
# Watch通过SSE/WebSocket提供变更流。
核心是遵守“用户只编写spec,status由系统编写”的边界。只要很好地遵守这个边界,API的职责归属就会清晰得多。
⚠️ 注意事项 / 常见错误
- 不要让用户写入status。 如果spec/status分离原则被打破,协调循环的基础就会动摇。必须在服务器端阻止对status的写入。
- 不要混用命令式端点。 一旦添加POST /users/{id}/activate之类的东西,声明式方法的优势就会减半。首先考虑激活是否可以用spec.active: true来表达。
- 版本兼容性通过GVK管理。 从一开始就考虑v1beta1 → v1的转换过程,并设计转换webhook或等效机制。
- 协调并非免费。 如果循环周期、退避策略和错误处理设计不当,可能会导致无限重试炸弹。指数退避和死信队列是必需的。
- 并非所有API都需要采用k8s风格。 简单的查询性CRUD(如博客文章列表)仍然更适合传统REST。当存在状态转换和长期运行(long-running)任务时,k8s风格才能大放异彩。
✅ 总结 / 结束语
k8s风格API成为标准的原因可以一言以蔽之:
“它在API设计层面以声明式方式解决了分布式系统的本质难题。”
- 命令式 → 声明式转变不仅仅是偏好差异,而是处理部分失败和收敛的工程优势。
- apiVersion / kind / metadata / spec / status 这五个骨架是可扩展性和兼容性的基础。
- CRD、kubectl、GitOps和Operator模式的网络效应使生态系统进入自我强化(self-reinforcing)阶段。
- 如果正在构建新的云平台或工具,遵循这种风格所获得的免费兼容性是巨大的。
下一步,建议尝试使用Operator SDK / Kubebuilder直接创建CRD和Controller,或者使用Crossplane以声明式方式管理云资源。一旦熟悉了这种模型,你会发现很难再回到传统的REST。

发表回复