26 février 2020, par Mody Sow

 

Comment assurer la résilience d’une architecture avec des milliers de

microservices dans Istio?


 

La résilience est l’un des défis les plus difficiles à relever lorsqu’on est dans une architecture distribuée comme les microservices. En effet, la résilience d’un système distribué se mesure par le comportement de celui-ci face à une situation d’indisponibilité ou de non joignabilité d’un ou de plusieurs de ses composants. Or ceci peut être dû à des facteurs divers et difficiles à prédire tels que la défaillance matérielle, les charges réseau, bugs etc.

Avec istio, on peut facilement mettre en place une politique de gestion de panne avec les trois mécanismes suivants: timeout, retry et circuit breaker. De plus, Istio nous permet d’injecter volontairement des défaillances au niveau de certaines couches de notre réseau de microservices afin de mettre en épreuve le système et vérifier s’il se comporte convenablement face à certaines situations. Par exemple pour une destination donnée, on peut volontairement retarder le temps de réponse des requêtes de quelques secondes ou retourner des codes d’erreurs 5xx et voir comment le système se comporte face à cette situation.

Nous allons voir plus en détail les différentes notions susmentionnées et comment on peut les utiliser afin d’améliorer la résilience de nos systèmes et de bien s’assurer que nos configurations sont en phase avec le fonctionnement de nos applications.

Timeout

Istio nous permet de définir, pour une route donnée, un timeout qui sera la durée maximale pendant laquelle un proxy Envoy doit attendre les réponses du service associé. Ce qui garantit que nos services ne restent pas en attente de réponse indéfiniment et que les requêtes réussissent ou échouent dans un délai prévisible. Par défaut, toutes les requêtes HTTP ont un délai d’attente de 15 secondes. Si pour une destination donnée, on veut ajuster ce délai, il faudra explicitement le configurer au niveau du VirtualService ou du DestinationRule chargé de router le trafic vers cette destination.

Par exemple voici un VirtualService qui spécifie un timeout de 10 secondes pour les appels vers la version «v1» du service «ratings».

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
 name: ratings
spec:
 hosts:
 - ratings
 http:
 - route:
   - destination:
       host: ratings
       subset: v1
   timeout: 10s

Retry

Dans un système distribué, il peut arriver qu’un service soit injoignable pour des problèmes transitoires tels qu’un service ou un réseau temporairement surchargé. Dans ces genres de situations, il n’est pas commode qu’un appel échoue complètement juste après une première tentative. Avec Istio, il est possible de configurer un nombre maximal de fois qu’un proxy Envoy doit tenter de recontacter un service, si l’appel initial échoue. Par défaut, cette valeur est à zéro. Comme pour les timeouts, si ce comportement par défaut ne convient pas pour une destination donnée, on peut explicitement configurer la valeur qui nous convient au niveau du VirtualService chargé de router le trafic vers cette destination.

L’exemple suivant configure un maximum de 3 nouvelles tentatives espacées de 2 secondes pour se connecter au service «ratings» «v1» après un échec d’appel initial.

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
 name: ratings
spec:
 hosts:
 - ratings
 http:
 - route:
   - destination:
       host: ratings
       subset: v1
   retries:
     attempts: 3
     perTryTimeout: 2s

Le champ attempts est utilisé pour indiquer le nombre maximal de nouvelles tentatives après un premier échec de connexion. Le Champ perTryTimeout indique l’intervalle de temps entre les tentatives successives.

Circuit Breakers

Istio nous permet d’implémenter des méthodes de disjonction nous permettant de limiter les dégâts que peuvent causer l’indisponibilité d’un ou de plusieurs microservices et/ou les pics de charges. En effet, on peut créer un objet DestinationRule et le configurer de sorte que le routage du trafic ne soit activé que lorsqu’on est en dessous de certaines limites qu’on aurait définies au préalable. 

On peut entre autre définir, des limites sur: 

  • Le pool de connexions tcp,
  • Le nombre de requêtes http simultanées, 
  • Le nombre de requêtes http en attente, 
  • Le nombre d’échecs de connexion sur un intervalle de temps 
  • etc.

Par exemple voici une DestinationRule qui limite le nombre de connexion tcp à 1, le nombre de requêtes http simultanées à 1 et le nombre de requêtes http en attente à 2, pour un service «productpage».

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
 name: productpage
spec:
 host: productpage
 trafficPolicy:
   connectionPool:
     tcp:
       maxConnections: 1
     http:
       http1MaxPendingRequests: 2
       maxRequestsPerConnection: 1

On utilise ici le champ connectionPool du trafficPolicy pour indiquer des limites sur les pools de connexions tcp et http.

Voici un autre exemple où on demande à Istio de bloquer le trafic vers un service «details»pendant au minimum 3 minutes lorsqu’on rencontre 2 erreurs d’état (code d’erreur 5xx, timeout, etc.) sur un intervalle de 1 seconde.

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
 name: details
spec:
 host: details
 trafficPolicy:
   outlierDetection:
     consecutiveErrors: 2
     interval: 1s
     baseEjectionTime: 3m
     maxEjectionPercent: 100

Le champ outlierDetection est le champ qui nous permet de définir des limites sur les erreurs relatives à l’état des services comme le renvoie des codes d’erreur 5xx, les timeouts, etc.
Le champ maxEjectionPercent est utilisé pour indiquer à Istio le pourcentage de service details éjectable. Là on lui indique qu’il peut, si nécessaire éjecter toutes les instances du service details.

Fault injection

L’injection de panne est une méthode de test qui permet d’introduire des défauts dans un système afin de s’assurer que celui-ci peut résister et se remettre de certaines conditions d’erreur. On peut utiliser cette technique dans Istio pour injecter une ou plusieurs pannes lors du transfert des requêtes http vers une destination donnée. Ces pannes incluent l’abandon prématuré de la demande d’un service en aval avec un code d’erreur (abort) et/ou une latence dans l’acheminement (delay).

HTTP Fault Injection Abort

Comme expliqué précédemment, on utilise la spécification abort pour abandonner prématurément une requête avec un code d’erreur à définir.

L’exemple suivant renverra un code d’erreur HTTP 400 pour 1 requête sur 1000 vers la version «v1» du service «ratings».

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
 name: ratings-route
spec:
 hosts:
 - ratings.prod.svc.cluster.local
 http:
 - route:
   - destination:
       host: ratings.prod.svc.cluster.local
       subset: v1
   fault:
     abort:
       percentage:
         value: 0.1
       httpStatus: 400

Le champ httpStatus est utilisé pour indiquer le code d’état HTTP à retourner à l’appelant. Le champ facultatif percentage peut être utilisé pour annuler uniquement un certain pourcentage de requêtes. S’il est non spécifié, toutes les requêtes seront abandonnées.

HTTP Fault Injection Delay

L’injection de retard est une manipulation chronologique sur le temps de réponse des requêtes. Ils imitent une latence réseau accrue ou un service en amont surchargé. 

L’exemple suivant introduira un délai de 5 secondes pour 1 requête sur 1000 à la version «v1» du service «reviews» de tous les pods ayant le label env: prod.

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
 name: reviews-route
spec:
 hosts:
 - reviews.prod.svc.cluster.local
 http:
 - match:
   - sourceLabels:
       env: prod
   route:
   - destination:
       host: reviews.prod.svc.cluster.local
       subset: v1
   fault:
     delay:
       percentage:
         value: 0.1
       fixedDelay: 5s

Le champ fixedDelay est utilisé pour indiquer le délai en secondes. Le champ facultatif percentage est utilisé pour retarder un certain pourcentage de requêtes. S’il n’est pas spécifié, toute les requêtes seront retardées.

Liens utiles:

https://istio.io/docs/concepts/traffic-management/#network-resilience-and-testing

https://istio.io/docs/tasks/traffic-management/fault-injection/