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

Imposer délais, reprises et autorisations entre services

Par mis à jour le

Améliorer cette page

Un maillage de services (service mesh) permet d'imposer un délai maximal, des reprises et une autorisation par identité entre deux services, sans modifier leur code. Cette leçon le montre avec Istio 1.31.1 en mode sidecar, sur un cluster kind : un VirtualService coupe un appel lent en 2 secondes, un second règle les reprises (avec le piège des valeurs par défaut), et une AuthorizationPolicy ne laisse passer qu'un appelant, reconnu à son identité. Chaque règle est prouvée par une mesure : code HTTP, durée, compteurs du proxy. Elle s'adresse à qui a déjà déployé des Services sur Kubernetes.

  • Installer Istio en profil minimal et injecter des sidecars dans un namespace.
  • Vérifier l'identité SPIFFE d'un appelant dans l'en-tête X-Forwarded-Client-Cert.
  • Imposer un délai avec timeout et reconnaître le 504 produit par le proxy.
  • Configurer des reprises avec retries, et prouver leur nombre.
  • Autoriser un seul appelant avec une AuthorizationPolicy, restreindre méthode et chemin.
  • Diagnostiquer les pièges : compteurs filtrés, politique non propagée, Pod hors maillage.

Cette leçon prolonge Le service mesh, qui explique ce qu'est un maillage, ses deux plans et le proxy sidecar : elle n'en redit pas les concepts. Elle suppose les niveaux 1 et 2 de la formation Kubernetes, décrits dans le parcours (Deployment, Service, ServiceAccount, kubectl exec).

Côté poste, il faut Docker, kind (voir Créer votre premier cluster local), kubectl, curl et sha256sum. Prévoyez de la mémoire : le profil minimal réserve 2 Gio pour istiod, et chaque sidecar occupe environ 106 Mo (mesures en fin de leçon). Versions de référence : kind v0.33.0, kindest/node:v1.37.0 et Istio 1.31.1.

Où chaque règle s'applique-t-elle dans le maillage ?

Section intitulée « Où chaque règle s'applique-t-elle dans le maillage ? »

Le délai et les reprises s'appliquent dans le sidecar de l'appelant, et l'autorisation dans le sidecar de l'appelé. C'est le point à comprendre avant de toucher un manifeste : une règle posée du mauvais côté n'a aucun effet. Le sidecar du client décide combien de temps attendre et s'il rejoue la requête ; le sidecar du serveur reçoit la requête en mTLS (TLS mutuel, où chaque côté présente un certificat) et connaît donc l'identité du client. Chaque sidecar est un conteneur istio-proxy qui fait tourner Envoy, le proxy qui exécute ces règles ; istiod, le plan de contrôle, lui pousse la configuration mais ne voit jamais le trafic.

Regardez sur le schéma où se placent les deux notes : la règle de délai et de reprises au niveau du premier proxy, l'autorisation au niveau du second. Un refus est renvoyé par le sidecar du serveur : l'application ne voit jamais la requête.

Séquence entre application cliente, sidecar du client, sidecar du serveur et application httpbin : le VirtualService s'applique dans le sidecar du client, l'AuthorizationPolicy dans celui du serveur, qui renvoie un 403 si l'appelant est refusé.ApplicationhttpbinSidecar duserveurSidecar duclientApplicationclienteVirtualService: délai etreprisesAuthorizationPolicy: identité,méthode, cheminalt[requêteautorisée][requêterefusée]requête en clairrequête en mTLSavec l'identité duclientrequête en clairréponseréponse403 RBAC: accessdeniedréponse, ou 504 si ledélai est dépassé
Source · SVG
Échap pour fermer, + et − pour zoomer, défilement pour se déplacer. 100 %

Comment installer Istio et injecter les sidecars ?

Section intitulée « Comment installer Istio et injecter les sidecars ? »

Trois gestes suffisent : un cluster, istioctl install avec le profil minimal, puis un namespace étiqueté istio-injection=enabled. Le profil minimal installe seulement istiod, le plan de contrôle : le trafic est-ouest (de service à service, à l'intérieur du cluster) n'a pas besoin de passerelle d'entrée, que le profil default ajouterait pour rien.

Le binaire istioctl se télécharge depuis la release GitHub, avec son fichier de somme sha256, sans curl | sh. Cette somme est publiée au même endroit que l'archive : elle protège d'une corruption, pas d'un dépôt compromis.

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

    Fenêtre de terminal
    kind create cluster --name cn-mesh \
    --image kindest/node:v1.37.0@sha256:a1ed56cfb0e7b93589bdf97c8cd566405a265939e3620fc4f5de89adff580ae5

    La sortie annonce Set kubectl context to "kind-cn-mesh", suivie de l'invite kubectl cluster-info.

  2. Télécharger et vérifier istioctl.

    Fenêtre de terminal
    curl -fsSLO https://github.com/istio/istio/releases/download/1.31.1/istioctl-1.31.1-linux-amd64.tar.gz
    curl -fsSLO https://github.com/istio/istio/releases/download/1.31.1/istioctl-1.31.1-linux-amd64.tar.gz.sha256
    sha256sum -c istioctl-1.31.1-linux-amd64.tar.gz.sha256
    mkdir istioctl-bin && tar -xzf istioctl-1.31.1-linux-amd64.tar.gz -C istioctl-bin
    export PATH="$PWD/istioctl-bin:$PATH"
    istioctl version --remote=false

    La vérification doit afficher istioctl-1.31.1-linux-amd64.tar.gz: OK, et la dernière commande client version: 1.31.1.

  3. Installer le plan de contrôle.

    Fenêtre de terminal
    istioctl install --set profile=minimal -y

    La sortie affiche ✔ Istiod installed, puis se termine par ✔ Installation complete.

  4. Déployer les applications dans un namespace injecté.

    Fenêtre de terminal
    kubectl apply -f 00-namespaces.yaml -f 10-apps.yaml
    kubectl -n mesh-lab rollout status deploy --timeout=300s
    kubectl -n mesh-lab get pods

    Les trois Pods doivent afficher 2/2 : l'application et le sidecar.

Enregistrez chaque manifeste de cette leçon sous le nom donné en titre de son bloc. Le namespace mesh-lab porte l'étiquette d'injection ; legacy n'en a pas. Il héberge un Pod outsider, hors maillage, qui servira à tester le refus.

00-namespaces.yaml
apiVersion: v1
kind: Namespace
metadata:
name: mesh-lab
labels:
istio-injection: enabled
---
apiVersion: v1
kind: Namespace
metadata:
name: legacy

Le backend est go-httpbin, un serveur de test qui sait répondre lentement (/delay/5), échouer (/status/503, /unstable) et afficher les en-têtes reçus. Deux clients curl (frontend et batch) tournent sous deux ServiceAccounts distincts : c'est ce qui leur donnera deux identités.

10-apps.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: httpbin
namespace: mesh-lab
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: frontend
namespace: mesh-lab
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: batch
namespace: mesh-lab
---
apiVersion: v1
kind: Service
metadata:
name: httpbin
namespace: mesh-lab
labels:
app: httpbin
spec:
selector:
app: httpbin
ports:
- name: http
port: 8080
targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin
namespace: mesh-lab
spec:
replicas: 1
selector:
matchLabels:
app: httpbin
template:
metadata:
labels:
app: httpbin
spec:
serviceAccountName: httpbin
containers:
- name: httpbin
image: mccutchen/go-httpbin:v2.15.0@sha256:24528cf5229d0b70065ac27e6c9e4d96f5452a84a3ce4433e56573c18d96827a
ports:
- containerPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
namespace: mesh-lab
spec:
replicas: 1
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
serviceAccountName: frontend
containers:
- name: curl
image: curlimages/curl:8.21.0@sha256:7c12af72ceb38b7432ab85e1a265cff6ae58e06f95539d539b654f2cfa64bb13
command: ["sleep", "infinity"]
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: batch
namespace: mesh-lab
spec:
replicas: 1
selector:
matchLabels:
app: batch
template:
metadata:
labels:
app: batch
spec:
serviceAccountName: batch
containers:
- name: curl
image: curlimages/curl:8.21.0@sha256:7c12af72ceb38b7432ab85e1a265cff6ae58e06f95539d539b654f2cfa64bb13
command: ["sleep", "infinity"]
---
apiVersion: v1
kind: Pod
metadata:
name: outsider
namespace: legacy
spec:
containers:
- name: curl
image: curlimages/curl:8.21.0@sha256:7c12af72ceb38b7432ab85e1a265cff6ae58e06f95539d539b654f2cfa64bb13
command: ["sleep", "infinity"]

Avec Istio 1.31.1, le sidecar istio-proxy est un conteneur d'initialisation natif (init: istio-init istio-proxy) : d'où les 2/2. Trois fonctions shell raccourcissent les commandes des sections qui suivent : elles lancent curl depuis l'un des trois Pods clients.

Fenêtre de terminal
fe() { kubectl -n mesh-lab exec deploy/frontend -c curl -- "$@"; }
ba() { kubectl -n mesh-lab exec deploy/batch -c curl -- "$@"; }
out() { kubectl -n legacy exec outsider -- "$@"; }

L'en-tête X-Forwarded-Client-Cert que reçoit httpbin porte l'identité SPIFFE de l'appelant : c'est la preuve que l'appel est authentifié en mTLS. Une identité SPIFFE est une URI de la forme spiffe://cluster.local/ns/<namespace>/sa/<serviceaccount>, tirée du ServiceAccount du Pod ; la leçon mTLS pod-to-pod détaille cette identité et le handshake. Ici, on se contente de la lire.

Fenêtre de terminal
fe curl -s http://httpbin.mesh-lab:8080/headers
"X-Forwarded-Client-Cert": [
"By=spiffe://cluster.local/ns/mesh-lab/sa/httpbin;Hash=fee84672444f1a304e0f1236ff9e60b040bd53c43bda44e27d695bf752456cc8;Subject=\"\";URI=spiffe://cluster.local/ns/mesh-lab/sa/frontend"
]

Le champ URI= nomme l'appelant (sa/frontend), le champ By= le destinataire. Ce résultat arrive alors qu'aucune PeerAuthentication n'existe (kubectl get peerauthentication -A répond No resources found) : le mode par défaut est PERMISSIVE, qui établit le mTLS automatiquement entre sidecars et tolère aussi le clair. istioctl x describe affiche ce mode (Workload mTLS mode: PERMISSIVE), mais il décrit la configuration, pas ce trafic précis : la preuve, c'est l'en-tête.

Un VirtualService avec timeout: 2s fait renvoyer un 504 par le sidecar du client dès que la réponse dépasse 2 secondes. Un VirtualService est une ressource Istio qui décrit comment router les requêtes adressées à un hôte, ici le Service httpbin. Sans lui, un appel lent bloque l'appelant aussi longtemps que le backend le veut.

20-timeout.yaml
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: httpbin
namespace: mesh-lab
spec:
hosts:
- httpbin
http:
- timeout: 2s
route:
- destination:
host: httpbin
port:
number: 8080

Mesurez d'abord l'appel sans règle, puis avec. L'option -w de curl affiche le code HTTP et la durée totale. Attendez trois secondes après kubectl apply : la configuration doit atteindre le sidecar.

Fenêtre de terminal
W='code=%{http_code} total=%{time_total}s\n'
fe curl -s -o /dev/null -w "$W" http://httpbin.mesh-lab:8080/delay/5
kubectl apply -f 20-timeout.yaml
sleep 3
fe curl -s -w "\n$W" http://httpbin.mesh-lab:8080/delay/5
fe curl -s -o /dev/null -w "$W" http://httpbin.mesh-lab:8080/delay/1
code=200 total=5.005470s
virtualservice.networking.istio.io/httpbin created
upstream request timeout
code=504 total=2.002792s
code=200 total=1.005369s

Sans règle, /delay/5 répond 200 en 5,005 s. Avec timeout: 2s, il répond 504 en 2,003 s, avec le corps upstream request timeout : ce texte vient d'Envoy, pas de l'application. /delay/1, sous le seuil, reste à 200. La règle vaut pour tous les clients du maillage : batch obtient aussi un 504.

Comment imposer des reprises entre deux services ?

Section intitulée « Comment imposer des reprises entre deux services ? »

Les reprises (retries) rejouent une requête échouée avant de renoncer, mais Istio en applique déjà par défaut, et elles ne couvrent pas le cas qui vous intéresse. Cette section prouve le comportement par défaut, puis celui d'une règle explicite. Rejouer une requête n'est sûr que si elle est idempotente, c'est-à-dire si la répéter donne le même résultat que la jouer une fois.

Par défaut, le sidecar du client rejoue 2 fois, mais seulement sur des échecs de connexion ou de transport, pas sur une réponse 5xx de l'application. La configuration de la route le montre, lue dans le sidecar de frontend :

Fenêtre de terminal
istioctl proxy-config route deploy/frontend.mesh-lab --name 8080 -o json | grep -A12 retryPolicy
"retryPolicy": {
"retryOn": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
"numRetries": 2,
...
}

numRetries: 2 est donc actif partout, mais retryOn ne cite pas le 5xx applicatif : retriable-status-codes, sans liste de codes associée, ne rejoue rien (la référence du VirtualService ne cite que les quatre premières valeurs de retryOn ; retriable-status-codes apparaît dans la configuration lue). La mesure le confirme plus bas : un appel à /status/503 n'atteint le serveur qu'une seule fois. Les reprises sur connect-failure n'ont pas été reproduites : seule leur configuration a été lue.

Deux réglages sont nécessaires pour compter les tentatives : les journaux d'accès, et les compteurs filtrés du proxy. Aucun n'est actif par défaut, et sans eux vous ne pouvez que deviner le nombre de reprises à partir de la durée.

Les journaux d'accès d'Envoy s'activent avec une ressource Telemetry. Les compteurs upstream_rq_retry, eux, sont filtrés par la configuration statistique par défaut du sidecar : il faut les inclure avec l'annotation de Pod proxy.istio.io/config, ce qui redémarre les Pods.

15-telemetry-accesslog.yaml
apiVersion: telemetry.istio.io/v1
kind: Telemetry
metadata:
name: access-logs
namespace: mesh-lab
spec:
accessLogging:
- providers:
- name: envoy
12-stats-patch.yaml
spec:
template:
metadata:
annotations:
proxy.istio.io/config: |
proxyStatsMatcher:
inclusionRegexps:
- ".*upstream_rq_retry.*"
- ".*upstream_rq_total.*"
- ".*upstream_rq_[0-9]xx.*"
- ".*upstream_rq_[0-9][0-9][0-9].*"
Fenêtre de terminal
kubectl apply -f 15-telemetry-accesslog.yaml
for d in httpbin frontend batch; do
kubectl -n mesh-lab patch deploy $d --patch-file 12-stats-patch.yaml
done
kubectl -n mesh-lab rollout status deploy --timeout=300s

Attendez la fin de rollout status avant de mesurer : juste après le patch, d'anciens Pods peuvent rester un moment en 0/2 Error ou Terminating, et kubectl logs deploy/... peut tomber sur l'un d'eux (message Found 2 pods, using pod/…). Les journaux d'accès ont aussi un léger retard : une ligne n'apparaît qu'1 à 2 secondes après la requête.

Une fonction lit les compteurs d'un sidecar, une autre les remet à zéro avant chaque mesure (reset_counters écrit un message d'information sur la sortie d'erreur, d'où la redirection). Les noms côté client portent un point-virgule avant le point final, produit par l'extraction des tags d'Envoy : un grep qui l'oublie ne trouve rien.

Fenêtre de terminal
stats() { kubectl -n mesh-lab exec deploy/$1 -c istio-proxy -- pilot-agent request GET stats; }
reset() { kubectl -n mesh-lab exec deploy/$1 -c istio-proxy -- pilot-agent request POST reset_counters >/dev/null 2>&1; }

Avec attempts: 3, le serveur reçoit 4 requêtes : attempts compte les reprises, pas les tentatives. Ce VirtualService active retryOn: 5xx, borne chaque tentative à 2 s avec perTryTimeout et le total à 10 s avec timeout.

30-retries.yaml
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: httpbin
namespace: mesh-lab
spec:
hosts:
- httpbin
http:
- timeout: 10s
retries:
attempts: 3
perTryTimeout: 2s
retryOn: 5xx
route:
- destination:
host: httpbin
port:
number: 8080

Les chiffres de cette leçon sont ceux d'une exécution : les durées, et surtout les résultats de /unstable, changent d'un tirage à l'autre. Retenez l'ordre de grandeur et la logique, pas la valeur exacte. On appelle /status/503, qui répond toujours 503, d'abord sans VirtualService (état initial, après suppression de celui du délai) puis avec celui-ci :

Fenêtre de terminal
kubectl -n mesh-lab delete virtualservice httpbin --ignore-not-found
reset frontend; reset httpbin
fe curl -s -o /dev/null -w "$W" http://httpbin.mesh-lab:8080/status/503
stats httpbin | grep -E "inbound.*upstream_rq_(total|503):"
kubectl apply -f 30-retries.yaml; sleep 4
reset frontend; reset httpbin
fe curl -s -o /dev/null -w "$W" http://httpbin.mesh-lab:8080/status/503
stats frontend | grep -E "httpbin.*upstream_rq_(total|retry|retry_success|retry_limit_exceeded|[0-9]{3}):"
stats httpbin | grep -E "inbound.*upstream_rq_(total|503):"
code=503 total=0.013083s
cluster.inbound|8080||;.upstream_rq_503: 1
cluster.inbound|8080||;.upstream_rq_total: 1
...
code=503 total=0.079555s
cluster.outbound|8080||httpbin.mesh-lab.svc.cluster.local;.upstream_rq_503: 1
cluster.outbound|8080||httpbin.mesh-lab.svc.cluster.local;.upstream_rq_retry: 3
cluster.outbound|8080||httpbin.mesh-lab.svc.cluster.local;.upstream_rq_retry_limit_exceeded: 1
cluster.outbound|8080||httpbin.mesh-lab.svc.cluster.local;.upstream_rq_retry_success: 0
cluster.outbound|8080||httpbin.mesh-lab.svc.cluster.local;.upstream_rq_total: 4
cluster.inbound|8080||;.upstream_rq_503: 4
cluster.inbound|8080||;.upstream_rq_total: 4

Ces deux extraits proviennent de deux exécutions du scénario, séparées par ... : le premier, d'un appel sans règle (13 ms) ; le second, de l'appel avec la règle (80 ms). Ils sont comparables, pas simultanés. Les grep renvoient en réalité quelques lignes de plus, que l'extrait a retirées : des variantes préfixées (.external.upstream_rq_503, .retry.upstream_rq_503) et une ligne d'information GOMEMLIMIT écrite par pilot-agent.

Sans règle, le serveur reçoit 1 requête : aucune reprise malgré numRetries: 2. Avec la règle, il en reçoit 4 (1 initiale et 3 reprises), le client compte upstream_rq_retry: 3 et retry_limit_exceeded: 1, et le client final reçoit quand même le 503. Le journal d'accès du serveur (kubectl -n mesh-lab logs deploy/httpbin -c istio-proxy --tail=4, après 1 à 2 secondes de délai d'écriture) montre les 4 lignes GET /status/503 avec le même identifiant de requête x-request-id, espacées de 20, 45 puis 9 ms (des intervalles irréguliers : Envoy espace les reprises par un recul exponentiel avec gigue). La ligne cliente porte le drapeau URX, « limite de reprises atteinte ».

Pour le scénario attempts: 0 du tableau ci-dessous, la seule forme acceptée par Istio désactive explicitement les reprises, sans perTryTimeout ni retryOn :

31-retries-off.yaml
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: httpbin
namespace: mesh-lab
spec:
hosts:
- httpbin
http:
- retries:
attempts: 0
route:
- destination:
host: httpbin
port:
number: 8080

Sur une défaillance intermittente, /unstable?failure_rate=0.5, 40 appels par scénario depuis frontend ont donné, lors de l'exécution de référence :

ScénarioCodes sur 40 appelsRequêtes reçues par le serveurReprises
attempts: 023 x 200, 17 x 500400
Défaut Istio (aucun VirtualService)17 x 200, 23 x 500400
attempts: 3, retryOn: 5xx35 x 200, 5 x 5008545 (16 sauvées)

Le défaut Istio ne réduit donc pas les erreurs 500. Avec retryOn: 5xx, le taux d'échec passe d'environ 50 % à 12,5 % (5 sur 40), mais la charge du backend augmente de 112 % : 85 requêtes pour 40 appels. Ces chiffres sont ceux d'une seule exécution : /unstable est aléatoire. Un rejeu a donné 20 x 200 et 20 x 500 sans règle, puis 37 x 200 et 3 x 500 avec attempts: 3. Seule la tendance se reproduit. Ne rejouez que des appels idempotents, et gardez attempts bas.

perTryTimeout borne chaque tentative, et un dépassement déclenche une reprise si retryOn le permet. Avec attempts: 2, perTryTimeout: 1s, retryOn: 5xx et timeout: 10s, un appel à /delay/3 donne :

upstream request timeout
code=504 total=3.059319s
cluster.outbound|8080||httpbin.mesh-lab.svc.cluster.local;.upstream_rq_retry: 2
cluster.outbound|8080||httpbin.mesh-lab.svc.cluster.local;.upstream_rq_total: 3
[...] "GET /delay/3 HTTP/1.1" 504 URX,UT upstream_per_try_timeout - ...

Le résultat est un 504 en 3,06 s : trois tentatives d'une seconde (1 initiale et 2 reprises), que confirment le drapeau URX,UT (UT : délai dépassé) et le détail upstream_per_try_timeout. Côté serveur, les trois requêtes sont journalisées avec le code 0 et le drapeau DC (downstream_remote_disconnect) : le backend a travaillé trois fois pour rien. Le total reste borné par timeout, ici jamais atteint.

Peut-on tester les reprises avec une panne injectée ?

Section intitulée « Peut-on tester les reprises avec une panne injectée ? »

Non : une panne injectée par fault.abort n'est jamais rejouée. Istio sait simuler une erreur sans toucher au backend, et l'on pourrait croire tester ainsi une politique de reprises. Mais cette erreur est produite par un filtre du sidecar client, avant le routeur qui applique les reprises.

32-fault-abort.yaml
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: httpbin
namespace: mesh-lab
spec:
hosts:
- httpbin
http:
- fault:
abort:
httpStatus: 503
percentage:
value: 50
timeout: 10s
retries:
attempts: 3
perTryTimeout: 2s
retryOn: 5xx
route:
- destination:
host: httpbin
port:
number: 8080

Mesuré sur 40 appels à /get avec cette règle : 20 x 200 et 20 x 503, 0 reprise et seulement 20 requêtes reçues par le serveur (les 20 autres n'y sont jamais arrivées). Pour tester des reprises, il faut un vrai backend défaillant : /status/503 ou /unstable.

Une AuthorizationPolicy de type ALLOW sur un principal ne laisse passer que l'identité nommée ; tout autre appelant reçoit un 403. Un principal est l'identité d'un appelant, ici dérivée de son ServiceAccount : cluster.local/ns/mesh-lab/sa/frontend (l'URI SPIFFE vue plus haut, sans le préfixe spiffe://). La politique est évaluée par le sidecar du serveur.

Cette politique cible les Pods app: httpbin et n'autorise que frontend :

40-authz-allow-frontend.yaml
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: httpbin-allow-frontend
namespace: mesh-lab
spec:
selector:
matchLabels:
app: httpbin
action: ALLOW
rules:
- from:
- source:
principals:
- cluster.local/ns/mesh-lab/sa/frontend

Une politique ne s'applique pas instantanément. Pendant quelques secondes après kubectl apply, les réponses peuvent varier d'un appel à l'autre : un frontend autorisé reçoit parfois un 403, un batch refusé passe parfois. La cause de ces états transitoires n'a pas été établie. La règle de mesure en découle : sondez en boucle jusqu'à obtenir une série stable (cinq lignes identiques d'affilée), et ne concluez jamais sur une seule requête.

Fenêtre de terminal
line() {
echo "$(fe curl -s -o /dev/null -w '%{http_code}' http://httpbin.mesh-lab:8080/get)" \
"$(ba curl -s -o /dev/null -w '%{http_code}' http://httpbin.mesh-lab:8080/get)" \
"$(fe curl -s -o /dev/null -w '%{http_code}' -X POST http://httpbin.mesh-lab:8080/post)" \
"$(out curl -s -o /dev/null -w '%{http_code}' http://httpbin.mesh-lab.svc.cluster.local:8080/get)"
}
kubectl apply -f 40-authz-allow-frontend.yaml
for i in $(seq 1 15); do line; sleep 1; done

Les quatre colonnes sont : frontend GET /get, batch GET /get, frontend POST /post, outsider GET /get. Lors de la mesure, les résultats se sont stabilisés au bout de 11 s (un rejeu : 2 à 5 s) :

t=0s 200 200 200 403
t=1s 200 403 200 403
t=3s 200 200 200 403
t=4s 200 403 200 403
...
STABLE apres 11s : 200 403 200 403

À l'état stable : frontend passe (200), batch est refusé (403), et outsider, qui n'a pas de sidecar donc pas de principal, est refusé aussi, même en mode PERMISSIVE. Le corps du refus est exactement RBAC: access denied : il vient du sidecar du serveur, httpbin ne voit jamais la requête. Le journal d'accès ajoute la cause, rbac_access_denied_matched_policy[none] : aucune règle ALLOW n'a correspondu.

Le bloc to.operation ajoute des conditions sur la méthode et le chemin : l'appelant autorisé ne peut alors faire que cela. Remplacez la politique par cette version, qui garde le même nom :

41-authz-allow-frontend-get.yaml
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: httpbin-allow-frontend
namespace: mesh-lab
spec:
selector:
matchLabels:
app: httpbin
action: ALLOW
rules:
- from:
- source:
principals:
- cluster.local/ns/mesh-lab/sa/frontend
to:
- operation:
methods: ["GET"]
paths: ["/get", "/headers"]

Une fois la série stabilisée, les colonnes valent 200 403 403 403 : le POST de frontend est maintenant refusé, tout comme un GET /anything (chemin non listé). La commande istioctl x authz check deploy/httpbin.mesh-lab liste les politiques effectives du proxy, avec une ligne _anonymous_match_nothing_ que cette leçon n'a pas explorée.

Une politique sans action ni règle refuse tout, mais seulement si elle est seule ; un DENY seul laisse passer tout ce qu'il ne nomme pas, y compris l'appelant anonyme. La documentation d'Istio donne spec: {} pour viser tout un namespace ; le manifeste testé ici ne porte qu'un selector sur httpbin. Deux idées reçues tombent ici, mesurées sur les mêmes quatre colonnes :

Politiques activesfrontend GETbatch GETfrontend POSToutsider
Aucune200200200200
ALLOW principal frontend200403200403
ALLOW frontend limité à GET /get, /headers200403403403
Politique vide (sans règle) seule403403403403
Politique vide (sans règle) et ALLOW frontend200403200403
DENY principal batch seule200403200200

Les politiques ALLOW s'additionnent : dès qu'une autre ALLOW autorise frontend, la politique vide ne bloque plus rien (ligne 5). Seule une action DENY est prioritaire. Et un DENY de batch laisse passer l'anonyme outsider (ligne 6). Pour n'autoriser que frontend, écrivez donc un ALLOW, pas un DENY des autres. Voici les manifestes des lignes 4 et 6 :

42-authz-deny-all.yaml
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: httpbin-deny-all
namespace: mesh-lab
spec:
selector:
matchLabels:
app: httpbin
43-authz-deny-batch.yaml
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: httpbin-deny-batch
namespace: mesh-lab
spec:
selector:
matchLabels:
app: httpbin
action: DENY
rules:
- from:
- source:
principals:
- cluster.local/ns/mesh-lab/sa/batch

Avec un DENY, le journal d'accès nomme la politique et la règle : rbac_access_denied_matched_policy[ns[mesh-lab]-policy[httpbin-deny-batch]-rule[0]]. Avant le test suivant, supprimez les politiques : kubectl -n mesh-lab delete authorizationpolicy --all.

En mode STRICT, un Pod sans sidecar ne reçoit plus de réponse : sa connexion est coupée avant tout échange HTTP. Une PeerAuthentication fixe le mode mTLS d'un namespace ; l'activer et la migrer pas à pas relève de la leçon mTLS pod-to-pod. Ici, on constate seulement l'effet sur outsider.

50-peerauth-strict.yaml
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: default
namespace: mesh-lab
spec:
mtls:
mode: STRICT
Fenêtre de terminal
out curl -sS -o /dev/null -w "$W" http://httpbin.mesh-lab.svc.cluster.local:8080/get
kubectl apply -f 50-peerauth-strict.yaml
sleep 10
out curl -sS -o /dev/null -w "$W" http://httpbin.mesh-lab.svc.cluster.local:8080/get
fe curl -s -o /dev/null -w "$W" http://httpbin.mesh-lab:8080/get
code=200 total=0.009783s
peerauthentication.security.istio.io/default created
curl: (56) Recv failure: Connection reset by peer
code=000 total=0.019588s
code=200 total=0.002077s

En PERMISSIVE, outsider obtient 200. En STRICT, il reçoit Connection reset by peer, code de sortie 56, pas de code HTTP (code=000) ; frontend, dans le maillage, passe toujours. Le sidecar du serveur n'écrit aucune requête HTTP dans son journal pour cette connexion refusée, seulement une ligne sans méthode ni chemin ("- - -" 0 NR filter_chain_not_found). Cela distingue deux refus : un reset sans HTTP est un refus de la couche mTLS, alors que le 403 RBAC: access denied est une réponse HTTP, donc un TLS abouti et une identité connue.

Prévoyez environ 130 Mo pour istiod et 106 Mo par Pod maillé, mais le profil minimal réserve bien davantage. kubectl top n'est pas disponible sur kind (pas de metrics-server) : les valeurs ont été lues dans /proc du conteneur, en fin de lab (trois Pods maillés plus istiod).

ComposantConsommation relevéeRequests déclarées
istiodVmRSS 123 Mo au repos, 137 Mo en fin de lab (pic 141 Mo)500m CPU, 2 Gi
Sidecar (par Pod)pilot-agent 36 Mo + envoy 70 Mo, soit environ 106 Mo100m CPU, 128 Mi (limites 2 CPU, 1 Gi)

Le nœud kind complet (Kubernetes, Istio et les Pods du lab) occupait 1,42 Gio selon docker stats. C'est la réservation de 2 Gi d'istiod, pas sa consommation, qui pèse sur un petit nœud : prévoyez au moins 4 Go de RAM libres pour ce lab.

Ce lab ne couvre que le mode sidecar, un seul replica de backend et des requêtes HTTP. Honnêtement, voici ce qui n'a pas été éprouvé, à ne pas déduire des mesures ci-dessus :

  • le mode ambient d'Istio (sans sidecar), mentionné dans la leçon Le service mesh ;
  • le circuit breaking et la détection d'instances défaillantes ;
  • le gRPC, et retriable-status-codes avec des codes explicites ;
  • la reprise vers un autre replica : avec un seul Pod httpbin, le prédicat qui évite de retenter le même hôte n'est pas observable ;
  • les reprises sur connect-failure, seulement lues dans la configuration ;
  • la vérification d'attestation de l'archive istioctl : seule la somme sha256 a été contrôlée.

Une réserve de compatibilité : la documentation d'Istio 1.31 liste Kubernetes 1.37 comme supporté, mais pas parmi les versions testées en CI amont. Le lab a tourné sans accroc, ce qui ne prouve que ce comportement-là. Les résultats sur /unstable sont aléatoires, ceux sur /status/503 et /delay/N déterministes.

Les pièges de cette leçon sont presque tous des signaux absents, pas des erreurs affichées. Le tableau part du symptôme que vous voyez.

SymptômeCauseSolution
grep upstream_rq_retry ne renvoie rien côté clientCompteurs de cluster filtrés par la configuration statistique du sidecarAnnoter les Pods avec proxy.istio.io/config et proxyStatsMatcher, redémarrer les Pods
Le grep des compteurs côté client ne trouve toujours rienLe nom contient un ; avant le point final : ...svc.cluster.local;.upstream_rq_retryChercher httpbin.*upstream_rq_retry sans ancrer sur le point
kubectl logs -c istio-proxy n'affiche que des lignes de démarrageJournaux d'accès désactivés par défautCréer une ressource Telemetry avec le fournisseur envoy
istioctl ou kubectl apply rejette attempts: 0 avec http retry policy configured when attempts are set to 0 (disabled)attempts: 0 est refusé dès qu'on y ajoute perTryTimeout ou retryOnÉcrire retries: {attempts: 0} seul (voir 31-retries-off.yaml)
Anciens Pods en 0/2 Error, ou kubectl logs deploy/... sur un ancien Pod (Found 2 pods, using pod/…)Rollout du patch pas terminéAttendre kubectl rollout status deploy --timeout=300s avant de lire journaux ou compteurs
Le journal d'accès n'affiche pas encore la requêteÉcriture différée d'1 à 2 secondesRelancer kubectl logs après une courte attente
1 seule requête atteint le serveur malgré numRetries: 2Les reprises par défaut ne portent pas sur le 5xx applicatifAjouter retries avec retryOn: 5xx
fault.abort ne déclenche aucune repriseLa panne est produite avant le routeur, jamais rejouéeTester avec un vrai backend défaillant (/status/503)
Résultats 200 et 403 mélangés après kubectl applyPolitique en cours de propagationSonder en boucle jusqu'à 5 lignes identiques d'affilée
outsider reçoit un 403 sous ALLOW sur principalsSans sidecar, pas de certificat, donc pas d'identitéComportement attendu ; placer le Pod dans le maillage
Connection reset by peer, code=000mTLS STRICT : le sidecar coupe la connexion d'un client sans sidecarInjecter le client, ou repasser en PERMISSIVE le temps de migrer
Pods Pending ou nœud saturé après installationRequests de 2 Gi pour istiod, 128 Mi par sidecarLibérer de la mémoire, ou donner plus de RAM à Docker

Supprimer le cluster libère toute la mémoire réservée. Il n'y a rien d'autre à défaire : aucun port-forward n'a été utilisé.

Fenêtre de terminal
kind delete cluster --name cn-mesh

La sortie annonce Deleting cluster "cn-mesh" ... puis Deleted nodes: ["cn-mesh-control-plane"].

  • Délai et reprises se posent dans le VirtualService, appliqués par le sidecar du client ; l'autorisation se pose dans une AuthorizationPolicy, appliquée par le sidecar du serveur.
  • Un timeout: 2s fait renvoyer un 504 (upstream request timeout) par le proxy : mesuré 2,003 s contre 5,005 s sans règle.
  • Istio rejoue 2 fois par défaut, mais pas sur un 5xx applicatif : ajoutez retryOn: 5xx pour le couvrir.
  • attempts: N compte des reprises : attempts: 3 a donné 4 requêtes au serveur, et environ +112 % de charge sur /unstable dans l'exécution mesurée. Ne rejouez que des appels idempotents.
  • fault.abort ne sert pas à tester les reprises : la panne n'est jamais rejouée.
  • Un ALLOW sur un principal (identité SPIFFE) refuse tout autre appelant avec un 403 RBAC: access denied ; les ALLOW s'additionnent, et un DENY laisse passer l'anonyme.
  • Après un kubectl apply de politique, sondez jusqu'à stabilité ; en STRICT, un Pod sans sidecar reçoit un reset de connexion, sans HTTP.
  • Comptez environ 106 Mo par sidecar et prévoyez la réservation de 2 Gi d'istiod.

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