Définition K8S : Le Service
- Déployer un pod est une chose, le rendre accessible en est une autre.
- Le Service expose les pods entre eux et vers l’extérieur du cluster.
- Types de Service et périmètre d’exposition.
- Des notions proches du load balancing (HAProxy, Netscaler, F5…).
Introduction
Service est un objet kubernetes. Son « shorname » est svc. Le détail de l’objet est disponible ici.
Publier un pod à travers un Deployment au sein d’un namespace est une chose, le rendre accessible à l’intérieur et à l’extérieur du cluster en est une autre.
C’est pour couvrir ce besoin qu’il existe l’objet Service.
Un Service va permettre d’exposer vos pods pour qu’ils puissent se solliciter les uns les autres et également les rendre accessibles à l’extérieur de votre cluster si nécessaire.
Fonctionnement et principes
Un pod s’exécute sur un node (un serveur physique ou virtuel). Ce node est sur un réseau “externe” qui n’est pas celui de votre pod.
Votre pod dispose d’une IP dans un subnet spécifique, interne au cluster et établie à l’installation de ce dernier. Par défaut seul les conteneurs qui sont présents au sein de votre pod peuvent discuter les uns avec les autres puisqu’ils se partagent cette même IP.
Si vous souhaitez à minima exposer les applications de ce pod à vos autres pods, il vous faudra passer par un Service.
Les Services disposent également de leur propre subnet interne, lui aussi déclaré à l'installation du cluster. Comme pour le subnet des pods, ce réseau n’est pas routé en dehors du cluster.
Un Service peut être déployé principalement selon trois types:
- Type ClusterIP: C’est le type par défaut. Il va permettre d’associer une IP du subnet associé aux services, à l’ensemble des pods présentant la même application. Si vous avez plusieurs pods identiques, réparties sur plusieurs nœuds différents, cette IP pourra servir de « front » pour relayer ensuite vos requêtes vers n’importe quel pod couvert par le service. Cette IP n’a d’existence qu’en interne de votre cluster, elle n’est pas joignable directement. Par contre toute application qui tourne au sein de votre cluster pourra exploiter cette IP pour joindre un des pods associés. Un objet additionnel appelé Ingress peut s’appuyer également sur ce type de service et rendre ce dernier exploitable depuis l’extérieur.
- Type NodePort: Dans ce cas de figure, le service va exposer un port, compris entre 30000 et 32767, sur chaque nœud. De cette manière en tapant l’adresse du serveur suivi du port, il est possible de joindre le service. Le service va prendre la requête reçue puis la relayer en interne sur son IP issue du subnet réservé aux services (configuré à l’installation du cluster). La requête va ensuite quitter le service à travers un port interne au service (différent du port extérieur) pour joindre le port TargetPort exposé par le pod. La requête pouvant ainsi être traitée par le pod. Si vous avez suivi, ce type de service exige autant de ports externes sur la plage 30000-32767 au niveau de votre serveur qu’il y’a de service à exposer.
- Type LoadBalancer: c’est un type un peu particulier qui est utilisé principalement par les cloud provider dans leur offre Kubernetes As A Service. Il repose sur le même principe que le nodePort mais va automatiquement provoquer la création d’un IP de loadbalancing dans l’écosystème du cloud provider. Cette IP accessible depuis l’extérieur va prendre connaissance du port « externe » utilisé par le service pour “load balancer” les requêtes vers les différents serveurs sur le port en question qui hébergent l’application. Déclarer un service de ce type sur une infra interne aura la plupart du temps le même effet que le type « nodeport », mais il existe des projets comme MetalLB qui cherche à proposer le même fonctionnement en onprem qu’avec un provider cloud.

Cliquez sur l'image pour l'agrandir.

Cliquez sur l'image pour l'agrandir.

Cliquez sur l'image pour l'agrandir.
Personnellement, en interne j’ai plutôt tendance à associer un load balancer autonome hors kubernetes qui renvoie vers des ports type 443/80 pour lesquelles j’ai configuré un Ingress Controler, qui lui-même pilote des Ingress rattaché à des Services type ClusterIP pour router mes demandes fonction de mes urls. Mais ce n’est pas l’objectif de l’article.
Manipulations
Déclarer un service se fait comme d’habitude avec un fichier yaml.
Exemple pour un ClusterIP:
---
apiVersion: v1
kind: Service
metadata:
name: mon-service-cluster-ip
spec:
selector:
app: ma-super-app
type: ClusterIP
ports:
- name: http
port: 80
targetPort: 8080
protocol: TCP
Exemple pour un NodePort:
---
apiVersion: v1
kind: Service
metadata:
name: mon-service-node-port
spec:
ports:
- port: 80
protocol: TCP
targetPort: 8080
nodePort: 32765
selector:
app: ma-super-app
type: NodePort
Exemple pour LoadBalancer:
---
apiVersion: v1
kind: Service
metadata:
name: mon-service-node-loadbalancer
spec:
ports:
- port: 80
protocol: TCP
targetPort: 8080
nodePort: 30216
selector:
run: ma-super-app
type: LoadBalancer
La section spec de l’objet permet d’indiquer le type de Service. Fonction du type de Service, certaines options peuvent différées les unes des autres avec néanmoins toujours une base commune.
Comme expliqué pour l’objet Deployment, kubernetes se base sur des labels pour associer les objets entre eux. Il en va de même pour les Services pour lesquels il faudra renseigner dans la section selection les labels des pods pour lesquels ils vont devoir fournir l’accessibilité.
C'est de cette manière qu'un service se raccroche à un pod.
Si vous voulez exposer un pod « ma-super-apps » via un service type NodePort , il faudra que votre pod « ma-super-apps » dispose des labels correspondant aux règles de sélection de votre service.
Le déploiement d'un service à partir de son fichier yaml est à l'identique de tous objets kubernetes:
kubectl apply -f monservice.yaml

Cliquez sur l'image pour l'agrandir.
une fois vos Services en place, il suffit d'utiliser la commande suivante pour les lister:
kubectl get svc

Cliquez sur l'image pour l'agrandir.
(pour l'illustration j'utilise un namespace spécifique d'ou l'ajout du suffixe -n)
Davantage de détails vous sont accessibles, si vous utilisez la commande:
kubectl describe mon_service

Cliquez sur l'image pour l'agrandir.
Vous y retrouverez l'IP de votre service et les labels qu'il utilise pour identifier les pods auquels il doit se rattacher.
Très pratique également la commande:
kubectl get endpoints
Elle vous permet de savoir si votre service est "orphelin" n'ayant trouvé aucun pod correspondant à ce qu'il a dans sa propriété selector

Cliquez sur l'image pour l'agrandir.
ou si au contraire il est bien rattaché à un ou des pods avec dans ce cas la liste des IPs de ces pods dans la colonne "ENDPOINT"

Cliquez sur l'image pour l'agrandir.
Conclusion
Les Service ont un rôle capital au sein de l’infrastructure kubernetes. Peu importe le type choisi, il est important de bien comprendre la logique de routage interne à K8S.
En dehors même de la problématique de choisir un provider réseaux au sein de votre cluster, gardez en tête quelques principes de bases, à commencer par l’existence d’un podSubnet et d’un serviceSubnet au sein de votre cluster.
Dans les deux cas, il s’agit de réseau interne à votre cluster qu’il vous est possible de choisir au déploiement de ce dernier (ne choisissez donc jamais des réseaux routés existants dans votre infra pour cela).
Chaque pod se voit assigner une IP issue du podSubnet et chaque service se voit associer une IP issue du serviceSubnet.
La communication d’un service et d’un pod se fait donc via un routage entre ces deux subnets avec l’usage d’un port de sortie propre au service et un port d’entrée propre au pod.
C’est la machinerie interne à K8S, le choix d’un service type ClusterIP, NodePort, LoadBalancer est surtout lié à la manière dont vous allez exposer votre service et à son périmètre d'exposition.
Globalement on retrouve dans les Services, des notions propres au load balancing. Pour ceux qui manipuleraient des outils de ce type (HAproxy, Netscaler, F5…), vous devriez retrouver des logiques communes.