Kubernetes services, Nodeport, LoadBalancer et externalTrafficPolicy
Les services Kubernetes de type LoadBalancer et Nodeport ont une option peu connue mais très utile appelée externalTrafficPolicy. Voyons dans cet article son fonctionnement.
Créer un service de type NodePort
Vous avez tout d’abord besoin d’un cluster Kubernetes. j’utiliserai ici le cloud Exoscale pour créer mon cluster (vous pouvez retrouver la documentation ci si cela vous intéresse).
Déployons maintenant un pod et un service sur ce cluster:
---
apiVersion: v1
kind: Pod
metadata:
name: test-python
labels:
app: python
spec:
containers:
- name: app
image: python:latest
command: ["python", "-m", "http.server", "31000"]
ports:
- containerPort: 31000
---
apiVersion: v1
kind: Service
metadata:
name: test-python
spec:
ports:
- port: 31000
protocol: TCP
targetPort: 31000
nodePort: 31000
selector:
app: python
type: NodePort
Le pod démarrera un serveur HTTP sur le port 31000. Nous exposons ensuite ce service via un service de type NodePort.
J’ai dans mon cluster 3 noeuds, ayant comme IP 194.182.162.70
, 194.182.163.108
, 194.182.161.153
. Le service de type NodePort est exposé sur chaque noeud à l’adresse 31000 (l’option nodePort
indiquant le port sur le noeud, targetPort
le port de l’application cible: ces valeurs peuvent être différentes), et redigirera le trafic vers mon application (qui elle n’a aucune instance).
Si je fais par exemple un curl 194.182.162.70:31000
, je verrai dans mes logs de mon application Python (kubectl logs test-python -f
):
192.168.49.128 - - [12/Dec/2021 16:35:36] "GET / HTTP/1.1" 200 -
192.168.49.128
n’est pas l’IP de ma machine cliente mais l’IP interne du noeud ciblé. On voit donc que Kubernetes a remplacé l’IP source de ma requête Kubernetes par l’IP du noeud, l’IP source étant définitivement perdue.
Cela est bien sûr un problème pour toutes les applications ayant besoin de cette information.
L’avantage de ce type de service est que vous pouvez envoyer vos requêtes sur n’importe quel noeud du cluster sur le port 31000 et le traffic sera redirigé sur une instance du pod, même si cette instance est sur une autre machine. J’ai par exemple ici 3 noeuds mais qu’une instance de mon application, mais l’application est accessible depuis mes 3 noeuds.
Il est à noter que configurer l’option nodePort
est optionnel, par défaut Kubernetes allouera un port aléatoire dans la range 30000-32767
(cette range est configurable dans l’API server)
externalTrafficPolicy
Ajoutez maintenant à la spec de notre service externalTrafficPolicy: Local
et réappliquez le.
Essayez encore une fois de faire des requêtes sur les IP de vos noeuds sur le port 31000. Seulement le noeud où votre pod tourne répondra, et ici l’IP affichée dans les logs est bien l’IP de votre client.
l’adresse IP est préservée.
Services de type LoadBalancer
Un service Kubernetes de type LoadBalancer permet de provisionner un load balancer ciblant un service donné chez votre Cloud provider.
Voici la définition à utiliser pour par exemple provisionner un load balancer chez Exoscale:
---
apiVersion: v1
kind: Service
metadata:
annotations:
service.beta.kubernetes.io/exoscale-loadbalancer-name: python
service.beta.kubernetes.io/exoscale-loadbalancer-service-healthcheck-interval: 10s
service.beta.kubernetes.io/exoscale-loadbalancer-service-healthcheck-mode: http
service.beta.kubernetes.io/exoscale-loadbalancer-service-healthcheck-retries: '1'
service.beta.kubernetes.io/exoscale-loadbalancer-service-healthcheck-timeout: 3s
service.beta.kubernetes.io/exoscale-loadbalancer-service-healthcheck-uri: /
service.beta.kubernetes.io/exoscale-loadbalancer-service-strategy: source-hash
name: python-lb
spec:
type: LoadBalancer
externalTrafficPolicy: Local
ports:
- name: http
port: 80
nodePort: 31001
protocol: TCP
targetPort: 31000
selector:
app: python
Comme vous le voyez ici, externalTrafficPolicy: Local
est configuré. Sans ce réglage vous perdez comme dit précédemment l’IP source, ce que nous ne voulons pas ici. A noter que le load balancer Exoscale conseerve également l’IP source.
Il y a ici une chose très intéressante qui se passe. J’ai créé un service avec nodePort: 31001
, j’ai donc sur chaque noeud le port 31001
exposant mon application (comme dans mon exemple précédent sur 31000
).
J’ai demandé la création d’un load balancer Exoscale qui devra exposer sur le port 80 (option port: 80
) ce service.
Pourtant, voici ce que je vois sur la définition de mon load balancer sur l’interface d’Exoscale:
On voit bien que le load balancer est configuré pour rediriger le port 80 vers 31001 comme attendu. Mais regardez la définition du healthcheck: elle se fait sur le port 31897
.
Quel est ce port ? Il n’a jamais été configuré de notre côté. Regardons la définition de notre service LoadBalancer avec kubectl
:
kubectl get service python-lb -o yaml | grep 31897
healthCheckNodePort: 31897
Kubernetes a automatiquement alloué le port 31897
pour la valeur healthCheckNodePort
, et c’est ce port qui est utilisé par mon load balancer pour vérifier si mon application est fonctionnelle ou non. Le healthcheck n’est donc pas fait directement sur votre application.
mais que retourne donc ce port ? Essayons deux requêtes curl, la première sur le noeud où tourne mon application (pod), la seconde sur un noeud où elle ne tourne pas:
# Mon pod tourne sur ce noeud
curl -v 194.182.163.108:31897
< HTTP/1.1 200 OK
{
"service": {
"namespace": "default",
"name": "python-lb"
},
"localEndpoints": 1
}
# Mon pod ne tourne pas sur ce noeud
curl -v 194.182.162.70:31897
< HTTP/1.1 503 Service Unavailable
{
"service": {
"namespace": "default",
"name": "python-lb"
},
"localEndpoints": 0
}
On voit que mes noeuds où mon pod tourne retournent un HTTP 200 OK
, avec une valeur 1
pour localEndpoints
. Les noeuds où mon application ne tournent pas retournent un HTTP 503 Service Unavailable
et 0
pour localEndpoints
.
C’est donc cette information retournée par Kubernetes qui fera réussir ou échouer le healthcheck de mon load balancer. localEndpoints
sera automatiquement mis à jour en fonction de la santé de votre application.
Conclusion
Comprendre comment fonctionnent ces services et plus spécifiquement les healthchecks sur un service de type LoadBalancer est très important.
Il m’est par exemple déjà arrivé de configurer un healthcheck de type tcp
sur un service de type LoadBalancer
ayant externalTrafficPolicy: Local
: grave erreur ! En effet, le port sera toujours ouvert et c’est au niveau HTTP que l’information sur l’état du service est retournée.
Add a comment
If you have a bug/issue with the commenting system, please send me an email (my email is in the "About" section).