Containers Should Set CPU And Memory Requests
More Info:
Verifies every container sets resources.requests so the scheduler can place the pod correctly and QoS is not BestEffort.
Risk Level
Low
Address
Security
Compliance Standards
- Cloudanix Best Practice
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
Identify noncompliant pods (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.nodeName // "") as $node| (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels| ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own| (.spec.containers // [])[]| ((.resources.requests.cpu != null) and (.resources.requests.memory != null)) as $ok| select($ok | not)| "kind=Pod ns=\($m.namespace) name=\($m.name) container=\(.name) image=\(.image)"][]' -
For pods managed by higher-level controllers (Deployment, StatefulSet, DaemonSet, Job, CronJob), edit the controller manifest to add requests (any machine with kubectl access). Example for a Deployment:
kubectl -n NAMESPACE edit deployment DEPLOYMENT_NAMEIn each affected container under
spec.template.spec.containers[], ensure a block like:resources:requests:cpu: "100m"memory: "128Mi"Save and exit to let Kubernetes roll out updated pods.
-
For standalone Pods (no controller in
ownerReferences), edit the Pod spec directly (any machine with kubectl access). Note this recreates the pod:kubectl -n NAMESPACE get pod POD_NAME -o yaml > /tmp/pod-POD_NAME.yamlIn
/tmp/pod-POD_NAME.yaml, under eachspec.containers[], add:resources:requests:cpu: "100m"memory: "128Mi"Remove ephemeral runtime fields (
status,metadata.resourceVersion,metadata.uid,metadata.creationTimestamp,metadata.managedFields) and apply:kubectl -n NAMESPACE delete pod POD_NAMEkubectl -n NAMESPACE apply -f /tmp/pod-POD_NAME.yaml -
If manifests are managed via GitOps or other IaC, make the same
resources.requests.cpuandresources.requests.memorychanges in the source YAML for each container, then let your deployment pipeline apply them. Do not rely onkubectl editfor these resources. -
Repeat steps 2–4 for all listed noncompliant pods until every container in each pod has both CPU and memory requests defined.
-
Verify compliance (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.nodeName // "") as $node| (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels| ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own| (.spec.containers // [])[]| ((.resources.requests.cpu != null) and (.resources.requests.memory != null)) as $ok| "kind=Pod ns=\($m.namespace) name=\($m.name) uid=\($m.uid) apiVersion=v1"+ (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)+ (if $node == "" then "" else " node=\($node)" end)+ (if $labels == "" then "" else " labels=\($labels)" end)+ (if $own == null then "" else " owner=\($own.kind)/\($m.namespace)/\($own.name)/\($own.uid)" end)+ " container=\(.name) image=\(.image)"+ " requestsCpu=\(.resources.requests.cpu // "unset") requestsMemory=\(.resources.requests.memory // "unset")"+ " is_compliant=\(if $ok then "true" else "false" end)"] as $rows| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'Confirm the output is
is_compliant=trueor that all listed containers showis_compliant=true.
Using kubectl
On any machine with kubectl access:
- Identify non-compliant pods and their controllers
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) ] | first) as $own
| (.spec.containers // [])[]
| ((.resources.requests.cpu != null) and (.resources.requests.memory != null)) as $ok
| select($ok|not)
| "ns=\($m.namespace) pod=\($m.name) ownerKind=\($own.kind // "Pod") ownerName=\($own.name // $m.name)"
] | unique[]'
- For each workload controller (Deployment/DaemonSet/StatefulSet/Job/CronJob), edit the manifest and add
resources.requestsfor every container.
Example for a Deployment:
kubectl -n NAMESPACE get deploy DEPLOYMENT_NAME -o yaml > /tmp/deploy-with-requests.yaml
Edit /tmp/deploy-with-requests.yaml and, for each container under spec.template.spec.containers, add:
resources:
requests:
cpu: "100m"
memory: "128Mi"
(Adjust values as appropriate for the application.)
Apply the updated manifest:
kubectl apply -f /tmp/deploy-with-requests.yaml
- For standalone Pods (no controller), recreate them with requests set.
Export, edit, and reapply:
kubectl -n NAMESPACE get pod POD_NAME -o yaml > /tmp/pod-with-requests.yaml
In /tmp/pod-with-requests.yaml:
- Remove the entire
metadata.uid,metadata.resourceVersion,metadata.creationTimestamp,metadata.managedFields,statussections. - Under each
spec.containers[], add:
resources:
requests:
cpu: "100m"
memory: "128Mi"
Then:
kubectl -n NAMESPACE delete pod POD_NAME
kubectl apply -f /tmp/pod-with-requests.yaml
- Verification
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 // [])[]
| ((.resources.requests.cpu != null) and (.resources.requests.memory != null)) as $ok
| select($ok|not)
] | if length==0 then "is_compliant=true" else . end'
Automation
#!/usr/bin/env bash
#
# Fix CBP C1.9: Ensure all containers set CPU and memory requests.
#
# This script:
# - Finds non-exempt pods whose containers lack CPU and/or memory requests.
# - Patches the owning workload (Deployment/StatefulSet/DaemonSet/Job/CronJob/Pod)
# to set default requests where missing.
# - Is idempotent: it only adds missing requests, and leaves existing ones intact.
# - Verifies compliance using the provided audit command.
#
# NOTE:
# - Requires: bash, kubectl, jq, yq (v4).
# - Run on: any machine with kubectl access to the cluster and appropriate RBAC.
#
# Default request values (change if needed):
DEFAULT_CPU_REQUEST="100m"
DEFAULT_MEMORY_REQUEST="128Mi"
set -euo pipefail
# Ensure required tools are available
for bin in kubectl jq yq; do
if ! command -v "$bin" >/dev/null 2>&1; then
echo "ERROR: Required binary '$bin' not found in PATH" >&2
exit 1
fi
done
echo "Discovering noncompliant containers..."
# Get list of non-exempt pods/containers that are non-compliant
NONCOMPLIANT_JSON=$(kubectl get pods --all-namespaces -o json | jq -c '
[
.items[]
| select(.metadata.namespace as $n
| ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| . as $pod
| (.metadata.ownerReferences // []) as $owners
| (.spec.containers // [])[]
| . as $c
| ((.resources.requests.cpu != null) and (.resources.requests.memory != null)) as $ok
| select($ok | not)
| {
podNamespace: $pod.metadata.namespace,
podName: $pod.metadata.name,
container: $c.name,
owner: (
[ $owners[]? | select(.controller) ] | first
)
}
]' )
if [[ -z "$NONCOMPLIANT_JSON" || "$NONCOMPLIANT_JSON" == "[]" ]]; then
echo "No noncompliant containers found; nothing to fix."
else
echo "$NONCOMPLIANT_JSON" | jq -r '
.[] |
"Noncompliant: ns=\(.podNamespace) pod=\(.podName) container=\(.container) ownerKind=\(.owner.kind // "Pod") ownerName=\(.owner.name // .podName)"
'
fi
# Group by owning workload (or Pod if no owner)
WORKLOADS_JSON=$(jq -c '
map({
ns: .podNamespace,
kind: (.owner.kind // "Pod"),
name: (.owner.name // .podName)
})
| unique
' <<<"$NONCOMPLIANT_JSON")
if [[ "$WORKLOADS_JSON" == "[]" ]]; then
echo "No owning workloads need patching."
else
echo
echo "Will patch the following owning workloads to set default requests where missing:"
echo "$WORKLOADS_JSON" | jq -r '.[] | "\(.ns) \(.kind) \(.name)"'
fi
patch_owner() {
local ns="$1" kind="$2" name="$3"
echo
echo "Processing owner: kind=$kind ns=$ns name=$name"
# Map owner kind to actual Kubernetes resource kind
local resKind pathPrefix
case "$kind" in
Deployment|StatefulSet|DaemonSet|Job)
resKind="$kind"
pathPrefix=".spec.template.spec"
;;
ReplicaSet)
# Usually managed by a Deployment; skip to avoid racing the controller.
echo " Skipping ReplicaSet $ns/$name (managed by a higher-level controller)."
return 0
;;
CronJob)
resKind="CronJob"
pathPrefix=".spec.jobTemplate.spec.template.spec"
;;
Pod)
resKind="Pod"
pathPrefix=".spec"
;;
*)
echo " WARNING: Unsupported owner kind '$kind' for $ns/$name; skipping."
return 0
;;
esac
# Fetch current YAML
tmpfile=$(mktemp)
if ! kubectl get "$resKind" "$name" -n "$ns" -o yaml >"$tmpfile" 2>/dev/null; then
echo " WARNING: Unable to fetch $resKind $ns/$name; it may have been deleted. Skipping."
rm -f "$tmpfile"
return 0
fi
# Build yq expression to set missing requests for containers and initContainers
# This preserves existing values and only fills in unset fields.
yq_expr='
# containers
'"$pathPrefix"'.containers // [] |
select(length > 0) |
path(.. | select(tag == "!!seq")) as $p |
.'$pathPrefix'.containers |= (
. // [] | map(
.resources.requests |= (
. // {} |
(.cpu // "'"$DEFAULT_CPU_REQUEST"'") as $cpu |
(.memory // "'"$DEFAULT_MEMORY_REQUEST"'") as $mem |
. * {"cpu": $cpu, "memory": $mem}
)
)
) |
# initContainers (if present)
'"$pathPrefix"'.initContainers // [] |
select(length > 0) |
path(.. | select(tag == "!!seq")) as $p2 |
.'$pathPrefix'.initContainers |= (
. // [] | map(
.resources.requests |= (
. // {} |
(.cpu // "'"$DEFAULT_CPU_REQUEST"'") as $cpu2 |
(.memory // "'"$DEFAULT_MEMORY_REQUEST"'") as $mem2 |
. * {"cpu": $cpu2, "memory": $mem2}
)
)
)
'
patched=$(yq eval "$yq_expr" "$tmpfile") || {
echo " ERROR: Failed to patch YAML for $resKind $ns/$name with yq."
rm -f "$tmpfile"
return 1
}
# Apply the patch if there is any change
if diff -q <(cat "$tmpfile") <(printf "%s\n" "$patched") >/dev/null 2>&1; then
echo " No changes needed for $resKind $ns/$name (already compliant or owner template unaffected)."
else
echo " Applying patched manifest to $resKind $ns/$name ..."
printf "%s\n" "$patched" | kubectl apply -f - >/dev/null
echo " Patch applied."
fi
rm -f "$tmpfile"
}
# Patch each owning workload
echo "$WORKLOADS_JSON" | jq -c '.[]' | while read -r w; do
ns=$(jq -r '.ns' <<<"$w")
kind=$(jq -r '.kind' <<<"$w")
name=$(jq -r '.name' <<<"$w")
patch_owner "$ns" "$kind" "$name"
done
echo
echo "Waiting for updated pods to roll out (this may take a few minutes)..."
# Simple wait: give controllers some time; can be tuned.
sleep 30
echo
echo "Verification: running benchmark audit command..."
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.nodeName // "") as $node
| (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
| ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
| (.spec.containers // [])[]
| ((.resources.requests.cpu != null) and (.resources.requests.memory != null)) as $ok
| "kind=Pod ns=\($m.namespace) name=\($m.name) uid=\($m.uid) apiVersion=v1"
+ (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
+ (if $node == "" then "" else " node=\($node)" end)
+ (if $labels == "" then "" else " labels=\($labels)" end)
+ (if $own == null then "" else " owner=\($own.kind)/\($m.namespace)/\($own.name)/\($own.uid)" end)
+ " container=\(.name) image=\(.image)"
+ " requestsCpu=\(.resources.requests.cpu // "unset") requestsMemory=\(.resources.requests.memory // "unset")"
+ " is_compliant=\(if $ok then "true" else "false" end)"
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end
'