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:
- Create a Deployment named
api-deploymentwith 3 replicas - Use the image
hashicorp/http-echo:0.2.3with the argument-text="Hello Kubernetes" - Expose port 5678
- Scale to 5 replicas
- Update the image to a different version
- 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:
- Create a ClusterIP Service for the previous Deployment
- Create a NodePort Service on port 30080
- 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:
- Create a ConfigMap with configuration variables
- Create a Secret for the database credentials
- 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:
- Deploy two applications:
frontendandbackend - Create a Service for each
- Configure an Ingress to route
/to frontend and/apito 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:
- Create a
dev-teamnamespace - Create a
dev-saServiceAccount - Create a Role allowing reading and creating Pods and Deployments
- Bind the Role to the ServiceAccount
- 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
- Master kubectl - Most questions require kubectl
- Use aliases -
alias k=kubectl - Know the documentation - kubernetes.io is accessible during the exam
- 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
| Certification | Focus | Duration |
|---|---|---|
| CKA | Cluster administration | 2h |
| CKAD | App development | 2h |
| CKS | Security | 2h |
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
Recommended next steps
- Helm - Kubernetes package manager
- Observability - Prometheus, Grafana
- Service Mesh - Istio, Linkerd
- GitOps - ArgoCD, Flux