Le scénario : vous êtes l'équipe ops. La direction veut l'application de vote en production pour le grand sondage de fin d'année. Vous partez d'un cluster vierge et d'une spécification, comme dans la vraie vie.
Vous avez construit cette application brique par brique pendant trois jours : cette fois, vous la déployez entièrement, en autonomie, puis vous la sécurisez et l'optimisez.
90 minutes (2 exercices de 45 minutes chacun, + un volet optionnel Gateway API)
💡 Note formateur : le volet Gateway API (exercice 1, consigne 9) et le duo ResourceQuota/LimitRange (exercice 2) sont les soupapes de ce TP : l'application est complète et pleinement accessible sans eux.
⚠️ Pas de « Point de départ » pour ce TP : c'est voulu. La spécification ci-dessous EST l'exercice : à vous d'écrire tous les manifestes. Les solutions complètes restent disponibles en cas de blocage.
┌──────────────────────────────────────────┐
navigateur ──▶│ NodePort 31000 (vote) / 31001 (result) │
│ + Gateway API en option (vote.local) │
└──────┬──────────────────────────┬────────┘
▼ ▼
vote (x2) ──▶ redis ──▶ worker ──▶ db-0 ──▶ result
[probes] [probes] (sans [StatefulSet [probes]
service) + PVC + Secret]
↑ HPA (2-6) … le tout sous Network Policies zero-trust
Déployer les 5 briques dans un namespace voting-prod, avec tout ce qu'exige la production : Secret, stockage persistant, probes, ressources, et une exposition NodePort (la Gateway API en volet optionnel).
| Composant | Image | Réplicas | Probes | Ressources (requests → limits) |
|---|---|---|---|---|
| db (StatefulSet) | postgres:16-alpine |
1 | pg_isready -U postgres (exec) |
128Mi/100m → 256Mi |
| redis | redis:8-alpine |
1 | tcpSocket 6379 |
64Mi/50m → 128Mi |
| worker | dockersamples/examplevotingapp_worker:latest |
1 | aucune | 128Mi/100m → 512Mi |
| vote | dockersamples/examplevotingapp_vote:latest |
2 | httpGet / port 80 |
128Mi/100m → 256Mi |
| result | dockersamples/examplevotingapp_result:latest |
1 | httpGet / port 80 |
128Mi/100m → 256Mi |
Créer le namespace voting-prod et en faire le namespace par défaut de votre session
kubectl config set-context --currentCréer le Secret db-credentials :
POSTGRES_USER=postgres et POSTGRES_PASSWORD=postgresstringData évite l'encodage base64 manuelDéployer la base de données :
db (port 5432)
clusterIP: None ; le nom db n'est pas négociable, les applications le cherchent en durdb (serviceName: db) :
volumeClaimTemplates : 1Gi, storageClassName: local-path, monté sur /var/lib/postgresql/dataPGDATA=/var/lib/postgresql/data/pgdata (PostgreSQL refuse la racine d'un volume monté)envFrom.secretRefpg_isready -U postgresDéployer la file d'attente :
redis + Service redis (ClusterIP, port 6379)tcpSocket sur 6379Déployer le worker :
worker, sans port ni ServiceDéployer l'interface de vote :
voting-options avec les options de votre choix (par exemple OPTION_A=Bleu, OPTION_B=Rouge)vote : 2 réplicas, envFrom sur la ConfigMap, probes httpGet, et requests.cpu: 100m (l'HPA de l'exercice 2 en dépend)vote : NodePort 31000 (port 80), l'accès principal de l'applicationDéployer l'interface des résultats :
result (probes httpGet sur 80) + Service result : NodePort 31001 (port 80)Validation de bout en bout :
psql), et constatez les pourcentages sur result (port 31001)(Optionnel) Exposer proprement avec la Gateway API :
kubectl apply --server-side -f https://github.com/envoyproxy/gateway/releases/download/v1.8.2/install.yaml (les CRD Gateway API sont incluses), puis attendre deployment/envoy-gateway dans envoy-gateway-systemeg (controller gateway.envoyproxy.io/gatewayclass-controller)voting-gateway dans voting-prod (listener HTTP, port 80)vote.local → Service vote:80, et result.local → Service result:80curl -H "Host: vote.local" via le NodePort du service Envoy créé pour la Gateway
kubectl get svc -n envoy-gateway-system ; le Service de la Gateway est de type LoadBalancer ; son EXTERNAL-IP restera <pending> sur Killercoda (pas de cloud), mais il porte aussi un NodePort utilisable1. Namespace :
kubectl create namespace voting-prod
kubectl config set-context --current --namespace=voting-prod
2. Secret db-credentials :
Fichier db-credentials-secret.yaml :
# db-credentials-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: voting-prod
type: Opaque
stringData:
POSTGRES_USER: "postgres"
POSTGRES_PASSWORD: "postgres"
kubectl apply -f db-credentials-secret.yaml
3. Base de données (Service headless + StatefulSet) :
Fichier db.yaml :
# db.yaml
apiVersion: v1
kind: Service
metadata:
name: db
namespace: voting-prod
labels:
app: db
spec:
clusterIP: None
selector:
app: db
ports:
- port: 5432
targetPort: 5432
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: db
namespace: voting-prod
labels:
app: db
tier: database
spec:
serviceName: db
replicas: 1
selector:
matchLabels:
app: db
template:
metadata:
labels:
app: db
tier: database
spec:
containers:
- name: postgres
image: postgres:16-alpine
envFrom:
- secretRef:
name: db-credentials
env:
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
ports:
- containerPort: 5432
readinessProbe:
exec:
command: ["pg_isready", "-U", "postgres"]
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
exec:
command: ["pg_isready", "-U", "postgres"]
initialDelaySeconds: 30
periodSeconds: 10
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: local-path
resources:
requests:
storage: 1Gi
kubectl apply -f db.yaml
kubectl get pods -l app=db
# NAME READY STATUS RESTARTS AGE
# db-0 1/1 Running 0 45s
4. Redis :
Fichier redis.yaml :
# redis.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: voting-prod
labels:
app: redis
tier: backend
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
tier: backend
spec:
containers:
- name: redis
image: redis:8-alpine
ports:
- containerPort: 6379
readinessProbe:
tcpSocket:
port: 6379
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
tcpSocket:
port: 6379
initialDelaySeconds: 10
periodSeconds: 10
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
---
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: voting-prod
labels:
app: redis
spec:
selector:
app: redis
ports:
- port: 6379
targetPort: 6379
kubectl apply -f redis.yaml
5. Worker :
Fichier worker.yaml :
# worker.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: worker
namespace: voting-prod
labels:
app: worker
tier: backend
spec:
replicas: 1
selector:
matchLabels:
app: worker
template:
metadata:
labels:
app: worker
tier: backend
spec:
containers:
- name: worker
image: dockersamples/examplevotingapp_worker:latest
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
kubectl apply -f worker.yaml
6. Interface de vote (ConfigMap + Deployment + Service NodePort) :
Fichier vote.yaml :
# vote.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: voting-options
namespace: voting-prod
labels:
app: vote
data:
OPTION_A: "Bleu"
OPTION_B: "Rouge"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: vote
namespace: voting-prod
labels:
app: vote
tier: frontend
spec:
replicas: 2
selector:
matchLabels:
app: vote
template:
metadata:
labels:
app: vote
tier: frontend
spec:
containers:
- name: vote
image: dockersamples/examplevotingapp_vote:latest
envFrom:
- configMapRef:
name: voting-options
ports:
- containerPort: 80
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10
periodSeconds: 10
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
---
apiVersion: v1
kind: Service
metadata:
name: vote
namespace: voting-prod
labels:
app: vote
spec:
type: NodePort
selector:
app: vote
ports:
- port: 80
targetPort: 80
nodePort: 31000
kubectl apply -f vote.yaml
7. Interface des résultats :
Fichier result.yaml :
# result.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: result
namespace: voting-prod
labels:
app: result
tier: frontend
spec:
replicas: 1
selector:
matchLabels:
app: result
template:
metadata:
labels:
app: result
tier: frontend
spec:
containers:
- name: result
image: dockersamples/examplevotingapp_result:latest
ports:
- containerPort: 80
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10
periodSeconds: 10
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
---
apiVersion: v1
kind: Service
metadata:
name: result
namespace: voting-prod
labels:
app: result
spec:
type: NodePort
selector:
app: result
ports:
- port: 80
targetPort: 80
nodePort: 31001
kubectl apply -f result.yaml
# État complet
kubectl get pods
NAME READY STATUS RESTARTS AGE
db-0 1/1 Running 0 8m
redis-6f4b8d7c9d-x2kqp 1/1 Running 0 6m
result-7c9f8b6d5e-k4wpz 1/1 Running 0 1m
vote-6b8c9d7f4e-2mkqw 1/1 Running 0 2m
vote-6b8c9d7f4e-9zvtn 1/1 Running 0 2m
worker-5b8f9d6c47-t8slk 1/1 Running 1 (4m ago) 5m
8. Validation de bout en bout :
# Voter en ligne de commande via le NodePort (option a = Bleu)
curl -s -X POST -d "vote=a" http://localhost:31000 > /dev/null
# Le vote est en base
kubectl exec db-0 -- psql -U postgres -c 'SELECT vote, COUNT(*) FROM votes GROUP BY vote;'
vote | count
------+-------
a | 1
(1 row)
Ouvrez le port 31001 (Traffic Port Accessor) : Bleu 100 %. L'application de vote est en production. 🚀
9. (Optionnel) Gateway API avec Envoy Gateway :
# Installation d'Envoy Gateway (CRD Gateway API incluses)
kubectl apply --server-side -f https://github.com/envoyproxy/gateway/releases/download/v1.8.2/install.yaml
kubectl wait --timeout=5m -n envoy-gateway-system deployment/envoy-gateway --for=condition=Available
Fichier gateway.yaml :
# gateway.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: eg
spec:
controllerName: gateway.envoyproxy.io/gatewayclass-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: voting-gateway
namespace: voting-prod
spec:
gatewayClassName: eg
listeners:
- name: http
protocol: HTTP
port: 80
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: vote-route
namespace: voting-prod
spec:
parentRefs:
- name: voting-gateway
hostnames:
- vote.local
rules:
- backendRefs:
- name: vote
port: 80
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: result-route
namespace: voting-prod
spec:
parentRefs:
- name: voting-gateway
hostnames:
- result.local
rules:
- backendRefs:
- name: result
port: 80
kubectl apply -f gateway.yaml
# La Gateway doit être programmée et les routes acceptées
kubectl get gateway voting-gateway
kubectl get httproute
NAME CLASS ADDRESS PROGRAMMED AGE
voting-gateway eg True 30s
NAME HOSTNAMES AGE
result-route ["result.local"] 30s
vote-route ["vote.local"] 30s
Test via le NodePort du Service Envoy créé pour la Gateway :
# Retrouver le Service Envoy de notre Gateway et son NodePort HTTP
ENVOY_SVC=$(kubectl get svc -n envoy-gateway-system \
-l gateway.envoyproxy.io/owning-gateway-namespace=voting-prod,gateway.envoyproxy.io/owning-gateway-name=voting-gateway \
-o jsonpath='{.items[0].metadata.name}')
ENVOY_PORT=$(kubectl get svc -n envoy-gateway-system $ENVOY_SVC -o jsonpath='{.spec.ports[0].nodePort}')
echo "Gateway joignable sur le NodePort $ENVOY_PORT"
# Le routage par nom d'hôte fonctionne
curl -s -H "Host: vote.local" http://localhost:$ENVOY_PORT | grep -o 'Bleu\|Rouge' | sort -u
curl -s -o /dev/null -w "%{http_code}\n" -H "Host: result.local" http://localhost:$ENVOY_PORT
Bleu
Rouge
200
(Dans le navigateur : ouvrez le port $ENVOY_PORT via le Traffic Port Accessor ; sans en-tête Host personnalisé, vous obtiendrez un 404 d'Envoy, c'est normal : le routage se fait par nom d'hôte. Les NodePorts 31000/31001 restent l'accès direct navigateur.)
Un dernier vote pour prouver le routage par nom d'hôte :
curl -s -X POST -H "Host: vote.local" -d "vote=a" http://localhost:$ENVOY_PORT > /dev/null
La même entrée sert désormais vote.local et result.local : c'est la Gateway qui aiguille.
Transformer ce déploiement fonctionnel en déploiement de production : accès d'équipe contrôlés, réseau zero-trust, autoscaling maîtrisé, supervision.
Partie 1 - RBAC pour l'équipe d'exploitation (10 min)
ops-team-sa dans voting-prod
ops-role qui autorise :
pods, pods/log, servicesdeployments et configmapssecretskubectl auth can-i
system:serviceaccount:<namespace>:<nom>Partie 2 - Network Policies zero-trust (15 min)
kube-system) ; 💡 l'oubli classique qui casse toutingress: [{}]Partie 3 - Autoscaling HPA v2 avec behavior (10 min)
--kubelet-insecure-tls sur Killercoda, comme au TP 7)autoscaling/v2) sur le Deployment vote :
behavior : scale-up réactif (jusqu'à +2 Pods toutes les 30 s, sans stabilisation), scale-down prudent (1 Pod/minute après 60 s de stabilisation)kubectl autoscale du TP 7Partie 4 - Supervision (5 min)
kubectl top nodes / top pods, logs du worker, kubectl get all, événements récents du namespacePartie 5 - ResourceQuota et LimitRange (Optionnel)
voting-prod :
resources et observez les valeurs par défaut appliquées ; consultez la consommation du quotaPartie 1 - RBAC ServiceAccount :
Fichier ops-rbac.yaml :
# ops-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: ops-team-sa
namespace: voting-prod
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: ops-role
namespace: voting-prod
rules:
- apiGroups: [""]
resources: ["pods", "pods/log", "services"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "watch", "update", "patch"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ops-binding
namespace: voting-prod
subjects:
- kind: ServiceAccount
name: ops-team-sa
namespace: voting-prod
roleRef:
kind: Role
name: ops-role
apiGroup: rbac.authorization.k8s.io
kubectl apply -f ops-rbac.yaml
# Vérifications
kubectl auth can-i list pods --as=system:serviceaccount:voting-prod:ops-team-sa -n voting-prod
kubectl auth can-i update deployments --as=system:serviceaccount:voting-prod:ops-team-sa -n voting-prod
kubectl auth can-i get secrets --as=system:serviceaccount:voting-prod:ops-team-sa -n voting-prod
kubectl auth can-i delete deployments --as=system:serviceaccount:voting-prod:ops-team-sa -n voting-prod
yes
yes
no
no
Partie 2 - Network Policies zero-trust :
Fichier voting-prod-netpol.yaml :
# voting-prod-netpol.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: voting-prod
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: voting-prod
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-vote-external
namespace: voting-prod
spec:
podSelector:
matchLabels:
app: vote
policyTypes:
- Ingress
ingress:
- {}
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-result-external
namespace: voting-prod
spec:
podSelector:
matchLabels:
app: result
policyTypes:
- Ingress
ingress:
- {}
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-redis-ingress
namespace: voting-prod
spec:
podSelector:
matchLabels:
app: redis
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: vote
- podSelector:
matchLabels:
app: worker
ports:
- protocol: TCP
port: 6379
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-db-ingress
namespace: voting-prod
spec:
podSelector:
matchLabels:
app: db
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: worker
- podSelector:
matchLabels:
app: result
ports:
- protocol: TCP
port: 5432
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-vote-egress
namespace: voting-prod
spec:
podSelector:
matchLabels:
app: vote
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: redis
ports:
- protocol: TCP
port: 6379
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-worker-egress
namespace: voting-prod
spec:
podSelector:
matchLabels:
app: worker
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: redis
ports:
- protocol: TCP
port: 6379
- to:
- podSelector:
matchLabels:
app: db
ports:
- protocol: TCP
port: 5432
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-result-egress
namespace: voting-prod
spec:
podSelector:
matchLabels:
app: result
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: db
ports:
- protocol: TCP
port: 5432
kubectl apply -f voting-prod-netpol.yaml
kubectl get networkpolicy
Tests :
# La chaîne légitime fonctionne : votez (31000 ou Gateway), vérifiez sur 31001
# Un intrus ne joint plus la base...
kubectl run intruder -n default --image=busybox:1.36 --restart=Never --rm -it -- nc -zv -w 2 db.voting-prod 5432
nc: db.voting-prod (10.101.88.24:5432): Connection timed out
# ...et un Pod du namespace ne peut plus exfiltrer vers l'extérieur
kubectl run exfil-test --image=busybox:1.36 --restart=Never --rm -it -- wget -q -T 3 -O- http://httpbin.org/ip
wget: download timed out
💡 Si ces tests « bloqués » passent, le CNI du cluster n'applique pas les Network Policies (voir la note du TP 7).
Partie 3 - HPA v2 avec behavior :
# Metrics Server (comme au TP 7)
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
kubectl patch deployment metrics-server -n kube-system --type='json' \
-p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'
kubectl wait --for=condition=Available deployment/metrics-server -n kube-system --timeout=120s
Fichier vote-hpa.yaml :
# vote-hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: vote
namespace: voting-prod
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: vote
minReplicas: 2
maxReplicas: 6
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
behavior:
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Pods
value: 2
periodSeconds: 30
scaleDown:
stabilizationWindowSeconds: 60
policies:
- type: Pods
value: 1
periodSeconds: 60
kubectl apply -f vote-hpa.yaml
# Générer de la charge (le générateur doit être DANS voting-prod : le zero-trust
# bloque l'ingress vers vote depuis les autres namespaces... sauf que vote est en
# ingress ouvert - un générateur local reste plus fiable et plus rapide)
kubectl run load-generator --image=busybox:1.36 --restart=Never -- sh -c "while true; do wget -q -O- http://vote > /dev/null; done"
kubectl get hpa vote -w
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
vote Deployment/vote cpu: 3%/50% 2 6 2 1m
vote Deployment/vote cpu: 74%/50% 2 6 2 2m
vote Deployment/vote cpu: 74%/50% 2 6 4 2m15s
vote Deployment/vote cpu: 38%/50% 2 6 4 3m
⚠️ Petite subtilité zero-trust : le générateur n'a pas de règle d'egress vers vote : ajoutez-la si le trafic ne passe pas, ou labellisez le générateur app: worker (pédagogiquement : constatez que le zero-trust s'applique aussi à vos outils !). Le plus simple :
kubectl label pod load-generator app=vote --overwrite
# (les Pods app=vote n'ont d'egress que vers redis : pour un test de charge propre,
# créez plutôt une policy allow-loadgen-egress vers app=vote port 80)
Fichier allow-loadgen.yaml (la manière propre) :
# allow-loadgen.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-loadgen-egress
namespace: voting-prod
spec:
podSelector:
matchLabels:
run: load-generator
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: vote
ports:
- protocol: TCP
port: 80
kubectl apply -f allow-loadgen.yaml
# Fin du test
kubectl delete pod load-generator
kubectl get hpa vote -w
# Scale-down d'un Pod par minute, après 60 s de stabilisation (notre behavior)
Partie 4 - Supervision :
kubectl top nodes
kubectl top pods
# Le worker raconte la vie de l'application
kubectl logs deployment/worker --tail=5
# Inventaire complet du namespace
kubectl get all
# Les événements récents
kubectl get events --sort-by=.metadata.creationTimestamp | tail -10
# L'état de l'autoscaler
kubectl describe hpa vote | grep -A5 Conditions
Partie 5 (Optionnel) - ResourceQuota et LimitRange :
Fichier voting-prod-quotas.yaml :
# voting-prod-quotas.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: voting-prod-quota
namespace: voting-prod
spec:
hard:
pods: "20"
requests.cpu: "2"
requests.memory: 2Gi
limits.memory: 4Gi
---
apiVersion: v1
kind: LimitRange
metadata:
name: voting-prod-limits
namespace: voting-prod
spec:
limits:
- type: Container
defaultRequest:
cpu: 100m
memory: 128Mi
default:
cpu: 200m
memory: 256Mi
kubectl apply -f voting-prod-quotas.yaml
# Un Pod créé SANS resources hérite des valeurs par défaut du LimitRange
kubectl run no-resources-test --image=busybox:1.36 --restart=Never -- sleep 300
kubectl get pod no-resources-test -o jsonpath='{.spec.containers[0].resources}'
echo
{"limits":{"cpu":"200m","memory":"256Mi"},"requests":{"cpu":"100m","memory":"128Mi"}}
# La consommation du quota
kubectl describe resourcequota voting-prod-quota
kubectl delete pod no-resources-test
db, et pourquoi est-il headless ?vote.local du navigateur jusqu'à un Pod vote ?ingress: [{}] plutôt qu'un podSelector ?allow-dns, quels symptômes observeriez-vous ?behavior de l'HPA par rapport au TP 7 ?Q1: Pourquoi le Service de la base doit-il s'appeler exactement db, et pourquoi est-il headless ?
R: Le worker et result cherchent l'hôte db codé en dur : le nom du Service est le contrat d'interface (leçon du TP 4). Il est headless (clusterIP: None) parce que c'est le Service support du StatefulSet : le DNS résout directement vers le Pod db-0, avec une identité stable par réplique.
Q2: (Volet Gateway API) Par quel chemin passe une requête vote.local du navigateur jusqu'à un Pod vote ?
R: Navigateur → NodePort du Service Envoy (créé par Envoy Gateway pour notre Gateway) → Pod Envoy → correspondance du header Host: vote.local avec l'HTTPRoute vote-route → Service vote → un des Pods vote (readiness OK uniquement).
Q3: Pourquoi les Pods vote et result ont-ils une règle ingress: [{}] plutôt qu'un podSelector ?
R: Leur trafic entrant vient de l'extérieur du cluster (NodePort) ou, si vous avez déployé la Gateway, d'un autre namespace (les Pods Envoy dans envoy-gateway-system) : un podSelector du namespace ne peut pas le décrire. ingress: [{}] ouvre l'entrée de ces deux frontaux ; le reste du namespace reste verrouillé.
Q4: Sans la policy allow-dns, quels symptômes observeriez-vous ?
R: Avec le deny-all egress, plus aucun Pod ne pourrait résoudre redis ou db : vote renverrait des erreurs 500, le worker bouclerait en crash, result ne se connecterait plus. Des timeouts DNS partout : le grand classique du zero-trust mal appliqué.
Q5: Que gagne-t-on avec le bloc behavior de l'HPA par rapport au TP 7 ?
R: Le contrôle fin du rythme : scale-up immédiat et par paliers de 2 Pods (absorber les pics), scale-down limité à 1 Pod/minute après stabilisation (éviter les oscillations et les à-coups sur l'application). Le kubectl autoscale du TP 7 subissait les valeurs globales par défaut.
Q6: Pourquoi un ServiceAccount pour l'équipe ops ici, alors que le TP 7 utilisait un User ?
R: Un User (certificat) identifie une personne ; un ServiceAccount identifie un usage machine : scripts, CI/CD, outils de supervision. Son token est géré par Kubernetes et injectable dans des Pods, et il se prête au même RBAC (Role/RoleBinding).
kubectl apply -fTP1 pods → TP2 architecture → TP3 deployments/rollouts → TP4 services/DNS
→ TP5 stockage/StatefulSet → TP6 ConfigMaps/Secrets → TP7 probes/HPA/RBAC/NetPol
→ TP8 : tout, seul, en production. 🚀
Bravo, et bonne route sur Kubernetes !