kube-apiserver 是控制平面的前端组件:以 REST 风格对外暴露资源对象的增删改查与 watch 接口,对内唯一有权读写 etcd。kubectl、调度器、控制器、kubelet 全都是它的客户端。本节讲清资源模型与星型拓扑,这是读懂一切 kubectl 行为与报错的底层语法。
上一章我们把声明写好、这一章把它送进门。进门这一步的所有规矩——资源怎么命名、请求怎么组织、组件怎么对话——都由 API Server 制定。先把它的地位置准,后面两节的安检与账本才有落脚点。
API Server 最容易被低估的属性是:它是整个集群唯一的大门。你可以把它想象成一座大楼的总服务台,任何访客(kubectl、Dashboard、CI 系统)要办事都得排队取号;楼内各部门(调度器、控制器、kubelet)之间也不串门,所有的信息交换都通过服务台的公告板完成。
这种星型拓扑带来三个工程收益:
kubectl 的每个动作都对应一次对 API Server 的 HTTP 请求。get 是读、apply 是写、watch 是订阅,verbs 与资源的组合构成权限模型,2.2 节的 RBAC 就是按这个组合授权的。
写 YAML 时开头的两行,其实是在查一张全局目录。Kubernetes 把资源按"组"分类:核心组(Pod、Service、ConfigMap 等,apiVersion 写 v1)、apps 组(Deployment、StatefulSet 等)、networking 组(Ingress、NetworkPolicy 等)。组之下再分版本,v1、v1beta1 之类,表示成熟度。用一个命令可以看到集群支持的整张目录:
# 列出集群全部资源类型(输出很长,节选) kubectl api-resources # NAME SHORTNAMES APIVERSION NAMESPACED KIND # pods po v1 true Pod # services svc v1 true Service # deployments deploy apps/v1 true Deployment # ingresses ing networking.k8s.io/v1 true Ingress
四列各有讲究:SHORTNAMES 是命令行缩写(po、deploy),日常敲命令能省一半字符;APIVERSION 就是 YAML 第一行要写的内容,写错了这扇门不认;NAMESPACED 决定资源属于某个命名空间还是全集群;KIND 是资源类型的正式名称,YAML 第二行的 kind 从这里取。
kubectl 本质上是这台 HTTP 服务的客户端,绕开它直接看原始应答能加深理解:
# 以原始接口读取一个资源对象(--raw 直接走 HTTP) kubectl get --raw /api/v1/namespaces/production/pods/orders-with-sidecar # 应答节选(JSON): # {"kind":"Pod","apiVersion":"v1", # "metadata":{"name":"orders-with-sidecar","namespace":"production", # "resourceVersion":"184553", "uid":"5c1a..."}, # "spec":{"containers":[...]}, # "status":{"phase":"Running","podIP":"10.244.1.5"}}
应答比 YAML 多出两块:metadata 里的 resourceVersion 与 uid(账本页码与唯一指纹,2.3 节的主角),以及整个 status 段。这里藏着资源模型的第二个关键认知:spec 是你的声明,status 是集群陈述的现状,两者同存于一个对象中,正是"差值驱动"得以实现的数据基础。
💡 关键直觉:读 kubectl 输出时心里默念"我在读一本账的某一页"。get 读页、apply 改页、watch 盯页的变更,整本账的名字叫 etcd。
把 1.2 节的 apply 动作拆开看往返细节。kubectl 并非盲目提交,它会先读后写:
# 提交前加 --dry-run=server 预演:走完全部安检但不落账 kubectl apply -f orders-deploy.yaml --dry-run=server # deployment.apps/orders-api created (server dry run) # 括号注明 dry run:声明合法,但账本未变 # 观察一次真实写入的详细过程 kubectl apply -f orders-deploy.yaml -v=6 # ... GET /apis/apps/v1/namespaces/production/deployments/orders-api (先查旧账) # ... respond 404:不存在,走创建分支 # ... POST /apis/apps/v1/namespaces/production/deployments (提交新账) # ... respond 201:受理成功 # deployment.apps/orders-api created
日志里的三步往返——先 GET 查旧账、404 确认不存在、再 POST 创建——就是 apply 与 create 的本质区别:apply 是"对账式提交",create 是"开户式提交"。这也解释了为什么重复 apply 是安全的:有旧账就打补丁,没旧账就开户,两种路径殊途同归。
背景:新同事把 orders-api 的声明提交到默认空间,找不到自己的 Deployment,怀疑集群坏了。
操作:先看当前上下文与目标空间的账:
# 看当前客户端上下文:命名空间是客户端状态 kubectl config view --minify --output 'jsonpath={..namespace}' # production # 分别查两个空间 kubectl get deployments -n production # NAME READY UP-TO-DATE AVAILABLE AGE # orders-api 3/3 3 3 2d kubectl get deployments -n default # No resources found in default namespace.
结果:声明一直好好地在 production 空间,客户端当时指向了 default 才什么都看不见。
解读:命名空间是资源模型中的逻辑分区,客户端的当前空间决定了"你在哪个房间找东西"。跨空间检索要么写明 -n,要么用 --all-namespaces。这类误会占新手"资源丢失"类问题的大头。
变式:团队协作时建议在 kubeconfig 里为每个人配好默认空间,或在命令别名里固定 -n 参数,把"记着切空间"这种人力负担交给配置;多环境(开发、测试、生产)则常用空间隔离,配合 2.2 节的 RBAC 做到"测试同学看不见生产空间"。
窗口的门规清楚了,下一节把安检流水线拆开:认证确认你是谁、授权决定你能动什么、准入决定这份声明要不要被改——三道关卡一道都少不了。