Skip to content

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 :

.
└── TP-kube-02
    ├── api
    ├── database
    └── front

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 :

DB_ENDPOINT
POSTGRES_DB
POSTGRES_USER
POSTGRES_PASSWORD

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

  1. Créez le fichier api-deployment.yaml associé à cette image et déployez-le.
  2. Créez le fichier api-service.yaml associé au Deployment et déployez-le.
  3. 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 :

    .
    ├── api-deployment.yaml
    ├── api-ingress.yaml
    └── api-service.yaml

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 :

POSTGRES_PASSWORD
POSTGRES_USER
POSTGRES_DB

À vous de jouer

  1. 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).
  2. 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”.
  3. Créez le Deployment : pg-deployment.yaml associé à l'image de la base de données.
  4. Créez le Service associé au Deployment : pg-service.yaml (le container écoute sur le port 5432).

Info

Pour le Deployment :

  1. Bien penser à mapper les variables d'environnement dans le Deployment avec le ConfigMap et le Secret.
  2. 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 :

.
├── pg-credentials.yaml  
├── pg-config.yaml  
├── pg-deployment.yaml  
└── pg-service.yaml  

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 :
psql -d database -U user

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 :

DB_ENDPOINT
POSTGRES_DB
POSTGRES_USER
POSTGRES_PASSWORD

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

  1. Créez le ConfigMap api-config.yaml avec les clefs DB_ENDPOINT et POSTGRES_DB et déployez-le.
  2. É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 :

API_URL

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

  1. 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).
  2. Créez le Deployment : front-deployment.yaml associé à cette image.
  3. Créez le Service : front-service.yaml associé à ce Deployment.
  4. 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 :

  1. Sur votre navigateur internet, ajoutez un nouveau computer avec le bouton Add.
  2. 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 :

.
├── front-config.yaml  
├── front-deployment.yaml  
├── front-service.yaml  
└── front-ingress.yaml  

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 :

  1. Créer la ressource de Type PV / PVC.
  2. 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

  1. Créez la ressource PVC pg-pvc.yaml et déployez-la :

    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: pg-db
    spec:
      storageClassName: gp2
      accessModes:
      - ReadWriteOnce
      volumeMode: Filesystem
      resources:
        requests:
          storage: 3Gi
    

    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.

  2. 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 :

  3. La déclaration du Persistant Volume en tant que volume (définition de son nom).

  4. 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 spec.template.spec du déploiement :

    1
    2
    3
    4
    volumes:
    - name: pg-data
      persistentVolumeClaim:
        claimName: pg-db
    
    Voici le block de point de montage du volume sur le Pod, à placer au niveau spec.template.spec.containers du déploiement :

    1
    2
    3
    volumeMounts:
    - mountPath: /var/lib/postgresql/data
      name: pg-data
    

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.

  1. Modifiez le ConfigMap pg-config.yaml et ajoutez la key:value suivante :

    db_path: "/var/lib/postgresql/data/pgdata"
    
  2. Utilisez ce nouveau paramètre dans le Deployment de votre base de données :

    1
    2
    3
    4
    5
    - name: PGDATA
      valueFrom:
        configMapKeyRef:
          name: pg-config  # Nom du configmap
          key: db_path     # nom de la clef dans le configMap contenant path ou installer la db dans le volume persistant
    

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.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: pgadmin
  labels:
    app: pgadmin
spec:
  replicas: 1
  selector:
    matchLabels:
      app: pgadmin
  template:
    metadata:
      labels:
        app: pgadmin
    spec:
      containers:
        - name: pgadmin
          image: "dpage/pgadmin4"
          imagePullPolicy: IfNotPresent
          envFrom:
            - configMapRef:
                name: pgadmin
          env:
            - name: PGADMIN_DEFAULT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: pgadmin
                  key: pgadmin-password
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
            - name: https
              containerPort: 443
              protocol: TCP
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: pgadmin
data:
  PGADMIN_DEFAULT_EMAIL: "admin@takima.io"
---
apiVersion: v1
kind: Secret
metadata:
  name: pgadmin
  labels:
    app: pgadmin
type: Opaque
data:
  pgadmin-password: "YWRtaW4xMjMq" #base64 of admin123*
---
apiVersion: v1
kind: Service
metadata:
  name: pgadmin
  labels:
    app: pgadmin
spec:
  type: NodePort
  ports:
    - port: 80
  selector:
    app: pgadmin

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 :

  1. Cliquez droit sur “servers” → “create” → “server”
  2. Renseignez le host avec “postres.votre_namespace”
  3. Indiquez les user/password
  4. 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.

  1. Éditez une ressource StatefulSet

    Le statefulSet fonctionne comme le deployment mais permet d'ajouter un block de provisioning de PVC

    1
    2
    3
    4
    5
    6
    7
    8
      volumeClaimTemplates:
      - metadata:
          name: pg-data
        spec:
          accessModes: [ "ReadWriteOnce" ]
          resources:
            requests:
              storage: 1Gi
    

    Ensuite le volume se consomme dans le container de la même manière (block volumeMounts:)

  2. Déployez cette ressource

  3. vérifiez qu'un PVC est bien provisionné, et observé son nom ainsi que le nom du pod créé.
  4. 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)

apiVersion: "acid.zalan.do/v1"
kind: postgresql
metadata:
  name: formation-cdb
spec:
  teamId: "formation"  # le team id doit matcher le préfixe dans le metadata.name, ici formation
  volume:
    size: 1Gi
  numberOfInstances: 2
  users:
    cdb:  # database owner
      - superuser
      - createdb
  databases:
    cdb: cdb  # dbname: owner
  postgresql:
    version: "14"

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 :

INFO: no action. I am (formation-cdb-0) the leader with the lock
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é des StatefulSet). 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 :

INFO: no action. I am a secondary (formation-cdb-1) and following a leader (formation-cdb-0)

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 :

INFO: no action. I am (formation-cdb-1) the leader with the lock

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 :

1
2
3
spec:
  enableLogicalBackup: true
  logicalBackupSchedule: 30 00 * * *

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.