# 1 - Installer le serveur de metrics (metrics-server) # Voir: https://ve2cuy.github.io/4204d4/Documentation/Kubernetes/Kubernetes-autoscalling.html # kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml # 2 - Patcher le déploiement pour ajouter --kubelet-insecure-tls (si pas de TLS sur les kubelets) # kubectl patch deployment metrics-server -n kube-system --type='json' -p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]' # 3 - Appliquer ce fichier : kubectl apply -f auto-scaling.yaml --- # ============================================================ # Namespace # ============================================================ apiVersion: v1 kind: Namespace metadata: name: hpa-demo --- # ============================================================ # ServiceAccount + RBAC — permet au pod UI de lire/écrire # les Deployments et de lire le HPA dans hpa-demo # ============================================================ apiVersion: v1 kind: ServiceAccount metadata: name: hpa-ui-sa namespace: hpa-demo --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: hpa-ui-role namespace: hpa-demo rules: - apiGroups: ["apps"] resources: ["deployments", "deployments/scale"] verbs: ["get", "patch"] - apiGroups: ["autoscaling"] resources: ["horizontalpodautoscalers"] verbs: ["get"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: hpa-ui-rb namespace: hpa-demo subjects: - kind: ServiceAccount name: hpa-ui-sa namespace: hpa-demo roleRef: kind: Role name: hpa-ui-role apiGroup: rbac.authorization.k8s.io --- # ============================================================ # ConfigMap — script Python (API proxy) # Écoute :5000, parle à l'API K8s via le token du ServiceAccount # ============================================================ apiVersion: v1 kind: ConfigMap metadata: name: hpa-api-proxy namespace: hpa-demo data: server.py: | #!/usr/bin/env python3 """ Proxy léger entre la page web et l'API Kubernetes. Endpoints : GET /api/status -> état HPA + load-generator replicas POST /api/load/start -> scale load-generator à 1 POST /api/load/stop -> scale load-generator à 0 """ import json, ssl, urllib.request, urllib.error from http.server import BaseHTTPRequestHandler, HTTPServer SA_DIR = "/var/run/secrets/kubernetes.io/serviceaccount" K8S_HOST = "https://kubernetes.default.svc" NS = "hpa-demo" def k8s_token(): with open(f"{SA_DIR}/token") as f: return f.read().strip() def k8s_ca(): return f"{SA_DIR}/ca.crt" def k8s_request(method, path, body=None): url = f"{K8S_HOST}{path}" data = json.dumps(body).encode() if body else None ct = "application/strategic-merge-patch+json" if method == "PATCH" else "application/json" headers = { "Authorization": f"Bearer {k8s_token()}", "Content-Type": ct, "Accept": "application/json", } ctx = ssl.create_default_context(cafile=k8s_ca()) req = urllib.request.Request(url, data=data, headers=headers, method=method) try: with urllib.request.urlopen(req, context=ctx, timeout=5) as resp: return json.loads(resp.read()) except urllib.error.HTTPError as e: return {"error": e.reason, "code": e.code} except Exception as e: return {"error": str(e)} def get_hpa(): return k8s_request("GET", f"/apis/autoscaling/v2/namespaces/{NS}/horizontalpodautoscalers/php-apache") def get_deploy(name): return k8s_request("GET", f"/apis/apps/v1/namespaces/{NS}/deployments/{name}") def scale_deploy(name, replicas): return k8s_request("PATCH", f"/apis/apps/v1/namespaces/{NS}/deployments/{name}", {"spec": {"replicas": replicas}}) class Handler(BaseHTTPRequestHandler): def log_message(self, fmt, *args): pass # silence access log def send_json(self, code, obj): body = json.dumps(obj).encode() self.send_response(code) self.send_header("Content-Type", "application/json") self.send_header("Content-Length", len(body)) self.send_header("Access-Control-Allow-Origin", "*") self.end_headers() self.wfile.write(body) def do_OPTIONS(self): self.send_response(204) self.send_header("Access-Control-Allow-Origin", "*") self.send_header("Access-Control-Allow-Methods", "GET,POST,OPTIONS") self.send_header("Access-Control-Allow-Headers", "Content-Type") self.end_headers() def do_GET(self): if self.path != "/api/status": self.send_json(404, {"error": "not found"}); return hpa = get_hpa() load = get_deploy("load-generator") cpu_cur = "" cpu_tgt = "50" hpa_rep = 1 if "status" in hpa: st = hpa["status"] hpa_rep = st.get("currentReplicas", 1) for m in st.get("currentMetrics", []): if m.get("type") == "Resource" and m["resource"]["name"] == "cpu": cpu_cur = str(m["resource"]["current"].get("averageUtilization", 0)) for m in hpa.get("spec", {}).get("metrics", []): if m.get("type") == "Resource" and m["resource"]["name"] == "cpu": cpu_tgt = str(m["resource"]["target"].get("averageUtilization", 50)) lg_rep = 0 if "spec" in load: lg_rep = load["spec"].get("replicas", 0) self.send_json(200, { "hpa": { "currentReplicas": hpa_rep, "cpuCurrent": cpu_cur, "cpuTarget": cpu_tgt, "minReplicas": hpa.get("spec", {}).get("minReplicas", 1), "maxReplicas": hpa.get("spec", {}).get("maxReplicas", 10), }, "loadGenerator": {"replicas": lg_rep}, }) def do_POST(self): if self.path == "/api/load/start": result = scale_deploy("load-generator", 1) elif self.path == "/api/load/stop": result = scale_deploy("load-generator", 0) else: self.send_json(404, {"error": "not found"}); return if "error" in result: self.send_json(500, result) else: self.send_json(200, {"ok": True}) if __name__ == "__main__": server = HTTPServer(("0.0.0.0", 5000), Handler) print("API proxy listening on :5000") server.serve_forever() --- # ============================================================ # ConfigMap — nginx.conf avec proxy_pass vers Flask sidecar # ============================================================ apiVersion: v1 kind: ConfigMap metadata: name: hpa-nginx-conf namespace: hpa-demo data: default.conf: | server { listen 80; root /usr/share/nginx/html; index index.html; location /api/ { proxy_pass http://127.0.0.1:5000; proxy_http_version 1.1; proxy_set_header Host $host; } location / { try_files $uri $uri/ /index.html; } } --- # ============================================================ # ConfigMap — page web (index.html) # Cette page Web est disponible via un ConfigMap # Version: 1.0 - (c) 2026 Alain Boudreault # ============================================================ apiVersion: v1 kind: ConfigMap metadata: name: hpa-ui-config namespace: hpa-demo data: index.html: | HPA Demo — Kubernetes Auto Scaling

⎈ Kubernetes - Démonstration HPA


420-4D4-ESH26 - Exemple d'un AutoScaler sur GCloud — Cible CPU  : 50 %



Générateur de charge

Vérification…

Métriques HPA — temps réel

CPU actuel
Cible HPA
50 %
Replicas actifs
Phase HPA
En attente…

kubectl get hpa php-apache --watch

$ kubectl get hpa php-apache --watch
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
--- # ============================================================ # Deployment — php-apache # ============================================================ apiVersion: apps/v1 kind: Deployment metadata: name: php-apache namespace: hpa-demo labels: app: php-apache spec: replicas: 1 selector: matchLabels: app: php-apache template: metadata: labels: app: php-apache spec: containers: - name: php-apache image: registry.k8s.io/hpa-example ports: - containerPort: 80 resources: requests: cpu: 200m memory: 64Mi limits: cpu: 500m memory: 128Mi --- # ============================================================ # Service — php-apache # ============================================================ apiVersion: v1 kind: Service metadata: name: php-apache namespace: hpa-demo spec: selector: app: php-apache ports: - port: 80 targetPort: 80 --- # ============================================================ # HorizontalPodAutoscaler # ============================================================ apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: php-apache namespace: hpa-demo spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: php-apache minReplicas: 1 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 50 behavior: scaleUp: stabilizationWindowSeconds: 30 policies: - type: Pods value: 4 periodSeconds: 60 scaleDown: stabilizationWindowSeconds: 120 policies: - type: Pods value: 2 periodSeconds: 60 --- # ============================================================ # Deployment — hpa-ui # 2 containers dans le même pod : # nginx -> sert le HTML + proxifie /api/* vers le sidecar # api-proxy -> Python, parle à l'API K8s via le ServiceAccount # ============================================================ apiVersion: apps/v1 kind: Deployment metadata: name: hpa-ui namespace: hpa-demo labels: app: hpa-ui spec: replicas: 1 selector: matchLabels: app: hpa-ui template: metadata: labels: app: hpa-ui spec: serviceAccountName: hpa-ui-sa containers: - name: nginx image: nginx:alpine ports: - containerPort: 80 volumeMounts: - name: html mountPath: /usr/share/nginx/html - name: nginx-conf mountPath: /etc/nginx/conf.d resources: requests: cpu: 50m memory: 32Mi limits: cpu: 100m memory: 64Mi - name: api-proxy image: python:3.12-alpine command: ["python", "/app/server.py"] ports: - containerPort: 5000 volumeMounts: - name: api-proxy-script mountPath: /app resources: requests: cpu: 50m memory: 32Mi limits: cpu: 100m memory: 64Mi volumes: - name: html configMap: name: hpa-ui-config - name: nginx-conf configMap: name: hpa-nginx-conf - name: api-proxy-script configMap: name: hpa-api-proxy --- # ============================================================ # Service — hpa-ui (NodePort 30080) # ============================================================ apiVersion: v1 kind: Service metadata: name: hpa-ui namespace: hpa-demo spec: selector: app: hpa-ui type: LoadBalancer ports: - port: 80 targetPort: 80 --- # ============================================================ # Deployment — Load Generator (0 replicas par défaut) # Contrôlé via les boutons de la page web # ============================================================ apiVersion: apps/v1 kind: Deployment metadata: name: load-generator namespace: hpa-demo labels: app: load-generator spec: replicas: 0 selector: matchLabels: app: load-generator template: metadata: labels: app: load-generator spec: containers: - name: load-generator image: busybox:latest command: - /bin/sh - -c - | echo "Démarrage de la génération de charge vers http://php-apache/" while true; do wget -q -O- http://php-apache/ > /dev/null 2>&1 done resources: requests: cpu: 50m memory: 16Mi limits: cpu: 200m memory: 32Mi restartPolicy: Always