Skip to main content

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

Manual Steps
  1. On any machine with kubectl access, list all non-compliant containers (those without CPU or memory requests):

    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)
    | "ns=\($m.namespace) pod=\($m.name) container=\(.name) image=\(.image)"
    ][]'
  2. For each affected Pod, identify whether it is controlled by a higher-level object (Deployment, DaemonSet, StatefulSet, Job, CronJob, etc.):

    kubectl get pod POD_NAME -n NAMESPACE -o jsonpath='{.metadata.ownerReferences[*].kind}{" "}{.metadata.ownerReferences[*].name}{"\n"}'
    • If there is an ownerReference, you must edit the owner (e.g., Deployment), not the Pod.
    • If there is no ownerReference, edit the Pod directly.
  3. For Pods controlled by a higher-level controller (example: Deployment), edit the controller spec and add CPU and memory requests for each container:

    kubectl edit deployment DEPLOYMENT_NAME -n NAMESPACE

    In the opened manifest, under each spec.template.spec.containers[].resources, ensure something like:

    resources:
    requests:
    cpu: "100m"
    memory: "128Mi"

    Save and exit; the controller will roll out new Pods with the defined requests.

  4. For standalone Pods (no ownerReference), patch them to add CPU and memory requests for a specific container (repeat per container as needed):

    kubectl patch pod POD_NAME -n NAMESPACE \
    --type='json' \
    -p='[
    {
    "op": "add",
    "path": "/spec/containers/0/resources",
    "value": {
    "requests": {
    "cpu": "100m",
    "memory": "128Mi"
    }
    }
    }
    ]'

    Adjust the container index in /spec/containers/0/... and request values as appropriate for your workload.

  5. For workloads managed via manifests/IaC (e.g., GitOps, Helm, Terraform), locate and edit the source manifest instead of using kubectl edit, then apply:

    kubectl apply -f PATH/TO/MANIFEST.yaml

    Ensure each container spec in the source includes resources.requests.cpu and resources.requests.memory before applying.

  6. Verification (on any machine with kubectl access): after changes roll out, rerun the compliance check and confirm is_compliant=true or no rows with is_compliant=false:

    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 that all listed containers show non-unset requestsCpu and requestsMemory and is_compliant=true.

Using kubectl

On any machine with kubectl access:

  1. 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) }
] | (reduce .[] as $i ({}; .["\($i.ns)|\($i.ownerKind)|\($i.ownerName)"]=1) | keys[])' \
| column -t -s'|'

This lists unique <namespace> <ownerKind> <ownerName> you need to edit (e.g. default Deployment myapp).

  1. Export the controller manifest, add requests, and re‑apply

Example for a Deployment named myapp in namespace default:

kubectl -n default get deploy myapp -o yaml > myapp-patched.yaml

Edit myapp-patched.yaml and, under each container, add resources.requests.cpu and resources.requests.memory if missing, for example:

spec:
template:
spec:
containers:
- name: myapp
image: 111122223333.dkr.ecr.us-east-1.amazonaws.com/myapp:1.2.3
resources:
requests:
cpu: "100m"
memory: "128Mi"
# (optional but recommended)
limits:
cpu: "500m"
memory: "512Mi"

Apply the updated manifest:

kubectl apply -f myapp-patched.yaml

Repeat these steps for each non‑compliant controller kind (Deployment, StatefulSet, DaemonSet, Job, CronJob, etc.). For standalone Pods you manage directly, export/edit/apply similarly:

kubectl -n default get pod mypod -o yaml > mypod-patched.yaml
# edit: add resources.requests for each container
kubectl apply -f mypod-patched.yaml
  1. Verify remediation

Run the benchmark audit command again from 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 either is_compliant=true overall or that all listed containers now show non‑unset requestsCpu and requestsMemory with is_compliant=true.

Automation
#!/usr/bin/env bash
# Remediate CBP C1.9: ensure all containers set CPU and memory requests
# Scope: any machine with kubectl access to the EKS cluster
# Requirements: kubectl, jq, yq (https://github.com/mikefarah/yq) in PATH

set -euo pipefail

DEFAULT_CPU_REQUEST="100m"
DEFAULT_MEM_REQUEST="128Mi"

echo "Discovering non-compliant pods (excluding kube-system, kube-public, kube-node-lease)..."

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((.resources.requests.cpu == null) or (.resources.requests.memory == null))
| {
ns: $pod.metadata.namespace,
pod: $pod.metadata.name,
container: .name
}
' | jq -s '.')

if [[ "$(echo "$NON_COMPLIANT_JSON" | jq 'length')" -eq 0 ]]; then
echo "No non-compliant running pods found. Nothing to patch."
else
echo "Found non-compliant containers:"
echo "$NON_COMPLIANT_JSON" | jq -r '.[] | "- ns=\(.ns) pod=\(.pod) container=\(.container)"'
fi

# Build unique owner references for non-compliant pods
echo "Identifying owning workload objects to patch (Deployment, StatefulSet, DaemonSet, Job, CronJob, ReplicaSet, ReplicationController)..."

OWNERS_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 as $containers
| [
$containers[]
| select((.resources.requests.cpu == null) or (.resources.requests.memory == null))
] as $bad
| select(($bad | length) > 0)
| (.metadata.ownerReferences // [])[]
| select(.controller == true)
| {
kind: .kind,
name: .name,
uid: .uid,
namespace: $pod.metadata.namespace
}
' | jq -s '
# de-duplicate by (kind,namespace,name,uid)
(unique_by(.kind,.namespace,.name,.uid))
')

if [[ "$(echo "$OWNERS_JSON" | jq 'length')" -eq 0 ]]; then
echo "Non-compliant pods have no controller owner (likely bare Pods)."
echo "Patching Pods directly; changes will not persist if something recreates them."

# Generate and apply patches for bare Pods
echo "$NON_COMPLIANT_JSON" | jq -r '.[] | "\(.ns) \(.pod) \(.container)"' | sort -u | while read -r NS POD CONTAINER; do
echo "Patching Pod/${POD} in namespace ${NS}, container ${CONTAINER}..."
PATCH=$(cat <<EOF
spec:
containers:
- name: ${CONTAINER}
resources:
requests:
cpu: "${DEFAULT_CPU_REQUEST}"
memory: "${DEFAULT_MEM_REQUEST}"
EOF
)
kubectl -n "${NS}" patch pod "${POD}" --type merge -p "$(echo "${PATCH}")" || true
done
else
echo "Found owning workload objects to patch:"
echo "$OWNERS_JSON" | jq -r '.[] | "- kind=\(.kind) ns=\(.namespace) name=\(.name)"'

# For each owner, fetch manifest, patch container resources, and apply
echo "$OWNERS_JSON" | jq -r '.[] | "\(.namespace) \(.kind) \(.name)"' | sort -u | while read -r NS KIND NAME; do
echo "Processing ${KIND}/${NAME} in namespace ${NS}..."

# Export current manifest (as YAML)
TMP_YAML=$(mktemp)
kubectl -n "${NS}" get "${KIND}" "${NAME}" -o yaml > "${TMP_YAML}"

# Patch containers in-place using yq:
# - For all .spec.template.spec.containers[].resources.requests.*
# set defaults only if not already set.
yq -i "
(.spec.template.spec.containers[]? |
select(.resources.requests.cpu == null) |
.resources.requests.cpu) |= \"${DEFAULT_CPU_REQUEST}\" |
(.spec.template.spec.containers[]? |
select(.resources.requests.memory == null) |
.resources.requests.memory) |= \"${DEFAULT_MEM_REQUEST}\"
" "${TMP_YAML}"

# Apply patched manifest (idempotent)
kubectl apply -f "${TMP_YAML}"

rm -f "${TMP_YAML}"
done

# Handle bare Pods that remain (if any)
BARE_PODS_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)
| select((.metadata.ownerReferences // []) | length == 0)
| . as $pod
| .spec.containers[]
| select((.resources.requests.cpu == null) or (.resources.requests.memory == null))
| {
ns: $pod.metadata.namespace,
pod: $pod.metadata.name,
container: .name
}
' | jq -s '.')
if [[ "$(echo "$BARE_PODS_JSON" | jq 'length')" -gt 0 ]]; then
echo "Also patching bare Pods lacking ownerReferences:"
echo "$BARE_PODS_JSON" | jq -r '.[] | "- ns=\(.ns) pod=\(.pod) container=\(.container)"'
echo "$BARE_PODS_JSON" | jq -r '.[] | "\(.ns) \(.pod) \(.container)"' | sort -u | while read -r NS POD CONTAINER; do
echo "Patching Pod/${POD} in namespace ${NS}, container ${CONTAINER}..."
PATCH=$(cat <<EOF
spec:
containers:
- name: ${CONTAINER}
resources:
requests:
cpu: "${DEFAULT_CPU_REQUEST}"
memory: "${DEFAULT_MEM_REQUEST}"
EOF
)
kubectl -n "${NS}" patch pod "${POD}" --type merge -p "$(echo "${PATCH}")" || true
done
fi
fi

echo "Waiting for updated workloads to roll out (if any)..."
kubectl get deploy,statefulset,daemonset -A >/dev/null 2>&1 || true

echo "Re-running compliance audit..."

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
| "ns=\($m.namespace) pod=\($m.name) container=\(.name) " +
"requestsCpu=\(.resources.requests.cpu // "unset") " +
"requestsMemory=\(.resources.requests.memory // "unset") " +
"is_compliant=\(if $ok then "true" else "false" end)"
] as $rows
| if ($rows | map(select(. | test("is_compliant=false$"))) | length) == 0
then "is_compliant=true"
else $rows[]
end
'