Kubernetes Resource Patterns
Canonical YAML patterns for all resources generated by this skill. Each section shows a full pattern with safe defaults, lists required fields, and notes common mistakes.
Overview
Kubernetes Resource Patterns
Canonical YAML patterns for all resources generated by this skill. Each section shows a full pattern with safe defaults, lists required fields, and notes common mistakes.
Deployment
Required fields: apiVersion, kind, metadata.name, spec.selector, spec.template, spec.template.spec.containers
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: default
labels:
app.kubernetes.io/name: my-app
app.kubernetes.io/instance: my-app
app.kubernetes.io/version: "1.0.0"
app.kubernetes.io/part-of: my-app
app.kubernetes.io/managed-by: kubectl
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: my-app
template:
metadata:
labels:
app.kubernetes.io/name: my-app # Must match selector.matchLabels
spec:
containers:
- name: my-app
image: my-app:1.0.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
Common mistakes:
selector.matchLabelsmust be a subset oftemplate.metadata.labels— mismatch causesInvalid valueerrorselectoris immutable after creation; changing it requires deleting and recreating the Deployment- Missing
resources.limitscauses pods to be scheduled without constraints
Service
Required fields: apiVersion, kind, metadata.name, spec.ports, spec.selector
# ClusterIP (default, internal only)
apiVersion: v1
kind: Service
metadata:
name: my-app
namespace: default
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: my-app
ports:
- name: http
port: 80 # Port exposed on the Service
targetPort: http # Named port or number on the Pod
protocol: TCP
---
# NodePort (external via node IP + port)
apiVersion: v1
kind: Service
metadata:
name: my-app-nodeport
namespace: default
spec:
type: NodePort
selector:
app.kubernetes.io/name: my-app
ports:
- name: http
port: 80
targetPort: http
nodePort: 30080 # 30000–32767; omit to auto-assign
---
# LoadBalancer (external via cloud LB)
apiVersion: v1
kind: Service
metadata:
name: my-app-lb
namespace: default
spec:
type: LoadBalancer
selector:
app.kubernetes.io/name: my-app
ports:
- name: http
port: 80
targetPort: http
Common mistakes:
targetPortmust match acontainerPortname or number defined in the pod specselectormust match labels on pods, not on the Deployment itself
StatefulSet
Required fields: spec.serviceName (must match a headless Service), spec.selector, spec.template, volumeClaimTemplates (if using persistent storage)
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: my-db
namespace: default
spec:
serviceName: my-db-headless # Must reference a headless Service (clusterIP: None)
replicas: 3
podManagementPolicy: OrderedReady # Or Parallel for faster scale
selector:
matchLabels:
app.kubernetes.io/name: my-db
template:
metadata:
labels:
app.kubernetes.io/name: my-db
spec:
containers:
- name: my-db
image: postgres:15
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
storageClassName: standard
resources:
requests:
storage: 10Gi
Common mistakes:
volumeClaimTemplatesare immutable; to resize, delete the StatefulSet (keep pods), update PVCs, then recreate- The headless Service (
clusterIP: None) must exist before the StatefulSet, or pods stay Pending - Pod DNS:
<pod>.<serviceName>.<namespace>.svc.cluster.local
DaemonSet
Required fields: spec.selector, spec.template
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: my-agent
namespace: default
spec:
selector:
matchLabels:
app.kubernetes.io/name: my-agent
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
template:
metadata:
labels:
app.kubernetes.io/name: my-agent
spec:
tolerations:
- key: node-role.kubernetes.io/control-plane
effect: NoSchedule
containers:
- name: my-agent
image: my-agent:1.0.0
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "200m"
Common mistakes:
- DaemonSets run on every node by default; use
tolerations+nodeSelectorto restrict to specific nodes - Without
tolerationsfor control-plane taint, the pod won't run on control-plane nodes
Job
Required fields: spec.template.spec.containers, spec.template.spec.restartPolicy (must be Never or OnFailure)
apiVersion: batch/v1
kind: Job
metadata:
name: my-batch-job
namespace: default
spec:
completions: 1
parallelism: 1
backoffLimit: 3
ttlSecondsAfterFinished: 300 # Auto-delete after 5 minutes
template:
spec:
restartPolicy: Never # Never or OnFailure; not Always
containers:
- name: my-job
image: my-job:1.0.0
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
Common mistakes:
restartPolicy: Alwaysis forbidden in Jobs — useNeverorOnFailure- Without
ttlSecondsAfterFinished, completed Jobs and their pods accumulate indefinitely
CronJob
Required fields: spec.schedule, spec.jobTemplate
apiVersion: batch/v1
kind: CronJob
metadata:
name: my-cron
namespace: default
spec:
schedule: "0 2 * * *" # Daily at 02:00 UTC
timeZone: "UTC" # Explicit timezone (K8s 1.27+ GA)
concurrencyPolicy: Forbid # Allow, Forbid, or Replace
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
startingDeadlineSeconds: 600
jobTemplate:
spec:
backoffLimit: 2
ttlSecondsAfterFinished: 600
template:
spec:
restartPolicy: OnFailure
containers:
- name: my-cron
image: my-job:1.0.0
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "200m"
Common mistakes:
scheduleuses UTC by default; usetimeZoneto specify local timezoneconcurrencyPolicy: Allow(default) can cause job pile-up if runs take longer than the interval
ConfigMap
Required fields: apiVersion, kind, metadata.name
apiVersion: v1
kind: ConfigMap
metadata:
name: my-app-config
namespace: default
immutable: false # Set to true to prevent accidental changes
data:
KEY: value
config.yaml: |
server:
port: 8080
binaryData:
logo.png: <base64-encoded bytes> # Use binaryData for non-UTF-8 content
Common mistakes:
datavalues must be strings; usebinaryDatafor binary contentimmutable: trueprevents updates; pod must be restarted manually after recreation
Secret
Required fields: apiVersion, kind, metadata.name, type
# Opaque (arbitrary key-value pairs)
apiVersion: v1
kind: Secret
metadata:
name: my-app-secret
namespace: default
type: Opaque
immutable: false
data:
username: bXktdXNlcg== # base64-encoded; echo -n 'my-user' | base64
password: c2VjcmV0 # base64-encoded
---
# TLS secret (for Ingress TLS)
apiVersion: v1
kind: Secret
metadata:
name: my-app-tls
namespace: default
type: kubernetes.io/tls
data:
tls.crt: <base64-encoded cert>
tls.key: <base64-encoded key>
Common mistakes:
- Values in
datamust be base64-encoded; usestringDatafor plain text (Kubernetes handles encoding) stringDatais write-only; it's always read back as base64 indata
PersistentVolumeClaim
Required fields: spec.accessModes, spec.resources.requests.storage
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-data-pvc
namespace: default
spec:
accessModes:
- ReadWriteOnce # RWO (single node), RWX (many nodes), ROX (read-only many)
storageClassName: standard
resources:
requests:
storage: 10Gi
Common mistakes:
accessModesmust match what the StorageClass/PV supports;ReadWriteManyrequires specific storage backends- PVC spec (except
resources.requests.storage) is immutable after binding
Ingress
Required fields: spec.rules
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
namespace: default
spec:
ingressClassName: nginx # Required in K8s 1.18+; do not use deprecated annotation
tls:
- hosts:
- my-app.example.com
secretName: my-app-tls
rules:
- host: my-app.example.com
http:
paths:
- path: /
pathType: Prefix # Prefix, Exact, or ImplementationSpecific
backend:
service:
name: my-app
port:
name: http
Common mistakes:
- Use
ingressClassNamefield, not the deprecatedkubernetes.io/ingress.classannotation pathType: ImplementationSpecificbehavior varies by controller; preferPrefixorExact
NetworkPolicy
Required fields: spec.podSelector (empty = select all pods in namespace)
# Default deny all ingress and egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: default
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
# Allow ingress from specific namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-monitoring
namespace: default
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: my-app
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
---
# Allow egress DNS only
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-egress-dns
namespace: default
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
Common mistakes:
- NetworkPolicy is additive: multiple policies union their rules; a pod not selected by any policy has no restrictions
- Always create a
default-deny-allbefore adding allow rules to avoid gaps - Forgetting to allow egress DNS (port 53) breaks service discovery
ServiceAccount
Required fields: apiVersion, kind, metadata.name
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app
namespace: default
automountServiceAccountToken: false # Disable for workloads that don't need API access
imagePullSecrets:
- name: my-registry-secret # Attach pull secrets to the SA instead of each pod
Role / ClusterRole
# Namespace-scoped
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: my-app-role
namespace: default
rules:
- apiGroups: [""] # "" = core API group
resources: ["configmaps"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["my-app-secret"] # Restrict to named resources
verbs: ["get"]
---
# Cluster-scoped
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: my-app-reader
rules:
- apiGroups: [""]
resources: ["nodes", "namespaces"]
verbs: ["get", "list", "watch"]
RoleBinding / ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: my-app-rolebinding
namespace: default
subjects:
- kind: ServiceAccount
name: my-app
namespace: default
roleRef:
kind: Role # Role or ClusterRole
name: my-app-role
apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: my-app-clusterrolebinding
subjects:
- kind: ServiceAccount
name: my-app
namespace: default
roleRef:
kind: ClusterRole
name: my-app-reader
apiGroup: rbac.authorization.k8s.io
Common mistakes:
roleRefis immutable; changing it requires deleting and recreating the binding- Use
Role+RoleBindingfor namespace-scoped access;ClusterRole+ClusterRoleBindingfor cluster-wide access - A
ClusterRolecan be bound with aRoleBindingto limit it to one namespace
HorizontalPodAutoscaler (v2)
Required fields: spec.scaleTargetRef, spec.minReplicas, spec.maxReplicas, spec.metrics
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-app-hpa
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
Common mistakes:
- Use
autoscaling/v2(available since Kubernetes v1.23, stable);autoscaling/v2beta2is deprecated - HPA requires
resources.requeststo be set on containers; without requests, CPU/memory utilization cannot be calculated
PodDisruptionBudget
Required fields: spec.selector, one of minAvailable or maxUnavailable
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: my-app-pdb
namespace: default
spec:
minAvailable: 1 # Or use maxUnavailable; not both
selector:
matchLabels:
app.kubernetes.io/name: my-app
Common mistakes:
- Use
policy/v1(GA since K8s 1.21);policy/v1beta1is removed - Setting
minAvailableequal toreplicasblocks all voluntary disruptions (node drain fails)
Namespace
apiVersion: v1
kind: Namespace
metadata:
name: my-namespace
labels:
kubernetes.io/metadata.name: my-namespace # Automatically set by K8s 1.21+
ResourceQuota
apiVersion: v1
kind: ResourceQuota
metadata:
name: my-namespace-quota
namespace: my-namespace
spec:
hard:
requests.cpu: "4"
requests.memory: 8Gi
limits.cpu: "8"
limits.memory: 16Gi
pods: "20"
services: "10"
persistentvolumeclaims: "10"
LimitRange
apiVersion: v1
kind: LimitRange
metadata:
name: my-namespace-limits
namespace: my-namespace
spec:
limits:
- type: Container
default: # Applied when container has no limits set
cpu: "500m"
memory: "256Mi"
defaultRequest: # Applied when container has no requests set