TP initiation Kubernetes (J2)
Objectifs
Vous savez dorénavant déployer et administrer un applicatif simple sur Kubernetes. Mais les applicatifs ne se limitent souvent pas qu'à un simple Front. Sur les projets, vous aurez par exemple besoin de créer des APIs, des bases de données, que vos données soient persistantes, de gérer des brokers, etc.
L'architecture la plus simple que l'on retrouve dans la majorité des projets est l'architecture 3 tiers. Elle contient les éléments suivants :
- Un front
- Une API
- Une base de données
Aujourd'hui, l'objectif est de mettre en application ce qui a été appris pour déployer un applicatif 3 tiers dans Kubernetes. Rien que ça !
Étant donné que chaque ressource Kubernetes correspond à un fichier yaml, vous allez utiliser une arborescence de dossiers précise et créer les dossiers suivants pour bien organiser vos fichiers :
Pour la suite du TP, un minimum de ressources concernant chaque service sera fourni et çe sera à vous de créer les ressources Kubernetes nécessaires !
Amusez-vous bien !
1ère étape : déployer l’API
Ressources nécessaires
L’image API à utiliser est la suivante :
registry.gitlab.com/takima-school/images/cdb/api:latest
Check
Attention, cette image est sur un registry qui nécessite des credentials (le secret takima-school-registry
placé dans votre namespace)
Info
L'image contient une API Java Spring Boot et expose ses services sur le port 8080.
Voici les variables d'environnements disponibles sur API :
Warning
La variable DB_ENDPOINT
prend pour cette image la forme host:port
! Pas besoin de définir un protocol.
Vous utiliserez les valeurs précises lors de l'étape 3.
À vous de jouer
- Créez le fichier
api-deployment.yaml
associé à cette image et déployez-le. - Créez le fichier
api-service.yaml
associé au Deployment et déployez-le. - Créez le fichier
api-ingress.yaml
pour accéder à ce service et déployez-le.
Question
Que se passe-t-il au niveau des Pods de l’API ? Vous pouvez jeter un œil aux logs. (kubectl logs -f nomdupod
)
L’API a donc besoin d’une base de données pour fonctionner. Vous vous doutez maintenant à quoi servent les variables d'environnements utilisables, nous les utiliserons plus tard.
Check
Vous devez avoir les fichiers suivants dans le dossier api
:
2ᵉ étape : créer la base de données
Comme l'API a besoin d’une base de données, il faut lui en fournir une. L’API utilise un SGBDR (Système de Gestion de Base de Données Relationnelles) Postgres.
Ressources nécessaires
Image de la base de données :
registry.takima.io/school/proxy/postgres:latest
Info
Postgres est accessible depuis le port 5432.
Variables d'environnement utilisables pour l'image postgres :
À vous de jouer
- Créez la ressource de type Secret :
pg-credentials.yaml
nommé “pg-credentials” contenant le username et le password (choisissez celui que vous souhaitez utiliser, mais attention, n’oubliez pas d'encoder les valeurs en base64, comme lors de la fin du tp 1). - Créez un Configmap :
pg-config.yaml
nommé “pg-config” contenant le nom de la base de données, nommer la base de données “cdb-db”. - Créez le Deployment :
pg-deployment.yaml
associé à l'image de la base de données. - Créez le Service associé au Deployment : pg-service.yaml (le container écoute sur le port 5432).
Info
Pour le Deployment :
- Bien penser à mapper les variables d'environnement dans le Deployment avec le ConfigMap et le Secret.
- Penser aux credentials pour pouvoir pull sur le registry privé.
Pour vérifier que la base de données fonctionne, vous pouvez afficher les logs et constater qu’elle est bien démarrée.
Check
Vous devez avoir 4 fichiers dans le dossier database
:
Info
L'initialisation de la base de donnée ainsi que du modèle de base de donnée est faite par l'API lors de son démarrage via la librairie Flyway.
Votre base est donc vide pour le moment tant qu'une API n'aura pas réussis à démarrer et se connecter à la DB. Ça tombe bien nous allons faire ça dans la prochaine étape.
Bonus
Consulter la base de données
Pour ce faire, vous devez réaliser les étapes suivantes :
- Utiliser la commande
kubectl exec
pour se connecter à Postgres. - Lancer la commande
psql
pour interagir avec la base. Elle devrait avoir la forme suivante :
Vous pouvez ensuite effectuer les actions suivantes :
- Lister les bases avec
\d
- Changer la base de données actuelle vers cdb-db :
\c DB_NAME
- Lister les tables avec
\l
3ᵉ étape : Faire pointer l’API sur la base de données
Ressources nécessaires
Vous disposez maintenant d'une base de données et d'une API, mais vous n’avez pas encore configuré le lien entre ces deux services : l’API doit se connecter à la base de données Postgres pour fonctionner. Les variables d’environnements reconnues par l’image de l’API seront utilisées :
L’API est une application qui tourne dans Kubernetes, il est donc possible pour elle d’attaquer la base de données sur son Service (en interne dans Kubernetes). Pour qu’un Pod puisse joindre un service du même Cluster Kubernetes, il doit simplement indiquer le nom du service ainsi que le namespace dans lequel est le service.
La nomenclature est la suivante : mon-service.mon-namespace
Exemple
Une API publiée derrière un service nommé api
dans le namespace “test” peut être requêtée de cette manière :
http://api.test
Pour la base de données, le fonctionnement est identique (sans http bien sûr, car ce n’est pas un serveur Web !).
D’ailleurs, quand l'application doit attaquer un service dans le même namespace, vous n'êtes même pas obligé d’indiquer le nom du namespace !
Info
Les services se voient attribuer un enregistrement de type DNS A
et ont un nom qui a pour forme : mon-service.mon-namespace.svc.cluster.local
. La résolution de ce nom donne l'adresse ClusterIP du service.
Plus d'informations sur l'attribution de DNS pour les services.
Question
Quel est le nom du service de la base de données ?
Avec ces indications, vous devriez être capable de créer un ConfigMap avec la variable DB_ENDPOINT
.
N'oubliez pas le port.
Remarquez que les autres variables POSTGRES_USER
et POSTGRES_PASSWORD
sont déjà présentes dans le Secret pg-credentials
créé dans la partie 2. Vous pourrez donc le réutiliser tel quel. De même pour POSTGRES_DB
Info
Lorsque vous utilisez des variables d'environnement, il faut toujours se demander si elle n'existe pas déjà dans un configmap existant, pour ne pas avoir de redondance et donc 2 endroits à changer en cas de modification. Ici bien configurer et réutiliser les variables dans les configMap : api-config, pg-config et secret : pg-credentials
À vous de jouer
- Créez le ConfigMap api-config.yaml avec les clefs DB_ENDPOINT et POSTGRES_DB et déployez-le.
- Éditez votre fichier Deployment de l’API
api-deployment.yaml
pour utiliser les 4 variables d’environnement (depuis le configMap api-config ET le Secret pg-credentials) et appliquez cette modification.
Votre API est maintenant fonctionnelle avec une base de données !
Requêtez votre API :
http://api.votre_nom.sessionX.takima.school/computers
Check
Vous devez avoir 1 nouveau fichier dans le dossier api
:
api-config.yaml
Vous devez avoir modifié un fichier existant :
api-deployment.yaml
4ᵉ étape : Rendre votre deployment parfait !
Les requests / limits
Maintenant, vous pouvez remettre des requests / limits adaptées à l'application et à son profil. Pour les déterminer efficacement, il faudrait profiler l'application.
Ici, on vous propose en request / limit :
- pour l'API
- memory : 192M / 256M
- cpu : 100m / 2
- pour la base de donnée
- memory : 192M / 256M
- cpu : 100m / 1
Les Sondes (Probes)
Afin d'améliorer la gestion du cycle de vie de l'application, nous devons définir les probes. Ça tombe bien, pour l'API, Spring Boot a déjà exposé des endpoints dédiés :
/actuator/health/liveness
pour la sonde de liveness/actuator/health/readiness
pour la sonde de readiness
Configurez correctement la startup, liveness et readiness probes pour être au top !
La sécurité
Jusqu'à maintenant, on ne se souciait pas de la sécurité dans nos containers. Par défaut, tous les containers sont lancés en root, ce qui n'est pas une bonne pratique de sécurité. Par chance, les images de votre application computer-database sont déjà prêtes à fonctionner avec un utilisateur.
- l'api peut fonctionner avec le UID 1001 et le GID 1001
- le front peut fonctionner avec le UID 101 et le GID 101
Mettez en œuvre le maximum de critères de sécurité pour votre application sur vos deployments. Appuyez-vous de la documentation officielle de Kubernetes.
5ᵉ étape : C'est au tour du Front.
C’est finalement le plus simple, car le Front n’a pas besoin d’attaquer de service interne.
Ressources nécessaires
L’image Front à utiliser est la suivante : registry.gitlab.com/takima-school/images/cdb/www:latest
Info
L'image contient un Nginx qui expose un index.html sur le port 8080.
Variables d'environnements utilisables :
Tip
Attention ! Ici, c'est l’URL qui sera utilisée côté navigateur client pour récupérer les informations de l’API. Il faudra donc indiquer l’URL (host) de l’ingress et non pas le nom du service interne Kubernetes de l’API.
Il existe un /health
pour la readiness probe.
À vous de jouer
- Créez le ConfigMap : front-config.yaml associé à la documentation (avec la valeur de l’API URL que vous avez indiqué dans l’Ingress de l’API).
- Créez le Deployment : front-deployment.yaml associé à cette image.
- Créez le Service : front-service.yaml associé à ce Deployment.
- Créez l’Ingress : front-ingress.yaml pour accéder à ce service (vous pouvez utiliser
front.votre_nom.sessionX.takima.school
par exemple)
Vous devriez pouvoir accéder à votre Front : http://front.votre_nom.sessionX.takima.school/
Parfait, vous avez maintenant un applicatif complet prêt à être utilisé. Enfin prêt… Pas tout à fait !
Il manque quelque chose d’important. Pour le constater :
- Sur votre navigateur internet, ajoutez un nouveau computer avec le bouton Add.
- Puis recherchez ce nouveau computer.
C’est parfait, rien d’anormal, il est bien là.
Maintenant, détruisez le Pod de la base de données Postgres (en utilisant Lens ou un kubectl delete pods…
).
Chouette, le Pod se reconstruit tout seul ! C’est génial, non ?
Une fois le Pod Postgres démarré, retournez sur votre navigateur internet et rafraichissez votre page...
Question
Pourquoi plus rien ne fonctionne ?
Pourquoi faut-il kubectl rollout restart deployment MON_API
Check
Vous devez avoir 4 nouveaux fichiers dans le dossier front
:
6ᵉ étape : La persistance dans Kubernetes
Les containers qui tournent par défaut sont donc Stateless. D'ailleurs, la configuration des Pods est immuable : on ne redémarre pas un Pod, on le détruit pour qu’un nouveau Pod réapparaisse. Ce qui a pour conséquence que quand on détruit un Pod, toutes ses données disparaissent avec lui...
Ce qu’on appelle la persistance des données est donc nécessaire pour éviter de perdre les nouveaux computers ajoutés.
Kubernetes apporte ce fonctionnement avec les ressources appelées Persistent Volume / Persistent Volume Claims.
Le Persistent Volume (PV) correspond à l’abstraction Kubernetes du volume physique mappé sur les serveurs, tandis que le Persistent Volume Claims (PVC) est une demande de stockage par un utilisateur. Il est similaire à un Pod. Les pods consomment des ressources de nœud et les PVC consomment des ressources PV. Les PVC peuvent demander une taille et des modes d'accès spécifiques (par exemple, ils peuvent être montés une fois en lecture/écriture ou plusieurs fois en lecture seule).
Pour définir les différentes propriétés et performance d'une PersistantVolume les administrateurs ou provider de Cloud mettent à disposition différents types de stockage. Pour définir ces types de stockage, il existe la ressource StorageClass de Kubernetes. Dans le cas du TP, le StorageClass est déjà implémenté : il s’agit de l’ElasticBlockStore (EBS) de type “gp2” (pour General Purpose) fourni par Amazon Web Service.
Il existe une multitude de Providers avec 3 types d’accès différents dont les noms parlent d’eux-mêmes :
- ReadWriteOnce
- ReadWriteMany
- ReadOnlyMany
Tableau très utile pour savoir les types d’accès permis par les Storage Providers.
Question
D’après le tableau, quel est le type d’accès implémenté par notre Storage Class EBS ? Pourquoi cela convient parfaitement pour la persistance de la base de données Postgres ?
Pour ajouter de la persistance dans un déploiement, il faut :
- Créer la ressource de Type PV / PVC.
- Utiliser la ressource PVC dans notre Deployment.
Note
La relation PV - PVC est une relation 1 - 1 et la création d’un PVC utilisant un Storage Class créera automatiquement le PV associé.
À vous de jouer
-
Créez la ressource PVC
pg-pvc.yaml
et déployez-la :Question
Vérifiez que le PVC est créé avec le PV. Quel est le nom du PV ?
Le volume est bien instancié, mais il n’est pas utilisé par un Pod pour le moment.
-
Montez le nouveau PVC dans le Pod Postgres :
Pour consommer un PVC dans un Pod, il faut le décrire dans la ressource Deployment. Il y a deux ajouts à effectuer :
-
La déclaration du Persistant Volume en tant que volume (définition de son nom).
-
Le montage de ce volume déclaré dans le Pod (sur un chemin (path) particulier du Container).
Voici le block de déclaration du volume sur le déploiement, à placer au niveau
Voici le block de point de montage du volume sur le Pod, à placer au niveauspec.template.spec
du déploiement :spec.template.spec.containers
du déploiement :
Attention
Dans le cas précis d'une base de données Postgres, une action de plus est nécessaire, car Postgres n'accepte pas d'avoir un dossier non vide pour s'initialiser, or le storage AWS est livré avec un dossier "lost/found". Vous pouvez le constater si vous lancez votre déploiement sans configurer ce qui va suivre : votre base de données va retourner une erreur. Pour éviter cela, nous allons installer Postgres dans un sous-chemin de notre point de montage. Notre image Postgres dispose d'une variable d'environnement PGDATA
qui permet de configurer ce comportement.
-
Modifiez le ConfigMap pg-config.yaml et ajoutez la key:value suivante :
-
Utilisez ce nouveau paramètre dans le Deployment de votre base de données :
Check
Vous devez avoir 1 nouveau fichier dans le dossier database
:
pg-pvc.yaml
Vous devez avoir modifié deux fichiers existants dans le dossier database
:
pg-deployment.yaml
pg-config.yaml
Vous pouvez maintenant essayer de supprimer un ordinateur sur le Front, puis supprimer le pod de la base de données et constater la persistance des données.
Bonus 1 : Administration de la base de données
Admin de la DB
Vous allez maintenant déployer un service permettant d’administrer la base de données. Il est possible de définir plusieurs ressources Kubernetes dans un unique fichier YAML.
Insérez ces ressources dans le fichier database/pgadmin.yaml
et appliquez-le.
La publication du service n'est pas souhaitée, car l’administration d’une base de données est une action sensible et doit se faire de manière sécurisée. Une fonctionnalité de l’outil Lens permet de monter une connexion distante sur un port local de votre ordinateur.
Au niveau Network → Service
, cliquez sur le service pgadmin.
Vous pourrez alors lancer le Port Forwarding. Cliquez dessus puis indiquez dans la fenêtre qui s’ouvre un port (par exemple 8081 si ce port n'est pas déjà utilisé sur votre machine) et cochez "open in browser" (pas besoin d'utiliser https ici).
Cette action devrait ouvrir votre navigateur par défaut et la page de pgAdmin.
Indiquez les user/password (dans l'exemple du TP : admin@takima.io / admin123*)
pgAdmin est une application qui tourne dans Kubernetes, il est donc possible pour elle d’attaquer la base de données via son Service. Rappel : pour qu’un Pod puisse joindre un service du même cluster Kubernetes, il doit simplement indiquer le nom du service ainsi que le Namespace dans lequel est le service.
Info
Fonctionnement de Service attaché à un Pod vu à l'étape 3
Toujours dans pgAdmin :
- Cliquez droit sur “servers” → “create” → “server”
- Renseignez le host avec “postres.votre_namespace”
- Indiquez les user/password
- Utilisez le bouton Save
Vous aurez alors une connexion à votre base de données. Vous retrouverez votre base avec le nom indiqué dans votre ConfigMap. Un initDb est utilisé dans l'image et a dû créer 2 tables dans le schéma public :
- Computer
- Company
- Operation
- Users
Bonus 2 : Les StatefulSets
Nous avons créé une database avec un deployment associé à un persistant volume. Cela fonctionne, mais quand on essaie de scaler le déploiement, on se retrouve bloqué, car en mode ReadWriteOnce, le volume ne peut pas être monté sur plusieurs pods.
Kubernetes a prévu une ressource spécialement pour ce genre de déploiement : les StatefulSets
Avec ces ressources, plus besoin de créer au préalable un PVC pour être consommé dans le POD. On déclare directement ce PVC dans la ressource StatefulSets
, et c'est celle-ci qui va piloter la création des PVC/PV
Mettons cela en pratique pour notre DB postgres.
-
Éditez une ressource StatefulSet
Le statefulSet fonctionne comme le deployment mais permet d'ajouter un block de provisioning de PVC
Ensuite le volume se consomme dans le container de la même manière (block
volumeMounts:
) -
Déployez cette ressource
- vérifiez qu'un PVC est bien provisionné, et observé son nom ainsi que le nom du pod créé.
- Essayez de scaler le statefulSet
Attention
Ici quand on scale, on a des postgres indépendant et pas un cluster Primary/Standby. Ils ne fonctionnent pas dans un même cluster postgres.
Bonus 3 : Operator Postgres
Comme vous avez pu le voir, nous pouvons désormais déployer une base de données Postgres et lui attacher un volume permettant la persistance de la donnée. Mais au-delà de vouloir sauvegarder la donnée, nous aimerions que la base soit accessible tout le temps, même si le pod venait à tomber. Heureusement pour nous, la communauté Kubernetes a travaillé dur pour fournir un Operator Postgres qui peut faire presque tout ce dont vous pouvez rêver (du moins, pour une base de données). Nous allons voir ça ensemble :
Déployez une ressource de type postgresql (c’est une CRD ajouté par l’operator)
Observez-les pods se créer : on peut voir le premier pod s’initialiser.
Observez les logs de ce premier pod, on doit retrouver à plusieurs reprise le message suivant :
Le deuxième pod (en mode standby) va également se créer dans un second temps (les pods se lancent les uns après les autres. C’est une particularité desStatefulSet
). Allez voir les logs du pod.
On doit retrouver son initialisation sur le master :
2022-02-02 18:24:39,452 INFO: trying to bootstrap from leader 'formation-cdb-0'
1024+0 records in
1024+0 records out
16777216 bytes (17 MB, 16 MiB) copied, 0.0123499 s, 1.4 GB/s
NOTICE: all required WAL segments have been archived
2022-02-02 18:24:41,013 INFO: replica has been created using basebackup_fast_xlog
2022-02-02 18:24:41,014 INFO: bootstrapped from leader 'formation-cdb-0'
Puis des logs indiquant qu’il est un réplica et qu’il suit le leader :
Simulons une perte du master
Détruisez le pod faisant tourner le postgres master et préparez-vous à observer les logs du pod replica.
On doit constater qu’il détecte quasi instantanément la perte du nœud primaire et qu’il va faire ce qu’on appelle une promotion pour devenir le nouveau nœud primaire. Après l’initialisation, on doit retrouver dans les logs :
On a donc bien un cluster avec de la haute disponibilité.
Pour aller plus loin
Supprimez votre ressource PostgresSQL (cela prend un peu de temps avec les StatefulSet
. Attendez que les pods disparaissent).
Éditez ensuite votre ressource yaml postgresql pour y ajouter la configuration suivante :
Puis redéployez cette ressource postgres.
Vous devez constater qu’une nouvelle ressource kubernetes est présente : le Cronjob.
C’est lui qui déclenche les backups logiques : déclenchez maintenant le job manuellement.
Un pod va être provisionné, faire un backup du postgres et envoyer tout cela dans un storage S3 (ce n’est pas magique tout a été configuré dans l’opérateur).
Demandez à un intervenant de voir si le backup a bien été réceptionné dans S3.