Tenant Namespaces Should Have A ResourceQuota
More Info:
Advisory: create a ResourceQuota per tenant namespace to bound aggregate CPU, memory and object counts, preventing one tenant from starving others.
Risk Level
Low
Address
Security
Compliance Standards
- Cloudanix Best Practice
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
Identify tenant namespaces (run on any machine with kubectl access):
kubectl get namespaces \--no-headers \| awk '!/kube-system|kube-public|kube-node-lease/ {print $1}' -
For each tenant namespace that has no ResourceQuota, create a baseline ResourceQuota manifest file (run on any machine with kubectl access). Example for namespace
tenant-a:cat > tenant-a-resourcequota.yaml << 'EOF'apiVersion: v1kind: ResourceQuotametadata:name: tenant-a-quotanamespace: tenant-aspec:hard:requests.cpu: "2"requests.memory: "4Gi"limits.cpu: "4"limits.memory: "8Gi"pods: "50"services: "10"configmaps: "50"persistentvolumeclaims: "10"secrets: "100"replicationcontrollers: "20"resourcequotas: "1"services.loadbalancers: "5"services.nodeports: "5"EOFAdjust the
hardvalues to match your tenant’s agreed limits. -
Apply the ResourceQuota for that tenant namespace (run on any machine with kubectl access):
kubectl apply -f tenant-a-resourcequota.yaml -
Repeat steps 2–3 for each remaining tenant namespace, changing the
name,namespace, and quota values as appropriate. -
(Optional) Review existing quotas in tenant namespaces to ensure they match expectations (run on any machine with kubectl access):
kubectl get resourcequota --all-namespaces -o wide -
Verify compliance (run on any machine with kubectl access):
{ kubectl get resourcequotas --all-namespaces -o jsonkubectl get namespaces -o json} | jq -rs '.[0] as $quotas | .[1] |[ .items[]| select(.metadata.name as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)| .metadata as $m| (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels| ([ $quotas.items[] | select(.metadata.namespace == $m.name) ] | length) as $count| "kind=Namespace name=\($m.name) uid=\($m.uid) apiVersion=v1"+ (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)+ (if $labels == "" then "" else " labels=\($labels)" end)+ " resourceQuotas=\($count)"+ " is_compliant=\(if $count > 0 then "true" else "false" end)"] as $rows| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'Confirm that all tenant namespaces now show
is_compliant=true.
Using kubectl
# 1) Identify tenant namespaces (run on any machine with kubectl access)
kubectl get ns \
--no-headers \
| awk '!/kube-system|kube-public|kube-node-lease/ {print $1}'
Create a ResourceQuota manifest per tenant namespace. Example for a tenant namespace tenant-a:
# tenant-a-resourcequota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: tenant-a-quota
namespace: tenant-a
spec:
hard:
requests.cpu: "4"
limits.cpu: "8"
requests.memory: "8Gi"
limits.memory: "16Gi"
pods: "50"
services: "20"
configmaps: "50"
secrets: "50"
persistentvolumeclaims: "20"
services.loadbalancers: "5"
services.nodeports: "5"
Apply it:
# 2) Apply ResourceQuota for tenant-a (run on any machine with kubectl access)
kubectl apply -f tenant-a-resourcequota.yaml
Repeat with adjusted names/values for each tenant namespace.
Verification (adapted from the audit):
# 3) Confirm every tenant namespace has at least one ResourceQuota
{ kubectl get resourcequotas --all-namespaces -o json
kubectl get namespaces -o json
} | jq -rs '
.[0] as $quotas | .[1] |
[ .items[]
| select(.metadata.name as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| .metadata as $m
| (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
| ([ $quotas.items[] | select(.metadata.namespace == $m.name) ] | length) as $count
| "kind=Namespace name=\($m.name) uid=\($m.uid) apiVersion=v1"
+ (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
+ (if $labels == "" then "" else " labels=\($labels)" end)
+ " resourceQuotas=\($count)"
+ " is_compliant=\(if $count > 0 then "true" else "false" end)"
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
Automation
#!/usr/bin/env bash
# Create a default ResourceQuota for every tenant namespace that lacks one.
# Run on: any machine with kubectl access and current context pointing at the target GKE cluster.
set -euo pipefail
# -----------------------------
# Configuration (edit as needed)
# -----------------------------
# Namespaces to skip (system / non-tenant)
EXCLUDED_NAMESPACES=(
"kube-system"
"kube-public"
"kube-node-lease"
)
# Default ResourceQuota template (cluster-wide defaults; adjust for your tenants)
# This is applied as-is to any non-excluded namespace that currently has 0 ResourceQuota objects.
read -r -d '' RQ_TEMPLATE_JSON << 'EOF'
{
"apiVersion": "v1",
"kind": "ResourceQuota",
"metadata": {
"name": "tenant-default-quota"
},
"spec": {
"hard": {
"requests.cpu": "4",
"requests.memory": "8Gi",
"limits.cpu": "8",
"limits.memory": "16Gi",
"pods": "50",
"services": "10",
"configmaps": "20",
"persistentvolumeclaims": "10",
"secrets": "50"
}
}
}
EOF
# -----------------------------
# Helper functions
# -----------------------------
is_excluded_ns() {
local ns="$1"
for e in "${EXCLUDED_NAMESPACES[@]}"; do
if [[ "$ns" == "$e" ]]; then
return 0
fi
done
return 1
}
# -----------------------------
# Main logic
# -----------------------------
echo "Discovering namespaces..."
NAMESPACES=$(kubectl get namespaces -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}')
# Track whether we made any changes
CHANGED=0
while IFS= read -r NS; do
[[ -z "$NS" ]] && continue
if is_excluded_ns "$NS"; then
echo "Skipping excluded namespace: $NS"
continue
fi
# Count existing ResourceQuotas in this namespace
COUNT=$(kubectl get resourcequota -n "$NS" --no-headers 2>/dev/null | wc -l | tr -d ' ')
if [[ "$COUNT" -gt 0 ]]; then
echo "Namespace '$NS' already has $COUNT ResourceQuota object(s); leaving as-is."
continue
fi
echo "Creating default ResourceQuota in namespace '$NS'..."
echo "$RQ_TEMPLATE_JSON" | kubectl apply -n "$NS" -f -
CHANGED=1
done <<< "$NAMESPACES"
if [[ "$CHANGED" -eq 0 ]]; then
echo "No changes made; all tenant namespaces already have at least one ResourceQuota."
fi
echo
echo "Verification (post-fix audit):"
echo "------------------------------------------------------------"
# Re-run the benchmark-style audit to confirm is_compliant=true for all rows.
{ kubectl get resourcequotas --all-namespaces -o json
kubectl get namespaces -o json
} | jq -rs '
.[0] as $quotas | .[1] |
[ .items[]
| select(.metadata.name as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| .metadata as $m
| (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
| ([ $quotas.items[] | select(.metadata.namespace == $m.name) ] | length) as $count
| "kind=Namespace name=\($m.name) uid=\($m.uid) apiVersion=v1"
+ (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
+ (if $labels == "" then "" else " labels=\($labels)" end)
+ " resourceQuotas=\($count)"
+ " is_compliant=\(if $count > 0 then "true" else "false" end)"
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
echo "------------------------------------------------------------"
echo "Ensure all tenant namespaces show is_compliant=true above."