Application Design and Build (20%)¶
This domain focuses on the core building blocks of Kubernetes applications: designing pods with multiple containers, building container images, and using workload resources like Jobs and CronJobs. You need to understand multi-container patterns, how to define and use persistent storage from the application side, and Dockerfile best practices.
Key Concepts¶
Multi-Container Pod Patterns¶
Pods can contain multiple containers that share networking and storage. The most common multi-container patterns are:
- Sidecar: A helper container that extends the main container's functionality (e.g., log shipping, proxy, syncing files)
- Init Container: A container that runs to completion before the main application containers start (e.g., database migrations, config fetching)
- Ambassador: A proxy container that abstracts access to external services (e.g., connecting to different databases in different environments)
- Adapter: A container that transforms or normalizes output from the main container (e.g., reformatting logs)
Init Containers¶
Init containers run sequentially before application containers start. They are useful for setup tasks.
apiVersion: v1
kind: Pod
metadata:
name: app-with-init
spec:
initContainers:
- name: init-db-check
image: busybox:1.36
command: ['sh', '-c', 'until nslookup db-service.default.svc.cluster.local; do echo waiting for db; sleep 2; done']
containers:
- name: app
image: nginx:1.27
ports:
- containerPort: 80
Sidecar Container¶
A sidecar runs alongside the main container for the entire lifecycle of the pod.
apiVersion: v1
kind: Pod
metadata:
name: app-with-sidecar
spec:
containers:
- name: app
image: nginx:1.27
volumeMounts:
- name: shared-logs
mountPath: /var/log/nginx
- name: log-shipper
image: busybox:1.36
command: ['sh', '-c', 'tail -F /var/log/nginx/access.log']
volumeMounts:
- name: shared-logs
mountPath: /var/log/nginx
volumes:
- name: shared-logs
emptyDir: {}
Exam Tip
The exam may not explicitly name the pattern (sidecar, ambassador, adapter). Instead, it will describe what needs to happen and you must design the correct multi-container pod. Focus on understanding when to use shared volumes vs. shared networking between containers.
Building Container Images¶
Understanding Dockerfile syntax is essential. You may be asked to inspect, fix, or create a Dockerfile.
Dockerfile Best Practices¶
# Use specific base image tags, not :latest
FROM python:3.12-slim
# Set a working directory
WORKDIR /app
# Copy dependency file first for better layer caching
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
# Use a non-root user
RUN useradd -r appuser
USER appuser
# Expose the application port
EXPOSE 8080
# Use ENTRYPOINT for the main process, CMD for default arguments
ENTRYPOINT ["python"]
CMD ["app.py"]
Key best practices:
- Use multi-stage builds to reduce image size
- Minimize layers by combining RUN instructions
- Use
.dockerignoreto exclude unnecessary files - Use specific image tags instead of
latest - Run as a non-root user
- Order layers from least to most frequently changing for cache efficiency
Multi-Stage Build Example¶
# Build stage
FROM golang:1.22 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /app/server .
# Runtime stage
FROM alpine:3.20
COPY --from=builder /app/server /usr/local/bin/server
USER 1000
ENTRYPOINT ["server"]
Jobs¶
Jobs create one or more pods and ensure a specified number of them successfully terminate. Use Jobs for batch processing, one-time tasks, or any work that needs to run to completion.
# Create a simple Job
kubectl create job my-job --image=busybox -- sh -c "echo Hello World"
# Create a Job with specific completions and parallelism
kubectl create job batch-job --image=busybox -- sh -c "echo processing"
# View Job status
kubectl get jobs
kubectl describe job my-job
# View logs of the Job's pod
kubectl logs job/my-job
Key Job fields:
completions: Number of successful completions requiredparallelism: Number of pods running concurrentlybackoffLimit: Number of retries before marking as failedactiveDeadlineSeconds: Maximum runtime for the JobrestartPolicy: Must beNeverorOnFailure(notAlways)
CronJobs¶
CronJobs create Jobs on a scheduled basis using cron syntax.
apiVersion: batch/v1
kind: CronJob
metadata:
name: cleanup-job
spec:
schedule: "0 */6 * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: cleanup
image: busybox:1.36
command: ['sh', '-c', 'echo "Running cleanup at $(date)"']
Key CronJob fields:
schedule: Cron expression (minute hour day-of-month month day-of-week)concurrencyPolicy:Allow,Forbid, orReplacesuccessfulJobsHistoryLimit: Number of completed Jobs to retainfailedJobsHistoryLimit: Number of failed Jobs to retain
PersistentVolumeClaims¶
From the application developer perspective, PersistentVolumeClaims (PVCs) are used to request storage without needing to know the underlying storage details.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data
spec:
accessModes:
- ReadWriteOnce
storageClassName: standard
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Pod
metadata:
name: app-with-storage
spec:
containers:
- name: app
image: nginx:1.27
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
volumes:
- name: data
persistentVolumeClaim:
claimName: app-data
Access modes:
ReadWriteOnce(RWO): Mounted as read-write by a single nodeReadOnlyMany(ROX): Mounted as read-only by many nodesReadWriteMany(RWX): Mounted as read-write by many nodes
Exam Tip
On the exam, if a StorageClass is available, the PVC will be dynamically provisioned. You typically only need to create the PVC and reference it in your Pod spec. Use kubectl get storageclass to check which StorageClasses are available.
Practice Exercises¶
Exercise 1: Create a Pod with an Init Container
Create a pod named web-app in the default namespace. It should have an init container that creates a file /work-dir/index.html with the content "Hello from init". The main container should be nginx:1.27 and serve that file. Use an emptyDir volume shared between both containers.
Solution
apiVersion: v1
kind: Pod
metadata:
name: web-app
spec:
initContainers:
- name: init
image: busybox:1.36
command: ['sh', '-c', 'echo "Hello from init" > /work-dir/index.html']
volumeMounts:
- name: workdir
mountPath: /work-dir
containers:
- name: nginx
image: nginx:1.27
volumeMounts:
- name: workdir
mountPath: /usr/share/nginx/html
volumes:
- name: workdir
emptyDir: {}
Exercise 2: Create a Job with Completions and Parallelism
Create a Job named parallel-job that runs the command echo "Processing batch item" and completes 6 times with a parallelism of 3. Set a backoff limit of 2.
Solution
kubectl create job parallel-job --image=busybox --dry-run=client -o yaml -- sh -c "echo Processing batch item" > job.yaml
Edit job.yaml to add completions, parallelism, and backoffLimit:
Exercise 3: Create a CronJob
Create a CronJob named log-cleanup that runs every 15 minutes. It should execute echo "Cleaning logs at $(date)" using the busybox:1.36 image. Set concurrencyPolicy to Forbid and keep only the last 2 successful job runs.
Solution
kubectl create cronjob log-cleanup \
--image=busybox:1.36 \
--schedule="*/15 * * * *" \
--dry-run=client -o yaml \
-- sh -c 'echo "Cleaning logs at $(date)"' > cronjob.yaml
Edit to add concurrencyPolicy and history limits:
apiVersion: batch/v1
kind: CronJob
metadata:
name: log-cleanup
spec:
schedule: "*/15 * * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 2
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: log-cleanup
image: busybox:1.36
command: ['sh', '-c', 'echo "Cleaning logs at $(date)"']
Exercise 4: Multi-Container Pod with Shared Volume
Create a pod named multi-container with two containers. The first container (writer) should use the busybox:1.36 image and write the current date to /data/log.txt every 5 seconds. The second container (reader) should use the busybox:1.36 image and continuously tail /data/log.txt. Both containers should share an emptyDir volume mounted at /data.
Solution
apiVersion: v1
kind: Pod
metadata:
name: multi-container
spec:
containers:
- name: writer
image: busybox:1.36
command: ['sh', '-c', 'while true; do date >> /data/log.txt; sleep 5; done']
volumeMounts:
- name: shared-data
mountPath: /data
- name: reader
image: busybox:1.36
command: ['sh', '-c', 'tail -F /data/log.txt']
volumeMounts:
- name: shared-data
mountPath: /data
volumes:
- name: shared-data
emptyDir: {}
Exercise 5: PVC and Pod
Create a PersistentVolumeClaim named my-pvc requesting 500Mi of storage with ReadWriteOnce access mode. Then create a pod named pvc-pod using nginx:1.27 that mounts this PVC at /usr/share/nginx/html.
Solution
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 500Mi
---
apiVersion: v1
kind: Pod
metadata:
name: pvc-pod
spec:
containers:
- name: nginx
image: nginx:1.27
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
persistentVolumeClaim:
claimName: my-pvc