Deep Hardening of Kubernetes Production Clusters: From Running to Sleeping Soundly
This guide walks through a systematic, seven‑layer hardening methodology for Kubernetes production clusters, covering admission control, supply‑chain security, network micro‑segmentation, runtime protection, control‑plane stability, workload engineering, and observability, and provides concrete YAML, policy, and script examples to turn a merely runnable cluster into a reliably stable one.
Why Many Clusters Appear Stable Yet Remain High‑Risk
Teams often mistake a running pod, healthy service connectivity, low CPU usage, and scanned images for a secure cluster, but real production incidents follow a chain: missing network policies, privileged pods, lateral scans, conntrack table exhaustion, kube‑proxy jitter, API‑server watch pressure, amplified thread‑pool retries, and finally 504 errors or cascading outages.
Seven‑Layer Defense Model
The article proposes a seven‑layer view that converges risk from the supply‑chain layer up to the observation layer, each layer addressing a specific attack surface:
Supply‑chain: image build, signing, SBOM, scanning.
Admission: PSA, OPA/Gatekeeper, namespace baselines.
Network: default‑deny, L7 policies, host firewall.
Runtime: seccomp, AppArmor/SELinux, Falco/Tetragon, sandbox runtimes.
Control plane: etcd, API server, kubelet, kernel parameters.
Workloads: HPA/VPA/KEDA, PDB, priority, topology spread.
Observability & response: metrics, audit logs, alerts.
Defining Goals and SLOs
Four categories of objectives are recommended:
Security baseline : enforce PSA baseline or restricted, block unsigned images, disallow :latest tags, require non‑root.
Availability : tolerate a single node or AZ failure, keep API‑server writes alive during restarts.
Performance : monitor API‑server P99 latency, etcd fsync latency, CoreDNS response time, conntrack usage.
Engineering : GitOps for policies, CI‑CD security scans, operator‑driven middleware, standardized emergency drills.
1. Admission Security – From PSA to Gatekeeper
PodSecurityAdmission (PSA) is the first gate. Use baseline for most workloads and upgrade to restricted for high‑risk namespaces. PSA cannot express complex conditions, so layer OPA/Gatekeeper policies on top.
Namespace layering example (production namespace):
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
---
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: production
spec:
limits:
- type: Container
default:
cpu: "1"
memory: 1Gi
defaultRequest:
cpu: 200m
memory: 256Mi
---
apiVersion: v1
kind: ResourceQuota
metadata:
name: production-quota
namespace: production
spec:
hard:
requests.cpu: "100"
requests.memory: 200Gi
limits.cpu: "200"
limits.memory: 400Gi
pods: "500"
persistentvolumeclaims: "100"Key outcomes of the above YAML:
Prevents pods without resource requests from stealing nodes.
Stops a single team from exhausting cluster capacity.
Combines PSA with explicit resource quotas for a double‑door security model.
Production‑grade Deployment example (order‑service):
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
namespace: production
spec:
replicas: 4
revisionHistoryLimit: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
selector:
matchLabels:
app: order-service
template:
metadata:
labels:
app: order-service
tier: backend
spec:
serviceAccountName: order-service
automountServiceAccountToken: false
terminationGracePeriodSeconds: 30
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: registry.example.com/order-service:v2.3.7
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
env:
- name: JAVA_TOOL_OPTIONS
value: "-XX:+UseContainerSupport -XX:MaxRAMPercentage=75"
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: "2"
memory: 2Gi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsUser: 10001
runAsGroup: 10001
capabilities:
drop: ["ALL"]
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 20
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
startupProbe:
httpGet:
path: /actuator/health
port: 8080
failureThreshold: 30
periodSeconds: 5
volumeMounts:
- name: tmp
mountPath: /tmp
- name: logs
mountPath: /var/log/app
volumes:
- name: tmp
emptyDir: {}
- name: logs
emptyDir: {}Four production‑grade details highlighted:
Disable automatic ServiceAccount token mounting.
Enforce read‑only root filesystem with explicit writable paths.
Separate startup from liveness probes to avoid premature restarts of cold‑start services.
Declare both resource requests and limits so HPA/VPA have accurate data.
Gatekeeper policy examples (disallow :latest, enforce resource requests, require owner/cost‑center/env labels, whitelist image registries, block host‑network, host‑PID, host‑path):
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8sdisallowlatest
spec:
crd:
spec:
names:
kind: K8sDisallowLatest
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sdisallowlatest
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
endswith(container.image, ":latest")
msg := sprintf("container %v uses disallowed latest tag", [container.name])
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sDisallowLatest
metadata:
name: disallow-latest-tag
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]2. Supply‑Chain Security
Key steps to keep untrusted images out of the cluster:
Image minimization – use distroless, Alpine, or an organization‑wide base image.
Vulnerability scanning – Trivy, Grype, or cloud‑provider scanners.
Generate SBOM for traceability.
Sign images with Cosign and verify signatures at admission.
Typical CI flow:
# Build image & generate SBOM
# Sign image with Cosign
# Push to internal registry
# Admission layer only accepts images from the internal registry with a valid signature3. Network Security – Micro‑Segmentation
Kubernetes defaults to a flat network. Production must switch to a whitelist model.
Default‑deny policies (ingress & egress):
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-egress
namespace: production
spec:
podSelector: {}
policyTypes:
- EgressWhitelist example allowing only frontend and DNS for the order-service pod:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: order-allow-frontend-and-dns
namespace: production
spec:
podSelector:
matchLabels:
app: order-service
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: frontend
ports:
- protocol: TCP
port: 8080
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: middleware
podSelector:
matchLabels:
app: kafka
ports:
- protocol: TCP
port: 9093Native NetworkPolicy works for small clusters but lacks L7, high‑performance data plane, and identity‑based policies. When those are needed, the article recommends moving to Cilium, which provides L7 rules, better observability, and host‑firewall integration.
Cilium L7 egress example for a payment API:
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: payment-egress-policy
namespace: production
spec:
endpointSelector:
matchLabels:
app: order-service
egress:
- toFQDNs:
- matchName: payment-api.internal.example.com
toPorts:
- ports:
- port: "443"
protocol: TCP
rules:
http:
- method: "POST"
path: "/api/v1/payments"
- method: "GET"
path: "/api/v1/payments/.*"Host firewall example to lock down SSH on worker nodes:
apiVersion: cilium.io/v2
kind: CiliumClusterwideNetworkPolicy
metadata:
name: restrict-node-ssh
spec:
nodeSelector:
matchLabels:
node-role.kubernetes.io/worker: ""
ingress:
- fromCIDR:
- 10.10.0.0/16
toPorts:
- ports:
- port: "22"
protocol: TCP4. Runtime Security
Before deploying Falco, lock down the kernel surface:
Set seccompProfile: RuntimeDefault on all pods.
Enable AppArmor or SELinux on nodes.
For high‑risk workloads, apply custom seccomp/AppArmor profiles.
Falco rule example detecting an interactive shell inside a container:
- rule: Terminal shell in container
desc: A shell was spawned in a container
condition: container and proc.name in (bash, sh, zsh, ash)
output: Shell started in container user=%user.name container=%container.name pod=%k8s.pod.name namespace=%k8s.ns.name
priority: WARNING
tags: [container, shell]Tetragon tracing policy to catch package‑manager binaries (apt, yum) execution:
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: detect-package-manager
spec:
kprobes:
- call: "execve"
syscall: true
args:
- index: 0
type: "string"
selectors:
- matchArgs:
- index: 0
operator: "Equal"
values:
- "/usr/bin/apt"
- "/usr/bin/apt-get"
- "/usr/bin/yum"Sandbox runtimes for untrusted code: gVisor – user‑space kernel interception, moderate overhead. Kata Containers – lightweight VM isolation, higher overhead.
RuntimeClass example for Kata:
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: kata
handler: kata
---
apiVersion: v1
kind: Pod
metadata:
name: tenant-job-runner
namespace: sandbox
spec:
runtimeClassName: kata
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: job
image: registry.example.com/tenant-runner:v1.0.0
securityContext:
allowPrivilegeEscalation: false
runAsUser: 655325. Control‑Plane Stability
etcd – use dedicated SSDs, avoid co‑locating high‑IO pods, run periodic compact and defrag, monitor backend size, fsync duration, and leader changes. Example etcd container args:
etcd \
--quota-backend-bytes=8589934592 \
--heartbeat-interval=250 \
--election-timeout=5000 \
--snapshot-count=10000 \
--auto-compaction-mode=periodic \
--auto-compaction-retention=1API Server – beyond --max-requests-inflight, enable API Priority and Fairness (APF) to protect system‑level controllers from noisy‑batch workloads.
kubelet & containerd – tune for high concurrency: increase maxPods, disable serializeImagePulls, set aggressive image GC thresholds, configure eviction hard limits, and set topologyManagerPolicy: best-effort.
# /var/lib/kubelet/config.yaml
maxPods: 200
serializeImagePulls: false
imageGCHighThresholdPercent: 80
imageGCLowThresholdPercent: 60
evictionHard:
memory.available: "500Mi"
nodefs.available: "10%"
imagefs.available: "15%"
topologyManagerPolicy: best-effortKernel parameters – tune for large clusters:
# /etc/sysctl.d/99-kubernetes.conf
net.core.somaxconn = 32768
net.ipv4.ip_local_port_range = 1024 65000
net.netfilter.nf_conntrack_max = 1048576
net.netfilter.nf_conntrack_tcp_timeout_established = 86400
fs.file-max = 1048576
fs.inotify.max_user_watches = 524288
vm.swappiness = 0
vm.overcommit_memory = 1Key metrics to watch: nf_conntrack usage, available local ports, disk latency.
6. Workload Engineering – Autoscaling, Disruption Budgets, Priority
Clarify responsibilities:
HPA – horizontal pod scaling based on CPU, memory, QPS, etc.
VPA – recommend resource requests for a pod.
KEDA – event‑driven scaling (e.g., Kafka lag).
Production‑grade HPA for order-service (fast scale‑up, slow scale‑down):
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: order-service
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: order-service
minReplicas: 4
maxReplicas: 30
behavior:
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 60
- type: Pods
value: 4
periodSeconds: 60
selectPolicy: Max
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 20
periodSeconds: 60
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 65KEDA ScaledObject for a Kafka consumer (lag‑driven scaling):
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: order-consumer
namespace: production
spec:
scaleTargetRef:
name: order-consumer
pollingInterval: 15
cooldownPeriod: 300
minReplicaCount: 3
maxReplicaCount: 50
triggers:
- type: kafka
metadata:
bootstrapServers: my-kafka-kafka-bootstrap.middleware:9092
consumerGroup: order-consumer-group
topic: order-events
lagThreshold: "5000"PodDisruptionBudget, PriorityClass, and topology spread constraints to protect against node maintenance and AZ failures:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: order-service-pdb
namespace: production
spec:
minAvailable: 3
selector:
matchLabels:
app: order-service
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: core-online-service
value: 100000
globalDefault: false
description: "Priority for core online services"Deployment topology spread:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: order-service7. Data‑Plane Middleware Production‑Grade
Prefer operators over raw StatefulSets to encode operational knowledge. Kafka on Kubernetes – Strimzi example with dual‑role node pool and storage configuration:
apiVersion: kafka.strimzi.io/v1beta2
kind: KafkaNodePool
metadata:
name: dual-role
labels:
strimzi.io/cluster: my-kafka
spec:
replicas: 3
roles:
- controller
- broker
storage:
type: jbod
volumes:
- id: 0
type: persistent-claim
size: 500Gi
class: ssd-storage-class
deleteClaim: false
---
apiVersion: kafka.strimzi.io/v1beta2
kind: Kafka
metadata:
name: my-kafka
namespace: middleware
spec:
kafka:
version: 4.0.0
replicas: 3
listeners:
- name: tls
port: 9093
type: internal
tls: true
config:
offsets.topic.replication.factor: 3
transaction.state.log.replication.factor: 3
transaction.state.log.min.isr: 2
default.replication.factor: 3
min.insync.replicas: 2
num.partitions: 24
log.retention.hours: 168
log.segment.bytes: 1073741824
template:
pod:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: strimzi.io/name
operator: In
values:
- my-kafka-kafka
topologyKey: topology.kubernetes.io/zoneCapacity estimation for a high‑throughput service (order‑service) follows four steps: measure per‑pod steady‑state QPS, compute required replicas (peak QPS ÷ pod QPS × safety factor), ensure node capacity for summed requests (reserve 15‑30% headroom), and verify downstream capacity (DB connections, Kafka partitions, cache hit rate).
8. Observability & Incident Response
Five metric groups to monitor:
Control plane – API Server latency/error, etcd fsync, leader changes.
Network – conntrack usage, DNS latency, packet loss.
Workloads – HPA behavior, pod restarts, P99 latency, queue backlog.
Security – PSA deny count, Gatekeeper violations, Falco/Tetragon events.
Middleware – Kafka lag, ISR changes, Nacos connections, MySQL response time.
Audit log must capture create/update/patch/delete, exec/port‑forward/attach, RBAC changes, and secret reads. A Bash script is provided to list namespaces with PSA labels, privileged pods, missing NetworkPolicies, and node conntrack usage.
#!/usr/bin/env bash
set -euo pipefail
echo "=== Namespaces with PSA labels ==="
kubectl get ns -o json | jq -r '.items[] | [.metadata.name, (.metadata.labels["pod-security.kubernetes.io/enforce"] // "none"), (.metadata.labels["pod-security.kubernetes.io/warn"] // "none")] | @tsv'
# ... (script continues as in the article) ...Alert design should combine symptom metrics (gateway 5xx, order‑service P99, Kafka lag) with root‑cause indicators (conntrack saturation, CoreDNS latency) to quickly pinpoint whether the issue is upstream load or underlying network/node failure.
9. Real‑World Case Study – Order Platform Transformation
The article walks through a concrete migration from a fragile state to a stable one:
Initial problems: missing PSA/NetworkPolicy, root containers, CPU‑only HPA, Kafka sharing nodes, no audit log retention.
Incident during load test: gateway 504s, order‑service RT spikes, Kafka lag explosion, conntrack near limit, CoreDNS slowdown.
Phase‑1 actions – enable baseline PSA, default‑deny policies, whitelist network rules.
Phase‑2 actions – RuntimeDefault seccomp, Falco audit mode, raise conntrack limits, move etcd to SSD, add API Server & CoreDNS alerts.
Phase‑3 actions – KEDA‑driven consumer scaling, migrate Kafka to Strimzi on dedicated nodes, add PDB/priority/topology spread, enforce image signature checks.
Result – reduced gateway errors, smoother scaling, no cascade failures, security policies automated via GitOps, faster MTTR.
10. Phased Rollout Plan
Phase 1 – Baseline visibility (1‑2 weeks) : inventory namespaces, enable PSA warn/audit, apply default‑deny NetworkPolicy template, ingest control‑plane and node metrics.
Phase 2 – Basic closure (2‑4 weeks) : enforce PSA baseline/restricted per namespace, add resource request policies, implement whitelist network rules for core services, add PDB, PriorityClass, topology spread.
Phase 3 – Deep defense (4‑8 weeks) : roll out Falco/Tetragon, sandbox high‑risk workloads with gVisor/Kata, enforce image signing at admission, harden Kafka/Nacos with operators and dedicated nodes.
Phase 4 – Continuous operation (ongoing) : manage all policies via GitOps, integrate security baseline checks into CI/CD, run regular capacity and chaos tests, review alert effectiveness and resource profiles.
11. Checklist – How Far Is Your Cluster From "Sleeping Soundly"?
Do new namespaces automatically receive PSA, quota, LimitRange, and default‑deny policies?
Can you enumerate pods still running as root or privileged?
Is any outbound traffic unrestricted?
Can you locate the source of an exec inside a container, RBAC change, or secret read within five minutes?
Do you know the safe water‑mark for nf_conntrack on each node?
Are etcd fsync latency and fragmentation monitored?
Do core services have PDB, topology spread, and priority class configured?
Are stateful middlewares (Kafka, Nacos) isolated on dedicated nodes with fault‑domain awareness?
Does the CI/CD pipeline verify image provenance, signatures, and version traceability?
Are all security policies version‑controlled, auditable, and rollback‑capable?
12. Final Takeaway
Turning a Kubernetes cluster from merely runnable into reliably stable is not about flipping a few switches; it is about systematically managing uncertainty across admission, network, runtime, control‑plane, workload, and observability layers. When each layer is hardened, the cluster can absorb traffic spikes, node failures, and malicious activity while keeping the business running and the team sleeping peacefully.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Cloud Architecture
Focuses on cloud‑native and distributed architecture engineering, sharing practical solutions and lessons learned. Covers microservice governance, Kubernetes, observability, and stability engineering to help your systems run stable, fast, and cost‑effectively.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
