Kubernetes Concepts - Core Objects and Patterns
Status: Active
Last Updated: 2026-01-30
Category: Containers - Kubernetes Fundamentals
Prerequisites: k0s-installation, docker-basics
Time: 3-4 hours
Tags: kubernetes, k8s, workloads, jobs, cronjobs, daemonsets, configmaps, secrets
Summary
Master core Kubernetes concepts and object types. Learn Jobs, CronJobs, DaemonSets, ConfigMaps, Secrets, resource management, and essential patterns for production workloads beyond basic Deployments.
π― What You'll Learn
By the end of this article, you'll be able to:
- β Understand Kubernetes architecture
- β Use Jobs and CronJobs
- β Deploy DaemonSets
- β Manage ConfigMaps and Secrets
- β Configure resource limits
- β Implement health checks
- β Use init containers
- β Apply pod affinity rules
ποΈ Kubernetes Architecture
Control Plane Components
βββββββββββββββββββββββββββββββββββββββββββββββββ
β Control Plane β
β β
β ββββββββββββ ββββββββββββ ββββββββββββ β
β β API β β Schedulerβ βControllerβ β
β β Server β β β β Manager β β
β ββββββββββββ ββββββββββββ ββββββββββββ β
β β
β ββββββββββββββββββββββββββββββββββββ β
β β etcd (Data Store) β β
β ββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββ
β Worker Nodes β
β β
β Node 1 Node 2 Node 3 β
β ββββββββββββ ββββββββββββ ββββββββ β
β β kubelet β β kubelet β βkubeletβ β
β β kube-proxy β kube-proxy βkube-prβ β
β β Container β Container βContainβ β
β β Runtime β β Runtime β βRuntimeβ β
β ββββββββββββ ββββββββββββ ββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββ
Control Plane:
- API Server: Frontend for Kubernetes API
- Scheduler: Assigns pods to nodes
- Controller Manager: Runs controllers (Deployment, ReplicaSet)
- etcd: Key-value store for cluster data
Worker Nodes:
- kubelet: Agent running on each node
- kube-proxy: Network proxy
- Container Runtime: Docker, containerd, CRI-O
π¦ Core Workload Types
Pod
Smallest deployable unit:
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
Note: Usually don't create Pods directly, use Deployments.
Deployment
For stateless applications (covered in k0s articles):
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.25
Use for: Web servers, APIs, microservices
StatefulSet
For stateful applications (covered in k0s-storage):
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: database
spec:
serviceName: database
replicas: 3
selector:
matchLabels:
app: db
template:
metadata:
labels:
app: db
spec:
containers:
- name: postgres
image: postgres:15
Use for: Databases, message queues, stateful apps
DaemonSet
Runs on every node:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-exporter
namespace: monitoring
spec:
selector:
matchLabels:
app: node-exporter
template:
metadata:
labels:
app: node-exporter
spec:
containers:
- name: node-exporter
image: prom/node-exporter:latest
ports:
- containerPort: 9100
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
Use for:
- Monitoring agents (node-exporter)
- Log collectors (fluentd)
- Network plugins
- Storage drivers
Verify:
kubectl get ds -n monitoring
# Output:
# NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
# node-exporter 3 3 3 3 3 <none> 2m
# Should have 1 pod per node
kubectl get pods -n monitoring -o wide
Job
Run to completion:
apiVersion: batch/v1
kind: Job
metadata:
name: data-migration
spec:
completions: 1 # Run once
backoffLimit: 4 # Retry 4 times on failure
activeDeadlineSeconds: 600 # Timeout after 10 minutes
template:
spec:
restartPolicy: Never # Don't restart on failure
containers:
- name: migrate
image: myapp:latest
command: ["python", "migrate.py"]
env:
- name: DATABASE_URL
value: "postgresql://db:5432/myapp"
Run job:
kubectl apply -f job.yaml
# Check status
kubectl get jobs
# Output:
# NAME COMPLETIONS DURATION AGE
# data-migration 1/1 45s 2m
# View logs
kubectl logs job/data-migration
Parallel jobs:
apiVersion: batch/v1
kind: Job
metadata:
name: batch-processing
spec:
parallelism: 5 # Run 5 pods in parallel
completions: 100 # Need 100 successful completions
template:
spec:
restartPolicy: OnFailure
containers:
- name: worker
image: worker:latest
command: ["./process-batch.sh"]
Use for:
- Database migrations
- Batch processing
- One-time tasks
- Data imports/exports
CronJob
Scheduled jobs:
apiVersion: batch/v1
kind: CronJob
metadata:
name: backup-database
spec:
schedule: "0 2 * * *" # 2 AM daily
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
jobTemplate:
spec:
backoffLimit: 2
template:
spec:
restartPolicy: OnFailure
containers:
- name: backup
image: postgres:15
command:
- /bin/sh
- -c
- |
pg_dump -h $DB_HOST -U $DB_USER $DB_NAME | \
gzip > /backup/db-$(date +%Y%m%d-%H%M%S).sql.gz
env:
- name: DB_HOST
value: "database.default.svc.cluster.local"
- name: DB_USER
value: "postgres"
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
volumeMounts:
- name: backup
mountPath: /backup
volumes:
- name: backup
persistentVolumeClaim:
claimName: backup-pvc
Cron schedule format:
# ββββββββββββββ minute (0 - 59)
# β ββββββββββββββ hour (0 - 23)
# β β ββββββββββββββ day of month (1 - 31)
# β β β ββββββββββββββ month (1 - 12)
# β β β β ββββββββββββββ day of week (0 - 6) (Sunday to Saturday)
# β β β β β
# * * * * *
"0 2 * * *" # 2 AM daily
"*/15 * * * *" # Every 15 minutes
"0 */6 * * *" # Every 6 hours
"0 0 * * 0" # Weekly (Sunday midnight)
"0 0 1 * *" # Monthly (1st of month)
Common CronJobs:
---
# Cleanup old data
apiVersion: batch/v1
kind: CronJob
metadata:
name: cleanup-old-data
spec:
schedule: "0 3 * * *" # 3 AM daily
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: cleanup
image: myapp:latest
command: ["python", "cleanup.py", "--days", "30"]
---
# Generate reports
apiVersion: batch/v1
kind: CronJob
metadata:
name: daily-report
spec:
schedule: "0 8 * * 1-5" # 8 AM weekdays
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: report
image: report-generator:latest
command: ["./generate-report.sh"]
Manage CronJobs:
# List CronJobs
kubectl get cronjobs
# See recent jobs
kubectl get jobs --sort-by=.status.startTime
# Manually trigger
kubectl create job --from=cronjob/backup-database manual-backup
# Suspend (stop scheduling)
kubectl patch cronjob backup-database -p '{"spec":{"suspend":true}}'
# Resume
kubectl patch cronjob backup-database -p '{"spec":{"suspend":false}}'
βοΈ ConfigMaps and Secrets
ConfigMap
Store configuration data:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
# Key-value pairs
app.env: "production"
log.level: "info"
# Multi-line configuration
nginx.conf: |
server {
listen 80;
server_name example.com;
root /var/www/html;
}
# JSON configuration
database.json: |
{
"host": "database.default.svc.cluster.local",
"port": 5432,
"name": "myapp"
}
Create from file:
# From file
kubectl create configmap app-config --from-file=app.properties
# From literal
kubectl create configmap app-config \
--from-literal=app.env=production \
--from-literal=log.level=info
# From directory
kubectl create configmap app-config --from-file=config/
Use in Pod:
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
containers:
- name: app
image: myapp:latest
# Method 1: Environment variables
env:
- name: APP_ENV
valueFrom:
configMapKeyRef:
name: app-config
key: app.env
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config
key: log.level
# Method 2: All keys as env vars
envFrom:
- configMapRef:
name: app-config
# Method 3: Mount as volume
volumeMounts:
- name: config
mountPath: /etc/config
readOnly: true
volumes:
- name: config
configMap:
name: app-config
Access in container:
# Environment variables
echo $APP_ENV
# Output: production
# Files (if mounted)
cat /etc/config/nginx.conf
Secrets
Store sensitive data:
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
# Base64 encoded (not encrypted!)
username: cG9zdGdyZXM= # postgres
password: c3VwZXJzZWNyZXQ= # supersecret
Create secret:
# From literal
kubectl create secret generic db-credentials \
--from-literal=username=postgres \
--from-literal=password=supersecret
# From file
kubectl create secret generic ssh-key --from-file=id_rsa=~/.ssh/id_rsa
# TLS certificate
kubectl create secret tls myapp-tls \
--cert=myapp.crt \
--key=myapp.key
Use in Pod:
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
containers:
- name: app
image: myapp:latest
# Environment variables
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
# Mount as volume
volumeMounts:
- name: secrets
mountPath: /etc/secrets
readOnly: true
volumes:
- name: secrets
secret:
secretName: db-credentials
defaultMode: 0400 # Read-only for owner
Security notes:
- β οΈ Secrets are base64 encoded, not encrypted
- π Enable encryption at rest in etcd
- π Use RBAC to restrict access
- π‘οΈ Consider external secrets (Vault, AWS Secrets Manager)
π― Resource Management
Resource Requests and Limits
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
containers:
- name: app
image: myapp:latest
resources:
requests:
cpu: 250m # 0.25 CPU cores (minimum)
memory: 512Mi # 512 MiB (minimum)
limits:
cpu: 500m # 0.5 CPU cores (maximum)
memory: 1Gi # 1 GiB (maximum)
Units:
- CPU:
100m= 0.1 cores,1= 1 core,2= 2 cores - Memory:
128Mi= 128 MiB,1Gi= 1 GiB
What happens:
- Requests: Scheduler ensures node has resources
- Limits: Container killed if exceeded (memory), throttled (CPU)
Quality of Service (QoS)
Guaranteed (highest priority):
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: 500m # Same as requests
memory: 1Gi # Same as requests
Burstable (medium priority):
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: 500m # Higher than requests
memory: 1Gi
BestEffort (lowest priority):
# No requests or limits
Priority during eviction:
- BestEffort pods killed first
- Burstable pods next
- Guaranteed pods last
LimitRange
Set defaults for namespace:
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: default
spec:
limits:
- default:
cpu: 500m
memory: 512Mi
defaultRequest:
cpu: 100m
memory: 128Mi
max:
cpu: 2
memory: 4Gi
min:
cpu: 50m
memory: 64Mi
type: Container
Enforces:
- Minimum resources
- Maximum resources
- Default requests/limits if not specified
ResourceQuota
Limit namespace resources:
apiVersion: v1
kind: ResourceQuota
metadata:
name: dev-quota
namespace: dev
spec:
hard:
requests.cpu: "10" # Max 10 CPU cores requested
requests.memory: 20Gi # Max 20 GiB memory requested
limits.cpu: "20" # Max 20 CPU cores limit
limits.memory: 40Gi # Max 40 GiB memory limit
pods: "50" # Max 50 pods
services: "20" # Max 20 services
persistentvolumeclaims: "10" # Max 10 PVCs
# Check quota usage
kubectl get resourcequota -n dev
# Output:
# NAME AGE REQUEST LIMIT
# dev-quota 5m requests.cpu: 5/10, requests.memory: 10Gi/20Gi limits.cpu: 10/20, limits.memory: 20Gi/40Gi
π₯ Health Checks
Liveness Probe
Restart container if unhealthy:
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
containers:
- name: app
image: myapp:latest
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30 # Wait 30s before first check
periodSeconds: 10 # Check every 10s
timeoutSeconds: 5 # Timeout after 5s
failureThreshold: 3 # Restart after 3 failures
Probe types:
# HTTP GET
livenessProbe:
httpGet:
path: /health
port: 8080
httpHeaders:
- name: Custom-Header
value: HealthCheck
# TCP Socket
livenessProbe:
tcpSocket:
port: 8080
# Command execution
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
Readiness Probe
Remove from service if not ready:
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
containers:
- name: app
image: myapp:latest
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 2
Difference from liveness:
- Liveness: Restart container
- Readiness: Remove from service endpoints
Startup Probe
For slow-starting containers:
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
containers:
- name: app
image: myapp:latest
startupProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 0
periodSeconds: 10
failureThreshold: 30 # 30 * 10 = 300s = 5 minutes max startup
livenessProbe:
httpGet:
path: /health
port: 8080
periodSeconds: 10
Use when: Application takes minutes to start
π§ Init Containers
Run before main containers:
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
# Init containers run sequentially, in order
initContainers:
- name: wait-for-db
image: busybox:1.36
command:
- sh
- -c
- |
until nc -z database 5432; do
echo "Waiting for database..."
sleep 2
done
echo "Database ready!"
- name: run-migrations
image: myapp:latest
command: ["python", "migrate.py"]
env:
- name: DATABASE_URL
value: "postgresql://database:5432/myapp"
# Main container starts after all init containers succeed
containers:
- name: app
image: myapp:latest
ports:
- containerPort: 8080
Use cases:
- Wait for dependencies
- Run database migrations
- Fetch configuration from remote
- Set up file permissions
- Clone git repositories
π Pod Affinity and Anti-Affinity
Node Affinity
Schedule pods on specific nodes:
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
affinity:
nodeAffinity:
# Required: Must match
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disktype
operator: In
values:
- ssd
# Preferred: Try to match
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: zone
operator: In
values:
- us-east-1a
containers:
- name: app
image: myapp:latest
Label nodes:
kubectl label nodes worker-1 disktype=ssd
kubectl label nodes worker-1 zone=us-east-1a
Pod Affinity
Schedule pods near other pods:
apiVersion: apps/v1
kind: Deployment
metadata:
name: cache
spec:
replicas: 3
template:
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web
topologyKey: kubernetes.io/hostname
containers:
- name: redis
image: redis:7
Schedules cache pods on same nodes as web pods.
Pod Anti-Affinity
Spread pods across nodes:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
template:
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web
topologyKey: kubernetes.io/hostname
containers:
- name: nginx
image: nginx:1.25
Ensures each web pod on different node (high availability).
π― Complete Example
Multi-Tier Application
Namespace with quota:
apiVersion: v1
kind: Namespace
metadata:
name: production
---
apiVersion: v1
kind: ResourceQuota
metadata:
name: prod-quota
namespace: production
spec:
hard:
requests.cpu: "20"
requests.memory: 40Gi
limits.cpu: "40"
limits.memory: 80Gi
pods: "100"
ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: production
data:
LOG_LEVEL: "info"
CACHE_TTL: "3600"
API_TIMEOUT: "30"
Secret:
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
namespace: production
type: Opaque
stringData:
DB_PASSWORD: "supersecret123"
API_KEY: "secret-api-key"
Web application:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: production
spec:
replicas: 5
selector:
matchLabels:
app: web
tier: frontend
template:
metadata:
labels:
app: web
tier: frontend
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web
topologyKey: kubernetes.io/hostname
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
envFrom:
- configMapRef:
name: app-config
env:
- name: API_KEY
valueFrom:
secretKeyRef:
name: app-secrets
key: API_KEY
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 80
initialDelaySeconds: 5
periodSeconds: 5
Background workers:
apiVersion: apps/v1
kind: Deployment
metadata:
name: worker
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: worker
tier: backend
template:
metadata:
labels:
app: worker
tier: backend
spec:
initContainers:
- name: wait-for-db
image: busybox:1.36
command: ['sh', '-c', 'until nc -z database 5432; do sleep 2; done']
containers:
- name: worker
image: worker:latest
envFrom:
- configMapRef:
name: app-config
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: app-secrets
key: DB_PASSWORD
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: 500m
memory: 1Gi
Scheduled jobs:
apiVersion: batch/v1
kind: CronJob
metadata:
name: cleanup
namespace: production
spec:
schedule: "0 2 * * *" # 2 AM daily
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: cleanup
image: worker:latest
command: ["python", "cleanup.py"]
envFrom:
- configMapRef:
name: app-config
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: app-secrets
key: DB_PASSWORD
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 200m
memory: 512Mi
Monitoring DaemonSet:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-exporter
namespace: production
spec:
selector:
matchLabels:
app: node-exporter
template:
metadata:
labels:
app: node-exporter
spec:
hostNetwork: true
hostPID: true
containers:
- name: node-exporter
image: prom/node-exporter:latest
ports:
- containerPort: 9100
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 100m
memory: 128Mi
π‘ Best Practices
1. Always Set Resource Limits
# Good
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
# Bad: No limits (can consume all node resources)
2. Use Health Checks
# At minimum: liveness probe
livenessProbe:
httpGet:
path: /health
port: 8080
periodSeconds: 10
# Better: liveness + readiness
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5
3. ConfigMaps for Configuration
# Good: Externalized configuration
configMapRef:
name: app-config
# Bad: Hardcoded in image
ENV DATABASE_URL=postgresql://...
4. Never Commit Secrets to Git
# Good: Create from command
kubectl create secret generic db-creds --from-literal=password=secret
# Bad: Commit secret YAML to Git
# Use Sealed Secrets or External Secrets Operator
5. Use Namespaces
# Separate environments
kubectl create namespace dev
kubectl create namespace staging
kubectl create namespace production
# Apply resource quotas per namespace
π What's Next?
Advanced Topics:
- kubernetes-operators - Custom controllers
- container-best-practices - Production patterns
Related:
- k0s-monitoring - Monitor workloads
- k0s-storage - Persistent storage
π Resources
Kubernetes:
Best Practices:
π Change Log
2026-01-30
- Created Kubernetes concepts guide
- Explained architecture
- Covered all workload types
- Demonstrated Jobs and CronJobs
- Showed ConfigMaps and Secrets
- Resource management patterns
- Health check strategies
- Init containers
- Affinity rules
- Complete production example
Next Article: kubernetes-operators - Extend Kubernetes!