Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English

Knative Serving : scale-to-zero mesuré sur kind

Par mis à jour le

Améliorer cette page

Le Deployment d'un Service Knative est ramené à zéro réplique environ 61 secondes après la dernière requête, et son Pod n'a complètement disparu qu'au bout de 91 secondes. Cette leçon installe Knative Serving et la passerelle Kourier sur un cluster kind, puis mesure ce que la leçon serverless ne fait qu'expliquer : le délai de passage à zéro, un démarrage à froid de 0,65 s contre 3 ms à chaud, l'effet de min-scale, la montée à 9 Pods sous charge et un découpage de trafic 70/30. Elle s'adresse à qui déploie déjà sur Kubernetes et veut des chiffres avant de décider.

  • Installer Knative Serving et Kourier avec des manifestes dont vous vérifiez la somme SHA-256.
  • Déployer un Service Knative épinglé par digest et lire ses ressources (ksvc, révision, route).
  • Chronométrer le passage à zéro Pod et relier le résultat aux réglages réels de config-autoscaler.
  • Comparer un démarrage à froid, un démarrage à froid avec image à télécharger et une requête à chaud.
  • Supprimer le froid avec min-scale, et mesurer son prix.
  • Régler la concurrence cible et lire la montée en Pods sous charge.
  • Découper le trafic entre deux révisions et diagnostiquer les erreurs de validation les plus courantes.
  • Les niveaux 1 « Fondamentaux » et 2 « Livrer une application » de la formation Kubernetes : Deployment, Service, sondes, HPA.
  • Docker et kind installés. Si ce n'est pas le cas, Créer votre premier cluster Kubernetes local avec kind l'explique pas à pas.
  • La leçon Serverless sur Kubernetes, qui définit le scale-to-zero, l'Activator et le queue-proxy.
  • Environ 1,5 Gio de mémoire libre, et curl. Ce chiffre vient des mesures de ce lab (nœud kind complet : 1,25 Gio au repos) ; la documentation d'installation de Knative demande davantage (6 CPU et 6 Go pour un nœud unique), ce lab n'ayant pas cherché à en vérifier le minimum réel.

Que montre cette leçon, et qu'est-ce qu'elle ne montre pas ?

Section intitulée « Que montre cette leçon, et qu'est-ce qu'elle ne montre pas ? »

Tout ce qui suit a été rejoué sur un seul nœud kind, avec les réglages par défaut de Knative : les chiffres décrivent ce poste, pas votre production. Les versions de référence sont Knative Serving 1.23.0 et net-kourier 1.23.0 sur un nœud kindest/node:v1.37.0. Aucune plage de versions Kubernetes n'est annoncée par Knative pour cette release : elle a été testée sur Kubernetes 1.37.0 sans anomalie, ce qui ne veut pas dire « supportée officiellement ».

Les accès passent par kubectl port-forward et l'en-tête HTTP Host:, sans DNS public. La leçon ne montre pas Knative Eventing, Knative Functions, le TLS, un cluster multi-nœuds ni une vraie charge de production. Les réglages de descente à zéro n'ont jamais été modifiés : seules leurs valeurs par défaut sont mesurées.

Comment installer Knative Serving et Kourier sur kind ?

Section intitulée « Comment installer Knative Serving et Kourier sur kind ? »

L'installation tient en quatre manifestes officiels et un correctif de configuration. Les CRD (définitions de ressources personnalisées) ajoutent les types Service, Configuration, Revision et Route de Knative ; le core déploie le plan de contrôle (activator, autoscaler, controller, webhook) ; Kourier est la couche réseau, une passerelle Envoy pilotée par Knative. Vérifier la somme SHA-256 de chaque fichier avant de l'appliquer est le geste de chaîne d'approvisionnement qui s'impose ici : ces manifestes créent des webhooks et des rôles à l'échelle du cluster.

  1. Créer le cluster. Le nœud est épinglé par digest.

    Fenêtre de terminal
    kind create cluster --name cn-knative \
    --image kindest/node:v1.37.0@sha256:a1ed56cfb0e7b93589bdf97c8cd566405a265939e3620fc4f5de89adff580ae5
    kubectl wait --for=condition=Ready node --all --timeout=90s

    Résultat attendu : node/cn-knative-control-plane condition met. Le nœud affiche NotReady pendant les premières secondes, d'où le wait.

  2. Télécharger les trois manifestes. Les URL sont celles des releases knative-v1.23.0.

    Fenêtre de terminal
    curl -fsSLO https://github.com/knative/serving/releases/download/knative-v1.23.0/serving-crds.yaml
    curl -fsSLO https://github.com/knative/serving/releases/download/knative-v1.23.0/serving-core.yaml
    curl -fsSLO https://github.com/knative-extensions/net-kourier/releases/download/knative-v1.23.0/kourier.yaml
  3. Vérifier les sommes. Ces valeurs sont celles calculées pendant le lab, identiques au fichier checksums.txt publié avec chaque release.

    Fenêtre de terminal
    cat > SHA256SUMS <<'EOF'
    b172ff4901ed50f8e4e09ff8616e54d22e264df7086ce8cb74f513a04812fe74 serving-crds.yaml
    be3f16c9c0ac9276cc173ef04871aaeac78537f9edb116310caa02f016e9cbc2 serving-core.yaml
    cded0c3c1d7669b1aa9f7484234b454ff3940a2b54a27d5ec4825c2d4003d01d kourier.yaml
    EOF
    sha256sum -c SHA256SUMS

    Résultat attendu : trois lignes OK. Une somme différente signifie un fichier modifié ou une autre release : n'appliquez rien.

  4. Appliquer Serving, puis attendre.

    Fenêtre de terminal
    kubectl apply -f serving-crds.yaml
    kubectl apply -f serving-core.yaml
    kubectl -n knative-serving rollout status deploy

    Résultat attendu : deployment "activator" successfully rolled out, puis la même ligne pour autoscaler, controller et webhook.

  5. Appliquer Kourier et le déclarer comme couche réseau. Sans le correctif de config-network, Knative garde sa classe d'ingress par défaut, Istio (istio.ingress.networking.knative.dev), et n'utilise pas Kourier. La clé s'écrit ingress-class, avec un tiret.

    Fenêtre de terminal
    kubectl apply -f kourier.yaml
    kubectl -n knative-serving patch configmap/config-network --type merge \
    -p '{"data":{"ingress-class":"kourier.ingress.networking.knative.dev"}}'
    kubectl -n kourier-system rollout status deploy
    kubectl -n kourier-system get pods,svc

    Résultat attendu : configmap/config-network patched, puis la passerelle 3scale-kourier-gateway en 1/1 Running. Le Service kourier reste en EXTERNAL-IP <pending> : kind n'a pas de load balancer, c'est normal, d'où les port-forward de l'étape suivante.

  6. Ouvrir deux accès locaux. Chaque commande occupe un terminal, ou se lance en arrière-plan.

    Fenêtre de terminal
    kubectl -n kourier-system port-forward svc/kourier 18100:80 # chemin externe
    kubectl -n kourier-system port-forward svc/kourier-internal 18101:80 # chemin cluster-local

L'installation compte 6 Pods : activator, autoscaler, controller, webhook et net-kourier-controller dans knative-serving, plus la passerelle dans kourier-system. Une limite de reproductibilité : le manifeste officiel kourier.yaml référence Envoy par le tag mouvant docker.io/envoyproxy/envoy:v1.37-latest. Le lab l'a résolu en envoyproxy/envoy@sha256:a384f50976a8ce7511316776383f907bb5d0edffabac393ada1fdd703a5c1318, mais un lecteur qui rejoue ce lab après coup obtiendra un autre Envoy. Pour éviter cela, remplacez la référence par ce digest dans une copie du manifeste et recalculez sa somme.

Un seul objet Service Knative crée tout le reste : une Configuration, une Révision, une Route et un Deployment. L'image est épinglée par digest, dans la forme canonique du site. La variable WHOAMI_NAME sert à reconnaître la révision qui répond, et traefik/whoami renvoie en corps de réponse les en-têtes qu'il a reçus, ce qui permet de lire par où est passée la requête.

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: hello
namespace: default
spec:
template:
spec:
containers:
- image: docker.io/traefik/whoami:v1.11.0@sha256:200689790a0a0ea48ca45992e0450bc26ccab5307375b41c84dfc4f2475937ab
ports:
- containerPort: 80
env:
- name: WHOAMI_NAME
value: v1

Enregistrez ce fichier sous hello-v1.yaml et appliquez-le. Knative affiche un avertissement securityContext à chaque création de révision : des valeurs par défaut de Kubernetes pourraient devenir plus strictes dans une version future de Knative. Il est inoffensif ici.

Fenêtre de terminal
kubectl apply -f hello-v1.yaml
kubectl wait --for=condition=Ready ksvc/hello --timeout=120s
kubectl get ksvc,configuration,revision,route
kubectl get pods

Le wait évite de lire l'état trop tôt : sans lui, READY affiche Unknown pendant la résolution du digest. ksvc est l'abréviation de services.serving.knative.dev, pour ne pas la confondre avec un Service Kubernetes. Résultat attendu, tronqué :

service.serving.knative.dev/hello created
NAME URL LATESTCREATED LATESTREADY READY
service.serving.knative.dev/hello http://hello.default.svc.cluster.local hello-00001 hello-00001 True
...
revision.serving.knative.dev/hello-00001 hello 1 True 1 1
...
hello-00001-deployment-6bdcfb866b-xnkcx 2/2 Running 0 25s

Trois points se lisent ici. L'URL est en svc.cluster.local : sans domaine configuré, le service reste cluster-local. Le Pod affiche 2/2 conteneurs, l'application (user-container) et le queue-proxy que Knative injecte. Et un Pod existe sans aucune requête : initial-scale vaut 1, puis le service redescend seul à zéro.

Testez maintenant l'accès. Un nom en svc.cluster.local est servi par la passerelle interne, donc par le port 18101 :

Fenêtre de terminal
curl -s -i -H 'Host: hello.default.svc.cluster.local' http://127.0.0.1:18101/

Résultat attendu : HTTP/1.1 200 OK, Name: v1, et l'en-tête K-Proxy-Request: activator, preuve du passage par l'Activator. La même requête sur le port 18100 (la passerelle externe) renvoie 404 Not Found avec server: envoy : ce n'est pas une panne, c'est le mauvais service. Un domaine configuré change la donne, voir le dépannage.

Après combien de temps un service passe-t-il à zéro ?

Section intitulée « Après combien de temps un service passe-t-il à zéro ? »

Avec les réglages par défaut, le Deployment passe à zéro réplique environ 61 secondes après la dernière requête, et le Pod disparaît environ 30 secondes après, vers 91 secondes. Ces délais ne sont pas affichés par Knative : il faut les chronométrer, en envoyant une requête puis en sondant chaque seconde le Deployment et les Pods.

Fenêtre de terminal
curl -s -o /dev/null -H 'Host: hello.default.svc.cluster.local' http://127.0.0.1:18101/
t0=$(date +%s%3N)
while true; do
el=$(( $(date +%s%3N) - t0 ))
rep=$(kubectl get deploy -l serving.knative.dev/service=hello \
-o jsonpath='{.items[0].spec.replicas}')
pods=$(kubectl get pods -l serving.knative.dev/service=hello --no-headers | awk '{print $2" "$3}')
echo "t+${el} ms replicas=${rep} pods=[${pods}]"
[ -z "$pods" ] && break
sleep 1
done

Trois essais ont été chronométrés (deux après une requête à froid, un après une requête à chaud) :

EssaiDeployment à 0 réplique, Pod TerminatingPod disparu
161,4 s91,6 s
260,4 s90,6 s
361,3 s91,5 s

Pour interpréter ces nombres, il faut lire les réglages en vigueur. Le ConfigMap config-autoscaler livré ne contient qu'une clé _example : aucune valeur n'est activée, et Knative applique les valeurs par défaut que ce bloc documente. Celles de la descente sont :

stable-window: "60s"
scale-to-zero-grace-period: "30s"
scale-to-zero-pod-retention-period: "0s"
enable-scale-to-zero: "true"

La fenêtre stable (stable-window, 60 s) est la période d'observation sur laquelle l'autoscaler moyenne le trafic : après 60 s sans requête, la concurrence moyenne tombe à zéro et il décide zéro. Cela correspond à la première mesure, 60 à 61 s. Le délai de grâce (scale-to-zero-grace-period, 30 s) est, selon le commentaire du ConfigMap, une limite haute laissée au réseau pour mettre l'Activator dans le chemin avant de supprimer le dernier Pod ; il peut valoir 0 si l'Activator y est déjà depuis assez longtemps, ce qui est le cas ici (voir la section sur l'Activator) et concorde avec l'absence de 30 s supplémentaires avant le passage à zéro du Deployment, sans que le réglage ait été modifié pour le prouver. La rétention (scale-to-zero-pod-retention-period, 0 s) fixe le temps minimal pendant lequel le dernier Pod reste après la décision de passer à zéro : à zéro seconde, elle n'a aucun effet.

Chronologie mesurée : à t égal 0 la dernière requête arrive, 60 secondes de silence passent, à environ 61 secondes le Deployment passe à zéro réplique et le Pod passe en Terminating, puis environ 30 secondes de plus, vers 91 secondes, plus aucun Pod n'existe.t = 0 s : dernière requête reçueSilence : fenêtre stable-window de 60 s(réglage par défaut)t ≈ 61 s : l'autoscaler décide 0Deployment à 0 réplique, Pod en TerminatingEnviron 30 s de Terminating(hypothèse : drain du queue-proxy)t ≈ 91 s : plus aucun Pod
Source · SVG
Échap pour fermer, + et − pour zoomer, défilement pour se déplacer. 100 %

Regardez la dernière flèche du schéma : le Deployment est déjà à zéro à 61 s, et pourtant le Pod reste visible, en Terminating, jusqu'à 91 s. Ces 30 s finales coïncident avec la valeur de scale-to-zero-grace-period, mais ce n'est pas ce réglage qui les fixe, puisque le Deployment est à zéro avant elles. Le Pod porte terminationGracePeriodSeconds: 300, recopié du timeoutSeconds de la révision (300 s par défaut), et le user-container un preStop qui interroge /wait-for-drain sur le port 8022 du queue-proxy. Dans le code de la release 1.23.0, ce point d'accès attend 30 s sans requête (constante drainSleepDuration, indépendante de config-autoscaler) : le drain du sidecar est donc l'explication la plus solide, établie par la lecture du code et par la concordance des durées, mais non vérifiée par une expérience. Faire varier stable-window ou scale-to-zero-grace-period pour voir bouger les mesures n'a pas non plus été fait.

Retenez la formule : une minute de silence pour décider, une trentaine de secondes de plus pour que le Pod parte vraiment. Sur un cluster auto-hébergé, seules les ressources du nœud se libèrent, pas la facture du cluster, comme le précise la leçon serverless.

Un démarrage à froid avec l'image déjà présente sur le nœud coûte environ 0,65 s, contre environ 3 ms pour une requête à chaud. La mesure a été répétée six fois à froid et dix fois à chaud : avant chaque requête à froid, un script attend que plus aucun Pod ne tourne, patiente 2 s, puis envoie la requête avec curl -w '%{time_total}'. Les dix requêtes à chaud suivent en rafale.

#Froid : time_total (s)Chaud : time_total (s)
10,6415760,002779
20,6473120,003333
30,6252120,002538
40,6586560,002756
50,6531220,002650
60,6425120,002419
7 à 100,002380 ; 0,017684 ; 0,002998 ; 0,002691
Moyenne0,645 s (min 0,625 ; max 0,659)0,004 s (médiane 0,0027)

La connexion TCP à froid ne dépasse pas 0,4 ms et le premier octet arrive presque en même temps que la fin de la réponse : tout le délai est une attente côté serveur, le temps que l'Activator retienne la requête pendant que le Pod démarre. Une dernière mesure ajoute le téléchargement de l'image : retirée du cache de containerd, elle a fait monter le premier appel à 3,07 s (2,455 s de pull pour 3 Mo). Mesure unique, donc indicative.

Le froid dépend donc de la présence de l'image sur le nœud et de la taille de l'application : whoami démarre en quelques millisecondes, une application lente à s'initialiser ajoutera son propre délai, que ces chiffres ne contiennent pas. Pendant ce démarrage, un événement Unhealthy ... connection refused apparaît : la sonde de disponibilité échoue avant que le queue-proxy n'écoute, c'est attendu.

À zéro Pod, la requête n'arrive pas à une application : elle est retenue par l'Activator. Quand une révision n'a aucun Pod, son ServerlessService passe en mode Proxy, son Service n'a plus de backend, et le trafic est dirigé vers l'Activator. Celui-ci met la requête en attente, prévient l'autoscaler, puis la transmet au Pod prêt. La commande kubectl get sks montre l'état de quatre révisions, dont trois à zéro Pod :

NAME MODE ACTIVATORS SERVICENAME PRIVATESERVICENAME READY REASON
hello-00001 Proxy 3 hello-00001 hello-00001-private Unknown NoHealthyBackends
hello-00004 Serve 2 hello-00004 hello-00004-private True

Le schéma suivant trace les chemins. Le trait épais est le chemin par défaut, qui passe par l'Activator ; les traits pointillés montrent le signal de charge envoyé à l'autoscaler et le raccourci direct vers le Pod.

Le client envoie sa requête à la passerelle Kourier, qui la transmet à l'Activator puis au queue-proxy du Pod applicatif. À zéro Pod, l'Activator signale la charge à l'autoscaler, qui demande un Pod. Quand un Pod existe et que target-burst-capacity vaut zéro, Kourier envoie la requête directement au queue-proxy.Pod applicatif (2/2)Pod présent ettarget-burst-capacity 0zéro Pod : signale la chargedemande un PodClient(curl avec en-tête Host)Kourier (Envoy)Activatorqueue-proxyAutoscaleruser-container
Source · SVG
Échap pour fermer, + et − pour zoomer, défilement pour se déplacer. 100 %

Un point surprend : avec les réglages par défaut, toutes les requêtes portent K-Proxy-Request: activator, froides ou chaudes. Le réglage en cause est target-burst-capacity, qui vaut 211 par défaut : l'Activator reste dans le chemin tant que la capacité libre du service est inférieure à ce nombre de requêtes simultanées, ce qui est le cas avec un seul Pod. La révision 4 (hello-v4-tbc0.yaml, min-scale: "1" plus target-burst-capacity: "0") sort l'Activator du chemin dès qu'un Pod existe : le sks passe en mode Serve et la réponse ne porte plus l'en-tête. Sans min-scale, avec target-burst-capacity: "0" seul, le Deployment est tombé à zéro 90,7 s après la dernière requête (une mesure) au lieu de 61 s : le délai de grâce de 30 s intervient alors avant la décision. Le gain de latence n'est pas mesurable ici, 3 à 4,4 ms dans les deux cas. La colonne ACTIVATORS varie de 2 à 4 alors qu'un seul Pod activator tourne : d'après le code de la release, c'est le nombre d'Activators placés dans le chemin, calculé comme le plus grand de 2 et de (capacité des Pods prêts + target-burst-capacity) / 100. Cela redonne les valeurs relevées (3 à zéro Pod, 4 avec un Pod, 2 avec target-burst-capacity à 0), mais ce n'est pas un nombre de Pods.

Comment supprimer le démarrage à froid avec min-scale ?

Section intitulée « Comment supprimer le démarrage à froid avec min-scale ? »

L'annotation autoscaling.knative.dev/min-scale: "1" garde un Pod en permanence : le froid disparaît, au prix d'un Pod toujours présent. Le fichier hello-v2-minscale.yaml reprend hello-v1.yaml en ajoutant, dans spec.template.metadata, un bloc annotations. Placée sur le template, l'annotation fait partie de la révision : la modifier crée une nouvelle révision, ici hello-00002.

spec:
template:
metadata:
annotations:
autoscaling.knative.dev/min-scale: "1"

Appliquez le manifeste complet (même image, même WHOAMI_NAME) puis lisez la route :

Fenêtre de terminal
kubectl apply -f hello-v2-minscale.yaml
kubectl wait --for=condition=Ready ksvc/hello --timeout=120s
kubectl get route hello -o jsonpath='{.status.traffic}'

Résultat attendu : [{"latestRevision":true,"percent":100,"revisionName":"hello-00002"}]. Environ 80 à 85 s après (deux mesures : 84 s et 81 s), hello-00001, qui ne reçoit plus de trafic, est redescendue à zéro Pod, tandis que hello-00002 garde le sien. Six requêtes envoyées après plus de deux minutes d'inactivité ont répondu entre 3 et 7 ms (3,3 à 4,3 ms lors de la première mesure, 5,3 à 6,5 ms au rejeu sur un poste plus chargé), contre 0,65 s sans min-scale : le froid a disparu.

Le prix est un Pod permanent : un user-container et un queue-proxy (25 m de CPU demandés) qui tournent sans servir personne. min-scale convient à un service sensible à la latence ; pour un service rare et tolérant, le scale-to-zero reste plus économe, au détriment du premier appelant.

Comment régler la concurrence et suivre la charge ?

Section intitulée « Comment régler la concurrence et suivre la charge ? »

La concurrence cible dit combien de requêtes simultanées un Pod doit absorber ; l'autoscaler ajoute des Pods pour la respecter. Par défaut, container-concurrency-target-default vaut 100 et container-concurrency-target-percentage 70 : l'autoscaler vise en moyenne 70 % de la cible de chaque Pod. Une annotation autoscaling.knative.dev/target la remplace pour une révision.

Le fichier hello-v3-target.yaml (révision 3) fixe min-scale: "1", max-scale: "10" et target: "5". Avec le pourcentage de 70, la cible effective est de 5 × 0,7 = 3,5 requêtes simultanées par Pod. Pour que chaque requête dure, whoami attend 1 s grâce au paramètre ?wait=1s, ce qui fabrique de la concurrence mesurable.

annotations:
autoscaling.knative.dev/min-scale: "1"
autoscaling.knative.dev/max-scale: "10"
autoscaling.knative.dev/target: "5"

La charge est une boucle curl parallèle, sans outil téléchargé : 30 clients, chacun enchaînant des requêtes pendant 60 s.

Fenêtre de terminal
worker() { while [ "$(date +%s)" -lt "$END" ]; do
curl -s -o /dev/null -H 'Host: hello.default.svc.cluster.local' \
-w '%{http_code} %{time_total}\n' 'http://127.0.0.1:18101/?wait=1s'; done; }
END=$(( $(date +%s) + 60 ))
for i in $(seq 1 30); do worker & done > load-results.txt; wait

Lancez ces lignes dans bash (la syntaxe des fonctions et des tâches de fond est celle de bash). Un second terminal (Ctrl-C pour l'arrêter) relève toutes les 5 s les Pods Running et les réplicas actuels et désirés de chaque révision :

Fenêtre de terminal
while true; do
n=$(kubectl get pods -l serving.knative.dev/service=hello --no-headers | grep -c Running)
d=$(kubectl get revision -l serving.knative.dev/service=hello \
-o jsonpath='{range .items[*]}{.metadata.name}={.status.actualReplicas}/{.status.desiredReplicas} {end}')
echo "pods_running=$n $d"; sleep 5
done

La montée observée (la révision hello-00001, à 0/0, est omise de l'extrait) :

t+0s pods_running=2 hello-00002=1/1 hello-00003=1/1
t+5s pods_running=6 hello-00003=5/9
t+10s pods_running=10 hello-00003=8/9
t+20s pods_running=10 hello-00003=9/9
t+31s pods_running=9 hello-00002=0/0 hello-00003=9/9
requetes: 1770 HTTP 200: 1770
temps moyen 1.008 s, max 1.048 s

Le calcul se vérifie : 30 requêtes simultanées divisées par 3,5 donnent 8,57, soit 9 Pods, chiffre observé. Le total de 10 à t+10 s compte la révision 2, non routée, redescendue à zéro ensuite. Les 1770 requêtes ont toutes répondu 200, en 1,008 s en moyenne pour 1 s de travail : aucune file d'attente visible, et les Pods supplémentaires étaient prêts en une dizaine de secondes. Le mode panique de l'autoscaler n'a pas été isolé.

Après l'arrêt de la charge, un relevé toutes les 10 s donne 8, 7, 5, 4, 2 puis 1 Pod en 50 s, et ce dernier Pod tient jusqu'à la fin du relevé (131 s) : c'est le plancher de min-scale. Cette descente est plus rapide que la minute de la fenêtre stable ; le lab ne l'a pas décortiquée.

Comment découper le trafic entre deux révisions ?

Section intitulée « Comment découper le trafic entre deux révisions ? »

Une Route Knative peut répartir le trafic entre plusieurs révisions et donner une URL à la révision candidate. Le fichier hello-v5-split.yaml crée une cinquième révision (WHOAMI_NAME=v2) et ajoute une section traffic : 70 % vers la révision 4, nommée explicitement, et 30 % vers la dernière révision avec le tag canary.

spec:
template:
spec:
containers:
- image: docker.io/traefik/whoami:v1.11.0@sha256:200689790a0a0ea48ca45992e0450bc26ccab5307375b41c84dfc4f2475937ab
ports:
- containerPort: 80
env:
- name: WHOAMI_NAME
value: v2
traffic:
- revisionName: hello-00004
percent: 70
- latestRevision: true
percent: 30
tag: canary

Après kubectl apply suivi d'un kubectl wait --for=condition=Ready ksvc/hello, la commande kubectl get route hello -o jsonpath='{range .status.traffic[*]}{.revisionName} {.percent}% tag={.tag} url={.url}{"\n"}{end}' confirme la répartition du trafic et publie l'URL du tag de la révision candidate :

hello-00004 70% tag= url=
hello-00005 30% tag=canary url=http://canary-hello.default.svc.cluster.local

Cent requêtes comptées avec sort | uniq -c ont donné 77 réponses Name: v1 et 23 Name: v2, un écart d'échantillonnage normal autour de 70/30. L'URL du tag, appelée avec Host: canary-hello.default.svc.cluster.local, a répondu v2 trois fois sur trois. C'est la base d'un déploiement canari : envoyer 30 % du trafic, ou ne donner que l'URL du tag à une équipe de test.

Un piège : réappliquer un manifeste sans section traffic (par exemple hello-v4-tbc0.yaml) fait disparaître le découpage, car le patch affiché par kubectl contient "traffic":null. Ce comportement est lu dans le patch ("traffic":null) et son effet a été rejoué : la route repasse à 100 % sur la dernière révision, sans tag.

Knative Serving et Kourier occupent environ 121 Mio de mémoire à vide sur ce nœud. La mesure, faite avec crictl stats (working set) faute de metrics-server, Service hello supprimé, donne 94,2 Mio pour knative-serving (cinq Pods) et 26,7 Mio pour la passerelle Envoy. Seul le total est fiable (deux conteneurs portent le même nom controller), et l'écart avec un nœud sans Knative n'a pas été mesuré.

Chaque Pod applicatif ajoute son queue-proxy, avec une requête de 25 m de CPU. Ce coût fixe suggère que Knative se justifie pour un parc de services intermittents plutôt que pour un seul service constamment sollicité.

Supprimez le cluster et vérifiez qu'il n'en reste aucun : les accès port-forward disparaissent avec lui, mais les manifestes téléchargés restent dans votre répertoire de travail.

Fenêtre de terminal
kind delete cluster --name cn-knative
kind get clusters

Résultat attendu : Deleted nodes: ["cn-knative-control-plane"], et cn-knative absent de la liste.

Les erreurs de ce tableau ont toutes été rencontrées pendant le lab, avec les messages exacts. Les trois erreurs de validation viennent du webhook de Knative, qui refuse un manifeste invalide avant qu'il ne crée quoi que ce soit : lisez le message jusqu'au bout, il nomme le champ fautif.

SymptômeCauseSolution
curl renvoie 404 Not Found (server: envoy) sur le port 18100Le domaine svc.cluster.local est servi par la passerelle interne, pas par kourierUtiliser svc/kourier-internal (18101), ou configurer un domaine (voir ci-dessous)
Le nœud affiche NotReady juste après kind create clusterLe CNI démarre encorekubectl wait --for=condition=Ready node --all --timeout=90s
Le Pod reste visible 90 s après la dernière requête, pas 60 s60 s de stable-window, puis environ 30 s de TerminatingNormal avec les valeurs par défaut ; mesurer avant de régler
Avertissement securityContext à chaque révisionValeurs par défaut de Kubernetes que Knative pourrait durcirInoffensif ; sécurisez le securityContext du conteneur si besoin
Traffic targets sum to 90, want 100: spec.trafficLes pourcentages de traffic ne totalisent pas 100Corriger les valeurs
max-scale=2 is less than min-scale=5min-scale supérieur à max-scaleAligner les deux annotations
target 0 should be at least 0.01target: "0" refusé par le webhookUtiliser une valeur d'au moins 0.01
READY False, raison RevisionMissing, message Unable to fetch image ... v9.9.9 ... 404 Not FoundUn tag d'image inexistant : Knative résout les tags en digests auprès du registreCorriger le tag, ou épingler un digest valide
zsh: command not found: kubectl --context kind-cn-knativeUne variable contenant la commande n'est pas découpée en mots par zshUtiliser un script d'enrobage ou kubectl config use-context
EXTERNAL-IP <pending> sur svc/kourierkind n'a pas de load balancerPasser par kubectl port-forward

Pour exposer le service par la passerelle externe, ajoutez un domaine dans config-domain : kubectl -n knative-serving patch cm config-domain --type merge -p '{"data":{"kind.local":""}}'. Le Service recréé reçoit l'URL http://hello.default.kind.local, joignable sur le port 18100 avec ce Host:. Aucun DNS public n'est nécessaire, et svc.cluster.local reste un 404 sur cette passerelle.

  • Knative Serving ramène un service à zéro Pod : avec les valeurs par défaut, le Deployment passe à zéro vers 61 s et le Pod disparaît vers 91 s.
  • Les 30 s finales de Terminating viennent, d'après le code de la release, du drain de 30 s du queue-proxy (une constante), pas de scale-to-zero-grace-period ; aucune expérience ne l'a démontré.
  • Un démarrage à froid coûte environ 0,65 s avec l'image en cache et 3,07 s avec téléchargement (une mesure) ; une requête à chaud, environ 3 ms.
  • À zéro Pod, c'est l'Activator qui retient la requête. Avec target-burst-capacity à 211 et un seul Pod, il reste dans le chemin même à chaud.
  • min-scale: "1" supprime le froid (3 à 7 ms après deux minutes d'inactivité) mais garde un Pod en permanence.
  • La cible de concurrence, corrigée par le pourcentage de 70, fixe le nombre de Pods : 30 requêtes simultanées pour une cible effective de 3,5 donnent 9 Pods.
  • Chaque modification du template crée une révision ; le champ traffic répartit entre elles, et un manifeste réappliqué sans traffic supprime le découpage du patch ("traffic":null), effet rejoué : la route repasse à 100 % sur la dernière révision.
  • Cette leçon n'a testé ni Eventing, ni Functions, ni le TLS, ni un cluster multi-nœuds, ni les réglages stable-window modifiés.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

Ce projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn