Skip to content

Déployer une application

Considérons un premier pod, Redis

yaml
# redis-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: "redis"
  labels:
    app: "redis"
    tier: "backend"
spec:
  containers:
    - name: redis
      image: "redis"
      ports:
        - containerPort: 6379

et son service associé

yaml
# redis-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: redis
  labels:
    app: redis
    tier: backend
spec:
  ports:
    - port: 6379
      targetPort: 6379
  selector:
    app: redis
    tier: backend

Considérons un second pod, guestbook

yaml
# guestbook-pod.yaml
apiVersion: v1
kind: Pod
metadata: 
  name: guestbook
  labels:
    app: guestbook
    tier: frontend
spec:
  containers:
    - name: guestbook
      image: my-provider/my-image 
      imagePullPolicy: Always
      ports:
      - name: http-server
        containerPort: 3000

et son service associé

yaml
# guestbook-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: frontend
  labels:
    app: guestbook 
    tier: frontend 
spec:
  ports:
    - port: 3000 
      targetPort: http-server 
  selector:
    app: guestbook 
  type: LoadBalancer

Qu'est-ce qu'un service ?

Dans un cluster Kubernetes (k8s), un service est une abstraction qui permet de définir une manière stable et accessible d’exposer un ensemble de pods, malgré leur caractère éphémère et dynamique. Il permet de connecter les applications entre elles ou de rendre un service accessible à l'extérieur du cluster, sans se soucier des adresses IP des pods individuels qui peuvent changer.

Voici les points clés à comprendre à propos d'un service dans Kubernetes :

Pods et IP instables

Les pods, qui sont des unités de base d'exécution dans Kubernetes, peuvent être créés et supprimés fréquemment. Chaque fois qu'un pod est recréé, il reçoit une nouvelle adresse IP. Si une application tente d'accéder à un pod directement via son IP, il est difficile de maintenir la connexion stable à cause de ce comportement dynamique.

Abstraction du service

Le service résout ce problème en agissant comme un point d'accès fixe et stable (via une IP virtuelle ou un nom DNS). Il crée un "groupement" de pods en fonction de labels (étiquettes) et permet aux applications de se connecter à ce groupement, sans avoir à se soucier de l'IP des pods individuels. Le service se charge de rediriger le trafic vers les pods disponibles.

Types de service

Il existe plusieurs types de services dans Kubernetes :

  • ClusterIP : Le service est accessible uniquement à l’intérieur du cluster, via une adresse IP interne. C'est le type par défaut.
  • NodePort : Le service expose une application sur un port spécifique de chaque nœud (machine) du cluster, ce qui permet un accès externe au cluster.
  • LoadBalancer : Le service demande à un fournisseur de cloud (comme AWS, GCP, Azure) de créer un équilibreur de charge (load balancer) qui dirige le trafic vers les nœuds du cluster.
  • ExternalName : Ce type de service permet de résoudre un nom DNS externe à l'extérieur du cluster.

Sélection des pods

Un service utilise un sélecteur de labels pour identifier quel ensemble de pods est associé à ce service. Par exemple, un service peut sélectionner tous les pods avec l’étiquette app=backend et rediriger tout le trafic vers ces pods.

Proxy et équilibrage de charge

Le service agit aussi comme un équilibreur de charge au sein du cluster. Lorsque plusieurs pods sont sélectionnés par le service, Kubernetes répartit le trafic réseau entrant vers ces pods en équilibrant la charge de manière efficace (généralement en utilisant un round-robin simple).

Best practices

Il est considéré comme une bonne pratique d'éclater la déclaration des différentes ressources Kubernetes (comme les Pods, Services, ConfigMaps, Ingress, etc.) en plusieurs fichiers YAML, plutôt que de tout mettre dans un seul fichier. Voici les raisons principales et quelques suggestions pratiques pour organiser vos fichiers Kubernetes.

Séparer les ressources dans plusieurs fichiers ?

Fragmenter la configuration en de multiples fichiers yml présente les vertus suivantes :

-> Lisibilité et maintenance

  • Un fichier unique peut rapidement devenir complexe et difficile à lire lorsque plusieurs types de ressources y sont déclarés (Pods, Services, ConfigMaps, etc.).
  • En séparant les ressources dans des fichiers distincts, chaque fichier est plus simple et clair. Il est plus facile de repérer des erreurs et de comprendre ce que chaque fichier fait.

-> Modularité

  • Si vous avez besoin de modifier ou mettre à jour une seule ressource, vous n'avez pas à manipuler un fichier géant. Cela permet de mieux gérer les mises à jour et modifications.

  • Vous pouvez réutiliser certains fichiers dans différents environnements ou configurations (par exemple, les fichiers de service, les fichiers de configuration pour des environnements de staging ou de production). -> Versionning

  • Dans un système de contrôle de version (comme Git), il est plus pratique d’avoir des fichiers séparés, car les changements dans les ressources sont plus faciles à suivre. Si tout est dans un fichier unique, les conflits de fusion peuvent devenir un problème.

-> Automatisation

  • Si vous utilisez des outils comme Helm, Kustomize, ou des pipelines CI/CD, la séparation en plusieurs fichiers rend l'intégration plus fluide. Ces outils sont conçus pour travailler avec des fichiers YAML fragmentés.

-> Exemple de structuration


my-project/
  ├── deployments/
  │     ├── app-deployment.yaml
  │     └── redis-deployment.yaml
  ├── services/
  │     ├── app-service.yaml
  │     └── redis-service.yaml
  ├── configmaps/
  │     └── app-config.yaml
  ├── secrets/
  │     └── redis-secret.yaml
  ├── persistent-volumes/
  │     └── redis-pv.yaml
  ├── ingress/
        └── ingress.yaml

Comment appliquer plusieurs fichiers ?

Vous pouvez appliquer tous vos fichiers en même temps en spécifiant le dossier où ils sont stockés, par exemple :

sh
kubectl apply -f my-project/

Mise en pratique

  • Déployez le pod redis
sh
kubectl apply -f redis-pod.yaml
# pod/redis created

NOTE

Si Minikube n'est pas démarré vous obtiendrez l'erreur suivante

error: error validating "redis-pod.yml": error validating data: failed to download openapi: Get "https://192.168.49.2:8443/openapi/v2?timeout=32s": dial tcp 192.168.49.2:8443: connect: no route to host; if you choose to ignore these errors, turn validation off with --validate=false

  • Listez les pods
sh
kubectl get pods
# NAME    READY   STATUS    RESTARTS   AGE
# redis   1/1     Running   0          3m15s
  • Affichez les logs
sh
kubectl logs redis
# 1:C 12 Oct 2024 07:10:47.319 * oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
# 1:C 12 Oct 2024 07:10:47.319 * Redis version=7.4.1, bits=64, commit=00000000, modified=0, pid=1, just started
# 1:C 12 Oct 2024 07:10:47.319 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
# 1:M 12 Oct 2024 07:10:47.319 * monotonic clock: POSIX clock_gettime
# 1:M 12 Oct 2024 07:10:47.320 * Running mode=standalone, port=6379.
# 1:M 12 Oct 2024 07:10:47.320 * Server initialized
# 1:M 12 Oct 2024 07:10:47.320 * Ready to accept connections tcp

TIP

Rajouter -f à la commande permet de follow les logs

  • Déployez le service redis
sh
kubectl apply -f redis-service.yml
# service/redis created
  • Affichez ensuite la liste des services et notez l'ip de redis
sh
kubectl get services
# NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
# kubernetes   ClusterIP   10.96.0.1      <none>        443/TCP    36h
# redis        ClusterIP   10.98.12.103   <none>        6379/TCP   65s

TIP

kubectl a des raccourcis pour toutes ses commandes. services peut être remplacé par svc.

  • Visualisez les détails de fonctionnement du service
sh
kubectl describe svc redis
# Name:                     redis
# Namespace:                default
# Labels:                   app=redis
#                           tier=backend
# Annotations:              <none>
# Selector:                 app=redis,tier=backend
# Type:                     ClusterIP
# IP Family Policy:         SingleStack
# IP Families:              IPv4
# IP:                       10.98.12.103
# IPs:                      10.98.12.103
# Port:                     <unset>  6379/TCP
# TargetPort:               6379/TCP
# Endpoints:                10.244.0.8:6379
# Session Affinity:         None
# Internal Traffic Policy:  Cluster
# Events:                   <none>
  • Visualisez ensuite les détails du pod
sh
kubectl describe pod redis
# Name:             redis
# Namespace:        default
# Priority:         0
# Service Account:  default
# Node:             minikube/192.168.49.2
# Start Time:       Sat, 12 Oct 2024 09:10:38 +0200
# Labels:           app=redis
#                   tier=backend
# Annotations:      <none>
# Status:           Running
# IP:               10.244.0.8
# IPs:
#   IP:  10.244.0.8
# ...

On constante que le Endpoints: 10.244.0.8:6379 du service matche avec l'ip du pod redis IP: 10.244.0.8. La connexion entre le service et le pod est bien établie.

  • Déployez maintenant le pod de l'application en commençant par vous authentifier sur votre registry
sh
kubectl apply -f guestbook-pod.yaml
# pod/guestbook created

TIP

kubectl get pods -w permet de traquer tout changement sur les pods

NOTE

Pour pouvoir déployer une image issue d'un container privé il vous faudra suivre les instructions suivantes https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/

Il ne faudra pas oublier de modifier le guestbook-pod.yaml en conséquence et rajouter imagePullSecrets

  • Décrivez maintenant le pod que vous venez de déployer
sh
kubectl describe pod guestbook
# Events:
#   Type    Reason     Age   From               Message
#   ----    ------     ----  ----               -------
#   Normal  Scheduled  12s   default-scheduler  Successfully assigned default/guestbook to minikube
#   Normal  Pulling    12s   kubelet            Pulling image "registry.gitlab.com/gonzalez-marc/guestbook:latest"
#   Normal  Pulled     11s   kubelet            Successfully pulled image "registry.gitlab.com/gonzalez-marc/guestbook:latest" in 1.234s (1.234s including waiting). Image size: 10785720 bytes.
#   Normal  Created    11s   kubelet            Created container guestbook
#   Normal  Started    11s   kubelet            Started container guestbook
  • Déployez le service associé
sh
kubectl apply -f guestbook-service.yaml
  • Constatez son fonctionnement
sh
kubectl get svc
#  NAME         TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
#  frontend     LoadBalancer   10.105.8.91    <pending>     3000:30812/TCP   14s
#  kubernetes   ClusterIP      10.96.0.1      <none>        443/TCP          40h
#  redis        ClusterIP      10.98.12.103   <none>        6379/TCP         4h12m

On constate que c'est bien un service de type loadBalancer qui a été démarré dont l'ip externe est en pending. Le comportement est normal, nous n'avons rien qui soit capable de fournir du load balancing comme pourrait le faire un cloud provider. Dans un environnement de production il faudrait donc vérifier que l'external IP est bien fournie.

  • Maintenant que l'application est déployée, vérifions les logs de l'application fraîchement déployée
sh
kubectl logs guestbook -f
# kubectl logs guestbook -f
# [negroni] listening on :3000
  • Vérifiez que le service a bien une ip affectée, ici 10.244.0.11
sh
kubectl describe svc frontend
# Name:                     frontend
# Namespace:                default
# Labels:                   app=guestbook
#                           tier=frontend
# Annotations:              <none>
# Selector:                 app=guestbook
# Type:                     LoadBalancer
# IP Family Policy:         SingleStack
# IP Families:              IPv4
# IP:                       10.105.8.91
# IPs:                      10.105.8.91
# Port:                     <unset>  3000/TCP
# TargetPort:               http-server/TCP
# NodePort:                 <unset>  30812/TCP
# Endpoints:                10.244.0.11:3000
# Session Affinity:         None
# External Traffic Policy:  Cluster
# Internal Traffic Policy:  Cluster
# Events:                   <none>
  • Afin de simuler un load balancer, minikube fournit un tunnel capable de simuler le comportement
sh
minikube tunnel
# Status:
#         machine: minikube
#         pid: 985893
#         route: 10.96.0.0/12 -> 192.168.49.2
#         minikube: Running
#         services: [frontend]
#     errors:
#                 minikube: no errors
#                 router: no errors
#                 loadbalancer emulator: no errors
#
  • Le listing des services devrait désormais indiquer qu'une IP externe a bien été attribuée
sh
kubectl get svc 
# NAME         TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
# frontend     LoadBalancer   10.105.8.91    10.105.8.91   3000:30812/TCP   16m
# kubernetes   ClusterIP      10.96.0.1      <none>        443/TCP          41h
# redis        ClusterIP      10.98.12.103   <none>        6379/TCP         4h29m

L'application est désormais accessible sur http://10.105.8.91:3000/

  • Simulez maintenant l'absence d'un pod
sh
kubectl delete pod redis
# pod "redis" deleted
  • Si vous observez les logs, vous devriez rencontrer une erreur
sh
# [negroni] PANIC: dial tcp 10.98.12.103:6379: connect: connection refused
# goroutine 2650 [running]:
# github.com/codegangsta/negroni.(*Recovery).ServeHTTP.func1()
#         /go/pkg/mod/github.com/codegangsta/negroni@v1.0.0/recovery.go:159 +0xad
# panic({0x7ae840?, 0xc000250550?})
#         /usr/local/go/src/runtime/panic.go:785 +0x132
# main.HandleError(...)
#         /app/main.go:54
# ...
  • Rafraîchissez l'application, l'erreur suivante apparaît
Waiting for database connection...
  • Redéployez alors le pod
sh
kubectl apply -f redis-pod.yml
# pod/redis created
  • Vérifiez les logs, l'application devrait être de nouveau opérationnelle
sh
kubectl logs guestbook
# [negroni] 2024-10-12T12:15:30Z | 200 |   1.070653ms | 10.105.8.91:3000 | GET /lrange/guestbook
# [negroni] 2024-10-12T12:15:32Z | 200 |   1.420933ms | 10.105.8.91:3000 | GET /lrange/guestbook
# [negroni] 2024-10-12T12:15:32Z | 200 |   974.26µs | 10.105.8.91:3000 | GET /lrange/guestbook

Rafraîchissez http://10.105.8.91:3000/ et saisissez à nouveau des données

Gestion des labels

Les labels sont très pratiques pour manager les objects du cluster

-> tous les pods avec l'ensemble des labels

sh
kubectl get pods --show-labels
# NAME        READY   STATUS    RESTARTS   AGE    LABELS
# guestbook   1/1     Running   0          55m    app=guestbook,tier=frontend
# redis       1/1     Running   0          6m6s   app=redis,tier=backend

-> Filtrer sur un label en particulier

sh
kubectl get pods -l app=guestbook
# NAME        READY   STATUS    RESTARTS   AGE
# guestbook   1/1     Running   0          56m

Dashboard

Minikube intègre la fonctionnalité du dashboard. En production il faudra passer par un ensemble d'étapes pour en configurer l'accès

https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/

sh
minikube dashboard

La liste des pods devrait être accessible via dashboard sur l'url suivante http://127.0.0.1:38737/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/#/pod?namespace=default.

La section Events est particulièrement intéressante quand il s'agit de debugger ce qui se passe. http://127.0.0.1:38737/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/#/event?namespace=default