| name | kubernetes-configuration |
| description | Guides expert-level kubernetes configuration implementation: cloud and best-practices decision frameworks, production-ready patterns, and concrete templates for kubernetes configuration workflows.
Use when the user asks about kubernetes configuration, kubernetes configuration configuration, or devops best practices for kubernetes projects.
Do NOT use when the user needs a different devops cloud capability -- check sibling skills in the devops cloud subcategory.
|
| license | Apache-2.0 |
| metadata | {"author":"foundry-skills","version":"1.0.0","tags":"devops cloud best-practices","category":"devops-cloud","subcategory":"devops-cloud","depends":"","disclaimer":"none","difficulty":"intermediate"} |
Kubernetes Configuration
When to Use
Use this skill when:
- User asks how to write or review Kubernetes manifests (Deployments, StatefulSets, DaemonSets, Jobs, CronJobs) for production workloads
- User wants guidance on resource requests/limits, pod disruption budgets, horizontal pod autoscaling, or vertical pod autoscaling configuration
- User needs to configure namespaces, RBAC, NetworkPolicies, PodSecurityAdmission, or ServiceAccounts for a Kubernetes cluster
- User is setting up ConfigMaps, Secrets, or external secret management integration (Vault, AWS Secrets Manager, Sealed Secrets) in Kubernetes
- User wants to implement multi-environment configuration patterns using Kustomize overlays, Helm values files, or GitOps workflows with ArgoCD or Flux
- User asks about cluster-level configuration such as LimitRanges, ResourceQuotas, admission webhooks, or Priority Classes
- User needs to tune ingress controllers (NGINX, Traefik), service mesh configuration (Istio, Linkerd), or CNI-level networking policies
- User is troubleshooting misconfigured pods (OOMKilled, CrashLoopBackOff, Pending, Evicted) and needs remediation guidance
- User wants to implement pod topology spread constraints, node affinity, taints/tolerations, or pod anti-affinity for high availability
Do NOT use this skill when:
- User needs CI/CD pipeline configuration that only incidentally deploys to Kubernetes -- use a dedicated CI/CD pipeline skill
- User is asking about Terraform or Pulumi infrastructure provisioning of the cluster itself (node groups, VPCs, IAM) -- use infrastructure-as-code skills
- User needs Dockerfile optimization or container image build advice -- use a container image skill
- User is asking about Helm chart authoring and packaging best practices beyond values configuration -- use a Helm chart development skill
- User needs service mesh traffic management (canary deployments, circuit breaking in Istio VirtualServices) beyond basic sidecar injection -- use a service mesh skill
- User is asking about Kubernetes operator development -- use an operator development skill
- User needs cloud-provider-specific managed Kubernetes setup (EKS node groups, GKE Autopilot classes, AKS node pool configuration) -- use provider-specific skills
Process
1. Establish Workload Characteristics and Requirements
Before writing a single line of YAML, gather the specific workload profile:
- Workload type: Is this a stateless HTTP service (Deployment), stateful database (StatefulSet), batch processor (Job/CronJob), or cluster agent (DaemonSet)? Each requires fundamentally different configuration patterns.
- Traffic profile: Peak requests per second, expected p99 latency target (e.g., <200ms), whether traffic is bursty or steady-state. This drives HPA min/max replica counts and CPU request sizing.
- Startup behavior: Does the container take >10 seconds to initialize? You need a
startupProbe separate from livenessProbe to avoid premature restarts during slow initialization.
- State and storage: Does the container write to local disk? If yes, you cannot use a simple Deployment -- you need a StatefulSet with a VolumeClaimTemplate or a ReadWriteMany PersistentVolume.
- Privilege requirements: Does the container need to bind to ports <1024, access the host network, or mount host paths? Document these as exceptions; they require explicit security context overrides.
- Dependency graph: Which services does this pod call, and which call it? This shapes NetworkPolicy allow-lists and readiness probe logic (don't mark ready until dependencies are reachable).
2. Set Resource Requests and Limits Precisely
Resource misconfiguration is the #1 cause of production Kubernetes incidents. Apply these concrete steps:
3. Configure Health Probes Correctly
Probe misconfiguration causes either unnecessary pod restarts (too aggressive) or failed traffic routing to broken pods (too permissive):
startupProbe: Use when container initialization takes >10 seconds. Set failureThreshold * periodSeconds to cover the worst-case startup time. Example: failureThreshold: 30, periodSeconds: 5 allows 150 seconds for startup. Once it succeeds once, it disables itself.
livenessProbe: Kills and restarts the container when it fails. Only check that the process is alive (not deadlocked). Do NOT check external dependencies (database connectivity) in livenessProbe -- this causes cascading restarts during a database outage.
readinessProbe: Removes the pod from Service endpoints when it fails. This probe CAN check external dependencies -- if the database is down, it is correct to stop routing traffic to this pod.
- Probe timing guidance: For HTTP probes,
initialDelaySeconds: 0 (use startupProbe instead), periodSeconds: 10, failureThreshold: 3, successThreshold: 1, timeoutSeconds: 3. Adjust timeoutSeconds to be less than your p99 response time under load.
- Prefer
/healthz (liveness) and /readyz (readiness) as separate endpoints. Never combine them. The /readyz endpoint should return 503 when the app cannot serve traffic. The /healthz endpoint should only return 500 when the process itself is broken.
- For TCP probes (databases, gRPC without health endpoint), use
tcpSocket with port: 5432 as a fallback, but implement gRPC health protocol or HTTP sidecar where possible for richer health semantics.
4. Design for High Availability and Disruption Tolerance
A single misconfigured field can cause 100% downtime during routine node maintenance:
- Replica count: Never run a production Deployment with
replicas: 1. Minimum is 2 for stateless services; 3 for services requiring zero-downtime rolling updates with maxUnavailable: 0.
- Pod Disruption Budget (PDB): This is mandatory for every production workload. It prevents node drain operations from simultaneously evicting all pods:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: api
Set minAvailable to the minimum number of replicas needed to serve production traffic. For a 3-replica deployment, minAvailable: 2 allows rolling node drain without service interruption.
- Pod Anti-Affinity: Spread replicas across nodes and, if applicable, across availability zones. Use
preferredDuringSchedulingIgnoredDuringExecution for flexibility, requiredDuringSchedulingIgnoredDuringExecution when zone isolation is mandatory:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values: ["api"]
topologyKey: "topology.kubernetes.io/zone"
- Topology Spread Constraints (preferred over podAntiAffinity for even distribution): Use
maxSkew: 1, topologyKey: topology.kubernetes.io/zone, whenUnsatisfiable: DoNotSchedule to enforce even zone distribution.
- Rolling update strategy: Set
maxUnavailable: 0 and maxSurge: 1 for zero-downtime deployments. This temporarily uses one extra pod slot during the update. Requires that resource quota accommodates the surge.
- Termination grace period: Set
terminationGracePeriodSeconds to the time your application needs to finish in-flight requests. For most HTTP services, 30-60 seconds is sufficient. Implement SIGTERM handling in your application to stop accepting new connections and drain existing ones.
5. Implement Security Controls at Every Layer
Apply the Kubernetes security hierarchy: cluster > namespace > pod > container:
- PodSecurityAdmission (PSA): Label namespaces with the appropriate policy level. Use
restricted for all production workloads unless a specific exception is documented:
metadata:
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: v1.28
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/audit: restricted
- Security context per container -- apply all of these unless you have a documented exception:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 3000
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
- Secrets management: Never store plaintext secrets in ConfigMaps. Never mount Secrets as environment variables for sensitive values (they appear in
kubectl describe output). Mount secrets as files at /run/secrets/ with mode 0400. Use External Secrets Operator with AWS Secrets Manager or HashiCorp Vault for production secret lifecycle management.
- RBAC: Create dedicated ServiceAccounts per application (never use the
default ServiceAccount). Grant only the exact API verbs and resources needed. Use ClusterRole only for cluster-wide resources; use Role + RoleBinding for namespace-scoped permissions. Audit with kubectl auth can-i --list --as=system:serviceaccount:production:api-service.
- NetworkPolicy: Default-deny all ingress and egress at namespace level, then allow-list specific flows. A minimal NetworkPolicy looks like:
spec:
podSelector: {}
policyTypes: ["Ingress", "Egress"]
Then add explicit allow rules for each required traffic path.
- Image security: Set
imagePullPolicy: Always for latest tags (though avoid latest in production -- use digest-pinned or semver tags). Enforce via OPA/Gatekeeper policy that images must come from approved registries.
6. Configure Multi-Environment Patterns with Kustomize or Helm
Avoid copy-pasting manifests between dev, staging, and production. Use structured parameterization:
- Kustomize approach: Maintain a
base/ directory with environment-agnostic manifests and an overlays/ directory for each environment. The overlay patches only what differs (replica counts, resource sizes, image tags, ingress hostnames). Never duplicate entire manifests in overlays.
k8s/
base/
deployment.yaml
service.yaml
kustomization.yaml
overlays/
dev/
kustomization.yaml # patches: replicas=1, resources=small
staging/
kustomization.yaml # patches: replicas=2, resources=medium
production/
kustomization.yaml # patches: replicas=5, resources=large + PDB
- Helm approach: Use a single
values.yaml as the production-equivalent default. Provide environment-specific values-dev.yaml, values-staging.yaml that override only what differs. Deploy with helm upgrade --install --values values.yaml --values values-production.yaml.
- Image tag management: In Kustomize, use the
images: field in kustomization.yaml to override the image tag per environment. In CI/CD pipelines, use kustomize edit set image to inject the build SHA before applying.
- GitOps with ArgoCD: Define one Application per environment, pointing to the corresponding overlay. Set
syncPolicy.automated.prune: true and syncPolicy.automated.selfHeal: true for production. Store encrypted secrets separately using Sealed Secrets or Vault Agent Injector.
- Configuration drift detection: With GitOps, use ArgoCD's drift detection (shows
OutOfSync when someone manually applies a change). For non-GitOps workflows, use kubectl diff in CI before kubectl apply.
7. Validate, Test, and Enforce Standards
Configuration that cannot be validated automatically will drift over time:
- Static analysis pipeline steps:
kubeval or kubeconform -- validates YAML against OpenAPI schemas for the target Kubernetes version. Catches API version deprecations (e.g., extensions/v1beta1 Ingress was removed in 1.22).
kube-score -- scores manifests against best practices. Flags missing resource limits, missing PodDisruptionBudgets, containers running as root.
checkov -- scans for security policy violations across IaC including Kubernetes manifests.
pluto -- detects deprecated API versions across the entire manifest tree.
- Admission controller enforcement: Deploy OPA/Gatekeeper or Kyverno ConstraintTemplates to enforce organizational policies at the API server level. Example policies: require resource limits on all containers, prohibit
hostNetwork: true without approval, require specific label keys (app, version, team).
- Dry-run validation: Always use
kubectl apply --dry-run=server before applying to production. Server-side dry-run invokes admission webhooks and validates against the actual cluster state, catching issues that client-side dry-run misses.
- CI pipeline order: lint YAML syntax --> validate with kubeconform --> score with kube-score --> security scan with checkov -->
kubectl diff against staging --> apply to staging --> smoke test --> apply to production with change window gate.
8. Implement Observability Hooks
A Kubernetes manifest that does not expose metrics and logs is not production-ready:
Output Format
When providing Kubernetes configuration guidance, produce:
1. Annotated YAML manifests with inline comments explaining non-obvious decisions:
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-api
namespace: production
labels:
app: payment-api
version: "1.4.2"
team: payments
component: api
annotations:
kubernetes.io/change-cause: "Bumped to v1.4.2 -- adds idempotency keys"
spec:
replicas: 3
revisionHistoryLimit: 5
selector:
matchLabels:
app: payment-api
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
template:
metadata:
labels:
app: payment-api
version: "1.4.2"
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/metrics"
spec:
serviceAccountName: payment-api-sa
terminationGracePeriodSeconds: 60
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
seccompProfile:
type: RuntimeDefault
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: payment-api
containers:
- name: payment-api
image: registry.company.com/payment-api:1.4.2
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
protocol: TCP
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
memory: 384Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
startupProbe:
httpGet:
path: /healthz/startup
port: http
failureThreshold: 24
periodSeconds: 5
livenessProbe:
httpGet:
path: /healthz/live
port: http
initialDelaySeconds: 0
periodSeconds: 10
failureThreshold: 3
timeoutSeconds: 3
readinessProbe:
httpGet:
path: /healthz/ready
port: http
periodSeconds: 10
failureThreshold: 3
successThreshold: 1
timeoutSeconds: 3
env:
- name: LOG_FORMAT
value: json
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: payment-api-config
key: log_level
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: payment-api-db-secret
key: password
volumeMounts:
- name: tmp
mountPath: /tmp
lifecycle:
preStop:
exec:
command: ["/bin/sleep", "5"]
volumes:
- name: tmp
emptyDir: {}
2. Decision matrix for key configuration choices:
| Decision Point | Option | When to Choose | Trade-off |
|---|
| CPU limits | Set to 2x request | Batch jobs, best-effort workloads | Predictable cost; may throttle latency-sensitive paths |
| CPU limits | Omit, use quota only | Latency-sensitive APIs | No throttling; requires namespace quota guardrail |
| Replicas | 2 | Dev/staging or very low traffic | Cost savings; single AZ failure causes degraded capacity |
| Replicas | 3+ | Production with HA requirement | Survives one zone failure at minAvailable:2 |
| Secret delivery | Environment variable | Non-sensitive config (log levels) | Visible in kubectl describe; acceptable for non-secrets |
| Secret delivery | Mounted file | Passwords, tokens, certs | Not visible in pod spec; requires readOnlyRootFilesystem exception for mount path |
| Image tag | semver digest-pinned | Production | Immutable; reproducible; imagePullPolicy: IfNotPresent works |
| Image tag | latest | Never in production | Non-deterministic; breaks rollback |
| Update strategy | RollingUpdate maxUnavailable:0 | Zero-downtime services | Requires one extra pod slot; slower rollout |
| Update strategy | Recreate | Dev environments, stateful singletons | Fast; causes downtime; not for production |
3. Checklist for production readiness review:
PRODUCTION READINESS CHECKLIST
================================
Workload & Scheduling
[ ] replicas >= 2 (3+ for HA with zone spread)
[ ] PodDisruptionBudget configured (minAvailable >= floor(replicas/2)+1)
[ ] topologySpreadConstraints or podAntiAffinity across zones
[ ] priorityClassName set for critical workloads
Resources
[ ] requests.cpu and requests.memory set on all containers
[ ] limits.memory set (with 20-50% headroom above request)
[ ] CPU limit decision documented (set or intentionally omitted)
[ ] namespace LimitRange and ResourceQuota exist
Health Probes
[ ] startupProbe configured (if startup > 10s)
[ ] livenessProbe does NOT check external dependencies
[ ] readinessProbe endpoint is distinct from liveness
[ ] terminationGracePeriodSeconds >= max request timeout
[ ] preStop sleep hook present
Security
[ ] dedicated ServiceAccount (not default)
[ ] runAsNonRoot: true
[ ] allowPrivilegeEscalation: false
[ ] readOnlyRootFilesystem: true
[ ] capabilities.drop: ["ALL"]
[ ] NetworkPolicy: default-deny + explicit allow rules
[ ] Secrets not stored in ConfigMap or git
Observability
[ ] Prometheus scrape annotations present
[ ] Consistent labels: app, version, team, component
[ ] LOG_FORMAT=json
[ ] revisionHistoryLimit set (recommend 5)
Validation
[ ] kubeconform passes for target cluster version
[ ] kube-score passes (no critical issues)
[ ] kubectl apply --dry-run=server passes
[ ] Manifest stored in git under version control
Rules
-
Never set identical values for requests and limits for CPU (QoS class Guaranteed sounds appealing but causes CPU throttling the instant the container tries to use burst capacity, causing latency spikes that are hard to diagnose). Reserve Guaranteed QoS intentionally for latency-critical system components only.
-
Never write a production Deployment without a PodDisruptionBudget. Cluster upgrades and node maintenance drain nodes one at a time. Without a PDB, Kubernetes is permitted to evict all pods of a Deployment simultaneously, causing complete service outage during routine operations.
-
Never use kubectl apply -f directly to production without a --dry-run=server pass first. Server-side dry-run validates against live admission webhooks and detects issues that local linting cannot catch, including quota violations and OPA policy rejections.
-
Never store Kubernetes Secrets in a git repository as plaintext YAML, even in private repositories. Use Sealed Secrets (encrypts with cluster public key), External Secrets Operator (fetches from Vault/AWS SSM at runtime), or Helm Secrets (SOPS-encrypted values files). The cluster should be the source of truth for secret values, not git.
-
Always set revisionHistoryLimit on Deployments. The default is 10 ReplicaSets. In clusters with many deployments and frequent releases, unset revisionHistoryLimit causes etcd bloat from hundreds of orphaned ReplicaSet objects. Set to 5 for most workloads, 10 for critical services where more rollback history is valuable.
-
Never combine liveness and readiness probe logic. A container that fails its liveness probe is killed and restarted -- a catastrophic response to a transient database timeout. A container that fails its readiness probe is simply removed from load balancer rotation -- the correct response. Misusing liveness for dependency checks causes cascading restarts that amplify incidents.
-
Always use namePrefix or explicit namespaces in Kustomize bases, never rely on the default namespace. A kubectl apply -k overlays/production run from the wrong context (pointing at a dev cluster) must fail safely, not silently deploy to the wrong namespace.
-
Never use hostNetwork: true, hostPID: true, or privileged: true without a documented security exception, a specific use case (CNI plugin DaemonSet, node monitoring agent), and enforcement via OPA/Gatekeeper to prevent accidental drift. These settings bypass namespace-level network isolation entirely.
-
Always specify imagePullPolicy: IfNotPresent for semver-tagged or digest-pinned images in production. Using Always on every pod start adds latency and creates a registry dependency on the pod startup critical path. The correct pattern is immutable image tags (digest-pinned or semver) with IfNotPresent.
-
Never set minAvailable: 0 or maxUnavailable: 100% on a PodDisruptionBudget. This is equivalent to having no PDB. Validate PDB effectiveness by running kubectl get pdb -n production and confirming ALLOWED DISRUPTIONS is at least 1 and MIN AVAILABLE is greater than zero.
-
Always apply seccompProfile.type: RuntimeDefault at the pod level for any workload targeting Kubernetes 1.19+. This enables the container runtime's default seccomp filter, blocking ~40 potentially dangerous syscalls with zero application code change and minimal performance overhead.
-
When using HPA, always set both minReplicas and maxReplicas explicitly, and ensure minReplicas satisfies the PDB minAvailable value. An HPA with minReplicas: 1 defeats the purpose of a PDB with minAvailable: 2 because the HPA can scale down to a level where disruption is impossible to permit safely.
Edge Cases
Stateful Workloads (Databases, Queues, Caches)
StatefulSets require different HA patterns than Deployments. The serviceName headless service must exist before the StatefulSet is created. Rolling updates to StatefulSets use OnDelete or RollingUpdate with partition for canary-style upgrades -- set updateStrategy.rollingUpdate.partition: N to update only pods with ordinal >= N, allowing manual validation before completing the rollout. PersistentVolumeClaims created by volumeClaimTemplates are NOT deleted when the StatefulSet is deleted -- this is intentional data protection. Explicitly manage PVC lifecycle with kubectl delete pvc only after confirming data is backed up or migrated. For Redis or similar in-memory stores, calculate resources.limits.memory as 110-120% of your expected dataset size plus overhead -- OOMKill on a Redis node causes full cache cold-start which cascades to database load spikes.
Jobs and CronJobs at Scale
For CronJobs, always set concurrencyPolicy: Forbid unless you have explicitly designed for concurrent runs, and always set successfulJobsHistoryLimit: 3 and failedJobsHistoryLimit: 5 (default is 3 and 1 respectively in older versions, but always set explicitly). For Jobs that process large datasets, use completions + parallelism for indexed job patterns rather than spawning many single-task Jobs. Set backoffLimit thoughtfully -- the default of 6 with exponential backoff can mean a failing job retries for up to 10 minutes before giving up. Set activeDeadlineSeconds on all production Jobs to bound worst-case runtime and prevent zombie jobs consuming cluster resources indefinitely.
Cluster Version Upgrades and API Deprecations
Before upgrading a cluster from Kubernetes 1.21 to 1.22 or later, audit manifests for removed APIs: extensions/v1beta1 and networking.k8s.io/v1beta1 Ingress, policy/v1beta1 PodSecurityPolicy (removed in 1.25), batch/v1beta1 CronJob (removed in 1.25), and autoscaling/v2beta2 HPA (removed in 1.26). Run pluto detect-files -d k8s/ against the entire manifest directory before initiating any cluster upgrade. Verify using kubectl convert to migrate manifests to the current API version. Always test in a staging cluster running the target Kubernetes version for at least one week under production-like load before upgrading production.
Very Large Pod Counts in a Single Deployment
When a single Deployment exceeds 50 replicas, rolling updates can cause thundering-herd problems: all 50+ new pods start simultaneously, each pulling their configuration from ConfigMaps and Secrets, potentially saturating the API server. Mitigate by setting maxSurge as an absolute number (not percentage) capped at 5-10 pods. Consider splitting a 100-replica monolithic Deployment into multiple smaller Deployments behind the same Service for independent update control. At 500+ replicas, evaluate whether the workload should be served by a DaemonSet (one pod per node) to eliminate scheduling overhead.
Mixed Node Types and Spot/Preemptible Instances
When your cluster uses a mix of on-demand and spot/preemptible nodes, use node labels (node.kubernetes.io/lifecycle: spot) combined with nodeAffinity and tolerations to express scheduling preferences. For stateless API tiers, prefer spot instances with preferredDuringSchedulingIgnoredDuringExecution so pods can still schedule on on-demand if no spot capacity is available. For stateful or latency-sensitive critical paths, use requiredDuringSchedulingIgnoredDuringExecution with on-demand nodes. Set terminationGracePeriodSeconds: 30 on spot workloads and ensure your application handles SIGTERM within that window -- spot preemption gives only a 30-second warning before SIGKILL. PodDisruptionBudgets still apply during spot eviction managed by the cluster autoscaler.
Namespace-Level Resource Exhaustion
When a pod enters Pending state, diagnose with: (1) kubectl describe pod <name> -- look for Insufficient cpu, Insufficient memory, or exceeded quota events, (2) kubectl describe resourcequota -n <namespace> -- check Used vs Hard for each resource dimension, (3) kubectl describe nodes -- check Allocatable vs Requests at the cluster level. If the issue is namespace quota exhaustion, do not simply raise the quota without understanding what consumed it -- use kubectl top pods -n <namespace> --sort-by=memory to identify over-consuming pods. If the issue is cluster-level node pressure, check if the cluster autoscaler is blocked by PDB constraints or node group max size limits.
Secrets Rotation Without Downtime
When rotating a database password or TLS certificate stored as a Kubernetes Secret, the naive approach (update Secret, restart pods) causes downtime. The zero-downtime pattern is: (1) add the new credential alongside the old one in the Secret as a new key (e.g., password_v2), (2) deploy a new application version that reads password_v2 (or falls back to password), (3) verify the new version is healthy, (4) remove the old key from the Secret, (5) remove the fallback logic in the next deployment cycle. For TLS certificates, mount the entire cert directory as a Secret volume -- when the Secret is updated, Kubernetes propagates the new files to the mounted volume within 60-90 seconds (kubelet sync period) without a pod restart, allowing the application to reload the certificate from disk.
Example
Input: "I have a Python FastAPI service that needs to run in production Kubernetes. It handles payment webhooks, takes about 8 seconds to start (loading ML model on startup), processes ~200 req/s at peak, needs a PostgreSQL password, and must have zero downtime during deployments. We're on Kubernetes 1.28, using 3 availability zones. What's the full configuration?"
Output:
Here is the complete production configuration for this FastAPI payment webhook service. Each section addresses a specific requirement.
Namespace Setup
apiVersion: v1
kind: Namespace
metadata:
name: payments
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: v1.28
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/audit: restricted
team: payments
apiVersion: v1
kind: ResourceQuota
metadata:
name: payments-quota
namespace: payments
spec:
hard:
requests.cpu: "8"
requests.memory: 8Gi
limits.memory: 16Gi
count/pods: "50"
count/services: "20"
count/persistentvolumeclaims: "10"
apiVersion: v1
kind: LimitRange
metadata:
name: payments-limits
namespace: payments
spec:
limits:
- type: Container
default:
memory: 256Mi
defaultRequest:
cpu: 100m
memory: 128Mi
max:
memory: 4Gi
ServiceAccount and RBAC
This service reads from PostgreSQL and calls an internal fraud detection API. It does not need any Kubernetes API access, so the ServiceAccount has no RoleBindings.
apiVersion: v1
kind: ServiceAccount
metadata:
name: webhook-api-sa
namespace: payments
annotations:
automountServiceAccountToken: false
ConfigMap and Secret
apiVersion: v1
kind: ConfigMap
metadata:
name: webhook-api-config
namespace: payments
data:
LOG_LEVEL: "info"
LOG_FORMAT: "json"
PORT: "8080"
WORKERS: "4"
DB_HOST: "postgres-service.data.svc.cluster.local"
DB_PORT: "5432"
DB_NAME: "payments"
MODEL_PATH: "/models/fraud_v3.pkl"
apiVersion: v1
kind: Secret
metadata:
name: webhook-api-db-secret
namespace: payments
type: Opaque
Core Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: webhook-api
namespace: payments
labels:
app: webhook-api
version: "2.1.0"
team: payments
component: api
annotations:
kubernetes.io/change-cause: "v2.1.0 -- upgraded fraud model to v3"
spec:
replicas: 3
revisionHistoryLimit: 5
selector:
matchLabels:
app: webhook-api
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
template:
metadata:
labels:
app: webhook-api
version: "2.1.0"
team: payments
component: api
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/metrics"
spec:
serviceAccountName: webhook-api-sa
automountServiceAccountToken: false
terminationGracePeriodSeconds: 60
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
seccompProfile:
type: RuntimeDefault
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: webhook-api
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: webhook-api
initContainers:
- name: model-downloader
image: registry.company.com/model-sync:1.2.0
command: ["/bin/sh", "-c", "cp /source/fraud_v3.pkl /models/fraud_v3.pkl"]
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
memory: 128Mi
securityContext:
runAsNonRoot: true
runAsUser: 1000
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: model-volume
mountPath: /models
- name: model-source
mountPath: /source
readOnly: true
containers:
- name: webhook-api
image: registry.company.com/webhook-api:2.1.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
protocol: TCP
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
memory: 768Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
startupProbe:
httpGet:
path: /healthz/startup
port: http
failureThreshold: 30
periodSeconds: 1
timeoutSeconds: 2
livenessProbe:
httpGet:
path: /healthz/live
port: http
periodSeconds: 15
failureThreshold: 3
timeoutSeconds: 3
readinessProbe:
httpGet:
path: /healthz/ready
port: http
periodSeconds: 10
failureThreshold: 3
successThreshold: 1
timeoutSeconds: 3
lifecycle:
preStop:
exec:
command: ["/bin/sleep", "5"]
envFrom:
- configMapRef:
name: webhook-api-config
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: webhook-api-db-secret
key: password
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
volumeMounts:
- name: tmp
mountPath: /tmp
- name: model-volume
mountPath: /models
readOnly: true
volumes:
- name: tmp
emptyDir: {}
- name: model-volume
emptyDir: {}
- name: model-source
configMap:
name: model-source-config
Pod Disruption Budget
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: webhook-api-pdb
namespace: payments
spec:
minAvailable: 2
selector:
matchLabels:
app: webhook-api
Horizontal Pod Autoscaler
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: webhook-api-hpa
namespace: payments
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: webhook-api
minReplicas: 3
maxReplicas: 12
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
- type: Resource
resource:
name: memory
target:
type: AverageValue
averageValue: 600Mi
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Pods
value: 1
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 30
policies:
- type: Pods
value: 3
periodSeconds: 60
Service and NetworkPolicy
apiVersion: v1
kind: Service
metadata:
name: webhook-api-service
namespace: payments
labels:
app: webhook-api
spec:
selector:
app: webhook-api
ports:
- name: http
port: 80
targetPort: http
protocol: TCP
type: ClusterIP
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: payments
spec:
podSelector: {}
policyTypes: ["Ingress", "Egress"]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: webhook-api-allow
namespace: payments
spec:
podSelector:
matchLabels:
app: webhook-api
policyTypes: ["Ingress", "Egress"]
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
ports:
- protocol: TCP
port: 8080
egress:
- ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: data
ports:
- protocol: TCP
port: 5432
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: fraud
podSelector:
matchLabels:
app: fraud-detection-api
ports:
- protocol: TCP
port: 8080
Key Configuration Decisions Summary
| Requirement | Solution | Rationale |
|---|
| 8-second startup | startupProbe failureThreshold:30 periodSeconds:1 | Gives 30s headroom; disables after first success; avoids false liveness kills |
| Zero downtime deploys | maxUnavailable:0 + maxSurge:1 + PDB minAvailable:2 | Zero capacity reduction during rollout; node drain blocked until safe |
| 3 AZ spread | topologySpreadConstraints zone + hostname | Zone failure loses 1 of 3 pods; PDB ensures min 2 remain during voluntary disruption |
| ML model loading | initContainer copies model to | |