Skip to main content

Exercises and Projects


1 - Hands-on exercises

Exercise 1: Pods and Containers

Objective: Create and manage Pods

# Créez un Pod avec les spécifications suivantes :
# - Nom : web-server
# - Image : nginx:1.25
# - Port : 80
# - Labels : app=web, tier=frontend
# - Resource requests : 100m CPU, 128Mi memory
# - Resource limits : 200m CPU, 256Mi memory
# - Liveness probe : HTTP GET /healthz port 80
# - Readiness probe : HTTP GET / port 80
Solution
apiVersion: v1
kind: Pod
metadata:
name: web-server
labels:
app: web
tier: frontend
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "200m"
memory: "256Mi"
livenessProbe:
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 10
periodSeconds: 5
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 3

Exercise 2: Deployments

Objective: Create a Deployment with scaling

Tasks:

  1. Create a Deployment named api-deployment with 3 replicas
  2. Use the image hashicorp/http-echo:0.2.3 with the argument -text="Hello Kubernetes"
  3. Expose port 5678
  4. Scale to 5 replicas
  5. Update the image to a different version
  6. Perform a rollback
Solution
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-deployment
spec:
replicas: 3
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: hashicorp/http-echo:0.2.3
args:
- "-text=Hello Kubernetes"
ports:
- containerPort: 5678
# Appliquer
kubectl apply -f deployment.yaml

# Scaler à 5 réplicas
kubectl scale deployment api-deployment --replicas=5

# Mettre à jour l'image
kubectl set image deployment/api-deployment api=hashicorp/http-echo:0.2.3

# Vérifier l'historique
kubectl rollout history deployment/api-deployment

# Rollback
kubectl rollout undo deployment/api-deployment

Exercise 3: Services

Objective: Expose an application with different Service types

Tasks:

  1. Create a ClusterIP Service for the previous Deployment
  2. Create a NodePort Service on port 30080
  3. Test access from inside the cluster
Solution
# service-clusterip.yaml
apiVersion: v1
kind: Service
metadata:
name: api-clusterip
spec:
type: ClusterIP
selector:
app: api
ports:
- port: 80
targetPort: 5678

---
# service-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: api-nodeport
spec:
type: NodePort
selector:
app: api
ports:
- port: 80
targetPort: 5678
nodePort: 30080
# Tester depuis l'intérieur du cluster
kubectl run test --rm -it --image=busybox -- wget -qO- http://api-clusterip

Exercise 4: ConfigMaps and Secrets

Objective: Manage an application's configuration

Tasks:

  1. Create a ConfigMap with configuration variables
  2. Create a Secret for the database credentials
  3. Use them in a Pod
Solution
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
APP_ENV: production
LOG_LEVEL: info
config.json: |
{
"apiUrl": "https://api.example.com",
"timeout": 30
}

---
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
stringData:
DB_HOST: mysql.default.svc.cluster.local
DB_USER: appuser
DB_PASSWORD: secretpassword123

---
# pod-with-config.yaml
apiVersion: v1
kind: Pod
metadata:
name: configured-app
spec:
containers:
- name: app
image: busybox
command: ['sh', '-c', 'env && sleep 3600']
envFrom:
- configMapRef:
name: app-config
- secretRef:
name: db-credentials
volumeMounts:
- name: config-file
mountPath: /etc/config
volumes:
- name: config-file
configMap:
name: app-config
items:
- key: config.json
path: config.json

Exercise 5: Ingress

Objective: Configure HTTP routing with Ingress

Tasks:

  1. Deploy two applications: frontend and backend
  2. Create a Service for each
  3. Configure an Ingress to route / to frontend and /api to backend
Solution
# deployments.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
replicas: 2
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: hashicorp/http-echo:0.2.3
args: ["-text=Frontend"]
ports:
- containerPort: 5678

---
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
spec:
replicas: 2
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: backend
image: hashicorp/http-echo:0.2.3
args: ["-text=Backend API"]
ports:
- containerPort: 5678

---
# services.yaml
apiVersion: v1
kind: Service
metadata:
name: frontend-svc
spec:
selector:
app: frontend
ports:
- port: 80
targetPort: 5678

---
apiVersion: v1
kind: Service
metadata:
name: backend-svc
spec:
selector:
app: backend
ports:
- port: 80
targetPort: 5678

---
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: app.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend-svc
port:
number: 80
- path: /api
pathType: Prefix
backend:
service:
name: backend-svc
port:
number: 80

Exercise 6: RBAC

Objective: Configure access control

Tasks:

  1. Create a dev-team namespace
  2. Create a dev-sa ServiceAccount
  3. Create a Role allowing reading and creating Pods and Deployments
  4. Bind the Role to the ServiceAccount
  5. Test the permissions
Solution
# rbac-setup.yaml
apiVersion: v1
kind: Namespace
metadata:
name: dev-team

---
apiVersion: v1
kind: ServiceAccount
metadata:
name: dev-sa
namespace: dev-team

---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: dev-role
namespace: dev-team
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "create", "update", "delete"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: dev-role-binding
namespace: dev-team
subjects:
- kind: ServiceAccount
name: dev-sa
namespace: dev-team
roleRef:
kind: Role
name: dev-role
apiGroup: rbac.authorization.k8s.io
# Tester les permissions
kubectl auth can-i create pods -n dev-team --as system:serviceaccount:dev-team:dev-sa
kubectl auth can-i delete secrets -n dev-team --as system:serviceaccount:dev-team:dev-sa

2 - Complete project: Voting application

Description

Deploy a complete voting application consisting of:

  • Frontend: User interface (voting-app)
  • API: Vote processing (worker)
  • Redis: Vote queue
  • PostgreSQL: Results storage
  • Results: Results display

Kubernetes architecture

# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: voting-app
labels:
app: voting

---
# redis.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: voting-app
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "200m"
memory: "256Mi"
---
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: voting-app
spec:
selector:
app: redis
ports:
- port: 6379

---
# postgres.yaml
apiVersion: v1
kind: Secret
metadata:
name: postgres-secret
namespace: voting-app
stringData:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres123

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
namespace: voting-app
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi

---
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
namespace: voting-app
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:15-alpine
ports:
- containerPort: 5432
envFrom:
- secretRef:
name: postgres-secret
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
resources:
requests:
cpu: "100m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
volumes:
- name: data
persistentVolumeClaim:
claimName: postgres-pvc

---
apiVersion: v1
kind: Service
metadata:
name: db
namespace: voting-app
spec:
selector:
app: postgres
ports:
- port: 5432

---
# voting-app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: voting-app
namespace: voting-app
spec:
replicas: 3
selector:
matchLabels:
app: voting-app
template:
metadata:
labels:
app: voting-app
spec:
containers:
- name: voting-app
image: dockersamples/examplevotingapp_vote
ports:
- containerPort: 80
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "200m"
memory: "256Mi"
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5

---
apiVersion: v1
kind: Service
metadata:
name: voting-app
namespace: voting-app
spec:
type: ClusterIP
selector:
app: voting-app
ports:
- port: 80

---
# worker.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: worker
namespace: voting-app
spec:
replicas: 1
selector:
matchLabels:
app: worker
template:
metadata:
labels:
app: worker
spec:
containers:
- name: worker
image: dockersamples/examplevotingapp_worker
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "200m"
memory: "256Mi"

---
# result-app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: result-app
namespace: voting-app
spec:
replicas: 2
selector:
matchLabels:
app: result-app
template:
metadata:
labels:
app: result-app
spec:
containers:
- name: result-app
image: dockersamples/examplevotingapp_result
ports:
- containerPort: 80
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "200m"
memory: "256Mi"

---
apiVersion: v1
kind: Service
metadata:
name: result-app
namespace: voting-app
spec:
type: ClusterIP
selector:
app: result-app
ports:
- port: 80

---
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: voting-ingress
namespace: voting-app
spec:
ingressClassName: nginx
rules:
- host: vote.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: voting-app
port:
number: 80
- host: results.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: result-app
port:
number: 80

---
# network-policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: voting-app
spec:
podSelector: {}
policyTypes:
- Ingress

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-voting-to-redis
namespace: voting-app
spec:
podSelector:
matchLabels:
app: redis
ingress:
- from:
- podSelector:
matchLabels:
app: voting-app
- podSelector:
matchLabels:
app: worker

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-worker-to-db
namespace: voting-app
spec:
podSelector:
matchLabels:
app: postgres
ingress:
- from:
- podSelector:
matchLabels:
app: worker
- podSelector:
matchLabels:
app: result-app

---
# resource-quota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: voting-quota
namespace: voting-app
spec:
hard:
requests.cpu: "2"
requests.memory: 2Gi
limits.cpu: "4"
limits.memory: 4Gi
pods: "20"

---
# hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: voting-app-hpa
namespace: voting-app
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: voting-app
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70

Deployment

# Créer tous les fichiers dans un dossier voting-app/
# Puis appliquer

kubectl apply -f voting-app/

# Vérifier le déploiement
kubectl get all -n voting-app

# Voir les logs
kubectl logs -n voting-app -l app=worker

# Tester l'application
# Ajouter dans /etc/hosts : <INGRESS_IP> vote.local results.local
# Ouvrir http://vote.local et http://results.local

3 - CKA/CKAD certification preparation

Exam tips

  1. Master kubectl - Most questions require kubectl
  2. Use aliases - alias k=kubectl
  3. Know the documentation - kubernetes.io is accessible during the exam
  4. Practice with real clusters - Minikube, Kind, or a playground

Essential commands

# Créer rapidement des ressources
kubectl run nginx --image=nginx --dry-run=client -o yaml > pod.yaml
kubectl create deployment nginx --image=nginx --dry-run=client -o yaml > deployment.yaml
kubectl expose deployment nginx --port=80 --type=ClusterIP --dry-run=client -o yaml > service.yaml

# Debugging
kubectl describe pod <pod-name>
kubectl logs <pod-name> -c <container-name>
kubectl exec -it <pod-name> -- /bin/bash
kubectl get events --sort-by='.lastTimestamp'

# Contextes et namespaces
kubectl config set-context --current --namespace=<namespace>
kubectl config use-context <context-name>

# Documentation rapide
kubectl explain pod.spec.containers
kubectl explain deployment.spec.strategy

Certification resources

CertificationFocusDuration
CKACluster administration2h
CKADApp development2h
CKSSecurity2h

Course summary

Congratulations! You have completed the Kubernetes course. You now have a solid grasp of:

  • The Kubernetes architecture and its components
  • Creating and managing Pods, Deployments, Services
  • Configuration with ConfigMaps and Secrets
  • Persistent storage with PV/PVC
  • Security with RBAC and Network Policies
  • Scaling and updates
  1. Helm - Kubernetes package manager
  2. Observability - Prometheus, Grafana
  3. Service Mesh - Istio, Linkerd
  4. GitOps - ArgoCD, Flux

← Back to the table of contents