Controller(Kubernetes)的PodControl解析

前言

阅读本文前请参看名词解释

PodControl是XxxController操作Pod的接口,比如ReplicaSetController创建、删除Pod。熟悉Clientset的读者肯定会质疑有必要再定义接口来操作Pod么?直接用Clientset提供的接口不就可以了么?答案肯定是有必要的,否则笔者就没必要介绍它了。因为很多Controller的子对象都是Pod,比如Job、Deamonset、ReplicaSet等等。这些Controller对于Pod都有类似的操作,比如设置Pod的拥有者(这一点不同于用kubectl create方式创建的Pod)。所以就有了PodControl接口提供给多个XxxController使用。

关于拥有者的介绍可以参看ControllerRefManager

本文引用源码为kubernetes的release-1.20分支。

PodControl

PodControlInterface

PodControlInterface定义了操作Pod的接口,源码链接:https://github.com/kubernetes/kubernetes/blob/release-1.20/pkg/controller/controller_utils.go#L449

type PodControlInterface interface {
    // 根据Pod模板创建Pod,这是XxxController创建Pod的共性,因为很多Workload都有Pod模板。
    CreatePods(namespace string, template *v1.PodTemplateSpec, object runtime.Object) error
    // CreatePodsOnNode()与CreatePods()一样根据Pod模板创建Pod,同时还指定了Pod运行的Node以及拥有者。
    // 遗憾的是CreatePodsOnNode()与CreatePods()都没有被XxxController所使用。
    CreatePodsOnNode(nodeName, namespace string, template *v1.PodTemplateSpec, object runtime.Object, controllerRef *metav1.OwnerReference) error
    // CreatePodsWithControllerRef()与CreatePods()一样根据Pod模板创建Pod,同时指定了拥有者。
    // 这是XxxController大量使用的接口,也证明了笔者前面的观点,即XxxController创建Pod是有共性的。
    CreatePodsWithControllerRef(namespace string, template *v1.PodTemplateSpec, object runtime.Object, controllerRef *metav1.OwnerReference) error
    // 删除一个Pod,这个接口与Clientset的接口没什么不同,无非多了个object参数。
    // 这也是XxxController操作Pod的另一个共性,就是为Pod的父对象记录操作Pod的事件。
    DeletePod(namespace string, podID string, object runtime.Object) error
    // Patch更新Pod。
    PatchPod(namespace, name string, data []byte) error
}

RealPodControl

RealPodControl实现了(仅此一个实现)PodControlInterface接口,源码链接:https://github.com/kubernetes/kubernetes/blob/release-1.20/pkg/controller/controller_utils.go#L464

type RealPodControl struct {
    // 利用Clientset操作Pod
    KubeClient clientset.Interface
    // 用于记录Pod父对象操作Pod的事件
    Recorder   record.EventRecorder
}

CreatePodXxx

虽然PodControlInterface定义了3个创建Pod的接口,但是笔者搜索发现只有CreatePodsWithControllerRef()被多大量使用,另外两个没有被使用。不排除早期版本中有使用,这并没有什么问题,因为这三个接口都是通过一个函数实现的。源码链接:https://github.com/kubernetes/kubernetes/blob/release-1.20/pkg/controller/controller_utils.go#L521

// CreatePods()实现了PodControlInterface.CreatePods()接口。
func (r RealPodControl) CreatePods(namespace string, template *v1.PodTemplateSpec, object runtime.Object) error {
    // createPods()下面有注释。
    return r.createPods("", namespace, template, object, nil)
}

// CreatePodsWithControllerRef()实现了PodControlInterface.CreatePodsWithControllerRef()接口。
func (r RealPodControl) CreatePodsWithControllerRef(namespace string, template *v1.PodTemplateSpec, controllerObject runtime.Object, controllerRef *metav1.OwnerReference) error {
    // 校验controllerRef的合法性
    if err := validateControllerRef(controllerRef); err != nil {
        return err
    }
    return r.createPods("", namespace, template, controllerObject, controllerRef)
}

// CreatePodsOnNode()实现了PodControlInterface.CreatePodsOnNode()接口。
func (r RealPodControl) CreatePodsOnNode(nodeName, namespace string, template *v1.PodTemplateSpec, object runtime.Object, controllerRef *metav1.OwnerReference) error {
    if err := validateControllerRef(controllerRef); err != nil {
        return err
    }
    return r.createPods(nodeName, namespace, template, object, controllerRef)
}

// createPods()是所有创建Pod接口的最终实现。
func (r RealPodControl) createPods(nodeName, namespace string, template *v1.PodTemplateSpec, object runtime.Object, controllerRef *metav1.OwnerReference) error {
    // 根据模板创建Pod的API对象,GetPodFromTemplate()下面有注释。
    pod, err := GetPodFromTemplate(template, object, controllerRef)
    if err != nil {
        return err
    }
    // 是否指定了Node,对应于CreatePodsOnNode()接口。
    if len(nodeName) != 0 {
        pod.Spec.NodeName = nodeName
    }
    // 不能创建没有标签的Pod,为什么?因为Controller需要根据Pod标签匹配,如果是Controller创建的Pod必须都有标签。
    if len(labels.Set(pod.Labels)) == 0 {
        return fmt.Errorf("unable to create pods, no labels")
    }
    // 通过Clientset创建Pod,有没有发现PodControl创建Pod的接口都没有metav1.CreateOptions?
    // 这也算是PodControl存在的另一个价值,省去了没用的参数。
    newPod, err := r.KubeClient.CoreV1().Pods(namespace).Create(context.TODO(), pod, metav1.CreateOptions{})
    if err != nil {
        if !apierrors.HasStatusCause(err, v1.NamespaceTerminatingCause) {
            // 记录为object创建Pod失败事件
            r.Recorder.Eventf(object, v1.EventTypeWarning, FailedCreatePodReason, "Error creating: %v", err)
        }
        return err
    }
    // 获取object对象的meta来打印日志
    accessor, err := meta.Accessor(object)
    if err != nil {
        klog.Errorf("parentObject does not have ObjectMeta, %v", err)
        return nil
    }
    // 记录为object创建Pod成功事件。
    klog.V(4).Infof("Controller %v created pod %v", accessor.GetName(), newPod.Name)
    r.Recorder.Eventf(object, v1.EventTypeNormal, SuccessfulCreatePodReason, "Created pod: %v", newPod.Name)

    return nil
}

// 根据Pod模板创建Pod的API对象。
func GetPodFromTemplate(template *v1.PodTemplateSpec, parentObject runtime.Object, controllerRef *metav1.OwnerReference) (*v1.Pod, error) {
    // 从模板中提取Labels、Finalizers和Annotations
    desiredLabels := getPodsLabelSet(template)
    desiredFinalizers := getPodsFinalizers(template)
    desiredAnnotations := getPodsAnnotationSet(template)
    accessor, err := meta.Accessor(parentObject)
    // 获取Pod父对象的meta
    if err != nil {
        return nil, fmt.Errorf("parentObject does not have ObjectMeta, %v", err)
    }
    // 利用父对象的名字+'-'作为Pod的前缀,这也是为什么我们看到Deployment创建出来的Pod都是xxx-yyy-zzz格式。
    // 其中xxx是Deployment名字,yyy是ReplicaSet名字,zzz是Pod名字,也就是Deployment创建了ReplicatSet,ReplicatSet创建了Pod
    prefix := getPodsPrefix(accessor.GetName())

    // 常见Pod的API对象
    pod := &v1.Pod{
        ObjectMeta: metav1.ObjectMeta{
            Labels:       desiredLabels,
            Annotations:  desiredAnnotations,
            GenerateName: prefix,
            Finalizers:   desiredFinalizers,
        },
    }
    // 如果指定了Pod的Controller引用,将其追加到Pod的拥有者引用中
    if controllerRef != nil {
        pod.OwnerReferences = append(pod.OwnerReferences, *controllerRef)
    }
    // 从Pod模板深度拷贝PodSpec。
    pod.Spec = *template.Spec.DeepCopy()
    return pod, nil
}

DeletePod

这个接口也没什么好解释的,直接上代码吧,源码链接:https://github.com/kubernetes/kubernetes/blob/release-1.20/pkg/controller/controller_utils.go#L599

// DeletePod()实现了PodControlInterface.DeletePod()接口。
func (r RealPodControl) DeletePod(namespace string, podID string, object runtime.Object) error {
    获取Pod父对象的meta,单纯为了打印日志,这应该是也是XxxController操作Pod的共性吧。
    accessor, err := meta.Accessor(object)
    if err != nil {
        return fmt.Errorf("object does not have ObjectMeta, %v", err)
    }
    klog.V(2).InfoS("Deleting pod", "controller", accessor.GetName(), "pod", klog.KRef(namespace, podID))
    // 通过Clientset删除Pod
    if err := r.KubeClient.CoreV1().Pods(namespace).Delete(context.TODO(), podID, metav1.DeleteOptions{}); err != nil {
        if apierrors.IsNotFound(err) {
            klog.V(4).Infof("pod %v/%v has already been deleted.", namespace, podID)
            return err
        }
        // 为object记录删除Pod失败的事件
        r.Recorder.Eventf(object, v1.EventTypeWarning, FailedDeletePodReason, "Error deleting: %v", err)
        return fmt.Errorf("unable to delete pods: %v", err)
    }
    // 为object记录删除Pod成功的事件
    r.Recorder.Eventf(object, v1.EventTypeNormal, SuccessfulDeletePodReason, "Deleted pod: %v", podID)

    return nil
}

PatchPod

Patch更新接口比Clientset的接口要简洁一些,少了一些看不懂的参数,这也是PodControl存在的价值,https://github.com/kubernetes/kubernetes/blob/release-1.20/pkg/controller/controller_utils.go#L539

// PatchPod()实现了PodControlInterface.PatchPod()接口。
func (r RealPodControl) PatchPod(namespace, name string, data []byte) error {
    // PatchPod()接口实现比较简单,直接用Clientset接口实现Patch更新。但是要求所有的XxxController都使用策略性合并(types.StrategicMergePatchType).
    // 为什么不为Patch更新Pod记录事件?这一点笔者也在思考。
    // 关于Patch类型的说明请参看:https://kubernetes.io/zh/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/
    _, err := r.KubeClient.CoreV1().Pods(namespace).Patch(context.TODO(), name, types.StrategicMergePatchType, data, metav1.PatchOptions{})
    return err
}

总结

  1. PodControl是Clientset操作Pod接口的再封装,但是在接口上去掉了一些对于XxxController'无用'的参数,比如metav1.PatchOptions、metav1.DeleteOptions、metav1.CreateOptions等,同时增加了XxxController需要的参数,比如父对象、ControllerRef等;
  2. PodControl能够为Pod的父对象记录操作Pod的事件,避免每个Controller都实现类似的代码;

猜你喜欢

转载自blog.csdn.net/weixin_42663840/article/details/115275349