Kubernetes en production: services

Il y a six mois, nous avons achevé la migration de tous nos services apatrides vers Kubernetes. À première vue, la tâche est assez simple: vous devez déployer un cluster, écrire des spécifications d'application et c'est parti. En raison de l'obsession d'assurer la stabilité dans le travail de notre service, j'ai dû immédiatement commencer à comprendre le fonctionnement de k8s et tester différents scénarios de défaillance. La plupart des questions que j'avais sur tout ce qui concernait le réseau. L'un de ces problèmes glissants est le fonctionnement des services dans kubernetes.


La documentation nous dit:


  • déployer l'application
  • définir des échantillons de vivacité / préparation
  • créer un service
  • alors tout fonctionnera: équilibrage de charge, basculement, etc.

Mais en pratique, tout est un peu plus compliqué. Voyons comment cela fonctionne réellement.


Un peu de théorie


De plus, je veux dire que le lecteur est déjà familier avec le périphérique kubernetes et sa terminologie; nous ne rappelons que ce qu'est un service.


Le service est l'essence même de k8s, qui décrit un ensemble de foyers et de méthodes pour y accéder.


Par exemple, nous avons lancé notre application:


apiVersion: apps/v1 kind: Deployment metadata: name: webapp spec: selector: matchLabels: app: webapp replicas: 2 template: metadata: labels: app: webapp spec: containers: - name: webapp image: defaultxz/webapp command: ["/webapp", "0.0.0.0:80"] ports: - containerPort: 80 readinessProbe: httpGet: {path: /, port: 80} initialDelaySeconds: 1 periodSeconds: 1 

 $ kubectl get pods -l app=webapp NAME READY STATUS RESTARTS AGE webapp-5d5d96f786-b2jxb 1/1 Running 0 3h webapp-5d5d96f786-rt6j7 1/1 Running 0 3h 

Maintenant, pour y accéder, nous devons créer un service dans lequel nous déterminons à quels canaux nous voulons avoir accès (sélecteur) et sur quels ports:


 kind: Service apiVersion: v1 metadata: name: webapp spec: selector: app: webapp ports: - protocol: TCP port: 80 targetPort: 80 

 $ kubectl get svc webapp NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE webapp ClusterIP 10.97.149.77 <none> 80/TCP 1d 

Nous pouvons désormais accéder à notre service depuis n'importe quelle machine du cluster:


 curl -i http://10.97.149.77 HTTP/1.1 200 OK Date: Mon, 24 Sep 2018 11:55:14 GMT Content-Length: 2 Content-Type: text/plain; charset=utf-8 

Comment ça marche



Très simplifié:


  • vous avez appliqué kubectl Spécifications de déploiement
  • la magie opère, dont les détails ne sont pas importants dans ce contexte
  • en conséquence, les nœuds de travail de l'application se sont avérés être sur certains nœuds
  • une fois que chaque intervalle de kubelet (agent k8s sur chaque nœud) effectue des échantillons de vivacité / disponibilité de tous les pods exécutés sur son nœud, il envoie les résultats à apiserver (interface avec les cerveaux k8s)
  • kube-proxy sur chaque nœud reçoit des notifications d'apiserver sur tous les changements dans les services et les foyers qui participent aux services
  • kube-proxy reflète tous les changements dans la configuration des sous-systèmes sous-jacents (iptables, ipvs)

Pour plus de simplicité, considérons la méthode proxy par défaut - iptables. Dans iptables, nous avons pour notre ip virtuelle 10.97.149.77:


 -A KUBE-SERVICES -d 10.97.149.77/32 -p tcp -m comment --comment "default/webapp: cluster IP" -m tcp --dport 80 -j KUBE-SVC-BL7FHTIPVYJBLWZN 

le trafic va à la chaîne KUBE-SVC-BL7FHTIPVYJBLWZN , dans laquelle il est réparti entre 2 autres chaînes


 -A KUBE-SVC-BL7FHTIPVYJBLWZN -m comment --comment "default/webapp:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-UPKHDYQWGW4MVMBS -A KUBE-SVC-BL7FHTIPVYJBLWZN -m comment --comment "default/webapp:" -j KUBE-SEP-FFCBJRUPEN3YPZQT 

ce sont nos pods:


 -A KUBE-SEP-UPKHDYQWGW4MVMBS -p tcp -m comment --comment "default/webapp:" -m tcp -j DNAT --to-destination 10.244.0.10:80 -A KUBE-SEP-FFCBJRUPEN3YPZQT -p tcp -m comment --comment "default/webapp:" -m tcp -j DNAT --to-destination 10.244.0.11:80 

Tester la défaillance d'un des foyers


Mon application de test webapp est capable de passer en mode "erreur éruption", pour cela vous devez extraire l'URL "/ err".


Les résultats de ab -c 50 -n 20 000 au milieu du test ont tiré "/ err" sur l'un des foyers:


 Complete requests: 20000 Failed requests: 3719 

Le point ici n'est pas le nombre spécifique d'erreurs (leur nombre variera en fonction de la charge), mais ce qu'elles sont. En général, nous avons déséquilibré le "mauvais", mais au moment de changer de client, le service a reçu des erreurs. La cause des erreurs est assez facile à expliquer: les tests de préparation sont effectués kubelet une fois par seconde + même un court laps de temps pour diffuser des informations qui n'ont pas répondu au test.


Le backend IPVS pour kube-proxy (expérimental) aidera-t-il?


Pas vraiment! Il résout le problème de l'optimisation du proxy, propose un algorithme d'équilibrage personnalisé, mais il ne résout pas le problème du traitement des échecs.


Comment être


Ce problème ne peut être résolu que par un équilibreur qui peut réessayer (nouvelles tentatives). En d'autres termes, pour http, nous avons besoin d'un équilibreur L7. De tels équilibreurs pour kubernetes sont déjà pleinement utilisés soit sous la forme d' entrée (impliquée comme un point dans le passage au cluster, mais dans l'ensemble, il fait exactement ce dont il a besoin), ou comme implémentation d'une couche distincte - un maillage de service, par exemple, istio .


Dans notre production, nous n'avons pas encore commencé à utiliser le maillage d'entrée ou de service en raison de la complexité supplémentaire. De telles abstractions, à mon avis, aident dans les cas où vous devez souvent configurer un grand nombre de services. Mais en même temps, vous «payez» la contrôlabilité et une infrastructure simple. Vous passerez plus de temps à comprendre comment configurer le rertai et les délais d'expiration pour un service particulier.


Comment


Nous utilisons les services k8s sans tête. Ces services n'ont pas d'IP virtuelle et, en conséquence, kube-proxy et iptables ne sont pas impliqués dans leur travail. Pour chacun de ces services, vous pouvez obtenir une liste des foyers actifs via le DNS ou via l'API.


Pour les applications qui interagissent avec d'autres services, nous fabriquons un sidecar container avec envoyé . Evoy reçoit périodiquement une liste à jour des pods pour tous les services nécessaires via DNS, et surtout, il est capable de faire des tentatives répétées pour interroger d'autres pods en cas d'erreur. Vous pouvez l'exécuter en tant que DaemonSet sur chaque nœud, mais si cette instance échoue, toutes les applications qui l'utilisent cessent de fonctionner. Étant donné que la consommation de ressources par ce proxy est assez faible, nous avons décidé de l'utiliser dans la variante de conteneur sidecar.


C'est essentiellement exactement ce que fait istio, mais dans notre cas, l'équilibre est passé à la simplicité (pas besoin d'apprendre istio, de rencontrer ses bugs). Peut-être que cet équilibre changera, et nous commencerons à utiliser quelque chose comme istio.


Chez okmeter.io kubernetes, nous avons définitivement pris racine et nous croyons en sa distribution future. La prise en charge de la surveillance des k8 dans notre service est en cours, restez à l'écoute!

Source: https://habr.com/ru/post/fr424229/


All Articles