Gérer et manipuler un Service Kubernetes

Dans ce chapitre, nous verrons comment gérer plus efficacement le réseau de vos Pods grâce aux Services.

Introduction

Dans cet article, nous aborderons les services Kubernetes. Nous nous pencherons sur leur création et utilisation.

C'est quoi un service Kubernetes 🤔 ?

Un service peut être défini comme une abstraction par-dessus les Pods, qui attribue aux pods leurs propres adresses IP et un nom DNS unique, et peut aussi équilibrer la charge entre eux.

En effet, les pods ont une durée de vie très limité et sont à maintes reprises créés et détruits, ce qui par conséquent altère continuellement leurs adresses IP. Interviennent alors les services qui vont quant à eux permettre aux clients d'échanger de manière plus fiable avec les conteneurs s’exécutant dans le Pod à l’aide d'une adresse IP virtuelle statique grâce au travail du composant kube-proxy.

Il existe quatre types de services pour une utilisation particulière :

  • ClusterIP : C'est le type par défaut. Il expose le Service sur une adresse IP interne du cluster. De ce fait, le service n'est accessible que depuis l'intérieur du cluster.
  • NodePort : Il expose le service vers l'extérieur du cluster à l'aide du NAT (la plage de ports autorisés est entre 30000 et 32767).
  • LoadBalancer : Il utilise l’équilibreur de charge des fournisseurs de cloud. Ainsi, les services NodePort et ClusterIP sont créés automatiquement et sont acheminés par l'équilibreur de charge externe.
  • ExternalName : Ce service effectue une simple redirection CNAME (par exemple rediriger le trafic vers le nom de domaine "example.com").

Sans transition, commençons directement par la manipulation des services.

Manipulation des Services

Dans cet article nous n'étudierons que le type ClusterIP et NodePort qui sont les plus communément utilisés.

ClusterIP

Nous allons commencer par manipuler le service ClusterIP, utilisé par défaut sur Kubernetes.

Pour ce chapitre, j'utiliserai ma propre image hajdaini/flask avec le tag random qui permet d'afficher depuis une requête http, le nom du conteneur et une chaîne de caractères aléatoires ce qui aura pour but d'analyser plus facilement l'acheminement du trafic effectué par l'agent kube-proxy.

Créons d'abord notre template sous le format YAML de manière à construire notre propre service, avec le contenu suivant :

apiVersion: v1
kind: Service
metadata:
  name: flask-service
spec:
  type: ClusterIP
  selector:
    app: flask
  ports:
  - port: 5000
    targetPort: 5000
    name: flask-cluster-ip

Place maintenant aux explications :

type: ClusterIP

Sans surprise, c'est ici que nous définissons le type de notre service.


selector:
  app: flask

Ici, nous indiquons que le service flask-service ne sera utilisé que par les Pods ayant le label app: flask.


- port: 5000
  targetPort: 5000
  name: flask-cluster-ip

Dans cette phase, le targetPort est le port qui est utilisé par les Pods de notre Deployment et le port est le numéro de port qui sera utilisé par notre service afin de communiquer avec lui depuis l'intérieur de notre cluster K8.

Information

Sur docker ça correspond à targetPort:port de la commande docker run.

Dorénavant, nous allons exécuter la commande suivante dans le but de créer notre Service Kubernetes :

kubectl create -f service.yaml

Vérifions ensuite la liste des services disponibles dans notre cluster Kubernetes :

kubectl get svc

Résultat :

NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
flask-service   ClusterIP   10.102.220.242   <none>        5000/TCP   20h
kubernetes      ClusterIP   10.96.0.1        <none>        443/TCP    20d

Nous avons donc le service kubernetes qui est créé par défaut par K8s et notre service flask-service qui écoute sur l'IP 10.102.220.242 et sur le port 5000.

Ensuite, nous construisons notre Deployment qui utilisera mon image hajdaini/flask:random en deux répliques :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: flask-deployment
spec:
  selector:
    matchLabels:
      app: flask
  replicas: 2
  template:
    metadata:
      labels:
        app: flask
    spec:
      containers:
      - name: flask-random
        image: hajdaini/flask:random

Comme vous pouvez le constater, nous avons spécifié le label app: flask, afin qu'il soit pris en compte par notre Service flask-service. Dès lors, créons notre Deployment :

kubectl create -f flask.yaml

À présent, récupérons le nom des Pods de notre Deployment :

kubectl get pods -o wide

Résultat :

NAME                               READY   STATUS    RESTARTS   AGE     IP              NODE       NOMINATED NODE   READINESS GATES
flask-deployment-8cfb9777b-s8hnp   1/1     Running   0          3m58s   192.168.1.177   worker-1   <none>           <none>
flask-deployment-8cfb9777b-wdxt9   1/1     Running   0          3m58s   192.168.1.176   worker-1   <none>           <none>

Comme je l'ai précisé plus haut, le service de type ClusterIP n'est accessible que depuis l'intérieur de notre cluster. Je vais donc devoir passer par le protocole ssh depuis mon nœud dénommer worker-1 dans l'intention de communiquer avec mon service, comme suit :

ssh vagrant@[WORKER_IP] curl -s http://[CLUSTER_IP]:5000

Rappel

l'Ip de mon nœud worker-1 est 192.168.50.11.

Soit, dans mon cas :

ssh vagrant@192.168.50.11 curl -s http://10.102.220.242:5000

Résultat :

My name is flask-deployment-8cfb9777b-wdxt9 and I say to you "WVYVOS"

On remarque que c'est le Pod nommé flask-deployment-8cfb9777b-wdxt9 qui répond à notre requête depuis le service flask-service sur le port 5000.

Maintenant utilisons une boucle for afin de vérifier comment l'agent kube-proxy achemine notre trafic :

ssh vagrant@192.168.50.11 "for i in {1..5}; do curl -s http://10.102.220.242:5000 && echo ""; done"

Résultat :

My name is flask-deployment-8cfb9777b-s8hnp and I say to you "XJY8F9"
My name is flask-deployment-8cfb9777b-wdxt9 and I say to you "Z25FZC"
My name is flask-deployment-8cfb9777b-s8hnp and I say to you "UV41OH"
My name is flask-deployment-8cfb9777b-wdxt9 and I say to you "A4MKPX"
My name is flask-deployment-8cfb9777b-wdxt9 and I say to you "V802S9"

On peut s'apercevoir qu'un LoadBalancer très basique est adopté par l'agent kube-proxy afin de rediriger le trafic sur les différents Pods de notre Deployment.

À partir de maintenant , vous pouvez détruire et recréer vos Pods et communiquer avec eux sereinement, sans vous soucier de leur nouvelle adresse IP, puisque dorénavant vous utilisez l'adresse IP de votre ClusterIP, offrant ainsi une meilleure stabilité.

NodePort

Imaginons, que nous avons besoin cette fois-ci d'accéder à notre Pod depuis l'extérieur de notre cluster Kubernetes. Nous exploiterons ainsi cette le type NodePort.

Sans la nécessité de détruire notre ancien service, nous allons modifier et appliquer les changements de notre ancien template YAML, en suivant les étapes suivantes :

Premièrement modifions notre ancien template YAML, comme suit :

apiVersion: v1
kind: Service
metadata:
  name: flask-service
spec:
  type: NodePort
  selector:
    app: flask
  ports:
  - port: 5000
    targetPort: 5000
    nodePort: 30001
    name: flask-np

Voici quelques explications sur nos nouveaux changements :

type: NodePort

Ici, Nous changeons d'abord le type de notre service.


ports:
- port: 5000
  targetPort: 5000
  nodePort: 30001
  name: flask-np

Ensuite, dans cette étape, nous translatons l'IP du service vers l'IP du nœud qui accueillera nos Pods et le port du service (ici 5000) vers le port 30001.

Information

Pour rappel la plage d'IP dans un service de type NodePort se situe entre 30000 et 32767. On ne peut donc pas dépasser cette limite dans le champ nodePort.

Deuxièmement, appliquons les changements de notre service :

kubectl apply -f flask.yaml

Résultat :

service/flask-service configured

Vérifions ensuite la liste des services disponibles dans note cluster :

kubectl get svc

Résultat :

NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
flask-service   NodePort    10.102.220.242   <none>        5000:30001/TCP   21h
kubernetes      ClusterIP   10.96.0.1        <none>        443/TCP          20d

Désormais, nous somme capables d'accéder à nos Pods depuis l'extérieur de notre cluster à travers l'IP de notre nœud (dans mon cas c'est le nœud nommé worker-1), comme suit :

curl -s http://192.168.50.11:30001

Résultat :

My name is flask-deployment-8cfb9777b-nwhrl and I say to you "P68NVW"

La commande kubectl expose

Plutôt que de rédiger un template YAML de notre service, on peut créer un service à l'aide de la commande kubectl expose afin d'exposer les pods notre Deployment.

Par exemple pour exposer notre Deployment flask-deployment à l'aide d'un service de type ClusterIP, comme vu précédemment, nous suivrons les étapes suivantes :

D'abord je commence par supprimer mon service Kubernetes flask-service. Pour ce faire nous lançons la commande suivante :

kubectl delete svc flask-service

Après ceci, on expose les pods de notre Deployment :

kubectl expose deployment flask-deployment --name flask-service \               1 ↵
    --type ClusterIP --protocol TCP --port 5000 --target-port 5000 --selector='app=flask'

Voici la commande pour exposer notre Deployment avec un service de type NodePort :

kubectl expose deployment flask-deployment --name flask-service \
    --type NodePort --protocol TCP --port 5000 --target-port 5000 --selector='app=flask'

Conclusion

On peut constater que les services restent à bon moyen de gérer plus efficacement la partie réseau de nos Pods. Comme pour chaque fin d'article, voici un antisèche des commandes des services kubernetes :

# Afficher la liste des Services
kubectl get service [En option <SERVICE NAME>]

# Créer un Service depuis un template
kubectl create -f <template.yaml>

# Créer un Service depuis sans template
kubectl expose deployment <DEPLOYMENT NAME>
    --name : nom du service
    --type : type du service
    --protocol : protocole à utiliser (TCP/UDP) 
    --port : port utilisé par le service 
    --target-port : port utilisé utilisé par le Pod 
    --selector='clé=valeur': le sélecteur utilisé par service 

# Supprimer un Service
kubectl delete service <SERVICE NAME>

# Appliquer des nouveaux changements à votre Service sans le détruire
kubectl apply -f <template.yaml>

# Modifier et appliquer les changements de votre Service instantanément sans le détruire
kubectl edit service <SERVICE NAME>

# Afficher les détails d'un Service
kubectl describe service <SERVICE NAME>

Espace commentaire

Écrire un commentaire

Rejoignez la discussion

Vous devez être connecté pour poster un message.

22 commentaires

ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Pensez aussi à vérifier vos NetworkPolicies si vous avez un plugin CNI restrictif. Parfois le service est ok mais le trafic est bloqué au niveau du pod.

02/09/2019 à 07:58
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Vérifie le targetPort. Si ton application dans le conteneur écoute sur 80 et que tu as mis 5000, ça ne répondra jamais.

02/09/2019 à 01:18
marthe-robert
Membre Actif
Avatar de marthe-robert
marthe-robert
Membre Actif

J'ai une erreur Connection refused sur le port 5000 alors que le pod est Running. Une piste ?

01/09/2019 à 20:32
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Non. En prod, privilégie toujours l'IaC avec tes fichiers YAML versionnés dans Git. kubectl expose c'est bien pour tester en dev ou débugger rapidement.

01/09/2019 à 14:53
bouvet-vincent
Membre Actif
Avatar de bouvet-vincent
bouvet-vincent
Membre Actif

Le kubectl expose est beaucoup plus rapide que d'écrire le YAML à la main. C'est considéré comme une bonne pratique en prod ?

01/09/2019 à 09:42
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Supprime simplement le champ nodePort de ton fichier YAML. Kubernetes t'en attribuera un automatiquement dans la plage autorisée.

01/09/2019 à 04:37
marc-sanchez
Membre Actif
Avatar de marc-sanchez
marc-sanchez
Membre Actif

J'ai un conflit de port 30001. Est-ce que je peux laisser Kubernetes choisir un port aléatoire ?

31/08/2019 à 22:24
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Oui, utilise kubectl edit service flask-service et change le champ type. K8s mettra à jour la config tout seul sans tout casser.

31/08/2019 à 18:23
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Salut @melhamdi Tu ne peux pas utiliser matchLabels dans le fichier de configuration d'un Service car il n'est pas encore pris en charge par ce type de ressource k8s, donc à la place tu peux simplement ajouter les labels que tu souhaites.

31/08/2019 à 13:28
alix-alves
Membre Actif
Avatar de alix-alves
alix-alves
Membre Actif

Est-ce qu'on peut changer le type de service sans supprimer le service ?

31/08/2019 à 11:38
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

C'est souvent un finalizer qui bloque. Essaie de patcher le service pour supprimer le finalizer :

kubectl patch svc flask-service -p '{"metadata":{"finalizers":null}}' --type=merge
31/08/2019 à 07:15
gaudin-caroline
Membre Actif
Avatar de gaudin-caroline
gaudin-caroline
Membre Actif

J'ai fait un kubectl delete svc mais il reste en Terminating. Comment forcer la suppression ?

31/08/2019 à 01:00
tnavarro
Membre Actif
Avatar de tnavarro
tnavarro
Membre Actif

J'ai une erreur au déploiement du Deployment : ImagePullBackOff. Le service est bien créé mais il ne pointe sur rien.

30/08/2019 à 18:33
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Ajoute simplement cette ligne dans ton spec :

sessionAffinity: ClientIP

Ça gardera le client sur le même pod pendant toute la session.

30/08/2019 à 13:45
lacombe-colette
Membre Actif
Avatar de lacombe-colette
lacombe-colette
Membre Actif

J'ai testé la boucle for pour le load balancing, ça répartit bien le trafic. Mais comment forcer le sessionAffinity ?

30/08/2019 à 09:02
melhamdi
Membre
Avatar de melhamdi
melhamdi
Membre

bonjour,

une question par rapport aux selecteurs,

pourqoui on utilse parfois le "selector" avec "matchlabels" et parfois on met directement le "selector" sans "matchlabels" ?

Merci d'avance

30/08/2019 à 06:20
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Utilise kubectl describe service flask-service et regarde la section Endpoints. Si elle est vide, c'est que ton label app: flask n'est pas présent sur tes pods.

30/08/2019 à 04:56
adele-dupre
Membre Actif
Avatar de adele-dupre
adele-dupre
Membre Actif

Comment je fais pour vérifier quels pods sont réellement derrière mon service ? J'ai l'impression que mon selector ne matche rien.

29/08/2019 à 22:03
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Oui c'est normal, le ClusterIP est une IP virtuelle routée uniquement à l'intérieur du réseau du cluster. Pour accéder depuis l'extérieur, il faut passer par un NodePort ou un LoadBalancer.

29/08/2019 à 14:57
eleonore-vallet
Membre Actif
Avatar de eleonore-vallet
eleonore-vallet
Membre Actif

Top le tuto. Par contre, mon ClusterIP est bien créé mais je n'arrive pas à ping l'adresse IP interne depuis mon laptop. C'est normal ?

29/08/2019 à 07:08
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Vérifie bien ta plage de ports autorisés. Le NodePort est limité entre 30000 et 32767. Si tu essaies de sortir de cette plage sans configurer kube-apiserver, ça passera jamais.

29/08/2019 à 01:14
hugues17
Membre Actif
Avatar de hugues17
hugues17
Membre Actif

J'essaie de configurer mon NodePort sur 30001 comme dans ton exemple mais j'ai une erreur Forbidden. Une idée ?

28/08/2019 à 19:10

Rejoindre la communauté

Recevoir les derniers articles gratuitement en créant un compte !

S'inscrire