Skip to content

Day 1: Discover Kubernetes

Initialisation des outils

Vous avez dû recevoir un petit cadeau dans votre boîte mail nommé kubeconfig. Ce fichier est la clef d’accès au cluster Kubernetes qui a été spécialement provisionné pour que vous puissiez vous amuser à déployer des ressources, publier des services et les scaler. Pour pouvoir l’utiliser, vous devrez d’abord installer kubectl. Il s’agit de l'outil d’administration en ligne de commande (CLI) pour manager Kubernetes.

Installation d'un éditeur de code

Note

Il est conseillé d'installer un éditeur de code complet pour l'édition des fichiers tout au long de cette formation.

Si vous n'en avez pas, vous pouvez utiliser VsCode .

Installation de kubectl

Linux / MacOS

Téléchargez le client :

Amd64

curl -LO "https://dl.k8s.io/release/v1.31.0/bin/linux/amd64/kubectl"

ou Silicon (Mac M1, M2,...)

curl -LO "https://dl.k8s.io/release/v1.31.0/bin/linux/darwin/kubectl"

Le rendre exécutable :

chmod +x ./kubectl

Déplacez le binaire dans votre PATH :

sudo mv ./kubectl /usr/local/bin/kubectl

Testez pour vous assurer que la version que vous avez installée est à jour :

kubectl version --client

Windows

Téléchargez le client :

curl.exe -LO "https://dl.k8s.io/release/v1.31.0/bin/windows/amd64/kubectl.exe"

Ajoutez l'exécutable dans le PATH. Tutoriel sous Windows 10

Testez pour vous assurer que la version que vous avez installée est à jour :

kubectl version --client

Tip

Nous recommandons vivement de mettre en place l'autocompletion qui simplifiera beaucoup l'usage de du CLI kubectl : lien pour la mise en place ici.

Kubeconfig

Votre client Kubectl est maintenant opérationnel. Pour administrer un cluster Kubernetes, vous allez devoir utiliser les informations de connexion présentes dans le fichier kubeconfig. Pour cela, vous devez simplement placer ce fichier dans $HOME/.kube/config.

Question

Quelles sont les informations que l'on retrouve dans ce fichier ?

Lens ou K9s

À vous de choisir.

Lens et K9s sont des IDEs (Integrated Development Environment) qui permettent d'administrer localement plusieurs clusters kubernetes. Ils vous permettront d'avoir une visualisation des ressources créées et de pouvoir les éditer (même si on va privilégier le CLI kubectl).

Contrairement à Lens, K9s ne propose pas d'interface graphique, tout se passe dans votre terminal. Il requiert Homebrew pour son installation.

Depuis 2023, Lens est devenu Closed Sourced pour éviter les forks et se monétiser.

Pour ce cours, vous pouvez utiliser la version Open Source nommée OpenLens : https://github.com/MuhammedKalkan/OpenLens

Premières commandes

Super ! Vous êtes maintenant prêt à utiliser Kubernetes. Comme tout bon outil d’administration, kubectl propose un helper assez fourni. Vous pouvez y jeter un coup d'œil :

kubectl -h

La doc suivante est également très intéressante pour approfondir l'utilisation de kubectl : kubectl CheatSheet

Assurez-vous d'être dans le bon context (bon cluster kubernetes utilisé) :

kubectl config view
kubectl config current-context

Pour commencer, il faut savoir que Kubernetes fonctionne avec des Namespaces pour séparer de manière logique les ressources. Cela permet de séparer les projets ou les environnements par exemple et de définir des droits utilisateur sur chacun d’entre eux.

Dans le cadre de cet atelier, vous aurez accès à un Namespace personnel qui a pour libellé votre username : si votre mail est toto@exemple.com, votre namespace se nommera toto.

Maintenant, essayez de connaître les pods qui tournent dans votre namespace :

kubectl get pods -n votre_namespace

Info

Ici un "No resources found ..." témoigne d'un succès de l'appel à l'api kubernetes. Il n'y a simplement pas de Pod à lister, pour le moment...

Maintenant, essayez la même commande sur un autre namespace :

kubectl get pods -n default

Question

Quelle est la différence ?

Étape 1 : Premières ressources : Pods/Replicaset/Deployment

Pods

Dans le monde Kubernetes, TOUT est ressource : un déploiement est une ressource, un pod est une ressource, une configuration est une ressource, un disque est une ressource, etc. Tous ces objets Kubernetes peuvent être décrits dans un fichier (souvent un fichier yaml).

Le Pod est la ressource minimale et triviale qu’il est possible de déployer dans Kubernetes en termes de ressources applicatives. Pour faire simple, un pod est une ressource qui héberge un ou plusieurs containers. Comme pour un conteneur démarré avec Docker, vous devez définir une image Docker à consommer, des variables, des volumes. Par exemple :

kubectl run mynginx --image registry.takima.io/school/proxy/nginx

Pour chaque ressource, nous proposerons un lien qui renvoie vers la documentation officielle de Kubernetes, qui est une source fiable et souvent utile pour se renseigner sur les différentes caractéristiques des ressources.

Pour le Pod, c'est par ici : https://kubernetes.io/docs/concepts/workloads/pods/

Info

Derrière cette commande, un fichier json de type POD est généré et est envoyé au KubeApi pour la création.

Observez la création du pod :

kubectl get pods mynginx

Vous pouvez aussi décrire le fichier yaml qui défini le pod :

kubectl get pods mynginx -o yaml

Question

Quelles sont les propriétés principales que l'on retrouve ?

Vous retrouvez dans les root properties :

  • apiVersion
  • kind
  • metadata
  • spec

Maintenant, détruisez le pod :

kubectl delete pods nom_du_pod

Voilà, vous avez créé et supprimé votre première ressource dans kubernetes. Ce n'est pas si compliqué n'est-ce pas ?

Pod Yaml Definition

Nous allons imaginer une application vide, qui s'appelle Unicorn. Pour l'instant, Unicorn n'est qu'un conteneur Nginx tout vide, c'est un front.

Par soucis de convention, nous vous proposons de créer un fichier unicorn-front-pod.yml

Note

Comme dans kubernetes TOUT est ressource yaml, nous te conseillons de créer un dossier et une arborescence pour pouvoir les éditer et les modifier. par exemple TP-kube-01/step-1 dans lequel vous placerez vos ressources yaml manipulées.

Pour appliquer une ressource kubernetes décrite en tant que yaml la commande est toujours la même : kubectl apply -f monfichier.yml

À vous de jouer. Essayez de créer la même ressource avec un fichier yaml :

Tip

Pour avoir le yaml correspondant à une ressource à créer, vous pouvez utiliser la fonction dry-run, exemple :

kubectl run mynginx2 --image registry.takima.io/school/proxy/nginx --dry-run=client -o yaml > unicorn-front-pod.yml

# Pour appliquer une ressource décrite dans un yaml :
kubectl apply -f unicorn-front-pod.yml

Cette commande ne créera pas le pod dans kubernetes mais dumpera l'équivalant du fichier yaml qui aurait été envoyé au KubeApi pour la création. C'est très utile pour avoir un template de ressource voulu et ne pas démarrer avec un yaml vierge

Il y a églament une commande kubectl create permettant pour créer d'autres ressources supportant le --dry-run=client -o yaml permettant de générer rapidement les yaml de nos ressources si besoin.

Comme avec docker, dans kubernetes, vous pouvez accéder à votre conteneur en mode exécution si besoin (remarquez que l’on retrouve des options Docker) :

kubectl exec nom_du_pod -it -- bash

# -it pour s'attacher de façon interactive et avoir la main dans l'invite de commande
# -i pour attacher stdin et -t pour faire de stdin un TTY
Pensez à sortir de votre container avec un ctrl + D ou exit.

La commande suivante permet de voir les logs :

kubectl logs nom_du_pod

Tip

Avec l'option -f --follow le CLI vous stream les logs sur votre terminal.

kubectl logs -f nom_du_pod

Détruisez de nouveau le pod :

kubectl delete pods nom_du_pod

Info

Attention, le pod ne s'appelle plus mynginx, vérifiez le fichier .yaml généré précédemment.

Impératif Vs Déclaratif

Vous venez de voir plusieurs façons de créer une ressource kubernetes :

  • kubectl run / kubectl create ...
  • kubectl apply -f my-ressources.yaml

Ces deux façons sont bien différentes :

  • La première est impérative : elle exécute ce qu'on lui demande
  • La seconde est déclarative : elle s'intéresse à l'état que l'on souhaite avoir et réalise les changements/créations nécessaires

De manière générale, on préférera la méthode déclarative pour plusieurs raisons :

  1. Lorsque l'on déploie des ressources, c'est l'état qui nous intéresse, et seuls les changements qui nous séparent de l'état voulu doivent être appliqués.
  2. La méthode déclarative s'appuie sur du yaml, permettant ainsi à l'état de nos ressources d'être décrit et conservé dans des fichiers (on verra plus tard que c'est très intéressant).
  3. C'est bien plus riche, tout n'est pas faisable en impératif.

Pour comprendre une autre différence entre impératif et déclaratif, déployez un pod :

kubectl run mynginx --image registry.takima.io/school/proxy/nginx

Maintenant, relancez la même commande :

kubectl run mynginx --image registry.takima.io/school/proxy/nginx

Question

Que se passe-t-il lors de cette deuxième création en impératif ?

Réalisez la même création avec la méthode déclarative :

kubectl run mynginx --image registry.takima.io/school/proxy/nginx --dry-run=client -o yaml > mynginx.yml

kubectl apply -f mynginx.yml

Question

Que se passe-t-il lors de cette deuxième création en déclaratif ?

Info

De la même manière que pour la création de ressources en déclaratif à l'aide d'un fichier yaml, vous pouvez détruire les ressources à partir du même fichier yaml. Par exemple dans le cas précédent kubectl delete -f mynginx.yml Vous pouvez aussi regrouper plusieurs ressources dans un même yaml pour décrire une stack complète à créer ou détruire.

Replicaset

Ok, c’est bien : vous avez réussi à déployer un conteneur dans un Pod ! Mais qu'en est-il de la haute disponibilité ou de la scalabilité promise ?

Pour scaler un applicatif, il faut tout simplement rajouter des Pods du même applicatif. Vous pourriez créer à la main plusieurs Pods avec la même image, mais cela ne serait pas très pratique. Il est beaucoup plus efficace de les regrouper au sein d’un groupe de ressources. C’est dans cet objectif que Kubernetes définit les ReplicaSet permettant de contrôler plusieurs pods d’un même applicatif (même image Docker) partageant les mêmes configs, les mêmes propriétés.

Voici un exemple de ressource ReplicaSet:

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: unicorn-front-replicaset
  labels:
    app: unicorn-front
spec:
  template:
    metadata:
      name: unicorn-front-pod
      labels:
        app: unicorn-front
    spec:
      containers:
      - name: unicorn-front
        image: registry.takima.io/school/proxy/nginx
  replicas: 3
  selector:
    matchLabels:
      app: unicorn-front

Question

Que remarquez-vous dans la description des properties spec: template ? À quoi sert le selector: matchLabels ?

Éditez le yaml unicorn-front-replicaset.yaml et déployez ce ReplicaSet sur votre cluster avec kubectl apply.

Question

Combien y a-t-il de pods déployés dans votre namespace ?

Maintenant, supprimez un Pod.

Question

Que se passe-t-il ?

Supprimez le ReplicaSet.

Question

Que se passe-t-il ?

Deployment

Une ressource Kubernetes de type Deployment permet de facilement piloter des ReplicaSet. En pratique, c'est typiquement avec cette ressource que sont déployées les applications.

En effet, le Deployment gère la notion de cycle de vie des Pods (via les versions de ReplicaSet pilotés), notamment avec la définition de RollingUpgrade lors d’un déploiement de nouvelles versions. Cependant, il faut que la stratégie par défaut de RollingUpdate soit conservée (.spec.strategy.type) : cela facilite les rollbacks vers une version antérieure.

Lors de la modification de la version d’une image Docker consommée dans un déploiement, un nouveau ReplicaSet sera créé. L’ancien ReplicaSet correspondant à l'ancienne version de l’image reste et les Pods sont vidés de l’ancien ReplicaSet vers le nouveau (RollingUpgrade). Ce transfert se fait au “fil de l'eau”, de manière à ce qu’il n’y ait pas d’interruption de service (par défaut Kubernetes tente de garder 75% des pods actifs). Dans le cas du TP, il ne détruit les Pods obsolètes qu’après que le nouveau Pod obtienne le statut Running.

De cette manière, il est possible de facilement Rollback (processus inverse, les Pods allant du nouveau ReplicaSet vers l’ancien).

La création d’un Deployment entraîne la création d’un ReplicaSet et donc la création d’un ou plusieurs Pods.

La ressource Deployment est très proche de celle d’un ReplicaSet :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: unicorn-front-deployment
  labels:
    app: unicorn-front
spec:
  replicas: 3
  selector:
    matchLabels:
      app: unicorn-front
  template:
    metadata:
      labels:
        app: unicorn-front
    spec:
      containers:
      - name: unicorn-front
        image: registry.takima.io/school/proxy/nginx:1.7.9
        ports:
        - containerPort: 80

Question

Quels sont les changements par rapport au ReplicaSet ?

Éditez un yaml unicorn-front-deployment.yaml et déployez ce Deployment.

Question

Combien y a-t'il de ReplicaSet ? De Pods ?

Commande utile :

kubectl get all

Maintenant, la notion d’upgrade : c’est en pratique ce qu’il se passe lorsque l’on réalise un déploiement applicatif d’une nouvelle release (via un Continuous Deployment par exemple).

Pour cela, changez la version de l’image nginx :

Modifier une image docker dans un deployment revient à modifier cette ressource deployment. Pour modifier une ressource, vous avez 3 solutions :

  1. kubectl set ou kubectl patch

    kubectl set image deployment/unicorn-front-deployment unicorn-front=registry.takima.io/school/proxy/nginx:1.9.1
    
  2. Éditez le fichier yaml en local et faire un kubectl apply -f monfichier.yaml

    Il est possible également d'éditer le yaml définition de déploiement en changeant l’image et d'utiliser cette commande :

    kubectl apply -f ...
    
  3. Éditez directement la ressource avec kubectl edit

    kubectl edit deployment.v1.apps/unicorn-front-deployment
    

Info

On préfèrera réaliser des updates de ressources en éditant son fichier yaml (ici unicorn-front-deployment.yaml) en utilisant donc la 2ème solution. Cela permet d'avoir la bonne version dans son dossier (fichier synchronisé avec ce qui est sur le cluster).

Regardez le statut du RollingUpgrade :

kubectl rollout status deployment.v1.apps/unicorn-front-deployment

Si vous utilisez la commande rapidement, vous verrez le RollingUpgrade en cours. Si vous n'êtes pas assez rapide vous verrez juste un succesfull.

Question

Une fois terminé, combien y a-t-il de replicaset ? Combien y a-t-il de Pods ? Allez voir les logs des événements du déploiement avec kubectl describe deployments. Qu’observez-vous ?

Commande utile :

kubectl describe deployments

Faire un Rollback

Mettez à jour votre déploiement avec une nouvelle version de l’image nginx.

kubectl set image deployment.v1.apps/unicorn-front-deployment unicorn-front=registry.takima.io/school/proxy/nginx:1.91-falseimage --record=true

Observez votre Deployment, vos Pods et vos ReplicaSets.

Question

Que se passe-t-il ? Pourquoi ?

Dans ce cas de figure, l'objectif est de revenir à une version stable de Deployment (une version fonctionnelle avec une bonne image nginx).

Pour commencer, vérifiez les révisions de ce déploiement :

kubectl rollout history deployment.v1.apps/unicorn-front-deployment

Question

Combien y a-t-il de révisions ? À quoi correspond le champ CHANGE-CAUSE ?

Vous pouvez afficher les détails de chaque révision (pour la révision 2 par exemple) avec cette commande :

kubectl rollout history deployment.v1.apps/unicorn-front-deployment --revision=2

Maintenant que la version stable est identifiée, voici la commande pour retourner à cette version :

kubectl rollout undo deployment.v1.apps/unicorn-front-deployment --to-revision=2

Info

Pour revenir à la version précédente, il est aussi possible d'utiliser cette commande :

kubectl rollout undo deployment.v1.apps/unicorn-front-deployment

Mettre à l'échelle

Le déploiement de nouvelles versions (releases) est maintenant maîtrisé, mais dans l'hypothèse que le site internet est promu au journal télévisé et qu’un afflux conséquent de visiteurs se connectent dessus, que faire ? Il faut tout simplement déployer plus de Pods de l'applicatif. C’est ce qui est appelé scaler une application.

Scalez le serveur nginx à 5 :

kubectl scale deployment.v1.apps/unicorn-front-deployment --replicas=5

Info

Encore une fois cette commande est pratique pour le cadre du TP. Mais en pratique, on préfèrera éditer son fichier yaml de deployment et mettre replicas: 5 puis faire un kubectl apply -f unicorn-front-deployment.yaml

Question

Combien y a-t'il de Pods?

Info

Pour aller plus loin, Kubernetes implémente, si c’est activé, un système d’autoscaling appelé HPA (Horizontal Pods Autoscaling). Kubernetes se chargera de scaler automatiquement l'application en fonction de trigger définis par exemple sur la consommation CPU ou la RAM. Si vous êtes rapide une implémentation du HPA est proposé en bonus.

Mettre en standby un deployment

Mettez en pause un Deployment :

kubectl rollout pause deployment.v1.apps/unicorn-front-deployment
kubectl set image deployment.v1.apps/unicorn-front-deployment unicorn-front=registry.takima.io/school/proxy/nginx:1.20.2

Question

Que se passe-t-il au niveau ReplicaSet ?

kubectl rollout resume deployment.v1.apps/unicorn-front-deployment

Question

Que se passe-t-il au niveau ReplicaSet ?

Info

La pause permet de modifier le déploiement (encore une autre version, des properties du Deployment comme les limits/ressources) sans déclencher de RollingUpgrade.

Bonus

Lorsque des applications sont déployées sous forme de containers (dans des Pods), il est indispensable de contrôler les ressources qu’elles consomment. Imaginez qu’un Pod se mette à consommer plusieurs dizaines de gigabytes de mémoire vive, quel serait l’impact sur les autres Pods tournant sur le même Worker Node (même serveur) ?

Pour empêcher cela, Kubernetes implémente la notion de Ressources Management.

Example

resources:
  requests:
    memory: "64Mi"
    cpu: "250m"
  limits:
    memory: "128Mi"
    cpu: "500m"

Ce block se place au niveau spec.container.

  • Le requests est la ressource qui est demandée au Worker Node pour allocation au conteneur.
  • La limits est le maximum de mémoire et CPU consommable par le Pod.

Implémentez une ressource management pour votre Deployment avec ces spécifications :

  • Requests : mémoire : 32 Mo / CPU : 100m
  • Limits : mémoire 256 Mo / CPU : 400m

Check

Vous devez avoir les fichiers suivants dans le dossier step-1 :

    .
    ├── unicorn-front-deployment.yaml
    ├── unicorn-front-replicaset.yaml
    └── unicorn-front-pod.yaml

Mise en application :

Dans cette mise en application, nous allons voir à quoi servent les ressources limites et quelle est l'impact de ces configurations sur le cycle de vie des containers.

Impact des limites Mémoire

Créez le pod suivant :

apiVersion: v1
kind: Pod
metadata:
  name: memory-limit
spec:
  containers:
  - name: memory-limit
    image: registry.takima.io/school/proxy/memory-stress
    resources:
      requests:
        memory: "50Mi"
      limits:
        memory: "100Mi"
    command: ["stress"]
    args: ["--vm", "1", "--vm-bytes", "250M", "--vm-hang", "1"]

Ce pod simule une application qui réclame 250M de RAM.

Observez le comportement du pod :

kubectl get pod memory-limit

Question

Que constatez-vous ? Pourquoi ?

Pour voir plus en détail :

kubectl describe pod memory-limit

Vous devrez voir ceci :

 Last State:     Terminated
      Reason:       OOMKilled

Détruisez votre pod

kubectl delete pod memory-limit

Impact des limites CPU

Créez le pod suivant :

apiVersion: v1
kind: Pod
metadata:
  name: cpu-limit
spec:
  containers:
  - name: cpu-limit
    image: registry.takima.io/school/proxy/cpu-stress
    resources:
      limits:
        cpu: "1"
      requests:
        cpu: "0.5"
    args:
    - -cpus
    - "2"

Ce pod va simuler une demande d'utilisation de 2 vCPUs.

Vérifiez le comportement du pod :

kubectl get pod cpu-limit

Vérifiez la consommation CPU du pod :

kubectl top pod cpu-limit

Question

Que constatez-vous ? Pourquoi ?

Supprimez votre pod de test :

kubectl delete pod cpu-limit

Étape 2 : Publication Service/Ingress

Note

Vous pouvez maintenant vous placer dans le dossier TP-kube-01/step-2 dans lequel vous placerez vos ressources yaml manipulées pendant cette étape. Gardez les ressources yaml du step-1 qui pourront vous servir de référence.

La plupart du temps l'applicatif qui tourne dans un Pod Kubernetes doit délivrer un service (que ce soit une Api, un Front ou une base de données, par exemple) et doit donc être requêté.

Kubernetes possède une ressource spécifique appelée Service.

Service

Le Service agit comme un Load Balancer qui va transmettre les requêtes qu’il reçoit vers les Pods auxquels il est rattaché. Ce rattachement se fait grâce aux Selector.

Pour publier le Deployment unicorn-front-deployment de l'étape 1, éditez le fichier unicorn-front-service.yaml et déployez-le avec kubectl apply:

apiVersion: v1
kind: Service
metadata:
  name: unicorn-front-service
spec:
  selector:
    app: unicorn-front
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

Note

  • spec.ports.port est le port sur lequel le service va écouter.
  • spec.ports.targetPort est le port exposé par le conteneur sur le pod.

Important : Vérifiez que le targetPort du service correspond bien au containerPort du deployment (ou du pod). Pour éviter de s'emmêler les pinceaux, vous pouvez aussi nommer le port dans votre deployment et l'appeler dans le service (spec.template.spec.containers[].ports[].name).

Par défaut, les services sont de type ClusterIp, c'est-à-dire qu'ils sont accessibles depuis l'intérieur du cluster Kubernetes (et donc pas publiquement sur internet) mais seulement pour les autres applications qui tournent dans le même cluster Kubernetes.

Un comportement idéal pour un Backend ou une base de données, mais non adapté pour le Front d’un portail Web qui doit être accessible à des utilisateurs.

Pour exposer ces services en dehors du cluster, il existe plusieurs solutions. Voici les deux les plus courantes :

  • Soit faire un service de type NodePort (pour exposer le service sur l’ensemble des serveurs Worker du cluster Kubernetes) ou LoadBalancer (pour provisionner un load balancer sur le cloud provider sur lequel il tourne (AWS, GCP, AZURE pour citer les principaux).
  • Soit faire un Ingress pour les expositions http / https. C’est la solution préconisée, car elle permet d'accéder aux applications en utilisant une même IP publique. L’ingress est en fait un Reverse Proxy (voir Api Gateway) et constitue le point d’entrée de tous les applicatifs à publier sur le Web. L’ingress transmet ensuite les requêtes au service de type ClusterIP.

Pour résumer, voilà l’idée :

UTILISATEUR → INGRESS → SERVICE → PODS

À noter que l’Ingress Controller utilise lui-même un service de type LoadBalancer ou NodePort partagé par tous les ingress qu’il va contrôler.

Ingress

Nous allons maintenant mettre en place un Ingress

Si vous êtes curieux, vous aurez remarqué qu’il y a déjà un IngressClass dans le Cluster qui a été provisionné. Il s’agit d’un Ingress Controller Nginx.

Qui dit Reverse Proxy, dit bien sûr URL. En effet, le Reverse Proxy utilise un ensemble URL+Path pour savoir vers quel service il doit transmettre les requêtes. Un DNS a déjà été créé et vous a été transmis par mail.

Note

Pour chaque personne, nous avons créé une entrée DNS wildcard pointant sur le loadbalancer de l'ingress controller.

Ainsi le DNS devrait être de la forme cequevousvoulez.${namespace}.sessionX.takima.school Exemple John Doe, à qui a été fourni un namespace nommé jdoe, peut utiliser un host unicorn.jdoe.session1.takima.school.

Éditez un fichier unicorn-front-ingress.yaml et déployez-le sur kubernetes :

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: unicorn-front-ingress
spec:
  ingressClassName: nginx
  rules:
    - host: replace-with-your-url
      http:
        paths:
          - backend:
              service:
                name: unicorn-front-service
                port:
                  number: 80
            path: /
            pathType: Prefix

Essayez d'accéder au service.

Check

Vous devez avoir les fichiers suivants dans le dossier step-2 :

    .
    ├── unicorn-front-service.yaml
    └── unicorn-front-ingress.yaml

Note

Il existe une autre approche utilisant des ressources kubernetes différentes pour publier un applicatif. Cette approche se nomme Gateway API. Elle se base sur 2 objets qui remplacent l'objet Ingress : * la ressource Gateway qui définit le point d'accès qui permet de rentrer dans le cluster. * Les Routes (comme les HTTPRoute ou les TLSRoute) qui définissent les règles de routage qui enverront le trafic au bon endroit (au bon Service le plus souvent). Les Routes sont associées à une Gateway qui, elle-même, implémente une GatewayClass. La logique est donc la même qu'un Ingress (d'ailleurs la technologie sous-jacente est souvent la même : implémentation d'un reverse proxy). Mais la logique est bien plus souple ( routes par protocole, gateway cross namespaces, plus comprhensible dans la gestion des header par exemple) et surtout la séparation des objets permet de mieux définir les rôles : objet Gateway plutôt geré par un Cluster Operator; objet HTTPRoute plutôt developpeur applicatif. Pour la suite, on continuera d'utiliser la notion d'Ingress qui reste encore le plus répandu

Bonus

Comme la sécurité, c'est important, il est possible d'utiliser HTTPS/TLS, mais pas d'inquiétude, les éventuels problèmes de certificats ont été réglés en amont grâce à CertManager. Le CertManager permet aux utilisateurs de créer un Ingress en demandant l'utilisation d'un cluster-issuer de certificat.

Info

Nous avons mis deux cluster-issuers dans le Cluster :

  • Un appelé letsencrypt-staging qui vous permet de tout tester avec des certificats de non-production générés par Letsencrypt.
  • L'autre appelé letsencrypt-prod qui vous permet de vraiment générer un certificat Let's Encrypt valid.

Nous vous demandons de faire vos tests avec letsencrypt-staging. Celui-ci vous permettra de générer un certificat TLS. Si vous n'avez pas ajouté dans votre trust store les root CA staging de LetEncrypt, vous aurez toujours un message d'alerte de sécurité. Il vous faudra le skip pour voir votre interface.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
 name: unicorn-front-ingress
 annotations:
  cert-manager.io/cluster-issuer: letsencrypt-staging
  kubernetes.io/tls-acme: 'true'
spec:
 ingressClassName: nginx
 rules:
 - host: replace-with-your-url
   http:
     paths:
     - backend:
         service:
           name: unicorn-front-service
           port:
             number: 80
       path: /
       pathType: Prefix
 tls:
 - hosts:
   - replace-with-your-url
   secretName: unicorn-front-tls

Vérifiez l’accès en SSL via votre browser ou via curl -kv https://replace-with-your-url / openssl s_client -connect replace-with-your-url:443.

Info

Pour la suite du TP exposez toujours vos ingress/applications en https, par contre Letsencrypt limite le nombre de requêtes pour générer des certificats de "production", c'est à dire qui n'auront pas les message d'alerte.

Pour ne pas se faire blacklister, nous avons créé un Wildcard certificate couvrant chacun de vos domaines. Pour l'utiliser plus besoin de CertManager ni de TLS acme, dans votre Ingress :

  • Supprimez les annotations cert-manager.io/cluster-issuer et kubernetes.io/tls-acme
  • Remplacez secretName que nous vous avons fourni et qui contient le Wildcard, c'est-à-dire : app-wildcard

Faites un kubectl get secrets pour récupérer le nom du secret wildcard puis ajoutez ce nom dans votre Ingress : au niveau secretName:, à la place de unicorn-front-tls

Nous verrons un peu plus tard comment utiliser les secrets.

Mise en situation

Pour bien se rendre compte du fonctionnement du Load Balancing, un Webservice simple a été préparé. Tout d'abord, commencez par nettoyer votre Namespace :

kubectl delete --all deployments
kubectl delete --all services
kubectl delete --all ingress

Éditez un nouveau Deployment avec l’image suivante pour avoir 3 Pods registry.gitlab.com/takima-school/images/simple-website:latest. Vous pouvez nommer le fichier hello-deployment.yaml

Info

Cette image contient un node qui expose ses services sur le port 3000.

Question

Que se passe-t-il ? Pourquoi ?

Vous utiliserez la plupart du temps des containers provenant de Container Registry privés, peu d’entreprises exposant publiquement leurs containers. Il faut alors gérer les accès au Registry privé.

Pour cela, il convient de créer une nouvelle ressource de type Secret dans Kubernetes. Cette ressource sera décrite plus en détail ultérieurement.

Pour le moment, créez la ressource avec les informations que vous avez reçues par mail :

kubectl create secret docker-registry takima-school-registry --docker-server=registry.gitlab.com --docker-username=readregcred --docker-password=LE_MOT_DE_PASSE_QUE_VOUS_AVEZ_RECU_PAR_MAIL

Puis modifiez votre Deployment pour indiquer que vous souhaitez utiliser ce Secret pour pull l’image.

Pour cela, éditez votre yaml, ajoutez y ce block au même niveau que spec.template.spec:

imagePullSecrets:
  - name: takima-school-registry

Une fois le Deployment réalisé (contrôlez que les Pods ont bien le status running), créez un Service ainsi qu’un Ingress pour accéder à la Web App (nouveau fichier hello-service.yaml et hello-ingress.yaml). Si vous ne l'avez pas fait dans votre Deployment, scalez votre Deployment à 3.

Question

Décrivez ce que répond la Web App ? Actualisez votre page avec CTRL + F5. Que se passe-t-il ?

Maintenant, nous allons ajouter des configs de variables d'environement, celles-ci pourront être utilisées dans les containers (et donc dans les applicatifs qui tournent dessus). Ajoutez les variables suivantes dans le deployment (au niveau spec.template.spec.container.env):

- name: K8S_NODE_NAME
  valueFrom:
    fieldRef:
      fieldPath: spec.nodeName
- name: K8S_POD_NAME
  valueFrom:
    fieldRef:
      fieldPath: metadata.name
- name: K8S_POD_IP
  valueFrom:
    fieldRef:
      fieldPath: status.podIP

Question

Que constatez-vous sur le navigateur ?

Check

Vous devez avoir les nouveaux fichiers suivants dans le dossier step-2 :

    .
    ├── hello-deployement.yaml
    ├── hello-service.yaml
    └── hello-ingress.yaml

Étape 3 : ConfigMap/Secret

La plupart du temps lorsque qu'une application est déployée, vous aurez besoin de lui fournir une configuration. Par exemple, vous lui indiquerez des valeurs pour les variables d’environnement (de la même manière que le .env du Docker Compose). Bien qu’il soit possible de configurer cela “en dur” dans le Pod ou le Deployment, il est d'adage que le concept de hardcoder des configs envoie droit à la catastrophe. Dans Kubernetes, une ressource est dédiée pour éviter cette destinée : il s’agit du ConfigMap.

La plupart du temps un ConfigMap défini un couple Key/Value qui sera ensuite utilisé dans le Pod pour configurer des variables d'environnement. Mais vous pourrez aussi définir un fichier de configuration entier et le monter comme un volume dans votre Pod.

Exemple :

Quelque chose est volontairement caché dans la Web App de démo. En effet, il est possible de lui définir une variable d’environnement nommée CUSTOM_COLOR pour forcer la couleur du background.

Pour cela, il faut :

  • Créer un ConfigMap en configurant cette variable, nouveau fichier hello-config.yaml.
  • Consommer la variable dans le Deployment.
apiVersion: v1
kind: ConfigMap
metadata:
  name: web-app
data:
  # property-like keys; each key maps to a simple value
  color: "#200"

Le bloc suivant est à placer dans le Deployment au niveau du template container (au même niveau que le name ou l’image du container utilisé dans le deployment spec.template.spec.container). Attention à l'indentation du yaml !

env:
  - name: CUSTOM_COLOR # Vrai key de la variable d'env. Peut-être différent de la valeur dans le configmap
    valueFrom:
      configMapKeyRef:
        name: web-app  # Nom du configmap
        key: color     # nom de la clef dans le configmap

Secret

Un Secret s’utilise comme un ConfigMap mais sera masqué dans le cluster Kubernetes et vous pourrez appliquer des droits différents sur ces ressources pour avoir des restrictions d’accès. D’ailleurs, pour le créer, vous devez encoder les données en Base64.

Deux solutions :
1. Soit directement en Command line avec kubectl create.

kubectl create secret generic my-secret --from-literal=username=user --from-literal=password='test123*'
Remarque : dans ce cas, on ne passe pas les data en base 64, la commande s'occupe de cela.
2. Soit via un yaml, mais attention, dans ce cas les valeurs des data doivent être en Base64. Il faut donc convertir les valeurs au préalable :
echo -n 'user' | base64
dXNlcg==
echo -n 'test123*' | base64
dGVzdDEyMyo=

Puis, le yaml peut être édité, nouveau fichier hello-secret.yaml :

apiVersion: v1
kind: Secret
metadata:
  name: hello-secret
type: Opaque
data:
  username: YWRtaW4=
  password: MWYyZDFlMmU2N2Rm

Il ne reste plus qu'à kubectl apply le fichier. Vérifier avec Lens par exemple que le secret est créé. Avec Lens, vous pouvez visualiser le contenu du secret en cliquant sur l'œil.

Check

Vous devriez avoir les fichiers suivants dans le dossier step-3 :

    .
    ├── hello-config.yaml
    └── hello-secret.yaml

Bonus

Bonus 1 : Rendre sa couleur secrète

Afin d'éviter que votre couleur préférée soit connue de tous, passez la variable du code couleur en mode Secret. Bonne chance !

Bonus 2 : Mon pod est-il en vie ?

Il peut arriver parfois que notre application démarre et soit capable de fonctionner de manière nominale pendant un temps avant de voir son service se dégrader (apparition d'un deadlock qui empêche l'application de fonctionner normalement).

Ce genre d'erreur peut survenir sans pour autant que votre application s'arrête d'elle-même. Dans ce cas-là, il convient de fournir à kubernetes un moyen de savoir si l'application devrait être redémarrée ou non.

Cela tombe bien puisque notre application hello-world propose un endpoint sur /health donnant l'information sur son état courant. Paramétrez votre Deployment pour qu'il fasse un appel régulier sur ce fameux endpoint /health. Pour vérifier que tout marche bien, vous pouvez changer l'état du healthcheck en faisant une requête GET sur l'endpoint /kill. Une fois fait, l'un de vos pods devraient redémarrer.

Bonus 3 : Let my kubernetes Scale !!!

Comme promis, voici une mise en pratique du HPA, la mise à l'échelle automatique d'un déploiement. Pour cela, commencez par supprimer vos ressources déployment existantes (pour avoir de la visibilité).

Note

Le HPA se base sur les ressources request définies dans votre déploiement. En effet, on va définir un pourcentage basé sur ces ressources. Nous vous conseillons donc de réaliser le bonus de l'étape 1, qui traite de cela avant de continuer.

Nous avons préparé pour vous un petit applicatif qui simule une charge à chaque fois qu'il reçoit une requête http.

Commencez par éditer et deployer le fichier hpa-deployment.yaml avec :

  • image : registry.gitlab.com/takima-school/images/hpa:latest
  • replicas à 1
  • nom hpa et label app: hpa
  • les ressources suivantes :
        resources:
          limits:
            cpu: 500m
          requests:
            cpu: 200m
  • la mise en place du imagePullSecrets car on pull un registry privé.

Éditez et déployez ensuite le service correspondant :

  • targetPort: 80
  • Port: 80
  • nom: hpa

Voilà le socle est prêt pour mettre à l'échelle notre application :

  • Mettez en place le hpa sur le déploiement :

kubectl autoscale deployment hpa --cpu-percent=50 --min=1 --max=10

Ici, on demande de scaler l'application dès que la ressource cpu dépasse 50% du request (ici donc 100m cpu).

Vérifiez l'état du HPA (on peut aussi le voir dans LENS)

laptop-3007:~$ kubectl get hpa
NAME   REFERENCE        TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
hpa    Deployment/hpa   0%/50%    1         10        1          105s

On voit qu'il n'est pas très chargé. Mais cela ne va pas durer !

Pour générer du traffic rien de tel qu'un pod qui attaque le service. Sur un autre terminal, lancez cette commande et laissez la tourner en fond :

kubectl run -i --tty load-generator --rm --image=registry.takima.io/school/proxy/busybox --restart=Never -- /bin/sh -c "while sleep 0.01; do wget -q -O- http://hpa; done"

Puis, commencez à observer la charge monter sur votre déploiement en lançant plusieurs kubectl get hpa ou avec un watch(ici à 160%)

laptop-3007:~$ kubectl get hpa
NAME   REFERENCE        TARGETS    MINPODS   MAXPODS   REPLICAS   AGE
hpa    Deployment/hpa   160%/50%   1         10        1          108s

Question

Observez ce qu'il se passe. Que constatez-vous ?

Voici ce qui est attendu :

scaling hpa

On voit bien notre applicatif scaler à 6 pods. On le constate aussi si l'on fait un kubectl get pods d'ailleurs.

De plus nous pouvons lancer un kubectl describe hpa hpa pour regarder les logs du hpa et ces événements :

scaling hpa

Les logs parlent d'eux-mêmes !

Maintenant, coupez le process qui tourne en fond sur votre deuxième terminal.

On remarque que la charge a diminué avec un kubectl get hpa

Et au bout de quelques minutes le hpa devrait downscale l'applicatif. Par défault, pour des raisons de stabilisation, le downscaling ne se fait qu'après 300 secondes sous le déclencheur (dans notre cas 50% de cpu request). Attendez donc un peu et vérifiez que le déploiement repasse à 1 replicas.

scaling hpa

Check

Vous devriez avoir 2 nouveaux fichiers dans le dossier hpa :

    .
    ├── hpa-deployment.yaml
    └── hpa-service.yaml

Bonus 4: Control my traffic !!!

Ici, nous allons voir comment utiliser une nouvelle ressource pour contrôler finement les flux entre les Pods. Cette ressource se nomme NetworkPolicy.

Par défaut comme vous l'avez surement déjà constaté, tout est open ! Et parfois (voire souvent) ce n'est pas une bonne pratique.

Attention : la NetworkPolicy est une fonctionnalité qui n'est pas prise en charge par tous les CNI (Controller Network Interface). Comme le CNI n'est pas l'objet de cette formation, sachez simplement que c'est une API de gestion du réseau d'un cluster K8S. Il existe une multitude d'implémentations, ici, nous avons provisionné un cluster avec un CNI qui prend en charge les NetworkPolicies (Calico en l'occurrence).

Pour comprendre comment cela fonctionne, commencez par deployer une ressource que vous maitrisez sur le bout des doigts : un deployment.

kubectl create deployment nginx --image=registry.takima.io/school/proxy/nginx:1.7.9

Ensuite, exposez ce deployment à travers un service :

kubectl expose deployment nginx --port=80

Pour tester les accès en interne, on lance la boite à outils busybox :

kubectl run busybox --rm -ti --image=registry.takima.io/school/proxy/busybox:1.28 -- /bin/sh

Cela va vous ouvrir un shell dans votre pod busybox (qui se trouve dans le même namespace que votre nginx). Vous pouvez tester que vous pouvez accéder à votre nginx :

wget --spider --timeout=1 nginx

La connection doit passer (car comme on l'a dit par default si on ne fait rien, c'est ouvert).

Maintenant, quittez votre shell busybox (le pod va s'autodétruire !!)

Appliquez une NetworkPolicy qui va limiter les accès au nginx :

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: access-nginx
spec:
  podSelector:
    matchLabels:
      app: nginx
  ingress:
  - from:
    - podSelector:
        matchLabels:
          access: "true"

Re-testez la même connexion au nginx via busybox :

kubectl run busybox --rm -ti --image=registry.takima.io/school/proxy/busybox:1.28 -- /bin/sh
wget --spider --timeout=1 nginx

Question

Que constatez-vous ?

Quittez votre busybox shell.

Maintenant, ajoutez le label permettant à votre pod busybox de matcher la règle spécifiée dans la NetworkPolicy et re-testez la connection :

kubectl run busybox --rm -ti --labels="access=true" --image=registry.takima.io/school/proxy/busybox:1.28 -- /bin/sh

Testez la connection :

wget --spider --timeout=1 nginx

Bonus 5: Run on one AZ

Le but ici est d'utiliser le concept d'affinity pour faire tourner les pods de notre déployment sur un seul AZ

Question

Qu'est-ce qu'une Avaibility Zone au niveau infrastructure ?

Ici, on a 3 AZ dans la région de Paris sur AWS (région eu-west-3)

Question

Faire en sorte de tourner sur une seule AZ, par exemple eu-west-3a

indice

les serveurs du cluster kubernetes ont une clef topology.kubernetes.io/zone qui définit sur quel AZ ils tournent.

Pour valider cela, vérifiez avec un intervenant que tes pods tournent bien sûr les bons serveurs

Bonus 6: Run one pod on each server

Créez un déployment qui va faire tourner vos pods sur chacun des worker nodes du cluster kubernetes avec l'image registry.takima.io/school/proxy/nginx:1.7.9

indice

il existe une ressource spéciale qui fait cela, à toi de la trouver.

Bonus 7: Change my rolling update policy

Par défaut, vous utilisez une policy pour vos upgrades des déploiements. Changez cette policy pour avoir un type recreate

Utilisez l'image registry.takima.io/school/proxy/nginx:1.7.9 avec un replicas à 5

Mettez à jour votre deployment vers une nouvelle image nginx : registry.takima.io/school/proxy/nginx:1.9.1

Question

que se passe-t-il au niveau de vos pods pendant l'update ?