Kubernetes 基础体系 · 第 6/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
Kubernetes Label、Selector、Annotation 与 OwnerReference:对象关系设计
Kubernetes 对象之间存在多种“关系”,但它们解决的问题并不相同:
- Label:把可被筛选、分组、索引的结构化属性附着到对象上。
- Selector:描述“哪些对象属于某个集合”。
- Annotation:保存给工具、控制器或人读取的非标识性元数据。
- OwnerReference:声明对象之间的所有权和生命周期依赖,供垃圾回收器和控制器使用。
这四者都位于对象的 metadata 或围绕 metadata 工作,但不能互相替代。一个常见设计是:
Deployment --OwnerReference--> ReplicaSet --OwnerReference--> Pod
Service --Selector-----------> Pod
Pod --Label------------------> app、版本、环境等属性
Pod --Annotation-------------> 构建信息、配置摘要等附加信息
这里有两个方向不同的关系:
OwnerReference是对象之间的显式拥有关系,从被拥有对象指向拥有者。Selector是一个对象对其他对象的查询关系,从选择器指向满足条件的对象。
理解这种方向差异,是避免 Kubernetes 对象关系设计错误的基础。
一、先从 Kubernetes 对象的 Metadata 模型开始
一个典型 Kubernetes 对象可以抽象为:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: default
labels: {}
annotations: {}
ownerReferences: []
spec: {}
status: {}
其中:
apiVersion和kind共同构成对象的 GVK,即 Group、Version、Kind。metadata.name和metadata.namespace定位对象的名称空间内身份。metadata.uid是 API Server 分配的稳定对象身份。metadata.resourceVersion用于并发控制和 Watch。metadata.labels、metadata.annotations、metadata.ownerReferences描述对象关系和附加信息。spec表示期望状态。status表示控制器观察到的实际状态。
Label、Selector、Annotation、OwnerReference 都服务于控制器的调谐循环,但作用层级不同:
- 控制器通过 Watch 或 List 发现对象变化。
- 通过 Label Selector、Field Selector 或对象引用缩小关注范围。
- 读取
spec、Labels、Annotations 和 OwnerReferences。 - 计算期望状态。
- 创建、更新或删除其他对象。
- 依靠下一轮 Watch 和最终一致性继续收敛。
因此,元数据不是装饰性字段。错误的 Label 或 OwnerReference,可能直接改变控制器管理的对象集合和垃圾回收行为。
二、Label:可查询的结构化属性
2.1 Label 的定义和用途
Label 是附着在对象上的键值对:
metadata:
labels:
app.kubernetes.io/name: web
app.kubernetes.io/component: frontend
app.kubernetes.io/part-of: shop
app.kubernetes.io/version: "1.4.2"
Label 的核心特征是:
- 结构化:键和值都遵循 Kubernetes 的格式约束。
- 可选择:API 请求、控制器和命令行工具可以使用 Label Selector。
- 可变更:多数对象的 Label 可以在生命周期中更新。
- 不直接表达生命周期所有权:添加相同 Label 不会让对象成为某个控制器的子对象。
Label 适合描述:
- 应用名称;
- 组件角色;
- 环境;
- 发布版本;
- 业务租户;
- 区域或拓扑;
- 控制器用于分组和选择的属性。
Label 不适合保存长文本、JSON 文档、错误堆栈或时间线信息,这些内容通常应放入 Annotation,或者放入独立的 ConfigMap、Secret、CR 状态中。
2.2 Label 键和值的格式
Label 的键可以是:
[prefix/]name
其中:
name必须是 DNS 子域名最后一段风格的名称;name最多 63 个字符;prefix如果存在,必须是 DNS 子域名;prefix与name之间使用一个/;- 值也受 Kubernetes Label 值格式约束,通常最多 63 个字符,不能为空或使用符合要求的空值。
例如:
metadata:
labels:
app: web
app.kubernetes.io/name: web
company.example.com/team: platform
以下形式通常不合法或不适合作为 Label:
metadata:
labels:
"this key contains spaces": value
组织或项目自有的 Label,推荐使用自己的 DNS 前缀,例如:
metadata:
labels:
platform.example.com/owner: team-a
而通用 Kubernetes 生态约定的标签通常使用 app.kubernetes.io/*,例如:
metadata:
labels:
app.kubernetes.io/name: payments
app.kubernetes.io/instance: payments-prod
app.kubernetes.io/version: "2025.03"
app.kubernetes.io/component: api
app.kubernetes.io/part-of: commerce
app.kubernetes.io/managed-by: Helm
这些推荐键是约定,不代表 API Server 会自动理解其业务语义。app.kubernetes.io/name 不会自动产生 Service、Deployment 或权限关系;真正产生关系的是具体控制器使用的 Selector 或 OwnerReference。
2.3 Label 的身份语义必须稳定
Label 虽然通常可变,但作为集合成员资格的 Label 不应随意变化。
例如,一个 Service:
spec:
selector:
app.kubernetes.io/name: web
它选择的集合可以形式化为:
如果某个 Pod 的这个 Label 从 web 改为 api,它会立即从 Service 的候选后端集合中消失。这个行为不是修改了 Service,而是修改了集合成员资格。
因此需要区分两类 Label:
- 身份或分组 Label:例如应用名、组件名,通常应稳定。
- 状态或版本 Label:例如发布版本,可能需要变化,但变化会影响 Selector 结果,必须明确设计。
Deployment 的 spec.selector 是这种稳定性要求的典型体现:
spec:
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
Deployment 要求 Pod Template 的 Label 满足自己的 Selector,否则对象会被 API Server 拒绝。对于 apps/v1 的 Deployment,Selector 在创建后通常不可变;改变 Pod Template 的其他 Label 可以触发新 ReplicaSet,但不能把 Deployment 改成管理另一组完全不同的 Pod。
三、Selector:把对象映射为集合
3.1 Selector 的形式化含义
Selector 不是一个对象属性,而是一个条件表达式。给定对象集合 ,Selector 选出的结果是:
例如:
app=web
表示:
如果对象没有 app Label,则条件为 false。
Selector 的用途包括:
- Deployment 选择自己管理的 Pod;
- Service 选择流量后端;
- ReplicaSet 选择 Pod;
- NetworkPolicy 选择适用的 Pod;
kubectl get pods -l ...过滤查询;- List/Watch 请求服务端过滤对象。
Selector 的结果通常是动态集合。对象新增、删除或 Label 变化,都可能改变集合结果。
3.2 两种常见 Selector 表达式
Kubernetes 常见的 Selector 语法包含等值匹配和集合匹配。
等值匹配
environment=production
environment==production
tier!=cache
在 YAML 中通常表示为:
selector:
matchLabels:
environment: production
tier: frontend
多个 matchLabels 条件之间是逻辑 AND:
environment=production AND tier=frontend
集合匹配
environment in (production, staging)
tier notin (cache)
metadata exists
在支持 matchExpressions 的 API 字段中:
selector:
matchExpressions:
- key: environment
operator: In
values:
- production
- staging
- key: tier
operator: NotIn
values:
- cache
所有 matchExpressions 之间也是 AND。因此上述表达式等价于:
environment ∈ {production, staging}
AND
tier ∉ {cache}
常见 Operator 包括:
In:Label 存在且值属于values;NotIn:Label 不存在,或者存在但值不属于values;Exists:Label 存在,values必须为空;DoesNotExist:Label 不存在,values必须为空。
不同 API 对 Selector 的字段形式并不完全相同。例如:
spec:
selector:
matchLabels: {}
matchExpressions: []
是 Deployment、ReplicaSet 等工作负载 API 常见的结构;而 Service 使用的是:
spec:
selector:
app: web
不能把一个 API 的 Selector YAML 结构机械复制到另一个 API。
3.3 Selector 没有通用的 OR 语法
一个容易误解的表达是:
app=web OR app=api
Kubernetes Label Selector 通常没有通用的 OR 运算符。集合匹配可以表达:
app in (web, api)
但这只适用于支持集合表达式的字段。
如果两个条件涉及不同的键,例如:
environment=production OR tier=frontend
不能直接用一个标准 Label Selector 表达。通常需要:
- 发起两次查询;
- 重新设计一个组合 Label;
- 让控制器执行多次选择和合并;
- 或使用业务对象自身的索引机制。
3.4 缺失 Label 和否定条件
假设对象如下:
objects:
- name: a
labels:
tier: frontend
- name: b
labels:
tier: cache
- name: c
labels: {}
对 Selector:
tier!=cache
对象 a 和 c 都可能匹配,因为 NotIn 对不存在的键也成立。
这与:
tier exists AND tier!=cache
不同。后者只会匹配 a。
因此在权限、流量、策略等高风险场景中,必须明确“缺失 Label”应该被视为:
- 不属于集合;
- 属于默认集合;
- 还是属于排除集合。
不能只凭自然语言中的“不是 cache”推断实际结果。
3.5 空 Selector 的风险
空 Selector 的语义依赖具体 API。常见情况包括:
- 某些 API 中空 Selector 表示匹配所有对象;
- 某些 API 中空 Selector 具有特殊默认行为;
- 某些 API 禁止空 Selector;
- Service 的空
spec.selector表示不自动选择 Pod,通常用于手工维护 EndpointSlice,而不是“选择全部 Pod”。
因此不能把“空 Selector”统一理解为“没有限制”或“匹配所有”。
尤其是控制器对象,错误的空 Selector 可能导致:
- 接管不应管理的 Pod;
- 删除或更新不属于本工作负载的对象;
- 大范围流量暴露;
- 调谐循环持续产生冲突。
应以具体资源的 API 文档和 OpenAPI schema 为准,并在应用前验证实际行为。
四、Selector 的典型关系:Deployment、ReplicaSet、Service
4.1 Deployment 与 ReplicaSet
下面是一个最小但完整的 Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
labels:
app.kubernetes.io/name: web
app.kubernetes.io/part-of: demo
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: web
template:
metadata:
labels:
app.kubernetes.io/name: web
app.kubernetes.io/part-of: demo
spec:
containers:
- name: web
image: nginx:1.27
ports:
- name: http
containerPort: 80
应用:
kubectl apply -f deployment.yaml
kubectl get deployment web
kubectl get replicasets -l app.kubernetes.io/name=web
kubectl get pods -l app.kubernetes.io/name=web --show-labels
预期可以看到:
- Deployment 创建或使用一个 ReplicaSet;
- ReplicaSet 创建两个 Pod;
- Pod 的
app.kubernetes.io/name=web满足 ReplicaSet 和 Deployment 模板中定义的选择条件。
这里有一个重要边界:Deployment 并不是直接通过自己的 Selector“拥有”所有 Pod。通常的生命周期链是:
Deployment
|
| OwnerReference
v
ReplicaSet
|
| OwnerReference
v
Pod
Deployment 通过 ReplicaSet 管理滚动发布;ReplicaSet 再通过 Selector 管理 Pod。Selector 负责发现和归组,OwnerReference 负责生命周期链。
4.2 Service 通过 Selector 选择后端
Service 示例:
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector:
app.kubernetes.io/name: web
ports:
- name: http
port: 80
targetPort: http
Service 的 Selector 选择同一命名空间内满足条件的 Pod。EndpointSlice 控制器会根据这些 Pod 生成或更新 EndpointSlice,代理组件再根据 EndpointSlice 转发流量。
数据流可以简化为:
Pod Label
|
v
Service Selector
|
v
EndpointSlice
|
v
kube-proxy 或其他数据面实现
|
v
Service 流量转发
因此,下列操作的效果不同:
kubectl label pod <pod-name> app.kubernetes.io/name-
这会删除 Pod 上的 Label。Pod 可能立即不再匹配 Service,EndpointSlice 也会最终移除对应端点。
但它不会:
- 删除 Pod;
- 修改 Deployment 的 OwnerReference;
- 修改 Service 本身的 Selector;
- 自动让 Pod 归属于其他控制器。
这是 Selector 关系和 OwnerReference 关系的根本区别。
4.3 不要让多个控制器使用重叠 Selector
假设两个 ReplicaSet 都使用:
spec:
selector:
matchLabels:
app: web
并且它们都能看到同一个 Pod 集合,那么两个控制器都可能认为自己应该管理这些 Pod。结果可能出现:
- 两个控制器竞争 Pod;
- 一个控制器采用或释放 Pod;
- 副本数在两个调谐循环之间来回变化;
- 事件中出现 adoption、orphaning 或冲突;
- 删除一个控制器后,另一个控制器意外接管残留 Pod。
对于 ReplicaSet、Deployment 等会采用孤儿对象的控制器,Selector 设计必须保证管理集合边界清晰。常见做法是为每个工作负载生成唯一且稳定的实例标识,例如:
labels:
app.kubernetes.io/name: web
app.kubernetes.io/instance: web-prod
但是否把 instance 放入某个 Selector,取决于组件的管理边界。Service 通常需要选择一个完整业务实例,而监控系统可能只按应用名和组件名聚合。
五、Annotation:非标识性附加元数据
5.1 Annotation 与 Label 的区别
Annotation 也是键值对:
metadata:
annotations:
example.com/build-id: "build-20250308-001"
example.com/source-revision: "abc1234"
但 Annotation 的值是字符串,不用于标准 Label Selector。它适合保存:
- 构建号、提交哈希;
- 工具配置;
- 原始 JSON 或其他序列化信息;
- 外部系统 ID;
- 变更原因;
- Ingress、监控、云控制器等实现特定配置;
- 控制器用于触发重新计算的摘要。
同一信息不应为了查询方便而无条件同时写入 Label 和 Annotation。需要参与集合筛选的短、稳定、结构化属性适合 Label;其他信息适合 Annotation。
例如:
metadata:
labels:
app.kubernetes.io/name: web
app.kubernetes.io/version: "1.4.2"
annotations:
example.com/source-revision: "a1b2c3d4"
example.com/build-metadata: '{"pipeline":"main","attempt":7}'
这里版本是否适合作为 Label,取决于是否需要按版本筛选。如果只是展示构建详情,Annotation 更合适。
5.2 Annotation 不建立关系
下面这个 Annotation:
metadata:
annotations:
owner: team-a
不会建立以下任何关系:
- 不会让对象被
team-a的控制器管理; - 不会影响垃圾回收;
- 不会自动改变 Service 后端;
- 不会被 Kubernetes 当作真正的 OwnerReference;
- 不会获得 API Server 的所有者校验和 UID 语义。
同样,下面的字符串:
metadata:
annotations:
ownerReference: "deployment/web"
也只是普通文本。真正的 OwnerReference 必须位于:
metadata:
ownerReferences:
- apiVersion: apps/v1
kind: Deployment
name: web
uid: <真实 UID>
5.3 Annotation 不适合作为高频、大规模索引
Kubernetes API Server 可以根据 Label Selector 和部分 Field Selector 过滤 List/Watch 请求;Annotation 不是标准的对象选择器维度。把大量结构化查询字段只放在 Annotation 中,会导致控制器需要:
- 获取更大的对象集合;
- 在客户端逐个解析 Annotation;
- 自己维护缓存或索引;
- 承受更高的 API、内存和调谐成本。
这并不意味着 Annotation 永远不能存结构化数据。它可以存 JSON,但应注意:
- 读取方必须约定序列化格式;
- 修改方不能无意覆盖其他工具写入的键;
- 内容不能接近对象大小限制;
- 不应把 Annotation 当作高性能数据库;
- 不应在 Annotation 中保存 Secret 明文。
Kubernetes 对对象元数据和整体对象大小有 API Server、存储和资源校验限制;Annotation 总量过大可能导致对象被拒绝或增加 etcd 压力。具体上限和行为受 Kubernetes 版本、API Server 配置及对象类型影响,不能把 Annotation 当作无限大小字段。
5.4 Annotation 触发滚动更新:一种约定,而非通用语义
许多控制器或发布工具会把配置摘要放入 Pod Template 的 Annotation:
spec:
template:
metadata:
annotations:
example.com/config-hash: "9f86d081884c"
当该 Annotation 位于 Deployment 的 spec.template.metadata 中时,Pod Template 发生变化,Deployment 通常会创建新的 ReplicaSet。常见配置变更流程是:
ConfigMap 内容变化
|
v
计算内容摘要
|
v
更新 Deployment Pod Template Annotation
|
v
Deployment 创建新的 ReplicaSet
|
v
滚动替换 Pod
关键点是:Kubernetes 并不会因为任意 ConfigMap 内容变化,就自动重新创建引用它的 Pod。重新发布通常依赖:
- 控制器显式更新 Pod Template;
kubectl rollout restart;- 使用该功能的外部工具或 Operator。
因此,“配置摘要 Annotation 能触发滚动更新”是 Deployment Pod Template 变更机制与工具约定共同产生的效果,不是 Annotation 本身拥有的通用事件语义。
六、OwnerReference:声明所有权和生命周期依赖
6.1 OwnerReference 的结构
OwnerReference 位于被拥有对象的 metadata.ownerReferences:
metadata:
ownerReferences:
- apiVersion: apps/v1
kind: ReplicaSet
name: web-7d8f7c6f
uid: 2f4d1c8e-....
controller: true
blockOwnerDeletion: true
主要字段含义:
apiVersion:Owner 的 API 版本;kind:Owner 的 Kind;name:Owner 名称;uid:Owner 的唯一身份;controller:是否表示该 Owner 是对象的控制器;blockOwnerDeletion:在满足权限条件时,是否阻止 Owner 在前台删除期间完成删除。
Owner 的身份不是 kind + name,而是至少依赖 UID。原因是对象删除后可以创建同名新对象:
Deployment web, UID=A 被删除
Deployment web, UID=B 重新创建
如果只按名称判断,旧子对象可能被误认为属于新 Deployment。UID 可以区分这两个不同生命周期的对象。
6.2 OwnerReference 与垃圾回收器
Kubernetes 垃圾回收器根据 OwnerReference 构建对象依赖图。
Owner
|
| ownerReferences
v
Dependent
当 Owner 被删除时,Dependent 的处理方式取决于删除传播策略:
- Background:先删除 Owner,垃圾回收器随后异步删除 Dependent;
- Foreground:先标记 Owner 删除并等待依赖对象删除,依赖清理后 Owner 才完成删除;
- Orphan:删除 Owner,但保留 Dependent。
例如:
kubectl delete deployment web --cascade=background
kubectl delete deployment web --cascade=foreground
kubectl delete deployment web --cascade=orphan
这些命令的差异是删除传播策略,不是修改 Selector。
在 Deployment 的常见生命周期中,删除 Deployment 通常还会导致 ReplicaSet 和 Pod 被回收,因为它们之间存在 OwnerReference。若使用 --cascade=orphan,则可能保留下属对象,但这些对象不一定仍然被某个有效控制器管理。
6.3 Controller Owner 与普通 Owner
controller: true 表示该 Owner 是对象的控制器 Owner。一个对象通常最多有一个 controller: true 的 OwnerReference,但可以有多个普通 OwnerReference。
例如,一个自定义控制器创建 ConfigMap:
metadata:
ownerReferences:
- apiVersion: example.com/v1
kind: Widget
name: widget-a
uid: 11111111-....
controller: true
blockOwnerDeletion: true
控制器会使用 UID 识别父对象,并在调谐时检查:
- 父对象是否仍存在;
- OwnerReference 是否指向当前父对象;
- 子对象是否被其他控制器控制;
- 子对象的期望状态是否需要更新。
在 Go 控制器中,常见做法是使用 controller-runtime 的工具设置控制器引用,例如 controllerutil.SetControllerReference。这类工具通常会处理 GVK、UID、命名空间关系和冲突检查,但仍然需要调用方正确设置 Scheme、权限和父子资源类型。
6.4 OwnerReference 的命名空间边界
OwnerReference 不能任意跨命名空间建立。
基本规则是:
- Namespaced Owner 只能拥有同一 Namespace 中的 Namespaced Dependent;
- Namespaced 对象不能成为 Cluster-scoped 对象的合法 Owner;
- Cluster-scoped Dependent 只能有 Cluster-scoped Owner;
- Namespaced Dependent 可以由合适的 Cluster-scoped Owner 管理,但这种设计必须确认其生命周期语义和 API 行为;
- 跨 Namespace 的 OwnerReference 不应使用,Kubernetes 垃圾回收器会将其视为无效或不可解析的依赖。
例如,位于 team-a 的 Deployment 不能合法拥有位于 team-b 的 Pod。Service 也不能通过 OwnerReference 表达“选择另一个 Namespace 的 Pod”;Service 的 Selector 本身只作用于其 Namespace 内的 Pod。
这也是为什么一个集群级 Operator 管理多个 Namespace 的资源时,通常让每个 Namespaced 子资源引用同 Namespace 内的自定义资源,或者由集群级资源通过逻辑字段管理它们,而不是构造跨 Namespace OwnerReference 图。
6.5 blockOwnerDeletion 不是 finalizer
这两个机制经常被混淆:
OwnerReference
表达:
对象 B 的生命周期依赖对象 A
主要用于垃圾回收和控制器发现。
Finalizer
表达:
对象删除前,某个参与者必须完成清理工作
例如:
metadata:
finalizers:
- example.com/cleanup
当对象被删除时,API Server 先设置 deletionTimestamp,对象进入 Terminating;只有负责该 Finalizer 的控制器移除 Finalizer,删除才会完成。
因此:
- OwnerReference 不等于清理代码;
- Finalizer 不等于所有权;
- 有 OwnerReference 的对象不一定会执行业务清理;
- 有 Finalizer 的对象也不一定拥有其他对象。
blockOwnerDeletion 只影响 Owner 删除传播中的阻塞行为,并不能替代 Finalizer。要让它有效,还涉及对 Owner 的删除权限等 API 安全条件,不能只把字段设为 true 就假定删除一定被阻止。
七、完整算例:同时使用 Label、Selector、Annotation 和 OwnerReference
下面设计一个名为 Widget 的自定义资源,并由控制器创建一个 ConfigMap。假设:
- Widget 是 Namespaced;
- ConfigMap 与 Widget 在同一 Namespace;
- ConfigMap 的 Label 用于查询;
- Annotation 保存配置摘要;
- OwnerReference 负责 Widget 删除后的垃圾回收;
- ConfigMap 的数据变化由控制器调谐,而不是依赖 Annotation 自动产生业务行为。
Widget:
apiVersion: example.com/v1
kind: Widget
metadata:
name: sample
namespace: default
labels:
app.kubernetes.io/name: widget
app.kubernetes.io/instance: sample
spec:
config:
endpoint: https://api.example.com
replicas: 3
控制器创建的 ConfigMap 可能是:
apiVersion: v1
kind: ConfigMap
metadata:
name: sample-config
namespace: default
labels:
app.kubernetes.io/name: widget-config
app.kubernetes.io/instance: sample
app.kubernetes.io/part-of: widget
annotations:
example.com/config-hash: "sha256:..."
ownerReferences:
- apiVersion: example.com/v1
kind: Widget
name: sample
uid: 8a7e7a4e-....
controller: true
blockOwnerDeletion: true
data:
endpoint: https://api.example.com
replicas: "3"
其中四种关系分别是:
app.kubernetes.io/instance=sample是 Label,可用于查询属于sample实例的 ConfigMap。example.com/config-hash是 Annotation,用于记录配置版本或触发控制器识别变化。ownerReferences表达 ConfigMap 由 Widget 控制器创建并依赖 Widget 生命周期。- ConfigMap 是否被某个 Service 选择,与 OwnerReference 没有直接关系。
查询:
kubectl get configmaps \
-l app.kubernetes.io/name=widget-config,app.kubernetes.io/instance=sample
预期只返回满足两个 Label 条件的 ConfigMap。删除 Widget:
kubectl delete widget sample
如果 OwnerReference 有效且删除传播未被改变,垃圾回收器会最终删除 sample-config。如果控制器还需要调用外部 API 删除云资源,则仍需使用 Finalizer;仅有 OwnerReference 不足以完成外部清理。
八、对象关系不是静态树,而是调谐过程中的动态状态
控制器通常同时使用 Selector 和 OwnerReference,但它们在调谐中的作用不同。
以 ReplicaSet 为例,可以抽象出以下步骤:
第一步:发现候选对象
ReplicaSet 通过 Watch 或 List 观察 Pod。Selector 过滤出满足条件的 Pod:
所有 Pod
-> matchLabels
-> 候选 Pod 集合
第二步:检查所有权
对于候选 Pod,控制器检查 OwnerReference:
- Pod 是否已经由当前 ReplicaSet 控制;
- Pod 是否是孤儿;
- Pod 是否已经由另一个控制器控制;
- Pod 的 Owner UID 是否匹配当前 ReplicaSet UID。
Selector 匹配并不自动等于控制权。
第三步:必要时采用孤儿 Pod
如果一个孤儿 Pod 满足 ReplicaSet Selector,控制器可能尝试为它设置 OwnerReference。这是 adoption。设置过程中可能发生:
- Pod 已被另一个控制器先采用;
- Pod 在更新前已删除;
- ResourceVersion 冲突;
- 权限不足;
- OwnerReference 命名空间或类型不合法。
第四步:计算副本差异
设期望副本数为 ,当前被控制的有效 Pod 数为 :
- 若 ,创建约 个 Pod;
- 若 ,删除约 个 Pod;
- 若 ,副本数量达到目标,但 Pod 仍可能需要更新。
这里的 不是简单的“Selector 匹配数量”,而是经过生命周期、所有权、删除状态和控制器逻辑过滤后的有效数量。
第五步:面对最终一致性
创建 Pod 后,API Server 可能已经接受对象,但调度器、kubelet 和容器运行时尚未使其变为 Running。控制器不能把一次 API 写入当作最终完成,而要继续通过 Watch、缓存和重排队观察:
期望副本数 2
-> 创建 Pod
-> Pod Pending
-> 调度
-> Pod Running
-> 下一次调谐确认实际状态
因此 Label、Selector 和 OwnerReference 都应服务于幂等调谐,而不是依赖一次性的命令执行。
九、对象关系图:Service 选择与工作负载拥有同时存在
graph TD
D[Deployment web]
R[ReplicaSet web-xxx]
P1[Pod web-xxx-1]
P2[Pod web-xxx-2]
S[Service web]
E[EndpointSlice]
D -->|OwnerReference| R
R -->|OwnerReference| P1
R -->|OwnerReference| P2
S -->|Selector: app=web| P1
S -->|Selector: app=web| P2
P1 -->|被 EndpointSlice 控制器发现| E
P2 -->|被 EndpointSlice 控制器发现| E
这张图包含两种完全不同的边:
Deployment -> ReplicaSet -> Pod:反向存储在子对象上的 OwnerReference,影响控制器管理和垃圾回收。Service -> Pod:Service Selector 计算出的动态匹配关系,影响流量后端。
如果删除 Service:
kubectl delete service web
Pod 通常不会被删除,因为 Service 不是 Pod 的 Owner。
如果删除 Deployment:
kubectl delete deployment web
ReplicaSet 和 Pod 通常会因 OwnerReference 被垃圾回收,但 Service 通常仍然存在,只是由于没有 Pod 匹配其 Selector,EndpointSlice 最终变为空。
十、常见错误及其失败表现
10.1 只设置 Pod Label,不设置工作负载 Selector
错误示例:
spec:
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: api
这个 Deployment 会被 API Server 拒绝,因为 Pod Template 的 Label 不满足 Deployment Selector。
诊断:
kubectl apply -f deployment.yaml
可能看到类似:
spec.template.metadata.labels: Invalid value:
selector does not match template labels
修复原则是让模板至少包含 Selector 的全部条件:
spec:
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
10.2 把版本 Label 放进不应变化的 Selector
例如:
spec:
selector:
matchLabels:
app: web
version: v1
发布 v2 时把模板改成:
metadata:
labels:
app: web
version: v2
对于 Deployment,这会与原 Selector 不一致;即便某些资源允许修改 Selector,也可能使旧对象脱离管理或导致服务流量切换异常。
更常见的设计是:
spec:
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
version: v2
Service 只选择稳定的 app: web,而版本 Label 用于观测、分组或灰度控制。若确实需要按版本分流,应明确创建不同 Service 或设计额外的稳定路由层,而不是随意改变核心控制器 Selector。
10.3 误以为 Annotation 可以被 -l 查询
下面的对象:
metadata:
annotations:
example.com/release: canary
不能通过以下命令按 Annotation 过滤:
kubectl get pods -l example.com/release=canary
因为 -l 只用于 Label Selector。应改为:
- 将需要筛选的短值复制到 Label;
- 或获取对象后使用 JSONPath、
jq等客户端处理; - 或由控制器维护专用索引和状态。
例如:
kubectl get pods -o json |
jq -r '
.items[]
| select(.metadata.annotations["example.com/release"] == "canary")
| .metadata.name
'
这会把对象集合先取回客户端,规模较大时可能产生明显 API 和内存压力。
10.4 手工伪造 OwnerReference
错误做法是只写:
ownerReferences:
- apiVersion: apps/v1
kind: Deployment
name: web
uid: fake
OwnerReference 中的 UID 必须是 API Server 中真实存在或曾存在的 Owner UID。即使对象被接受,伪造 UID 也不会产生有效的所有权关系,垃圾回收器无法把它解析为当前 Deployment。
查看真实 UID:
kubectl get deployment web \
-o jsonpath='{.metadata.uid}{"\n"}'
实际控制器应从 API 缓存或 API Server 读取 Owner 对象,并使用其真实 UID 设置引用。不要只根据名称拼接。
10.5 误把同名对象当成同一 Owner
删除并重建对象后,名称可能不变,但 UID 会变化:
kubectl get deployment web -o jsonpath='{.metadata.uid}{"\n"}'
kubectl delete deployment web
kubectl create -f deployment.yaml
kubectl get deployment web -o jsonpath='{.metadata.uid}{"\n"}'
两次 UID 不同。旧子对象如果仍引用旧 UID,不应被新 Deployment 直接视为自己的子对象。控制器必须按 UID 验证身份,而不是只比较 kind/name。
10.6 使用过宽 Selector 造成控制器竞争
诊断一个 Pod 被谁控制:
kubectl get pod <pod-name> -o jsonpath='{.metadata.ownerReferences}{"\n"}'
查看一个工作负载的 Selector:
kubectl get deployment web \
-o jsonpath='{.spec.selector}{"\n"}'
查看匹配集合:
kubectl get pods -l app=web --show-labels
如果发现多个控制器的 Selector 重叠,应进一步检查:
kubectl get deployments,replicasets -A -o yaml
生产环境中不应通过“手动删 Pod”来掩盖选择器设计问题。删 Pod 只会触发控制器重新计算,根因仍然存在,甚至可能造成更多对象被错误采用。
十一、如何选择四种机制
可以用以下判断过程设计对象关系。
问题一:这个字段是否需要被选择或分组?
如果需要,例如:
所有 production 环境的 frontend Pod
使用 Label:
labels:
environment: production
tier: frontend
然后使用 Selector:
environment=production,tier=frontend
问题二:这个字段是否是对象集合的身份边界?
如果它决定某个控制器管理哪些对象,应把它纳入稳定的 Selector,并确保:
- 创建时满足 API 校验;
- 生命周期中不会被无意修改;
- 不与其他控制器产生重叠;
- 不包含发布过程中必然变化的字段,除非资源明确支持这种语义。
问题三:这个字段只是附加信息吗?
如果它是构建号、提交哈希、工具配置或变更原因,使用 Annotation:
annotations:
example.com/source-revision: abc123
不要把 Annotation 当作隐式关系或权限边界。
问题四:删除父对象时,子对象是否必须随之消失?
如果是对象生命周期依赖,应使用 OwnerReference:
ownerReferences:
- apiVersion: example.com/v1
kind: Widget
name: sample
uid: <真实 UID>
如果还需要在删除前执行业务清理,例如删除云端负载均衡器,则增加 Finalizer,而不是只依赖 OwnerReference。
十二、生产设计中的边界和取舍
12.1 Label 不是权限系统
Label 可以被用于选择对象,但不能天然证明调用者有权访问这些对象。基于 Label 的业务逻辑必须叠加 Kubernetes RBAC、Namespace 隔离和控制器自身的安全校验。
例如,控制器不能因为某个用户给 Pod 加上:
labels:
team: platform
就自动认为该 Pod 受平台团队授权或属于平台团队。Label 是对象数据,权限判断需要独立的认证和授权来源。
12.2 Selector 不是强一致关系
Selector 的结果随缓存和对象变化而变化。控制器通常通过 Informer 缓存处理对象,但缓存存在传播延迟。不能假定:
刚修改 Label
=> 所有组件在同一时刻看到新集合
Service、EndpointSlice 控制器、代理数据面和应用观察到变化的时间可能不同,这属于 Kubernetes 最终一致性模型。
12.3 OwnerReference 不是跨系统所有权
OwnerReference 只描述 Kubernetes API 对象之间的依赖。它不能自动管理:
- 云厂商资源;
- 数据库表;
- 外部 DNS 记录;
- Git 仓库;
- 远端 SaaS 对象。
这些资源通常需要控制器调谐、Finalizer、重试和幂等删除逻辑。Owner 被删除后,外部资源不会因为 Kubernetes 垃圾回收器存在就自动消失。
12.4 控制器写入 Metadata 时要避免覆盖
多个控制器可能同时写入同一个对象的 Labels 或 Annotations。使用全量替换:
metadata:
labels:
app: web
可能意外删除其他控制器写入的 Label。客户端更新时应:
- 使用 Patch 或结构化更新;
- 只修改自己拥有的键;
- 处理
resourceVersion冲突; - 不覆盖未知字段;
- 对冲突进行重试。
这与 Kubernetes API 的并发更新和调谐循环直接相关。
十三、实用诊断命令
查看对象的完整 Metadata:
kubectl get pod <pod-name> -o yaml
只查看 Labels:
kubectl get pod <pod-name> \
-o jsonpath='{.metadata.labels}{"\n"}'
只查看 Annotations:
kubectl get pod <pod-name> \
-o jsonpath='{.metadata.annotations}{"\n"}'
查看 OwnerReferences:
kubectl get pod <pod-name> \
-o jsonpath='{range .metadata.ownerReferences[*]}{.apiVersion}{" "}{.kind}{" "}{.name}{" "}{.uid}{" controller="}{.controller}{"\n"}{end}'
查看 Deployment Selector:
kubectl get deployment <name> \
-o jsonpath='{.spec.selector}{"\n"}'
按多个 Label 条件查询:
kubectl get pods \
-l 'app.kubernetes.io/name=web,environment=production'
按集合条件查询:
kubectl get pods \
-l 'environment in (production,staging)'
查看 Service 实际生成的 EndpointSlice:
kubectl get endpointslice \
-l kubernetes.io/service-name=web \
-o yaml
如果 Service 没有后端,按以下顺序检查:
- Service 和 Pod 是否在同一 Namespace;
- Service Selector 的键和值;
- Pod 是否存在对应 Label;
- Pod 是否处于可作为端点的状态;
- EndpointSlice 是否已更新;
- 端口名称或
targetPort是否正确; - NetworkPolicy、代理实现或云厂商负载均衡器是否另有约束。
十四、最终关系模型
可以把四种机制归纳为四个不同的问题:
| 机制 | 核心问题 | 关系方向 | 是否影响生命周期 | 是否用于标准选择 |
|---|---|---|---|---|
| Label | 对象具有什么属性 | 对象自身的属性 | 否 | 是 |
| Selector | 哪些对象满足条件 | 查询者到对象集合 | 否 | 是 |
| Annotation | 还有哪些附加信息 | 对象自身的附加数据 | 否 | 否 |
| OwnerReference | 对象依赖哪个 Kubernetes Owner | Dependent 指向 Owner | 是 | 否 |
一个健壮的对象关系设计通常满足:
Label 表达可查询、稳定、结构化的属性
Selector 表达清晰且不重叠的管理集合
Annotation 保存非标识性的附加信息
OwnerReference 表达真实、同边界、基于 UID 的生命周期依赖
Finalizer 负责必须完成的业务清理
当一个系统出现“Service 找不到 Pod”“Deployment 管理了错误的 Pod”“删除父对象后子对象残留”“配置变化没有触发发布”等问题时,通常不是 Kubernetes 缺少某种关系机制,而是把 Label、Selector、Annotation、OwnerReference 或 Finalizer 的语义混用了。理解它们各自的身份、方向、生命周期和一致性边界,才能让 Kubernetes 对象关系在创建、更新、删除和故障恢复过程中保持可预测。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 上一篇:kubectl 完整工作流:Context、查询、Patch、Debug、输出和安全
- 下一篇:Kubernetes Namespace:作用域、资源配额、权限、多租户和删除
- 延伸:Kubernetes API 对象:GVK、Metadata、Spec、Status、版本和兼容
- 延伸:Kubernetes 调谐循环:Desired State、Watch、Queue、幂等和最终一致
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论