L’article qui va suivre considère que le proxy-mode utilisé par Kube-proxy est iptables, utilisé par défaut à partir de la version 1.2 de Kubernetes. Partant d’un nom de service Kubernetes qu’un pod essaie d’atteindre, le cheminement est le suivant :
- Le service Kube-DNS (désormais pris en charge par CoreDNS à l’heure où j’écris ces lignes) retourne l’adresse IP du service
- Le pod tente une connexion sur l’adresse IP, avec le protocole et port voulus
- Iptables va alors gérer le cheminement jusqu’aux pods exposés par le service grâce eux Endpoints qui ont été créés par le endpoint controller
Note sur les Endpoints
Quand vous exposez un ou des pods depuis un service Kubernetes en utilisant un service, un autre type d’objet que “Service” est créé dans l’API, les “Endpoints”. Cet objet, créé et maintenu par le endpoints controller répertorie tous les couples IP de pod / port d’application correspondant à un service, quand celui-ci utilise un selector. L’exemple suivant montre l’unique endpoint créé quand un service expose un seul port, et qu’un seul pod correspond au selector
guillaume@GuiHome:~$ kubectl get services -nkube-system kubernetes-dashboard
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes-dashboard ClusterIP 192.168.18.202 <none> 443/TCP 111d
guillaume@GuiHome:~$ kubectl get endpoints -nkube-system kubernetes-dashboard
NAME ENDPOINTS AGE
kubernetes-dashboard 192.168.34.17:8443 111d
spec:
clusterIP: 192.168.18.202
ports:
- port: 443
protocol: TCP
targetPort: 8443
selector:
k8s-app: kubernetes-dashboard
Ce résultat d’Endpoints va bien évidemment changer en temps réel si on monte le nombre de pods ayant le label k8s-app: kubernetes-dashboard
Iptables entre dans l’arène
Jusqu’ici, nous avons donc un service avec un IP stable, et on comprends maintenant le lien vers les pods finaux grâce à l’existence des Endpoints. Mais à aucun moment un pod qui veut contacter un service va être au courant de l’existence de ces endpoints, ceux-là sont utiles à kube-proxy, qui va alors créer des règles iptables (dans sa configuration proxy-mode: iptables) qui vont mener le flux à bon port.
Remontons ensemble la chaine de ces règles de filtrage, qui
En reprenant l’exemple kubernetes-dashboard, on essaie de joindre le service à l’IP 192.168.18.202 sur le port 443. La règle iptables qui va réagir aux flux est la suivante:
Chain KUBE-SERVICES (2 references)
KUBE-SVC-XGLOHA7QRQ3V22RZ tcp -- 0.0.0.0/0 192.168.18.202 /* kube-system/kubernetes-dashboard: cluster IP */ tcp dpt:443
La chaine KUBE-SERVICES match donc et fait référence à une autre chaine personnalisée, KUBE-SVC-XGLOHA7QRQ3V22RZ. Regardons-là de plus près :
Chain KUBE-SVC-XGLOHA7QRQ3V22RZ (1 references)
target prot opt source destination
KUBE-SEP-NG56X4MU77FFZ5RK all -- 0.0.0.0/0 0.0.0.0/0 /* kube-system/kubernetes-dashboard: */
Cette chaîne définit donc le point d’accès vers le service via un nom généré, facilement reconnaissable par son “SVC” dans le nom. Une nouvelle chaîne est alors référencée, KUBE-SEP-NG56X4MU77FFZ5RK. SEP ici fait alors référence à Service EndPoint (au singulier).
Chain KUBE-SEP-NG56X4MU77FFZ5RK (1 references)
target prot opt source destination
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 /* kube-system/kubernetes-dashboard: */ tcp to:192.168.34.17:8443
On retrouve alors ici notre IP de pod 192.168.34.17, avec le port originel de l’application, 8443 (targetPort défini dans le service), et la boucle est bouclée. La couche réseau prend alors le relai via le routage aux pods qui ne sera pas abordé dans ce article, mais qui dépend de votre Network Provider (Calico, Flannel…).
Une règle d’endpoint (KUBE-SEP-*) ne concerne qu’un seul pod à la fois. Si le service expose plusieurs pods d’un coup, nous allons avoir plusieurs chaînes SEP références à partir d’une chaîne SVC, example :
Chain KUBE-SVC-FAITROITGXHS3QVF (1 references)
target prot opt source destination
KUBE-SEP-TJ2CXVZOWVM6T4YV all -- 0.0.0.0/0 0.0.0.0/0 /* kube-system/coredns:dns-tcp */
statistic mode random probability 0.50000000000
KUBE-SEP-HWX34FYUV4Y7PLZG all -- 0.0.0.0/0 0.0.0.0/0 /* kube-system/coredns:dns-tcp */
Chacune de ces deux chaînes renvoyées vont s’occuper de l’accès à un pod. On a donc ici un load balancing random (avec une probabilité de 0.5 de matcher la première chaine), je pense que vous aurez vous-même compris comment ça se passe quand on augmente notre nombre de pods au delà de deux ;)