Containers Should Define Liveness And Readiness Probes
More Info:​
Advisory: long-running containers should define livenessProbe and readinessProbe so Kubernetes can restart hung pods and keep traffic off pods that are not ready.
Risk Level​
Informational
Address​
Security
Compliance Standards​
- Cloudanix Best Practice
Triage and Remediation​
- Remediation
Remediation​
Manual Steps
-
Identify the noncompliant pods and their owning workload (Deployment, StatefulSet, DaemonSet, etc.). Run on any machine with kubectl access:
kubectl get pods --all-namespaces -o json | jq -r '[ .items[]| select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)| .metadata as $m| (.spec.containers // [])[]| (.livenessProbe != null) as $live| (.readinessProbe != null) as $ready| select((($live and $ready) | not))| "ns=\($m.namespace) pod=\($m.name)" ] | unique[]'For each listed pod, get its controller:
kubectl get pod POD_NAME -n NAMESPACE -o jsonpath='{.metadata.ownerReferences}' | jq -
For each owning workload, export the current manifest. Run on any machine with kubectl access:
# Example for a Deploymentkubectl get deployment DEPLOYMENT_NAME -n NAMESPACE -o yaml > /tmp/deployment-DEPLOYMENT_NAME.yaml# For a StatefulSetkubectl get statefulset STATEFULSET_NAME -n NAMESPACE -o yaml > /tmp/statefulset-STATEFULSET_NAME.yaml# For a DaemonSetkubectl get daemonset DAEMONSET_NAME -n NAMESPACE -o yaml > /tmp/daemonset-DAEMONSET_NAME.yaml -
Edit the manifest to add
livenessProbeandreadinessProbeto each long‑running container. Run on any machine with kubectl access:vi /tmp/deployment-DEPLOYMENT_NAME.yamlUnder
spec.template.spec.containers[].for each applicable container, add probes suited to the app. Example HTTP container snippet (adapt values for your app):spec:template:spec:containers:- name: appimage: your-image:tagports:- containerPort: 8080livenessProbe:httpGet:path: /healthzport: 8080initialDelaySeconds: 30periodSeconds: 10readinessProbe:httpGet:path: /readyport: 8080initialDelaySeconds: 5periodSeconds: 5For non-HTTP apps, use
tcpSocketorexecprobes instead ofhttpGet, as appropriate. -
Apply the updated workload manifests. Run on any machine with kubectl access:
kubectl apply -f /tmp/deployment-DEPLOYMENT_NAME.yaml# Repeat for statefulsets/daemonsets or other controllers you editedKubernetes will perform a rolling update; pods for that workload will be recreated with the new probes.
-
For standalone Pods not managed by a controller (if any), edit them or their source manifests, understanding that direct
kubectl edit podis ephemeral. Prefer updating the underlying manifest and re-creating the pod:kubectl get pod POD_NAME -n NAMESPACE -o yaml > /tmp/pod-POD_NAME.yamlvi /tmp/pod-POD_NAME.yaml# Add livenessProbe and readinessProbe under spec.containers[] as in step 3kubectl delete pod POD_NAME -n NAMESPACEkubectl apply -f /tmp/pod-POD_NAME.yaml -
Verify that all long‑running containers now define both probes. Run on any machine with kubectl access:
kubectl get pods --all-namespaces -o json | jq -r '[ .items[]| select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)| .metadata as $m| (.spec.containers // [])[]| (.livenessProbe != null) as $live| (.readinessProbe != null) as $ready| "ns=\($m.namespace) pod=\($m.name) container=\(.name) is_compliant=\(if ($live and $ready) then "true" else "false" end)"][]' | grep 'is_compliant=false' || echo "All checked containers are compliant"
Using kubectl
On any machine with kubectl access:
- Identify non-compliant pods and their controllers (Deployment/StatefulSet/etc.):
kubectl get pods --all-namespaces -o json | jq -r '
[ .items[]
| select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| .metadata as $m
| (($m.ownerReferences // [])[] | select(.controller) | .kind + " " + .name) // "POD " + $m.name
] | unique[]' | sort
- For each long-running workload (example: a Deployment
my-appin namespaceprod), export the current manifest:
kubectl get deploy my-app -n prod -o yaml > /tmp/deploy-my-app.yaml
- Edit the manifest to add
livenessProbeandreadinessProbeunder each long-running container inspec.template.spec.containers. Example patch to apply inside a container spec:
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
Adjust probe type, paths, ports, and timings to match the container.
- Apply the updated manifest:
kubectl apply -f /tmp/deploy-my-app.yaml
Repeat steps 2–4 for each affected Deployment/StatefulSet/DaemonSet or standalone Pod that represents a long-running workload.
- Verification (same audit logic):
kubectl get pods --all-namespaces -o json | jq -r '
[ .items[]
| select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| .metadata as $m
| (.spec.containers // [])[]
| (.livenessProbe != null) as $live
| (.readinessProbe != null) as $ready
| "ns=\($m.namespace) pod=\($m.name) container=\(.name) is_compliant=\(if ($live and $ready) then "true" else "false" end)"
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
Automation
#!/usr/bin/env bash
# Purpose: Report and optionally patch Kubernetes workloads on EKS whose containers
# lack livenessProbe and/or readinessProbe, by generating patched manifests.
# Scope: Run on any machine with kubectl access to the cluster.
#
# NOTES:
# - This script does NOT directly modify running objects server-side.
# It generates patch files you can review and apply with kubectl.
# - You must decide appropriate probe types/paths/ports per application.
# This script only scaffolds probes where missing.
#
# Requirements: kubectl, jq, yq (https://mikefarah.gitbook.io/yq/)
set -euo pipefail
# -------- Configurable defaults for newly added probes --------
# Adjust these defaults before using in production.
DEFAULT_HTTP_PATH="/healthz"
DEFAULT_HTTP_PORT=8080
DEFAULT_INITIAL_DELAY=10
DEFAULT_PERIOD_SECONDS=10
DEFAULT_TIMEOUT_SECONDS=1
DEFAULT_FAILURE_THRESHOLD=3
DEFAULT_SUCCESS_THRESHOLD=1
OUTDIR="probe-patches-$(date +%Y%m%d-%H%M%S)"
mkdir -p "${OUTDIR}"
echo "Discovering pods with containers missing liveness/readiness probes ..."
echo "Output directory for generated manifests: ${OUTDIR}"
echo
# Reuse the audit logic to identify non-compliant containers (excluding system namespaces)
NON_COMPLIANT_JSON="$(kubectl get pods --all-namespaces -o json | jq '
.items[]
| select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| . as $pod
| .spec.containers[]
| select((.livenessProbe == null) or (.readinessProbe == null))
| {
namespace: $pod.metadata.namespace,
pod: $pod.metadata.name,
uid: $pod.metadata.uid,
owner: ($pod.metadata.ownerReferences // [] | map(select(.controller)) | first),
container: .name
}'
)"
if [ -z "${NON_COMPLIANT_JSON}" ]; then
echo "No pods with missing liveness/readiness probes found (outside system namespaces)."
exit 0
fi
echo "Non-compliant containers detected:"
echo "${NON_COMPLIANT_JSON}" | jq -r '. | "ns=\(.namespace) pod=\(.pod) owner_kind=\(.owner.kind // "Pod") owner_name=\(.owner.name // .pod) container=\(.container)"' | sort
echo
# Build a list of unique owning workload objects (kind/ns/name) to export manifests
# If pod has no controller owner, treat the Pod itself as the target object.
TARGETS_JSON="$(echo "${NON_COMPLIANT_JSON}" | jq -r '
{
ns: .namespace,
kind: (.owner.kind // "Pod"),
name: (.owner.name // .pod)
}' | jq -s 'unique')"
TARGET_COUNT="$(echo "${TARGETS_JSON}" | jq 'length')"
echo "Unique owning workloads to export and patch: ${TARGET_COUNT}"
echo
# Export and patch each target workload
for i in $(seq 0 $((TARGET_COUNT-1))); do
NS="$(echo "${TARGETS_JSON}" | jq -r ".[$i].ns")"
KIND="$(echo "${TARGETS_JSON}" | jq -r ".[$i].kind")"
NAME="$(echo "${TARGETS_JSON}" | jq -r ".[$i].name")"
# Map controller kinds to kubectl resource types where needed
case "${KIND}" in
ReplicaSet) RES="replicaset" ;;
Deployment) RES="deployment" ;;
DaemonSet) RES="daemonset" ;;
StatefulSet) RES="statefulset" ;;
Job) RES="job" ;;
CronJob) RES="cronjob" ;;
Pod) RES="pod" ;;
*) RES=$(echo "${KIND}" | tr '[:upper:]' '[:lower:]') ;;
esac
echo "Exporting ${KIND}/${NS}/${NAME} ..."
MANIFEST="${OUTDIR}/${NS}_${RES}_${NAME}.yaml"
# Export the current manifest (without cluster-specific status)
kubectl get "${RES}" "${NAME}" -n "${NS}" -o yaml \
| yq 'del(.metadata.uid, .metadata.resourceVersion, .metadata.creationTimestamp, .metadata.generation, .metadata.managedFields, .status)' \
> "${MANIFEST}"
echo "Patching containers in ${MANIFEST} to add missing probes (non-destructive) ..."
# Patch spec.containers (for Pod or controller templates)
yq -i "
(.. | select(has(\"containers\")) | .containers[] ) |= (
. as \$c |
(if \$c.livenessProbe == null then
.livenessProbe = {
httpGet: { path: \"${DEFAULT_HTTP_PATH}\", port: ${DEFAULT_HTTP_PORT} },
initialDelaySeconds: ${DEFAULT_INITIAL_DELAY},
periodSeconds: ${DEFAULT_PERIOD_SECONDS},
timeoutSeconds: ${DEFAULT_TIMEOUT_SECONDS},
failureThreshold: ${DEFAULT_FAILURE_THRESHOLD},
successThreshold: ${DEFAULT_SUCCESS_THRESHOLD}
}
else . end
)
|
(if \$c.readinessProbe == null then
.readinessProbe = {
httpGet: { path: \"${DEFAULT_HTTP_PATH}\", port: ${DEFAULT_HTTP_PORT} },
initialDelaySeconds: ${DEFAULT_INITIAL_DELAY},
periodSeconds: ${DEFAULT_PERIOD_SECONDS},
timeoutSeconds: ${DEFAULT_TIMEOUT_SECONDS},
failureThreshold: ${DEFAULT_FAILURE_THRESHOLD},
successThreshold: ${DEFAULT_SUCCESS_THRESHOLD}
}
else . end
)
)
" "${MANIFEST}"
echo "Patched manifest created: ${MANIFEST}"
echo
done
cat <<EOF
REVIEW & APPLY MANUALLY
1) Review each generated manifest under:
${OUTDIR}
Ensure the livenessProbe and readinessProbe definitions are correct for each container
(HTTP path, port, initialDelaySeconds, etc.). Adjust as needed.
2) Apply the updated manifests back to the cluster (this is idempotent):
# Example for all files in the directory:
kubectl apply -f ${OUTDIR}
This will trigger rolling updates for Deployments/DaemonSets/StatefulSets/Jobs
and direct updates for standalone Pods.
3) Verify all non-system pods now have both probes defined:
kubectl get pods --all-namespaces -o json | jq -r '
[ .items[]
| select(.metadata.namespace as \$n | [\"kube-system\",\"kube-public\",\"kube-node-lease\"] | index(\$n) | not)
| .metadata as \$m
| (.spec.containers // [])[]
| (.livenessProbe != null) as \$live
| (.readinessProbe != null) as \$ready
| \"ns=\(\$m.namespace) pod=\(\$m.name) container=\(.name) is_compliant=\(if (\$live and \$ready) then \"true\" else \"false\" end)\"
] as \$rows
| if (\$rows | map(select(contains(\"is_compliant=false\"))) | length) == 0
then \"All checked containers have livenessProbe and readinessProbe defined.\"
else \$rows[]
end
'
EOF