Exploring Mirror Pod Deletion in Kubernetes: Understanding its Impact on Static Pod

This is also an article about the research on the process of removing pods, focusing on the removal of mirror pods. The term “mirror pod” may sound unfamiliar, it is a type of pod within Kubernetes.

Let’s first introduce the classification of pods. Pods come from file, http, and apiserver sources. Pods from the apiserver are called ordinary pods, while pods from other sources are called static pods (the control plane installed using kubeadm runs with static pods). To manage pods conveniently, kubelet generates corresponding pods for static pods on the apiserver. These types of pods are called mirror pods, essentially mirroring the static pod (almost identical, except for a different UID and the addition of “kubernetes.io/config.mirror” in annotations).

So, what happens when you delete a mirror pod? Will it remove static pods on the node?

Pod Removal Process Series Articles:

This article is based on Kubernetes version 1.23 and log level 4.

The kubelet’s mechanism generates static pods with a name that is the pod name in the configuration plus the node’s name. In this example, the defined pod name is “nginx-static-pod,” and the generated pod name is “nginx-static-pod-10.11.251.2.”

Here is the static pod configuration on kubelet:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
apiVersion: v1
kind: Pod
metadata:
  name: nginx-static-pod
  namespace: default
spec:
  containers:
  - name: nginx-container
    image: nginx:latest
    ports:
    - containerPort: 80

Corresponding mirror pod:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
apiVersion: v1
kind: Pod
metadata:
  annotations:
    kubernetes.io/config.hash: a8712c005851ee6b29cff91b9ab4b9c6
    kubernetes.io/config.mirror: a8712c005851ee6b29cff91b9ab4b9c6
    kubernetes.io/config.seen: "2023-11-23T14:05:21.400551188+08:00"
    kubernetes.io/config.source: file
  creationTimestamp: "2023-11-23T06:05:21Z"
  name: nginx-static-pod-10.11.251.2
  namespace: default
  ownerReferences:
  - apiVersion: v1
    controller: true
    kind: Node
    name: 10.11.251.2
    uid: d2e3ebf6-2a70-434f-9142-0f74a1dd16bc
  resourceVersion: "292107092"
  uid: 1facfed2-ba67-40f8-9982-ab86a5e7fd33
  spec:
    containers:
    - image: nginx:latest
      imagePullPolicy: Always
      name: nginx-container
      ports:
      - containerPort: 80
        protocol: TCP
      resources: {}
      terminationMessagePath: /dev/termination-log
      terminationMessagePolicy: File
    dnsPolicy: ClusterFirst
    enableServiceLinks: true
    nodeName: 10.11.251.2
    preemptionPolicy: PreemptLowerPriority
    priority: 0
    restartPolicy: Always
    schedulerName: default-scheduler
    securityContext: {}
    terminationGracePeriodSeconds: 30
    tolerations:
    - effect: NoExecute
      operator: Exists
  status:
    conditions:
    - lastProbeTime: null
      lastTransitionTime: "2023-11-23T06:05:21Z"
      status: "True"
      type: Initialized
    - lastProbeTime: null
      lastTransitionTime: "2023-11-23T06:05:29Z"
      status: "True"
      type: Ready
    - lastProbeTime: null
      lastTransitionTime: "2023-11-23T06:05:29Z"
      status: "True"
      type: ContainersReady
    - lastProbeTime: null
      lastTransitionTime: "2023-11-23T06:05:21Z"
      status: "True"
      type: PodScheduled
    containerStatuses:
    - containerID: docker://b6ca55d329230c8f5776eb1160fe161d6fefa01f2b31e55dbb820add90aadccc
      image: nginx:latest
      imageID: docker-pullable://nginx@sha256:10d1f5b58f74683ad34eb29287e07dab1e90f10af243f151bb50aa5dbb4d62ee
      lastState: {}
      name: nginx-container
      ready: true
      restartCount: 0
      started: true
      state:
        running:
          startedAt: "2023-11-23T06:05:29Z"
    hostIP: 10.11.251.2
    phase: Running
    podIP: 10.26.124.96
    podIPs:
    - ip: 10.26.124.96
    qosClass: BestEffort
    startTime: "2023-11-23T06:05:21Z"

Let’s conduct an experiment by directly deleting the mirror pod using kubectl and then observing the changes in pod status.

The complete kubelet logs and watch records are available at The log for mirror pod in kubelet.

The UID of the mirror pod is 1facfed2-ba67-40f8-9982-ab86a5e7fd33.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# kubectl get pod nginx-static-pod-10.11.251.2 -o yaml -w
apiVersion: v1
kind: Pod
metadata:
  annotations:
    kubernetes.io/config.hash: a8712c005851ee6b29cff91b9ab4b9c6
    kubernetes.io/config.mirror: a8712c005851ee6b29cff91b9ab4b9c6
    kubernetes.io/config.seen: "2023-11-23T14:05:21.400551188+08:00"
    kubernetes.io/config.source: file
  creationTimestamp: "2023-11-23T06:05:21Z"
  name: nginx-static-pod-10.11.251.2
  namespace: default
  ownerReferences:
  - apiVersion: v1
    controller: true
    kind: Node
    name: 10.11.251.2
    uid: d2e3ebf6-2a70-434f-9142-0f74a1dd16bc
  resourceVersion: "292107092"
  uid: 1facfed2-ba67-40f8-9982-ab86a5e7fd33

The pod is deleted; the deletionTimestamp field has a value, and deletionGracePeriodSeconds is set to 30.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: v1
kind: Pod
metadata:
  annotations:
    kubernetes.io/config.hash: a8712c005851ee6b29cff91b9ab4b9c6
    kubernetes.io/config.mirror: a8712c005851ee6b29cff91b9ab4b9c6
    kubernetes.io/config.seen: "2023-11-23T14:05:21.400551188+08:00"
    kubernetes.io/config.source: file
  creationTimestamp: "2023-11-23T06:05:21Z"
  deletionGracePeriodSeconds: 30
  deletionTimestamp: "2023-11-23T06:11:45Z"
  name: nginx-static-pod-10.11.251.2
  namespace: default
  ownerReferences:
  - apiVersion: v1
    controller: true
    kind: Node
    name: 10.11.251.2
    uid: d2e3ebf6-2a70-434f-9142-0f74a1dd16bc
  resourceVersion: "292110805"
  uid: 1facfed2-ba67-40f8-9982-ab86a5e7fd33

Deleted again, and deletionGracePeriodSeconds is set to 0.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: v1
kind: Pod
metadata:
  annotations:
    kubernetes.io/config.hash: a8712c005851ee6b29cff91b9ab4b9c6
    kubernetes.io/config.mirror: a8712c005851ee6b29cff91b9ab4b9c6
    kubernetes.io/config.seen: "2023-11-23T14:05:21.400551188+08:00"
    kubernetes.io/config.source: file
  creationTimestamp: "2023-11-23T06:05:21Z"
  deletionGracePeriodSeconds: 0
  deletionTimestamp: "2023-11-23T06:11:15Z"
  name: nginx-static-pod-10.11.251.2
  namespace: default
  ownerReferences:
  - apiVersion: v1
    controller: true
    kind: Node
    name: 10.11.251.2
    uid: d2e3ebf6-2a70-434f-9142-0f74a1dd16bc
  resourceVersion: "292110806"
  uid: 1facfed2-ba67-40f8-9982-ab86a5e7fd33

The mirror pod is removed from the apiserver (only the resourceVersion has changed).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: v1
kind: Pod
metadata:
  annotations:
    kubernetes.io/config.hash: a8712c005851ee6b29cff91b9ab4b9c6
    kubernetes.io/config.mirror: a8712c005851ee6b29cff91b9ab4b9c6
    kubernetes.io/config.seen: "2023-11-23T14:05:21.400551188+08:00"
    kubernetes.io/config.source: file
  creationTimestamp: "2023-11-23T06:05:21Z"
  deletionGracePeriodSeconds: 0
  deletionTimestamp: "2023-11-23T06:11:15Z"
  name: nginx-static-pod-10.11.251.2
  namespace: default
  ownerReferences:
  - apiVersion: v1
    controller: true
    kind: Node
    name: 10.11.251.2
    uid: d2e3ebf6-2a70-434f-9142-0f74a1dd16bc
  resourceVersion: "292110807"
  uid: 1facfed2-ba67-40f8-9982-ab86a5e7fd33

A new mirror pod is created; the UID is 7bdace8a-8cc5-4cb4-886b-f3961f07d3b8 (the pod’s UID has changed, and only the phase and qosClass fields are present in the status).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
apiVersion: v1
kind: Pod
metadata:
  annotations:
    kubernetes.io/config.hash: a8712c005851ee6b29cff91b9ab4b9c6
    kubernetes.io/config.mirror: a8712c005851ee6b29cff91b9ab4b9c6
    kubernetes.io/config.seen: "2023-11-23T14:05:21.400551188+08:00"
    kubernetes.io/config.source: file
  creationTimestamp: "2023-11-23T06:11:15Z"
  name: nginx-static-pod-10.11.251.2
  namespace: default
  ownerReferences:
  - apiVersion: v1


    controller: true
    kind: Node
    name: 10.11.251.2
    uid: d2e3ebf6-2a70-434f-9142-0f74a1dd16bc
  resourceVersion: "292110808"
  uid: 7bdace8a-8cc5-4cb4-886b-f3961f07d3b8
  ...
status:
  phase: Pending
  qosClass: BestEffort

The status of the mirror pod is updated.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
status:
  conditions:
  - lastProbeTime: null
    lastTransitionTime: "2023-11-23T06:05:21Z"
    status: "True"
    type: Initialized
  - lastProbeTime: null
    lastTransitionTime: "2023-11-23T06:05:29Z"
    status: "True"
    type: Ready
  - lastProbeTime: null
    lastTransitionTime: "2023-11-23T06:05:29Z"
    status: "True"
    type: ContainersReady
  - lastProbeTime: null
    lastTransitionTime: "2023-11-23T06:05:21Z"
    status: "True"
    type: PodScheduled
  containerStatuses:
  - containerID: docker://b6ca55d329230c8f5776eb1160fe161d6fefa01f2b31e55dbb820add90aadccc
    image: nginx:latest
    imageID: docker-pullable://nginx@sha256:10d1f5b58f74683ad34eb29287e07dab1e90f10af243f151bb50aa5dbb4d62ee
    lastState: {}
    name: nginx-container
    ready: true
    restartCount: 0
    started: true
    state:
      running:
        startedAt: "2023-11-23T06:05:29Z"
  hostIP: 10.11.251.2
  phase: Running
  podIP: 10.26.124.96
  podIPs:
  - ip: 10.26.124.96
  qosClass: BestEffort
  startTime: "2023-11-23T06:05:21Z"

The mirror pod is deleted.

1
2
I1123 14:11:15.664065  315900 config.go:278] "Setting pods for source" source="api"
I1123 14:11:15.664544  315900 kubelet.go:2130] "SyncLoop DELETE" source="api" pods=[default/nginx-static-pod-10.11.251.2]

podWorker executes syncPod (as static pod is used to determine status in podWorker, syncTerminatingPod is not executed since the static pod is not deleted).

1
2
I1123 14:11:15.664591  315900 pod_workers.go:888] "Processing pod event" pod="default/nginx-static-pod-10.11.251.2" podUID=a8712c005851ee6b29cff91b9ab4b9c6 updateType=0
I1123 14:11:15.664610  315900 kubelet.go:1554] "syncPod enter" 

In syncPod, mirror pod is deleted again.

1
2
I1123 14:11:15.664893  315900 kubelet.go:1724] "Trying to delete pod" pod="default/nginx-static-pod-10.11.251.2" podUID=1facfed2-ba67-40f8-9982-ab86a5e7fd33
I1123 14:11:15.664900  315900 mirror_client.go:130] "Deleting a mirror pod" pod="default/nginx-static-pod-10.11.251.2" podUID=1facfed2-ba67-40f8-9982-ab86a5e7fd33

Perceiving the deletion of the pod again, then removing it from the apiserver.

1
2
3
4
I1123 14:11:15.671615  315900 config.go:278] "Setting pods for source" source="api"
I1123 14:11:15.671989  315900 kubelet.go:2130] "SyncLoop DELETE" source="api" pods=[default/nginx-static-pod-10.11.251.2]
I1123 14:11:15.674620  315900 config.go:278] "Setting pods for source" source="api"
I1123 14:11:15.675067  315900 kubelet.go:2124] "SyncLoop REMOVE" source="api" pods=[default/nginx-static-pod-10.11.251.2]

Deletion is completed, and a new mirror pod is created.

1
2
3
I1123 14:11:15.675108  315900 kubelet.go:1729] "Deleted mirror pod because it is outdated" pod="default/nginx-static-pod-10.11.251.2"
I1123 14:11:15.675121  315900 kubelet.go:1740] "Creating a mirror pod for static pod" pod="default/nginx-static-pod-10.11.251.2"
I1123 14:11:15.683985  315900 config.go:383] "Receiving a new pod" 

syncPod execution is completed.

1
2
I1123 14:11:15.684056  315900 kubelet.go:1556] "syncPod exit" pod="default/nginx-static-pod-10.11.251.2" podUID=a8712c005851ee6b29cff91b9ab4b9c6 isTerminal=false
I1123 14:11:15.684085  315900 pod_workers.go:988] "Processing pod event done" pod="default/nginx-static-pod-10.11.251.2" podUID=a8712c005851ee6b29cff91b9ab4b9c6 updateType=0

podWorker continues to execute syncPod (this is triggered by the previous sync REMOVE).

1
2
I1123 14:11:15.684093  315900 pod_workers.go:888] "Processing pod event" pod="default/nginx-static-pod-10.11.251.2" podUID=a8712c005851ee6b29cff91b9ab4b9c6 updateType=0
I1123 14:11:16.053579  315900 kubelet.go:1554] "syncPod enter" pod="default/nginx-static-pod-10.11.251.2" podUID=a8712c005851ee6b29cff91b9ab4b9c6

Perceiving the newly created mirror pod.

1
I1123 14:11:15.684281  315900 kubelet.go:2114] "SyncLoop ADD" source="api" pods=[default/nginx-static-pod-10.11.251.2]

Executing deletion of the mirror pod again; syncPod execution is completed, and podWorker execution is completed.

1
2
3
I1123 14:11:16.053912  315900 mirror_client.go:130] "Deleting a mirror pod" pod="default/nginx-static-pod-10.11.251.2" podUID=1facfed2-ba67-40f8-9982-ab86a5e7fd33
I1123 14:11:16.058466  315900 kubelet.go:1556] "syncPod exit" pod="default/nginx-static-pod-10.11.251.2" podUID=a8712c005851ee6b29cff91b9ab4b9c6 isTerminal=false
I1123 14:11:16.058484  315900 pod_workers.go:988] "Processing pod event done" pod="default/nginx-static-pod-10.11.251.2" podUID=a8712c005851ee6b29cff91b9ab4b9c6 updateType=0 

podWorker continues to execute syncPod (triggered by SyncLoop ADD), and then execution is completed.

1
2
3
4
5
6
I1123 14:11:16.058491  315900 pod_workers.go:888] "Processing pod event" pod="default/nginx-static-pod-10.11.251.2" podUID=a8712c005851ee6b29cff91b9ab4b9c6 updateType=0
I1123 14:11:17.057755  315900 kubelet.go:1554] "syncPod enter" pod="default/nginx-static-pod-10.11.251.2" podUID=a8712c005851ee6b29cff91b9ab4b9c6
I1123 14:11:17.058187  315900 kubelet.go:1556] "syncPod exit" pod="default/nginx-static-pod-10.11.251.2" podUID=a8712c005851ee6b29cff91b9ab4b

9c6 isTerminal=false
I1123 14:11:17.058208  315900 pod_workers.go:988] "Processing pod event done" pod="default/nginx-static-pod-10.11.251.2" podUID=a8712c005851ee6b29cff91b9ab4b9c6 updateType=0

Mirror pod status update (this is triggered by the syncBatch in statusManager).

1
I1123 14:11:21.465237  315900 kubelet.go:2127] "SyncLoop RECONCILE" source="api" pods=[default/nginx-static-pod-10.11.251.2]

Perceiving the update of the mirror pod status.

1
I1123 14:11:21.465237  315900 kubelet.go:2127] "SyncLoop RECONCILE" source="api" pods=[default/nginx-static-pod-10.11.251.2]

kubelet-mirror-pod-delete

Based on the observations above, deleting a mirror pod does not remove the static pod. Furthermore, when the kubelet recreates a mirror pod, it lacks status conditions and container status. Later, the pod status is synchronized. In reality, operations on mirror pods do not impact static pods because static pods are only visible within the kubelet, and external queries for static pod status can be made through the mirror pod on the apiserver or the /runningpods/ interface on the kubelet’s HTTP server.

From the source code analysis, every time the syncLoop triggers, podWorker executes syncPod.

In the syncPod function, the logic for handling static pods is as follows:

  • If the pod is a static pod and a mirror pod exists, check if the mirror pod is a valid image pod for this static pod. If it is not a valid mirror pod, delete the mirror pod.
  • If the mirror pod has already been deleted, perform the final deletion action (setting deletionGracePeriodSeconds to 0).
  • If the static pod has no mirror pod, create a mirror pod.

The code is located in pkg/kubelet/kubelet.go#L1761-L1798:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// Create Mirror Pod for Static Pod if it doesn't already exist
// If it's a static pod, check if the mirror pod exists, and create one when it doesn't
if kubetypes.IsStaticPod(pod) {
    deleted := false
    // If the mirror pod exists
    if mirrorPod != nil {
        // If the mirror pod is deleted or not the mirror pod of the pod
        if mirrorPod.DeletionTimestamp != nil || !kl.podManager.IsMirrorPodOf(mirrorPod, pod) {
            // The mirror pod is semantically different from the static pod. Remove
            // it. The mirror pod will get recreated later.
            klog.InfoS("Trying to delete pod", "pod", klog.KObj(pod), "podUID", mirrorPod.ObjectMeta.UID)
            // pod.Name + "_" + pod.Namespace
            podFullName := kubecontainer.GetPodFullName(pod)
            var err error
            // Call API to delete mirror pod (parse name and namespace from podFullName)
            deleted, err = kl.podManager.DeleteMirrorPod(podFullName, &mirrorPod.ObjectMeta.UID)
            if deleted {
                klog.InfoS("Deleted mirror pod because it is outdated", "pod", klog.KObj(mirrorPod))
            } else if err != nil {
                klog.ErrorS(err, "Failed deleting mirror pod", "pod", klog.KObj(mirrorPod))
            }
        }
    }
    // If there is no mirror pod for static pod or the mirror pod is deleted,
    // then create a new mirror pod as long as there is no error when getting the node
    // and the node is not deleted
    if mirrorPod == nil || deleted {
        node, err := kl.GetNode()
        // If there is an error getting the node or the node is deleted, no need to create a mirror pod
        if err != nil || node.DeletionTimestamp != nil {
            klog.V(4).InfoS("No need to create a mirror pod, since the node has been removed from the cluster", "node", klog.KRef("", string(kl.nodeName)))
        } else {
            klog.V(4).InfoS("Creating a mirror pod for a static pod", "pod", klog.KObj(pod))
            // If the node is not deleted, create a mirror pod for the static pod
            if err := kl.podManager.CreateMirrorPod(pod); err != nil {
                klog.ErrorS(err, "Failed creating a mirror pod for", "pod", klog.KObj(pod))
            }
        }
    }
}

Related Content