为什么kubernetes的ownerReference要设计成列表,存在多个ownerReference的情况吗?
为什么kubernetes的ownerReference要设计成列表,存在多个ownerReference的情况吗?
Joshua为什么kubernetes的ownerReference要设计成列表,存在多个ownerReference的情况吗?
如果我们仔细查看kubernetes的metadata
字段,我们会发现owenerReference
被设计成了一个列表。按照常理理解,一个资源难道不是只有一个控制器吗?比如Pods
资源类型,我们去查看其ownerReference
我们在绝大多数情况下(其实在我的集群上,可以描述为“全部”)只会见到类型为ReplicaSets
的owner。那么我们是否应该将其改为单一的值类型呢?
先说结论:不能。
要想弄清楚为什么它的类型为list,我们非常有必要理解一下ownerReference
字段是做什么用的。
ownerReference字段的作用
1. 作用一 :垃圾清理
在kubernetes中,一些资源是被一些其他资源自动控制的,一个例子就是我们上文提到的ReplicaSets
和Pods
,一个ReplicaSet
是一组Pods
的parent。
现在我们可以将这个概念几乎拓展到kubernetes中的所有资源,这意味着这个可以控制多个资源之间的parent与child关系。
我们先来试试一组ownerReference的情况
举个栗子,我们在kubernetes中有一个CRD(自定义资源)叫Database
,我们想要每一个Database
对象拥有一个属于自己的数据库配置configmap
,Kubernetes
在字段metadata
中提供了一个这样的字段ownerReference
可以用来定义对象和命名空间的关系,我们可以用文字这样来描述:
我有一个
Database
对象叫做“Postgres”,并且它的owner为一个叫“Postgres-cm”的configmap
我们来尝试通过实操来理解的更加深刻一些:
我们先来创建一个CRD和Configmap
下面的crd.yaml
定义了一个叫Database
的资源:
apiVersion: apiextensions.k8s.io/v1 |
使用命令,创建这个crd
kubectl create -f crd.yaml |
创建一个名为postgres-cm的Configmap
在postgres-cm
中创建如下资源:
apiVersion: v1 |
使用命令,创建这个configmap
kubectl create -f postgres-cm.yaml |
创建一个使用这个CRD的资源postgres
在postgres.yaml
中创建如下的postgres资源
apiVersion: test.joshua.su/v1 |
使用命令,创建这个资源
kubectl create -f postgres.yaml |
设置ownerReference & 创建父子关系
现在,存在一个Kubernetes提供的独一无二的字段—owenerReference
,通过它我们可以将一个资源映射到它的父资源。这意味着一旦这个映射建立完成,如果父资源被删除了那么子资源也会随之被删除。当我们看向我们的这个实验时,理论上当我们删除postgres
这个database
类型的资源,configmap postgres-cm
也会自动触发gc被清理掉。
现在我们要把postgres
这个资源和我们刚创建的configmap进行关联。我们应该能够保证当postgres
这个资源被删除后,cm也应该会被gc。
所以我们继续我们的实验,我们创建一些字段进我们刚刚创建的configmap的yaml中的metadata中。
apiVersion: v1 |
你可能想知道:
上面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-kindsname
-必选-
这个引用的名字. 更多参阅:
https://kubernetes.io/docs/concepts/overview/working-with-objects/names#namesuid
-必选-
这个引用的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}' |
有了这个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 |
我们成功创建了一个ownerReference字段。
观察GC现象
-
通过下面的命令确定一下我们创建的资源是否真的存在
kubectl get cm postgres-cm
NAME STATUS AGE
postgres-cm 1 3m46s
kubectl get database postgres
NAME AGE
postgres 3m46s -
删除
postgres
资源
kubectl delete database postgres |
- 删除成功后,观察
postgres-ns namespace
的情况
kubectl get cm postgres-cm |
hao dehao de显然postgres-cm
已经被删除了
我们再试试多个组的情况
是的,你没听错,现在 postgres-cm
可以被多个数据库对象拥有,毕竟作为懒狗,给多个数据库使用相同的配置,也是合情合理的。 就比如现在有postgres
和 postgres-1
共用一个postgres-cm
,那么我们就可以在postgres-cm
中设置他们两个数据库对象为ownerReference
。这里需要注意的一点是,只有在这两个数据库对象都被删除后,postgres-cm
才会失去其作用域并被垃圾回收。此外,其中一个 ownerReference 的 controller 字段必须设置为 true,至于这个的作用我们会在下文的另外一个实验当中探索。
-
首先我们创建两个数据库对象,并获取他们的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 -
接着,老样子,把
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"}]}]'
-
查看patch的情况
kubectl get cm postgres-cm -o yaml |
- 删除
postgres
,我们再看看postgres-cm
还在不在
kubectl delete database postgres |
看来,还在
-
把
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的日益流行的这个时代,利用该标记解决资源之间的依赖还是非常好的。