所有者与依赖者
在 Kubernetes 中,所有者(owner)对象可以拥有(own)一个或多个依赖者(dependent)对象。所有者对象通常是控制器资源,比如 ReplicaSet、StatefulSet、Deployment 或 DaemonSet 等。这些控制器创建(即“拥有”)Pod,Pod 就是它们的依赖者。
所有者与依赖者的关系是通过 ControllerRef 字段建立的。ControllerRef 是一个字段,它指向创建它的控制器对象。每个 Kubernetes 对象都可以有一个 ControllerRef,但只能有一个。如果一个对象有多个所有者,那么它实际上没有所有者。
垃圾收集
所有者与依赖者的关系在 Kubernetes 的垃圾收集(garbage collection)机制中发挥着重要作用。当所有者对象被删除时,Kubernetes 垃圾收集器会负责删除所有拥有该所有者的依赖者对象。这种机制有助于确保在删除控制器时,其管理的所有 Pod 也会被相应地删除。
垃圾收集器默认启用,并且作用于所有具有 ControllerRef 的对象。但是,用户可以通过设置对象的 metadata.finalizers 字段来禁用垃圾收集。当 finalizer 列表不为空时,Kubernetes 不会从 API 中删除对象,即使删除请求已经提交。这允许外部控制器在对象被删除前执行任何必要的清理逻辑。
设置所有者引用
在创建依赖者对象时,可以通过在其 metadata 字段中包含 ownerReferences 字段来设置所有者引用。ownerReferences 字段是一个 OwnerReference 对象的列表,每个 OwnerReference 对象都表示一个对所有者对象的引用。
OwnerReference 对象包含以下字段:
- apiVersion:所有者对象的 API 版本。
- kind:所有者对象的种类。
- name:所有者对象的名称。
- uid:所有者对象的唯一标识符。
- controller:一个布尔值,表示此引用是否由 Kubernetes 控制器管理。如果为 true,则当所有者被删除时,此依赖者也会被垃圾收集器删除。
- blockOwnerDeletion:如果为 true,则当存在任何具有此值的依赖者时,不可以删除所有者对象。这可以防止级联删除。
使用场景
所有者与依赖者的概念在 Kubernetes 中有多种应用场景。以下是一些常见的例子:
- 控制器与 Pod:如前所述,控制器(如 Deployment、StatefulSet 等)创建并管理 Pod。在这种情况下,控制器是所有者,Pod 是依赖者。当控制器被删除时,其管理的所有 Pod 也将被删除。
- 自定义资源与依赖对象:当用户定义自定义资源(Custom Resource, CR)并创建相应的控制器来管理这些资源时,自定义资源可以成为其他对象的所有者。例如,一个自定义的数据库资源可以拥有一个用于存储数据的 PersistentVolumeClaim。当数据库资源被删除时,相应的 PersistentVolumeClaim 也将被删除。
- 防止级联删除:通过设置 blockOwnerDeletion 字段为 true,可以确保在删除所有者对象之前不会删除其依赖者。这在某些场景下非常有用,例如当依赖者对象包含重要数据或配置时,用户可能希望在删除所有者之前手动处理这些依赖者。
通过理解和利用所有者与依赖者的概念,用户可以更有效地管理 Kubernetes 集群中的资源,并确保在删除或更新对象时保持资源的完整性和一致性。
举个例子,当你尝试删除一个仍然被 Pod 使用的 PersistentVolume 时,由于 PersistentVolume 上有 kubernetes.io/pv-protection finalizer,删除操作不会立即生效。相反,该 PersistentVolume 会保持在 Terminating 状态,直到 Kubernetes 清除了 finalizer,而这只会在 PersistentVolume 不再绑定到任何 Pod 时发生。
package main
import (
"context"
"flag"
"fmt"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)
func main() {
kubeconfig := flag.String("kubeconfig", "", "Path to the kubeconfig file")
flag.Parse()
// 创建 Kubernetes 客户端
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
if err != nil {
panic(err)
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err)
}
// 创建一个测试 Namespace
namespace := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "test-namespace",
},
}
_, err = clientset.CoreV1().Namespaces().Create(context.TODO(), namespace, metav1.CreateOptions{})
if err != nil {
panic(err)
}
// 创建一个测试 Pod,将 Namespace 设置为 owner
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod",
Namespace: "test-namespace",
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "v1",
Kind: "Namespace",
Name: "test-namespace",
UID: namespace.UID,
Controller: func(b bool) *bool { return &b }(true),
},
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "nginx",
Image: "nginx",
},
},
},
}
_, err = clientset.CoreV1().Pods("test-namespace").Create(context.TODO(), pod, metav1.CreateOptions{})
if err != nil {
panic(err)
}
// 向 Pod 添加 finalizer
podWithFinalizer, err := clientset.CoreV1().Pods("test-namespace").Get(context.TODO(), "test-pod", metav1.GetOptions{})
if err != nil {
panic(err)
}
podWithFinalizer.ObjectMeta.Finalizers = append(podWithFinalizer.ObjectMeta.Finalizers, "finalizer.example.com/cleanup")
_, err = clientset.CoreV1().Pods("test-namespace").Update(context.TODO(), podWithFinalizer, metav1.UpdateOptions{})
if err != nil {
panic(err)
}
// 删除 Pod,触发 finalizer 清理逻辑
err = clientset.CoreV1().Pods("test-namespace").Delete(context.TODO(), "test-pod", metav1.DeleteOptions{})
if err != nil {
panic(err)
}
fmt.Println("Pod deletion triggered")
// 在这里可以添加对 finalizer 的清理逻辑
// 比如发送通知邮件等操作
}