Kubernetes · DevOps

kubectl wait

After writing a few million scripts over the years,I’ve learnt a thing or two (I hope). One of those things is how painful it is to wait for some condition before continuing with the next step in a script.

I’ve used expect scripts, I’ve added sleep calls.

I’ve tried a few other things too. It’s never fun :(

kubectl comes with a wait option.

You can ask kubectl to wait for any number of conditions eg:

Wait for a namespace named shane to become Active

$ kubectl wait --for jsonpath='{.status.phase}=Active' --timeout=5s namespace/shane

Wait for a deployment to become Available

$ kubectl wait deployment -n cnpg-system cnpg-controller-manager --for condition=Available=True --timeout=210s

Note that the condition can be specified using the jsonpath, or, in some cases, by name.

The timeout is a maximum time to wait for the condition to become true.

Finding the condition can be difficult. Typically it’s a matter of digging through .yaml files looking for selectors, or, getting whatever you want running, then inspecting it. eg

$ kubectl get  pods -n shane
NAME                            READY   STATUS    RESTARTS   AGE
accountserver-c8897d469-js9qs   1/1     Running   0          17m
accountserver-c8897d469-mr7jn   1/1     Running   0          17m
accountserver-c8897d469-xdzpn   1/1     Running   0          17m
apiserver-57474fd944-7zxmb      1/1     Running   0          18m
postgres-1                      1/1     Running   0          22m
postgres-2                      1/1     Running   0          21m
postgres-3                      1/1     Running   0          19m

Followed by getting all the metadata on the object, in json format

 $ kubectl get  pods postgres-1 -n shane -o json
{
    "apiVersion": "v1",
    "kind": "Pod",
    "metadata": {
        "annotations": {
            "cnpg.io/nodeSerial": "1",
            "cnpg.io/operatorVersion": "1.21.0",
            "cnpg.io/podEnvHash": "fcffbc754",
            "cnpg.io/podSpec": "{\"volumes\":[{\"name\":\"pgdata\",\"persistentVolumeClaim\":{\"claimName\":\"postgres-1\"}},{\"name\":\"scratch-data\",\"emptyDir\":{}},{\"name\":\"shm\",\"emptyDir\":{\"medium\":\"Memory\"}},{\"name\":\"app-secret\",\"secret\":{\"secretName\":\"account-secret\"}}],\"initContainers\":[{\"name\":\"bootstrap-controller\",\"image\":\"ghcr.io/cloudnative-pg/cloudnative-pg:1.21.0\",\"command\":[\"/manager\",\"bootstrap\",\"/controller/manager\",\"--log-level=info\"],\"resources\":{},\"volumeMounts\":[{\"name\":\"pgdata\",\"mountPath\":\"/var/lib/postgresql/data\"},{\"name\":\"scratch-data\",\"mountPath\":\"/run\"},{\"name\":\"scratch-data\",\"mountPath\":\"/controller\"},{\"name\":\"shm\",\"mountPath\":\"/dev/shm\"},{\"name\":\"app-secret\",\"mountPath\":\"/etc/app-secret\"}],\"securityContext\":{\"capabilities\":{\"drop\":[\"ALL\"]},\"privileged\":false,\"runAsNonRoot\":true,\"readOnlyRootFilesystem\":true,\"allowPrivilegeEscalation\":false,\"seccompProfile\":{\"type\":\"RuntimeDefault\"}}}],\"containers\":[{\"name\":\"postgres\",\"image\":\"ghcr.io/cloudnative-pg/postgresql:16.0\",\"command\":[\"/controller/manager\",\"instance\",\"run\",\"--log-level=info\"],\"ports\":[{\"name\":\"postgresql\",\"containerPort\":5432,\"protocol\":\"TCP\"},{\"name\":\"metrics\",\"containerPort\":9187,\"protocol\":\"TCP\"},{\"name\":\"status\",\"containerPort\":8000,\"protocol\":\"TCP\"}],\"env\":[{\"name\":\"PGDATA\",\"value\":\"/var/lib/postgresql/data/pgdata\"},{\"name\":\"POD_NAME\",\"value\":\"postgres-1\"},{\"name\":\"NAMESPACE\",\"value\":\"shane\"},{\"name\":\"CLUSTER_NAME\",\"value\":\"postgres\"},{\"name\":\"PGPORT\",\"value\":\"5432\"},{\"name\":\"PGHOST\",\"value\":\"/controller/run\"}],\"resources\":{},\"volumeMounts\":[{\"name\":\"pgdata\",\"mountPath\":\"/var/lib/postgresql/data\"},{\"name\":\"scratch-data\",\"mountPath\":\"/run\"},{\"name\":\"scratch-data\",\"mountPath\":\"/controller\"},{\"name\":\"shm\",\"mountPath\":\"/dev/shm\"},{\"name\":\"app-secret\",\"mountPath\":\"/etc/app-secret\"}],\"livenessProbe\":{\"httpGet\":{\"path\":\"/healthz\",\"port\":8000},\"timeoutSeconds\":5,\"periodSeconds\":10},\"readinessProbe\":{\"httpGet\":{\"path\":\"/readyz\",\"port\":8000},\"timeoutSeconds\":5,\"periodSeconds\":10},\"startupProbe\":{\"httpGet\":{\"path\":\"/healthz\",\"port\":8000},\"timeoutSeconds\":5,\"periodSeconds\":10,\"failureThreshold\":360},\"securityContext\":{\"capabilities\":{\"drop\":[\"ALL\"]},\"privileged\":false,\"runAsNonRoot\":true,\"readOnlyRootFilesystem\":true,\"allowPrivilegeEscalation\":false,\"seccompProfile\":{\"type\":\"RuntimeDefault\"}}}],\"terminationGracePeriodSeconds\":1800,\"serviceAccountName\":\"postgres\",\"securityContext\":{\"runAsUser\":26,\"runAsGroup\":26,\"runAsNonRoot\":true,\"fsGroup\":26,\"seccompProfile\":{\"type\":\"RuntimeDefault\"}},\"hostname\":\"postgres-1\",\"affinity\":{\"podAntiAffinity\":{\"preferredDuringSchedulingIgnoredDuringExecution\":[{\"weight\":100,\"podAffinityTerm\":{\"labelSelector\":{\"matchExpressions\":[{\"key\":\"cnpg.io/cluster\",\"operator\":\"In\",\"values\":[\"postgres\"]}]},\"topologyKey\":\"kubernetes.io/hostname\"}}]}}}"
        },
        "creationTimestamp": "2023-12-18T07:40:02Z",
        "labels": {
            "cnpg.io/cluster": "postgres",
            "cnpg.io/instanceName": "postgres-1",
            "cnpg.io/instanceRole": "primary",
            "cnpg.io/podRole": "instance",
            "role": "primary"
        },
        "name": "postgres-1",
        "namespace": "shane",
        "ownerReferences": [
            {
                "apiVersion": "postgresql.cnpg.io/v1",
                "blockOwnerDeletion": true,
                "controller": true,
                "kind": "Cluster",
                "name": "postgres",
                "uid": "261148e6-26ac-4d58-8c1e-b248f188aae1"
            }
        ],
        "resourceVersion": "9936",
        "uid": "1a003c2f-ee83-4d25-af0e-cf192495a235"
    },
    "spec": {
        "affinity": {
            "podAntiAffinity": {
                "preferredDuringSchedulingIgnoredDuringExecution": [
                    {
                        "podAffinityTerm": {
                            "labelSelector": {
                                "matchExpressions": [
                                    {
                                        "key": "cnpg.io/cluster",
                                        "operator": "In",
                                        "values": [
                                            "postgres"
                                        ]
                                    }
                                ]
                            },
                            "topologyKey": "kubernetes.io/hostname"
                        },
                        "weight": 100
                    }
                ]
            }
        },
        "containers": [
            {
                "command": [
                    "/controller/manager",
                    "instance",
                    "run",
                    "--log-level=info"
                ],
                "env": [
                    {
                        "name": "PGDATA",
                        "value": "/var/lib/postgresql/data/pgdata"
                    },
                    {
                        "name": "POD_NAME",
                        "value": "postgres-1"
                    },
                    {
                        "name": "NAMESPACE",
                        "value": "shane"
                    },
                    {
                        "name": "CLUSTER_NAME",
                        "value": "postgres"
                    },
                    {
                        "name": "PGPORT",
                        "value": "5432"
                    },
                    {
                        "name": "PGHOST",
                        "value": "/controller/run"
                    }
                ],
                "image": "ghcr.io/cloudnative-pg/postgresql:16.0",
                "imagePullPolicy": "IfNotPresent",
                "livenessProbe": {
                    "failureThreshold": 3,
                    "httpGet": {
                        "path": "/healthz",
                        "port": 8000,
                        "scheme": "HTTP"
                    },
                    "periodSeconds": 10,
                    "successThreshold": 1,
                    "timeoutSeconds": 5
                },
                "name": "postgres",
                "ports": [
                    {
                        "containerPort": 5432,
                        "name": "postgresql",
                        "protocol": "TCP"
                    },
                    {
                        "containerPort": 9187,
                        "name": "metrics",
                        "protocol": "TCP"
                    },
                    {
                        "containerPort": 8000,
                        "name": "status",
                        "protocol": "TCP"
                    }
                ],
                "readinessProbe": {
                    "failureThreshold": 3,
                    "httpGet": {
                        "path": "/readyz",
                        "port": 8000,
                        "scheme": "HTTP"
                    },
                    "periodSeconds": 10,
                    "successThreshold": 1,
                    "timeoutSeconds": 5
                },
                "resources": {},
                "securityContext": {
                    "allowPrivilegeEscalation": false,
                    "capabilities": {
                        "drop": [
                            "ALL"
                        ]
                    },
                    "privileged": false,
                    "readOnlyRootFilesystem": true,
                    "runAsNonRoot": true,
                    "seccompProfile": {
                        "type": "RuntimeDefault"
                    }
                },
                "startupProbe": {
                    "failureThreshold": 360,
                    "httpGet": {
                        "path": "/healthz",
                        "port": 8000,
                        "scheme": "HTTP"
                    },
                    "periodSeconds": 10,
                    "successThreshold": 1,
                    "timeoutSeconds": 5
                },
                "terminationMessagePath": "/dev/termination-log",
                "terminationMessagePolicy": "File",
                "volumeMounts": [
                    {
                        "mountPath": "/var/lib/postgresql/data",
                        "name": "pgdata"
                    },
                    {
                        "mountPath": "/run",
                        "name": "scratch-data"
                    },
                    {
                        "mountPath": "/controller",
                        "name": "scratch-data"
                    },
                    {
                        "mountPath": "/dev/shm",
                        "name": "shm"
                    },
                    {
                        "mountPath": "/etc/app-secret",
                        "name": "app-secret"
                    },
                    {
                        "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount",
                        "name": "kube-api-access-s9n8l",
                        "readOnly": true
                    }
                ]
            }
        ],
        "dnsPolicy": "ClusterFirst",
        "enableServiceLinks": true,
        "hostname": "postgres-1",
        "initContainers": [
            {
                "command": [
                    "/manager",
                    "bootstrap",
                    "/controller/manager",
                    "--log-level=info"
                ],
                "image": "ghcr.io/cloudnative-pg/cloudnative-pg:1.21.0",
                "imagePullPolicy": "IfNotPresent",
                "name": "bootstrap-controller",
                "resources": {},
                "securityContext": {
                    "allowPrivilegeEscalation": false,
                    "capabilities": {
                        "drop": [
                            "ALL"
                        ]
                    },
                    "privileged": false,
                    "readOnlyRootFilesystem": true,
                    "runAsNonRoot": true,
                    "seccompProfile": {
                        "type": "RuntimeDefault"
                    }
                },
                "terminationMessagePath": "/dev/termination-log",
                "terminationMessagePolicy": "File",
                "volumeMounts": [
                    {
                        "mountPath": "/var/lib/postgresql/data",
                        "name": "pgdata"
                    },
                    {
                        "mountPath": "/run",
                        "name": "scratch-data"
                    },
                    {
                        "mountPath": "/controller",
                        "name": "scratch-data"
                    },
                    {
                        "mountPath": "/dev/shm",
                        "name": "shm"
                    },
                    {
                        "mountPath": "/etc/app-secret",
                        "name": "app-secret"
                    },
                    {
                        "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount",
                        "name": "kube-api-access-s9n8l",
                        "readOnly": true
                    }
                ]
            }
        ],
        "nodeName": "minikube",
        "preemptionPolicy": "PreemptLowerPriority",
        "priority": 0,
        "restartPolicy": "Always",
        "schedulerName": "default-scheduler",
        "securityContext": {
            "fsGroup": 26,
            "runAsGroup": 26,
            "runAsNonRoot": true,
            "runAsUser": 26,
            "seccompProfile": {
                "type": "RuntimeDefault"
            }
        },
        "serviceAccount": "postgres",
        "serviceAccountName": "postgres",
        "terminationGracePeriodSeconds": 1800,
        "tolerations": [
            {
                "effect": "NoExecute",
                "key": "node.kubernetes.io/not-ready",
                "operator": "Exists",
                "tolerationSeconds": 300
            },
            {
                "effect": "NoExecute",
                "key": "node.kubernetes.io/unreachable",
                "operator": "Exists",
                "tolerationSeconds": 300
            }
        ],
        "volumes": [
            {
                "name": "pgdata",
                "persistentVolumeClaim": {
                    "claimName": "postgres-1"
                }
            },
            {
                "emptyDir": {},
                "name": "scratch-data"
            },
            {
                "emptyDir": {
                    "medium": "Memory"
                },
                "name": "shm"
            },
            {
                "name": "app-secret",
                "secret": {
                    "defaultMode": 420,
                    "secretName": "account-secret"
                }
            },
            {
                "name": "kube-api-access-s9n8l",
                "projected": {
                    "defaultMode": 420,
                    "sources": [
                        {
                            "serviceAccountToken": {
                                "expirationSeconds": 3607,
                                "path": "token"
                            }
                        },
                        {
                            "configMap": {
                                "items": [
                                    {
                                        "key": "ca.crt",
                                        "path": "ca.crt"
                                    }
                                ],
                                "name": "kube-root-ca.crt"
                            }
                        },
                        {
                            "downwardAPI": {
                                "items": [
                                    {
                                        "fieldRef": {
                                            "apiVersion": "v1",
                                            "fieldPath": "metadata.namespace"
                                        },
                                        "path": "namespace"
                                    }
                                ]
                            }
                        }
                    ]
                }
            }
        ]
    },
    "status": {
        "conditions": [
            {
                "lastProbeTime": null,
                "lastTransitionTime": "2023-12-18T07:40:08Z",
                "status": "True",
                "type": "Initialized"
            },
            {
                "lastProbeTime": null,
                "lastTransitionTime": "2023-12-18T07:40:22Z",
                "status": "True",
                "type": "Ready"
            },
            {
                "lastProbeTime": null,
                "lastTransitionTime": "2023-12-18T07:40:22Z",
                "status": "True",
                "type": "ContainersReady"
            },
            {
                "lastProbeTime": null,
                "lastTransitionTime": "2023-12-18T07:40:02Z",
                "status": "True",
                "type": "PodScheduled"
            }
        ],
        "containerStatuses": [
            {
                "containerID": "docker://56603914ff01f3b781174deac8e7d25fae9e5bd764139eea547715d0c1690351",
                "image": "ghcr.io/cloudnative-pg/postgresql:16.0",
                "imageID": "docker-pullable://ghcr.io/cloudnative-pg/postgresql@sha256:5baf99e0fb30a051d8e108eeecb558af65a7119e068a280641873a2b98e35457",
                "lastState": {},
                "name": "postgres",
                "ready": true,
                "restartCount": 0,
                "started": true,
                "state": {
                    "running": {
                        "startedAt": "2023-12-18T07:40:10Z"
                    }
                }
            }
        ],
        "hostIP": "192.168.49.2",
        "initContainerStatuses": [
            {
                "containerID": "docker://70792f2a6c3f5f40424d2a1bb9818d201a768d4c8f63935e80be1bbe700838ff",
                "image": "ghcr.io/cloudnative-pg/cloudnative-pg:1.21.0",
                "imageID": "docker-pullable://ghcr.io/cloudnative-pg/cloudnative-pg@sha256:44f6132d5335528417427ea12634c5e6c96371e25e00dc898c3b6d091a279c21",
                "lastState": {},
                "name": "bootstrap-controller",
                "ready": true,
                "restartCount": 0,
                "started": false,
                "state": {
                    "terminated": {
                        "containerID": "docker://70792f2a6c3f5f40424d2a1bb9818d201a768d4c8f63935e80be1bbe700838ff",
                        "exitCode": 0,
                        "finishedAt": "2023-12-18T07:40:06Z",
                        "reason": "Completed",
                        "startedAt": "2023-12-18T07:40:06Z"
                    }
                }
            }
        ],
        "phase": "Running",
        "podIP": "10.244.0.18",
        "podIPs": [
            {
                "ip": "10.244.0.18"
            }
        ],
        "qosClass": "BestEffort",
        "startTime": "2023-12-18T07:40:02Z"
    }
}

We can use almost anything in that, let’s say when the pod is assigned an IP. We can see the path is status.podIPs and it’s an array, with the field inside each podIP being ip

$ kubectl wait --for jsonpath='{.status.podIPs[0].ip}' --timeout=5s pod/postgres-1 -n shane
pod/postgres-1 condition met

Simple :)

Summary

Just a quick note for using what’s a very helpful feature of kubectl, wait, which is now firmly in my toolkit.

Published:
comments powered by Disqus