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: {}

其中:

  • apiVersionkind 共同构成对象的 GVK,即 Group、Version、Kind。
  • metadata.namemetadata.namespace 定位对象的名称空间内身份。
  • metadata.uid 是 API Server 分配的稳定对象身份。
  • metadata.resourceVersion 用于并发控制和 Watch。
  • metadata.labelsmetadata.annotationsmetadata.ownerReferences 描述对象关系和附加信息。
  • spec 表示期望状态。
  • status 表示控制器观察到的实际状态。

Label、Selector、Annotation、OwnerReference 都服务于控制器的调谐循环,但作用层级不同:

  1. 控制器通过 Watch 或 List 发现对象变化。
  2. 通过 Label Selector、Field Selector 或对象引用缩小关注范围。
  3. 读取 spec、Labels、Annotations 和 OwnerReferences。
  4. 计算期望状态。
  5. 创建、更新或删除其他对象。
  6. 依靠下一轮 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 的核心特征是:

  1. 结构化:键和值都遵循 Kubernetes 的格式约束。
  2. 可选择:API 请求、控制器和命令行工具可以使用 Label Selector。
  3. 可变更:多数对象的 Label 可以在生命周期中更新。
  4. 不直接表达生命周期所有权:添加相同 Label 不会让对象成为某个控制器的子对象。

Label 适合描述:

  • 应用名称;
  • 组件角色;
  • 环境;
  • 发布版本;
  • 业务租户;
  • 区域或拓扑;
  • 控制器用于分组和选择的属性。

Label 不适合保存长文本、JSON 文档、错误堆栈或时间线信息,这些内容通常应放入 Annotation,或者放入独立的 ConfigMap、Secret、CR 状态中。


2.2 Label 键和值的格式

Label 的键可以是:

[prefix/]name

其中:

  • name 必须是 DNS 子域名最后一段风格的名称;
  • name 最多 63 个字符;
  • prefix 如果存在,必须是 DNS 子域名;
  • prefixname 之间使用一个 /
  • 值也受 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

它选择的集合可以形式化为:

S={xx.labels["app.kubernetes.io/name"]="web"}S = \{x \mid x.labels["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 不是一个对象属性,而是一个条件表达式。给定对象集合 OO,Selector qq 选出的结果是:

M(q,O)={xOq(x)=true}M(q, O) = \{x \in O \mid q(x) = true\}

例如:

app=web

表示:

q(x)=(x.labels["app"]="web")q(x) = (x.labels["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

对象 ac 都可能匹配,因为 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 中,会导致控制器需要:

  1. 获取更大的对象集合;
  2. 在客户端逐个解析 Annotation;
  3. 自己维护缓存或索引;
  4. 承受更高的 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"

其中四种关系分别是:

  1. app.kubernetes.io/instance=sample 是 Label,可用于查询属于 sample 实例的 ConfigMap。
  2. example.com/config-hash 是 Annotation,用于记录配置版本或触发控制器识别变化。
  3. ownerReferences 表达 ConfigMap 由 Widget 控制器创建并依赖 Widget 生命周期。
  4. 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 命名空间或类型不合法。

第四步:计算副本差异

设期望副本数为 DD,当前被控制的有效 Pod 数为 AA

Δ=DA\Delta = D - A

  • Δ>0\Delta > 0,创建约 Δ\Delta 个 Pod;
  • Δ<0\Delta < 0,删除约 Δ|\Delta| 个 Pod;
  • Δ=0\Delta = 0,副本数量达到目标,但 Pod 仍可能需要更新。

这里的 AA 不是简单的“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 没有后端,按以下顺序检查:

  1. Service 和 Pod 是否在同一 Namespace;
  2. Service Selector 的键和值;
  3. Pod 是否存在对应 Label;
  4. Pod 是否处于可作为端点的状态;
  5. EndpointSlice 是否已更新;
  6. 端口名称或 targetPort 是否正确;
  7. 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、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。