Définition K8S : Le Service

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.
  • Service de type ClusterIP

    Cliquez sur l'image pour l'agrandir.

  • 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.
  • Service de type NodePort

    Cliquez sur l'image pour l'agrandir.

  • 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.
  • Service de type LoadBalancer

    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
Deploiement d'un service

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
Lister les services

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
Obtenir le détail d'un 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

Service sans Endpoint

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"

Service avec 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.