Déployer une application
Considérons un premier pod, Redis
# redis-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: "redis"
labels:
app: "redis"
tier: "backend"
spec:
containers:
- name: redis
image: "redis"
ports:
- containerPort: 6379et son service associé
# 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: backendConsidérons un second pod, guestbook
# 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: 3000et son service associé
# 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: LoadBalancerQu'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.yamlComment 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 :
kubectl apply -f my-project/Mise en pratique
- Déployez le pod redis
kubectl apply -f redis-pod.yaml
# pod/redis createdNOTE
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
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# redis 1/1 Running 0 3m15s- Affichez les logs
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 tcpTIP
Rajouter -f à la commande permet de follow les logs
- Déployez le service redis
kubectl apply -f redis-service.yml
# service/redis created- Affichez ensuite la liste des services et notez l'ip de redis
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 65sTIP
kubectl a des raccourcis pour toutes ses commandes. services peut être remplacé par svc.
- Visualisez les détails de fonctionnement du service
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
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
kubectl apply -f guestbook-pod.yaml
# pod/guestbook createdTIP
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
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é
kubectl apply -f guestbook-service.yaml- Constatez son fonctionnement
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 4h12mOn 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
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
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
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
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 4h29mL'application est désormais accessible sur http://10.105.8.91:3000/
- Simulez maintenant l'absence d'un pod
kubectl delete pod redis
# pod "redis" deleted- Si vous observez les logs, vous devriez rencontrer une erreur
# [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
kubectl apply -f redis-pod.yml
# pod/redis created- Vérifiez les logs, l'application devrait être de nouveau opérationnelle
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/guestbookRafraî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
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
kubectl get pods -l app=guestbook
# NAME READY STATUS RESTARTS AGE
# guestbook 1/1 Running 0 56mDashboard
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/
minikube dashboardLa 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