为什么kubernetes的ownerReference要设计成列表,存在多个ownerReference的情况吗?

为什么kubernetes的ownerReference要设计成列表,存在多个ownerReference的情况吗?

如果我们仔细查看kubernetes的metadata字段,我们会发现owenerReference被设计成了一个列表。按照常理理解,一个资源难道不是只有一个控制器吗?比如Pods资源类型,我们去查看其ownerReference我们在绝大多数情况下(其实在我的集群上,可以描述为“全部”)只会见到类型为ReplicaSets的owner。那么我们是否应该将其改为单一的值类型呢?

先说结论:不能。

要想弄清楚为什么它的类型为list,我们非常有必要理解一下ownerReference字段是做什么用的。

ownerReference字段的作用

1. 作用一 :垃圾清理

在kubernetes中,一些资源是被一些其他资源自动控制的,一个例子就是我们上文提到的ReplicaSetsPods,一个ReplicaSet是一组Pods的parent。

现在我们可以将这个概念几乎拓展到kubernetes中的所有资源,这意味着这个可以控制多个资源之间的parent与child关系。

我们先来试试一组ownerReference的情况

举个栗子,我们在kubernetes中有一个CRD(自定义资源)叫Database,我们想要每一个Database对象拥有一个属于自己的数据库配置configmapKubernetes在字段metadata中提供了一个这样的字段ownerReference可以用来定义对象和命名空间的关系,我们可以用文字这样来描述:

我有一个Database对象叫做“Postgres”,并且它的owner为一个叫“Postgres-cm”的configmap

我们来尝试通过实操来理解的更加深刻一些:

我们先来创建一个CRD和Configmap

下面的crd.yaml定义了一个叫Database的资源:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: databases.test.joshua.su
spec:
group: test.joshua.su
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
scope: Namespaced
names:
plural: databases
singular: database
kind: Database
shortNames:hao de
- db

使用命令,创建这个crd

$ kubectl create -f crd.yaml
创建一个名为postgres-cm的Configmap

postgres-cm中创建如下资源:

apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-cm
data:
config: "114514"

使用命令,创建这个configmap

$ kubectl create -f postgres-cm.yaml
创建一个使用这个CRD的资源postgres

postgres.yaml中创建如下的postgres资源

apiVersion: test.joshua.su/v1
kind: Database
metadata:
name: postgres

使用命令,创建这个资源

$ kubectl create -f postgres.yaml
设置ownerReference & 创建父子关系

现在,存在一个Kubernetes提供的独一无二的字段—owenerReference,通过它我们可以将一个资源映射到它的父资源。这意味着一旦这个映射建立完成,如果父资源被删除了那么子资源也会随之被删除。当我们看向我们的这个实验时,理论上当我们删除postgres这个database类型的资源,configmap postgres-cm也会自动触发gc被清理掉。

现在我们要把postgres这个资源和我们刚创建的configmap进行关联。我们应该能够保证当postgres这个资源被删除后,cm也应该会被gc。

所以我们继续我们的实验,我们创建一些字段进我们刚刚创建的configmap的yaml中的metadata中。

apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-cm
ownerReferences:
- apiVersion: v1
controller: true
blockOwnerDeletion: true
kind: Database
name: postgres
uid:
data:
config: "114514"

你可能想知道:

上面ownerReference中字段的解释:

owner apiVersion -必选-
该引用的api version版本

blockOwnerDeletion
如果为 true,并且拥有者具有 “foregroundDeletion” 终结器(finalizer),则在删除此引用之前,拥有者 无法从键值存储中删除。请参阅 https://kubernetes.io/docs/concepts/architecture/garbage-collection/#foreground-deletion 了解垃圾回收器如何与此字段交互并执行前台删除。默认为 false。要设置此字段,用户需要拥有拥有者的 “delete” 权限,否则将返回 422(无法处理的实体)错误。

controller
如果为 true,此引用指向管理控制器。

kind -必选-
这个引用的类型,更多参阅:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds

name -必选-
这个引用的名字. 更多参阅:
https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names

uid -必选-
这个引用的uid. 更多参阅:
https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids

UID,UID UID!

现在你可能会注意到上面的ownerReferences字段是空的。UID是在kubernetes中所有资源中都独一无二的标识。kubernetes实际上利用这个来管理在众多资源中的父子关系。为了获得UID,你可以使用下面的命令:

$ kubectl get database postgres -o=jsonpath='{.metadata.uid}'
b948ac56-fac2-4996-ba5a-f38fa6983cf3

有了这个UID,我们可以轻松的将configmap和database postgres关联起来。所以我们现在可以把刚刚空的UID字段填上,然后使用命令:

$ kubectl patch configmap postgres-cm --type 'json' -p '[{"op": "add", "path": "/metadata/ownerReferences", "value": [{"apiVersion": "test.joshua.su/v1", "blockOwnerDeletion": true, "controller": true, "kind": "Database", "name": "postgres", "uid": "b948ac56-fac2-4996-ba5a-f38fa6983cf3"}]}]'

我们查看一下patch的结果

$ kubectl get cm postgres-cm -o yaml
apiVersion: v1
data:
config: "114514"
kind: ConfigMap
metadata:
creationTimestamp: "2023-10-09T07:30:54Z"
name: postgres-cm
namespace: default
ownerReferences:
- apiVersion: test.joshua.su/v1
blockOwnerDeletion: true
controller: true
kind: Database
name: postgres
uid: b948ac56-fac2-4996-ba5a-f38fa6983cf3
resourceVersion: "27421223"
uid: 95d740a2-de1e-49de-aab8-ef6ee316e670

我们成功创建了一个ownerReference字段。

观察GC现象
  1. 通过下面的命令确定一下我们创建的资源是否真的存在

    $ kubectl get cm postgres-cm
    NAME STATUS AGE
    postgres-cm 1 3m46s
    $ kubectl get database postgres
    NAME AGE
    postgres 3m46s
  2. 删除postgres资源

$ kubectl delete database postgres
database.test.joshua.su "postgres" deleted
  1. 删除成功后,观察postgres-ns namespace的情况
$ kubectl get cm postgres-cm
Error from server (NotFound): configmaps "postgres-cm" not found

hao dehao de显然postgres-cm已经被删除了

我们再试试多个组的情况

是的,你没听错,现在 postgres-cm 可以被多个数据库对象拥有,毕竟作为懒狗,给多个数据库使用相同的配置,也是合情合理的。 就比如现在有postgrespostgres-1 共用一个postgres-cm,那么我们就可以在postgres-cm中设置他们两个数据库对象为ownerReference。这里需要注意的一点是,只有在这两个数据库对象都被删除后,postgres-cm 才会失去其作用域并被垃圾回收。此外,其中一个 ownerReference 的 controller 字段必须设置为 true,至于这个的作用我们会在下文的另外一个实验当中探索。

  1. 首先我们创建两个数据库对象,并获取他们的uid

    $ kubectl create -f postgres.yaml -f postgres1.yaml -f postgres-cm.yaml
    database.test.joshua.su/postgres created
    database.test.joshua.su/postgres1 created
    configmap/postgres-cm created
    $ kubectl get database postgres -o=jsonpath='{.metadata.uid}'
    8c335d76-68f7-4a32-b924-8754da5d1193
    $ kubectl get database postgres1 -o=jsonpath='{.metadata.uid}'
    9ea66aff-db57-48e5-ae65-880aee133078
  2. 接着,老样子,把ownerReferences patch 上去

    $ kubectl patch configmap postgres-cm --type 'json' -p '[{"op": "add", "path": "/metadata/ownerReferences", "value": [{"apiVersion": "test.joshua.su/v1", "blockOwnerDeletion": true, "controller": true, "kind": "Database", "name": "postgres", "uid": "8c335d76-68f7-4a32-b924-8754da5d1193"}, {"apiVersion": "test.joshua.su/v1", "blockOwnerDeletion": true, "kind": "Database", "name": "postgres1", "uid": "9ea66aff-db57-48e5-ae65-880aee133078"}]}]'
  3. 查看patch的情况

$ kubectl get cm postgres-cm -o yaml
apiVersion: v1
data:
config: "114514"
kind: ConfigMap
metadata:
creationTimestamp: "2023-10-09T08:02:06Z"
name: postgres-cm
namespace: default
ownerReferences:
- apiVersion: test.joshua.su/v1
blockOwnerDeletion: true
controller: true
kind: Database
name: postgres
uid: 8c335d76-68f7-4a32-b924-8754da5d1193
- apiVersion: test.joshua.su/v1
blockOwnerDeletion: true
kind: Database
name: postgres1
uid: 9ea66aff-db57-48e5-ae65-880aee133078
resourceVersion: "27430191"
uid: f981cb2d-7b90-43fa-ad36-3ca5e5f884f9
  1. 删除postgres,我们再看看postgres-cm还在不在
$ kubectl delete database postgres
database.test.joshua.su "postgres" deleted
$ kubectl get cm postgres-cm
NAME DATA AGE
postgres-cm 1 6m53s

看来,还在

  1. postgres1也删了呢?

    $ kubectl delete database postgres1
    database.test.joshua.su "postgres1" deleted
    $ kubectl get cm postgres-cm
    Error from server (NotFound): configmaps "postgres-cm" not found

    看来,都被删干净了!

通过上面的小实验,想必大家已经理解了ownerReference删除级联的作用。是时候再看另外一个作用了

2. 作用二:控制权标记

现想一个问题,Deployment如何知道应该控制哪些pod呢?

答:我们可以通过deployment中的.Spec.matchLabels字段来控制Deployment控制的pod。

言下之意,我们是可以通过Deployment去正向推出有哪些pod的。但是知道一个pod如何便捷的反推出是谁控制呢?

ownerReference就可以做到这个效果,通过看pod的ownerReference我们就可以看到当前资源的父资源。

一个假设

试着想象一下,在一个缺乏规范约束的集群中,人为的创建了一个pod,并且这个pod包含了一个非常常见的label,如:“group: redrock",然后恰好有多个deployment的selector都是"group: redrock"(显然这个是不正常的行为), 控制权此时发生了撞车。deployment之间会打起架来,因为大家都不知道到底谁应该控制这个资源,他们都想把这个pod达到自己想要的状态。

2017年的一次的提议中建议给ownerReference添加一个字段ControllerRef,可以很好的解决这个问题。

该字段规定了,在所有的ownerReference中,只有第一个进行申请的owner才能将ControllerRef位置为true.当其它服务发现当前资源已经有了一个存在的ContorllerRef时其无法再将自己的ControllerRef置为true,并且也不应该再把当前资源算作自己期望目标的一部分。

至于具体细节是如何实现的,还是直接看提议中的描述吧。

总结

通过对ownerReference的作用总结,我们可以发现一个资源拥有多个owner完全是合理的,并且官方也已经设法解决了多个owner冲撞的问题。因此虽然多个ownerReference在官方自己的Kind中还是很少见的,但是在如今operator的日益流行的这个时代,利用该标记解决资源之间的依赖还是非常好的。