跳到主要内容

资源对象与 API 设计

API Group​

Kubernetes 里所有资源都通过 API Server 的 REST API 操作,URL 路径本身就体现了资源的组织方式。为了避免所有资源挤在一起,K8s 按功能领域把 API 分成若干组(Group):

  • 核心组(core group):历史最早的一批资源,组名为空,路径是 /api/v1(单数 api)。Pod、Service、Node、Namespace、ConfigMap、Secret、PV/PVC 都在这一组。
  • 命名组(named group):后来新增的资源都归属某个具体的组,路径是 /apis/组名/版本(复数 apis)。例如 Deployment 在 apps 组,路径是 /apis/apps/v1。

几条完整的资源路径长这样:

# 命名组、命名空间级资源
/apis/apps/v1/namespaces/default/deployments/web

# 核心组、命名空间级资源
/api/v1/namespaces/default/pods/web-6d9f8c5b7-x2k9m

# 集群级资源(路径里没有 namespaces 这一段)
/apis/storage.k8s.io/v1/storageclasses/standard
/api/v1/nodes/node-1

命名空间级资源的路径里带 namespaces/空间名 一段;集群级资源(Node、StorageClass、ClusterRole 等)不带。

常见组与资源对照:

组路径常见资源
核心组(组名为空)/api/v1Pod、Service、Node、Namespace、ConfigMap、Secret、PersistentVolume、PersistentVolumeClaim
apps/apis/apps/v1Deployment、StatefulSet、DaemonSet、ReplicaSet
batch/apis/batch/v1Job、CronJob
networking.k8s.io/apis/networking.k8s.io/v1Ingress、NetworkPolicy
rbac.authorization.k8s.io/apis/rbac.authorization.k8s.io/v1Role、ClusterRole、RoleBinding、ClusterRoleBinding
storage.k8s.io/apis/storage.k8s.io/v1StorageClass、CSIDriver
apiextensions.k8s.io/apis/apiextensions.k8s.io/v1CustomResourceDefinition(CRD)
policy/apis/policy/v1PodDisruptionBudget

分组的意义在于:每个子系统可以独立演进版本、独立开关,CRD 扩展本质上也是往 API Server 注册一个新的组。

想直接看原始 API 也不需要 Dashboard:

# 看核心组下有哪些资源
kubectl get --raw /api/v1 | head -c 500

# 看 apps 组 v1 版本下的资源列表
kubectl get --raw /apis/apps/v1 | python3 -m json.tool | head -40
apiVersion 字段的两种写法

YAML 里的 apiVersion 就是「组/版本」的拼接:核心组没有组名,只写 v1;命名组要写完整,如 apps/v1、batch/v1、networking.k8s.io/v1。

Version​

同一个组里可以并存多个版本,版本号代表这套 API 的成熟度:

级别典型命名默认开启兼容性承诺适用场景
Alphav1alpha1否(需要显式开 feature gate)不保证,可能有 bug,随时整个删掉尝鲜、测试环境
Betav1beta1通常是功能基本稳定,但字段细节仍可能变非关键业务可谨慎使用
Stable(GA)v1、v2是长期维护、向后兼容生产环境

几点实践经验:

  • 一个对象在 etcd 里只存一份(按内部的 storage version 存储),不同版本之间由 API Server 做转换。你用 v1 读写的对象,控制器内部可能用的是另一个版本,互相不影响。
  • beta 资源毕业到 stable 后,老的 beta 版本会被标记废弃(deprecated),若干个大版本后彻底移除。最出名的例子是 Ingress:extensions/v1beta1 → networking.k8s.io/v1beta1 → networking.k8s.io/v1(1.19 GA,旧版本从 1.22 起全部移除)。

查看集群当前支持的所有「组/版本」组合:

kubectl api-versions
admissionregistration.k8s.io/v1
apiextensions.k8s.io/v1
apps/v1
authentication.k8s.io/v1
authorization.k8s.io/v1
autoscaling/v1
autoscaling/v2
batch/v1
certificates.k8s.io/v1
coordination.k8s.io/v1
networking.k8s.io/v1
policy/v1
rbac.authorization.k8s.io/v1
scheduling.k8s.io/v1
storage.k8s.io/v1
v1
(输出有删减)

注意最后那行孤零零的 v1,它就是核心组——没有组名,所以只显示版本号。

升级集群前先查 beta API 依赖

beta 版 API 会随集群升级被移除,还在用它的资源升级后会创建失败。升级前对照发行说明检查 kubectl api-resources 的输出,或者用 pluto 这类工具扫描现有清单里是否还引用已废弃的 apiVersion。

Kind​

Kind 是资源的类型名。YAML 里 apiVersion + kind 两个字段共同确定「我要创建的是什么东西」。

常用 Kind 与 apiVersion 对照表:

KindapiVersion说明
Podv1最小调度单位,一组共享网络的容器
Servicev1服务发现与负载均衡入口
ConfigMap / Secretv1配置与敏感信息
Namespacev1命名空间(集群级)
Nodev1集群节点(集群级)
PersistentVolumev1集群级存储资源
PersistentVolumeClaimv1存储申请(命名空间级)
ServiceAccountv1Pod 访问 API 的身份
Deploymentapps/v1无状态应用
StatefulSetapps/v1有状态应用
DaemonSetapps/v1每个节点跑一份
ReplicaSetapps/v1副本集,一般由 Deployment 管理,不直接用
Jobbatch/v1一次性任务
CronJobbatch/v1定时任务
Ingressnetworking.k8s.io/v1七层流量入口
NetworkPolicynetworking.k8s.io/v1Pod 间网络访问控制
StorageClassstorage.k8s.io/v1存储类,动态供给 PV(集群级)
Role / ClusterRolerbac.authorization.k8s.io/v1RBAC 权限定义
HorizontalPodAutoscalerautoscaling/v2自动扩缩容
CustomResourceDefinitionapiextensions.k8s.io/v1自定义资源定义(集群级)

这张表不用背,日常用命令现查更靠谱:

kubectl api-resources
NAME                              SHORTNAMES   APIVERSION                        NAMESPACED   KIND
configmaps cm v1 true ConfigMap
endpoints ep v1 true Endpoints
namespaces ns v1 false Namespace
nodes no v1 false Node
persistentvolumeclaims pvc v1 true PersistentVolumeClaim
persistentvolumes pv v1 false PersistentVolume
pods po v1 true Pod
secrets v1 true Secret
serviceaccounts sa v1 true ServiceAccount
services svc v1 true Service
daemonsets ds apps/v1 true DaemonSet
deployments deploy apps/v1 true Deployment
replicasets rs apps/v1 true ReplicaSet
statefulsets sts apps/v1 true StatefulSet
cronjobs cj batch/v1 true CronJob
jobs batch/v1 true Job
ingresses ing networking.k8s.io/v1 true Ingress
networkpolicies netpol networking.k8s.io/v1 true NetworkPolicy
clusterroles rbac.authorization.k8s.io/v1 false ClusterRole
clusterrolebindings rbac.authorization.k8s.io/v1 false ClusterRoleBinding
(输出有删减)

输出里每一列都有用:

  • SHORTNAMES 是简写,kubectl get po 等价于 kubectl get pods。
  • NAMESPACED 表示是否命名空间级资源(见下文 Namespace 一节)。
  • 想精确过滤可以加参数:kubectl api-resources --namespaced=false 只看集群级资源,kubectl api-resources --api-group=apps 只看 apps 组。

再进一步,想知道某个 Kind 的字段怎么写,用 explain 查,比翻官方文档快:

kubectl explain deployment.spec.template.spec.containers
记不住没关系

api-resources、api-versions、explain 这三个命令分别回答「集群里有哪些资源」「支持哪些版本」「字段怎么写」三个问题,够用了。

Metadata​

每个资源对象都有一个 metadata 段,记录对象的身份信息和公共字段。常用字段如下:

字段谁填说明
name用户对象名,同一命名空间内同类型唯一。小写字母、数字、-、.,最长 253 字符
namespace用户所属命名空间,不写则用 kubectl 当前 context 的默认空间
labels用户键值对标签,用于组织和选择(见 Labels 一节)
annotations用户/系统键值对注解,存非标识性的附加信息(见 Annotation 一节)
uid系统集群内全局唯一 ID,由 API Server 在创建时分配
resourceVersion系统对象内部版本号,每修改一次就递增,用于乐观并发控制
generation系统spec 的代数,spec 每变一次加 1,控制器靠它判断「这版期望状态处理过没有」
creationTimestamp系统创建时间(RFC 3339 格式)
deletionTimestamp系统对象带 finalizer 时,删除请求会先写这个字段,表示「正在删除中」
ownerReferences控制器记录属主对象(见 OwnerReference 一节)
finalizers控制器删除前必须完成的清理动作列表(见 Finalizer 一节)

一份真实的 metadata 大概长这样:

metadata:
name: web
namespace: default
labels:
app.kubernetes.io/name: web
annotations:
kubernetes.io/change-cause: "image updated to nginx:1.27"
# ---- 以下字段由系统维护,不需要也不应该手写 ----
uid: 3f8c1d2e-7a4b-4c5d-9e6f-1a2b3c4d5e6f
resourceVersion: "482135"
generation: 2
creationTimestamp: "2024-05-01T08:00:00Z"
name、namespace、uid 创建后不可改

想「改名」只能删掉重建。resourceVersion 则参与并发控制:两个客户端同时改一个对象,后提交的会因为版本号对不上而冲突,必须重新读取再改。

Labels​

Label 是挂在对象上的键值对标签,是 K8s 组织和选择资源的基础机制:Service 靠 label 找到要代理的 Pod,Deployment 靠 label 管理 Pod,日常运维查询也靠 label 过滤。

格式要求:

  • key 可以带一段 DNS 前缀,形如 app.kubernetes.io/name;不带前缀的 app、env 也合法。
  • value 最长 63 字符,字母或数字开头结尾,中间可以有 -、_、.。

官方约定了一套推荐标签(前缀 app.kubernetes.io),建议直接采用:

标签含义示例
app.kubernetes.io/name应用名mysql
app.kubernetes.io/instance实例名,区分同一应用的多份部署mysql-prod
app.kubernetes.io/version应用版本"8.0.36"
app.kubernetes.io/component在架构中的角色database
app.kubernetes.io/part-of所属的大系统mall
app.kubernetes.io/managed-by管理工具helm

YAML 示例:

metadata:
name: web
labels:
app.kubernetes.io/name: web
app.kubernetes.io/instance: web-prod
app.kubernetes.io/version: "1.2.3"
env: prod
tier: frontend

命令行增删改:

# 打标签
kubectl label pod web-6d9f8c5b7-x2k9m env=prod

# 改标签(key 已存在时要加 --overwrite)
kubectl label pod web-6d9f8c5b7-x2k9m env=staging --overwrite

# 删标签(key 后面跟个减号)
kubectl label pod web-6d9f8c5b7-x2k9m env-

# 查询时显示标签列
kubectl get pods --show-labels

Label 创建后可以随时改,改 label 不会重建对象——这也是它好用的地方。但要小心:改了 label 可能让 Pod 脱离(或被错误纳入)某个 Service、Deployment 的管理范围。

Selector​

Selector(标签选择器)是「按 label 筛选对象」的表达式,有两种语法。

等值匹配(Equality-based)​

最简单的一种:判断 key 等不等于某个值。支持 =、==、!= 三种操作符,多个条件之间是与关系。YAML 里对应 matchLabels:

selector:
matchLabels:
app: web
tier: frontend

集合匹配(Set-based)​

更灵活:判断 key 的值是否落在某个集合里。YAML 里对应 matchExpressions,支持四种运算符:

运算符含义是否需要 values
In值在集合中是
NotIn值不在集合中是
Exists存在这个 key(不管值是什么)否
DoesNotExist不存在这个 key否
selector:
matchLabels:
app: web
matchExpressions:
- key: tier
operator: In
values: ["frontend", "backend"]
- key: track
operator: NotIn
values: ["canary"]
- key: debug
operator: DoesNotExist

matchLabels 和 matchExpressions 可以混用,所有条件之间都是与关系。

kubectl 的 -l 查询​

日常排障最常用的是 kubectl get 的 -l 参数,两种语法都支持:

# 等值匹配
kubectl get pods -l app=web
kubectl get pods -l 'app=web,tier=frontend'
kubectl get pods -l 'app!=web'

# 集合匹配
kubectl get pods -l 'env in (prod,staging)'
kubectl get pods -l 'env notin (dev)'
kubectl get pods -l 'debug' # Exists:有这个 key 就行
kubectl get pods -l '!debug' # DoesNotExist:没有这个 key

哪里会用到 selector​

  • Service 的 spec.selector:只支持等值匹配,选出要代理的 Pod。
  • Deployment / StatefulSet / DaemonSet / Job 的 spec.selector:管理 Pod 的依据,必须和 Pod 模板里的 labels 对得上,支持两种语法。
  • NetworkPolicy 的 podSelector、PVC 的 spec.selector、HPA 等也都靠 selector 找目标。
Deployment 的 selector 创建后不可改

spec.selector 是不可变字段,创建后再改,apply 会直接报错。规划标签时先想清楚,别把每次发版都变的值(比如版本号)放进 selector 命中的 label 里。

Annotation​

Annotation 和 Label 一样是键值对,但定位完全不同:它不参与选择,只用来记录附加信息。

对比项LabelAnnotation
用途标识对象、被 selector 选择存说明性、辅助性信息
能否用于查询筛选能(-l、selector)不能
value 限制最长 63 字符,字符集受限宽松,可以存一大段文本(如 JSON),但一个对象所有 annotation 总和不能超过 256 KB
典型使用者人和控制器工具、控制器、第三方系统

常见的系统注解(看对象详情时经常会遇到):

注解含义
kubectl.kubernetes.io/last-applied-configurationkubectl apply 记录的上一次完整配置,三方合并靠它
kubernetes.io/change-cause本次变更的说明,会显示在 rollout history 里
deployment.kubernetes.io/revisionDeployment 的修订号,回滚靠它定位
pv.kubernetes.io/bound-by-controllerPVC 由绑定控制器完成绑定的标记
service.beta.kubernetes.io/ 开头的一系列云厂商负载均衡器的各种配置(类型、内网、证书等)
nginx.ingress.kubernetes.io/rewrite-targetingress-nginx 的 URL 重写规则

命令行操作和 label 几乎一样:

# 写注解
kubectl annotate deployment web kubernetes.io/change-cause="image updated to nginx:1.27"

# 查看
kubectl describe pod web-6d9f8c5b7-x2k9m | grep -A5 Annotations

# 删除(key 后面跟个减号)
kubectl annotate deployment web kubernetes.io/change-cause-

实际使用中的分工习惯:会被筛选查询的信息放 label(app、env、tier 这类),说明性、配置性、给工具读的信息放 annotation。

Namespace​

Namespace 把一个物理集群划分成多个逻辑空间:同一类资源的名字在不同命名空间里可以重复;RBAC 权限、ResourceQuota 配额、NetworkPolicy 网络策略也都以命名空间为配置边界。

注意它提供的是弱隔离:默认情况下不同命名空间的 Pod 网络是互通的。要做到真正的多租户隔离,得配合 RBAC、NetworkPolicy、ResourceQuota 一起用。

初始的四个命名空间​

新集群建好后自带四个:

命名空间用途
default不指定 namespace 时的默认落点
kube-system控制平面组件(apiserver、scheduler、CoreDNS 等)所在空间,别把业务放进去
kube-public所有人(包括未认证用户)可读,主要放集群信息 ConfigMap,一般用不到
kube-node-lease存放每个节点的 Lease 对象,kubelet 靠它上报心跳,决定节点是否 Ready

哪些资源是命名空间级,哪些是集群级​

命名空间级(属于某个 ns)集群级(不属于任何 ns)
Pod、Service、EndpointsNode
Deployment、StatefulSet、DaemonSet、ReplicaSetNamespace 自身
Job、CronJobPersistentVolume、StorageClass
ConfigMap、Secret、ServiceAccountClusterRole、ClusterRoleBinding
PersistentVolumeClaimCustomResourceDefinition
Ingress、NetworkPolicyAPIService、Webhook 配置
Role、RoleBinding、ResourceQuota、LimitRange、HPA以及所有声明为 Cluster 作用域的 CRD

记不清就用命令查(就是 api-resources 输出里的 NAMESPACED 列):

# 只看命名空间级资源
kubectl api-resources --namespaced=true

# 只看集群级资源
kubectl api-resources --namespaced=false

常用操作:

kubectl get ns                                       # 列出所有命名空间
kubectl create ns web # 创建
kubectl get pods -n kube-system # 指定命名空间查询
kubectl get pods -A # 跨所有命名空间查询
kubectl config set-context --current --namespace=web # 把当前 context 的默认空间切到 web
kubectl delete ns 会删掉里面的一切

删除命名空间会连带删除其中的所有资源——Pod、Service、ConfigMap,以及 PVC 绑定的数据,且没有回收站。生产环境执行前务必确认空间名没敲错。

OwnerReference​

K8s 里很多对象不是孤立的:Deployment 创建 ReplicaSet,ReplicaSet 创建 Pod。这种父子关系靠子对象 metadata 里的 ownerReferences 字段记录:

# 一个由 ReplicaSet 创建的 Pod
metadata:
name: web-6d9f8c5b7-x2k9m
ownerReferences:
- apiVersion: apps/v1
kind: ReplicaSet
name: web-6d9f8c5b7
uid: 3f8c1d2e-7a4b-4c5d-9e6f-1a2b3c4d5e6f
controller: true
blockOwnerDeletion: true

字段含义:

  • apiVersion / kind / name / uid:四元组精确指向属主对象,uid 用来防止「同名不同对象」的混淆。
  • controller: true:表示这个属主是当前的管理控制器,一个对象最多只能有一个 controller 属主。
  • blockOwnerDeletion: true:前台级联删除时,属主要等这个子对象删完才能消失(见 Garbage Collection 一节)。

这个字段一般不需要手写——控制器创建子对象时会自动填上。你只要知道它存在,因为接下来的级联删除、垃圾回收全靠它工作。

查看某个 Pod 的属主:

kubectl get pod web-6d9f8c5b7-x2k9m -o jsonpath='{.metadata.ownerReferences}'

几条约束要知道:

  • 命名空间级对象(如 Pod)的属主必须在同一个命名空间内,或者是集群级对象;集群级对象的属主只能是集群级对象。
  • 跨命名空间设置属主是无效的,垃圾回收器会把它当作「属主不存在」处理,可能误删子对象——不要这么干。

Finalizer​

Finalizer 是 metadata 里的一个字符串列表,作用只有一个:只要这个列表非空,对象就删不掉。

有了 finalizer,删除流程变成两段:

  1. 你发出删除请求,API Server 不真删,只给对象打上 deletionTimestamp,对象进入「删除中」状态。
  2. 对应的控制器看到这个状态后开始清理(比如释放云盘、通知负载均衡器),清理完把自己负责的那个 finalizer 从列表里摘掉。等列表空了,对象才真正从 etcd 里消失。

常见的 finalizer:

Finalizer出现在哪保护什么
kubernetes.io/pv-protectionPersistentVolumePV 还被 PVC 绑定时禁止删除,防止数据被误回收
kubernetes.io/pvc-protectionPersistentVolumeClaimPVC 正被 Pod 使用时禁止删除
kubernetesNamespace命名空间控制器要先把空间内所有资源删干净,才放行空间本身
foregroundDeletion各种对象前台级联删除时使用(见下一节)

举个例子,直接删一个还在绑定状态的 PV:

kubectl delete pv data-pv
persistentvolume "data-pv" deleted

命令虽然返回了 deleted,但 kubectl get pv 会看到它卡在 Terminating 状态——直到它和 PVC 解绑、控制器摘掉 pv-protection 之后,才真正消失。

命名空间卡在 Terminating​

这是新手最常踩的坑:kubectl delete ns xxx 之后,空间永远卡在 Terminating。原因几乎都是某个 finalizer 对应的控制器已经不在了(比如装过某个 Operator 又卸载了、有残留的 APIService),没人来摘 finalizer,删除流程就一直等下去。

排查和手动清理:

# 看卡在哪个 finalizer 上
kubectl get ns xxx -o jsonpath='{.metadata.finalizers}'

# 确认原因后,手动清空 finalizer 强制释放(merge patch)
kubectl patch ns xxx -p '{"metadata":{"finalizers":null}}' --type=merge

# 普通资源同理,例如卡住的 PV
kubectl patch pv data-pv -p '{"metadata":{"finalizers":null}}' --type=merge
手动清 finalizer 是最后手段

清空 finalizer 等于跳过了控制器本该做的清理:云盘可能没释放、负载均衡器可能还挂着、外部资源可能泄漏。先搞清楚为什么卡住(通常是控制器异常或被提前卸载),能恢复控制器就别手动摘。

Garbage Collection​

垃圾回收器(Garbage Collector)是 controller-manager 里的一个组件,工作方式很直接:盯着 ownerReferences,属主被删了,就把它的子对象也删掉。K8s 的级联删除就是它实现的。

删属主时有三种传播策略(propagationPolicy):

策略行为适用场景
background(默认)属主立即删除,子对象由 GC 在后台异步清理日常删除,速度最快
foreground属主先进入「删除中」状态(挂上 foregroundDeletion finalizer),等所有子对象删完,属主才消失想确认删干净了再继续,比如删完马上重建
orphan只删属主,子对象留下变成「孤儿」,ownerReferences 被摘掉想保留 Pod 现场排查,或把资源移交给别的控制器接管

用 --cascade 参数指定:

# 默认就是 background,可以不写
kubectl delete deployment web

# 前台删除:命令会阻塞,直到 RS、Pod 全部删完才返回
kubectl delete deployment web --cascade=foreground

# 孤儿删除:Deployment 没了,但 Pod 还在跑
kubectl delete deployment web --cascade=orphan

现在理解上一节的内容了:foreground 策略正是靠 foregroundDeletion finalizer 实现的——finalizer 卡住属主,GC 先删子对象,删完摘 finalizer,属主才真正消失。三个机制是一套组合拳。

orphan 的实际用途

线上 Pod 出问题想保留现场(日志、进程状态)慢慢看,又想先删掉 Deployment 阻止它重建——--cascade=orphan 就是干这个的。排查完记得手动删 Pod,孤儿对象没有控制器管理,会一直占着资源。

kubectl​

kubectl 是操作集群的唯一入口,本质上是个 API Server 的 HTTP 客户端。这节只讲日常最高频的几块。

kubeconfig 与 context​

kubectl 靠 kubeconfig 文件找到集群并完成认证,默认位置是 ~/.kube/config。文件里有三类信息:

  • clusters:集群地址和 CA 证书
  • users:认证凭据(客户端证书或 token)
  • contexts:把「哪个集群 + 哪个用户 + 默认哪个命名空间」绑成一组,起个名字

多集群管理的日常就是切 context:

kubectl config get-contexts          # 列出所有 context,带 * 的是当前
kubectl config current-context # 看当前用的是哪个
kubectl config use-context prod # 切换
kubectl config set-context --current --namespace=web # 改当前 context 的默认命名空间

也可以用环境变量 KUBECONFIG 或 --kubeconfig 参数临时指定别的配置文件,多个文件用冒号拼接会合并生效。

声明式 apply vs 命令式 create​

两种操作风格:

# 命令式:直接下指令,快,但操作过程不可复现
kubectl create deployment web --image=nginx:1.27
kubectl scale deployment web --replicas=3

# 声明式:写 YAML 描述期望状态,apply 让集群向它对齐
kubectl apply -f web.yaml

apply 的特点是可重复执行:文件没变就什么都不做,变了就增量更新,靠 kubectl.kubernetes.io/last-applied-configuration 注解做三方合并。生产环境一律用 apply,YAML 纳入 Git 管理;命令式只在临时调试时用。

get 的常用输出​

kubectl get pods                        # 简要列表
kubectl get pods -o wide # 多几列:Pod IP、所在节点
kubectl get pod web-xxx -o yaml # 完整对象,排障时常看
kubectl get pod web-xxx -o json # JSON 格式,方便交给 jq 处理

# jsonpath 精确取字段
kubectl get pods -o jsonpath='{.items[*].metadata.name}'
kubectl get pod web-xxx -o jsonpath='{.spec.containers[0].image}'

# 自定义列
kubectl get pods -o custom-columns=NAME:.metadata.name,NODE:.spec.nodeName

# 持续观察变化
kubectl get pods -w

describe:排障第一站​

get 只能看概要,对象出问题时先看 describe——它会给出详情、状态和关联信息,尤其末尾的 Events:为什么调度失败、为什么镜像拉不下来,基本都写在这里。

kubectl describe pod web-xxx
kubectl describe node node-1

--dry-run=client 生成 YAML 骨架​

手写 YAML 最容易忘字段,让 kubectl 先生成骨架再改,是日常使用频率最高的技巧之一:

# 生成 Deployment 骨架(不真正创建)
kubectl create deployment web --image=nginx:1.27 --dry-run=client -o yaml > web.yaml

# Service、ConfigMap、Job 同理
kubectl create service clusterip web --tcp=80:80 --dry-run=client -o yaml
kubectl create configmap app-config --from-literal=LOG_LEVEL=info --dry-run=client -o yaml
kubectl create job test --image=busybox:1.36 --dry-run=client -o yaml -- echo hello

两种 dry-run 的区别:--dry-run=client 只在本地渲染,不联系集群;--dry-run=server 会把请求发给 API Server 走一遍校验和准入控制,但不落库,适合提交前验证。

这两个参数值得背下来

--dry-run=client -o yaml 生成骨架、--dry-run=server 提交前校验,能减少一大半「YAML 写错被 API Server 拒掉」的来回折腾。

YAML 最佳实践​

新手写清单容易乱,按下面的清单过一遍,能避开大部分问题。

结构:固定四段式​

一份资源清单固定四段,按这个顺序写:

apiVersion: apps/v1   # 1. 用哪个 API 版本
kind: Deployment # 2. 什么类型
metadata: # 3. 元数据:名字、命名空间、标签
name: web
spec: # 4. 期望状态
...

版本与标签​

  • 镜像不用 latest:写 image: nginx:1.27.5 这种明确版本。latest 会让不同节点拉到不同镜像,回滚也说不清回到哪。
  • apiVersion 用 stable:生产环境优先 v1、apps/v1 这类 GA 版本,别用 alpha/beta。
  • labels 配齐全:至少带上 app.kubernetes.io/name、instance、version,后面监控、灰度、查询都靠它们。
  • namespace 显式写出:别依赖 context 的默认值,换台机器 apply 就落到别的空间去了。

resources 必须配​

不配 requests/limits 的容器是最低 QoS 等级,节点资源紧张时最先被杀;反过来也可能把节点资源吃光拖垮邻居。哪怕先估个值也要写:

resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi

格式细节​

  • 缩进统一 2 个空格,禁用 Tab(YAML 语法不允许 Tab)。
  • 数字型字符串要加引号:version: "1.0"、端口类环境变量 value: "8080",否则会被解析成数字。
  • 小心 YAML 的布尔坑:yes/no/on/off 不加引号会被当成布尔值,值是这类词时统一加引号。
  • 多个资源可以放一个文件用 --- 分隔;但建议一个文件一个资源,配合目录管理更清晰。

提交前校验​

# 语法 + 服务端校验(推荐,会走一遍准入控制)
kubectl apply -f web.yaml --dry-run=server

# 离线按官方 schema 校验(适合 CI,kubeconform 是 kubeval 的继任者)
kubeconform -strict web.yaml
一份可以直接抄的模板
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: production
labels:
app.kubernetes.io/name: web
app.kubernetes.io/instance: web-prod
app.kubernetes.io/version: "1.2.3"
spec:
replicas: 3
selector:
matchLabels:
app.kubernetes.io/name: web
template:
metadata:
labels:
app.kubernetes.io/name: web
spec:
containers:
- name: web
image: nginx:1.27.5
ports:
- containerPort: 80
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi