Retour au site

Uptime Formation - Formations Uptime

Supports de formation : Elie Gavoty, Alexandre Aubin et Hadrien Pélissier
Sous licence CC-BY-NC-SA - Formations Uptime


Table des matières :

Kubernetes

01 - Cours - Présentation de Kubernetes

  • Kubernetes est une solution d’orchestration de conteneurs extrêmement populaire.
  • Le projet est très ambitieux : une façon de considérer son ampleur est de voir Kubernetes comme un système d’exploitation (et un standard ouvert) pour les applications distribuées et le cloud.
  • Le projet est développé en Open Source au sein de la Cloud Native Computing Foundation.

Concrètement : Architecture de Kubernetes

  • Kubernetes rassemble en un cluster et fait coopérer un groupe de serveurs appelés noeuds(nodes).

  • Kubernetes a une architecture Master/workers (cf. cours 2) composée d’un control plane et de nœuds de calculs (workers).

  • Cette architecture permet essentiellement de rassembler les machines en un cluster unique sur lequel on peut faire tourner des “charges de calcul” (workloads) très diverses.

  • Sur un tel cluster le déploiement d’un workload prend la forme de ressources (objets k8s) qu’on décrit sous forme de code et qu’on crée ensuite effectivement via l’API Kubernetes.

  • Pour uniformiser les déploiement logiciel Kubernetes est basé sur le standard des conteneurs (défini aujourd’hui sous le nom Container Runtime Interface, Docker est l’implémentation la plus connue).

  • Plutôt que de déployer directement des conteneurs, Kubernetes crée des aggrégats de un ou plusieurs conteneurs appelés des Pods. Les pods sont donc l’unité de base de Kubernetes.

Philosophie derrière Kubernetes et le mouvement “Cloud Native”

Historique et popularité

Kubernetes est un logiciel développé originellement par Google et basé sur une dizaine d’années d’expérience de déploiement d’applications énormes (distribuées) sur des clusters de machines.

Dans la mythologie Cloud Native on raconte que son ancêtre est l’orchestrateur borg utilisé par Google dans les années 2000.

La première version est sortie en 2015 et k8s est devenu depuis l’un des projets open source les plus populaires du monde.

L’écosystème logiciel de Kubernetes s’est développée autour la Cloud Native Computing Foundation qui comprend notamment : Google, CoreOS, Mesosphere, Red Hat, Twitter, Huawei, Intel, Cisco, IBM, Docker, Univa et VMware. Cette fondation vise au pilotage et au financement collaboratif du développement de Kubernetes (un peut comme la Linux Foundation).

Trois transformations profondes de l’informatique

Kubernetes se trouve au coeur de trois transformations profondes techniques, humaines et économiques de l’informatique:

  • Le cloud
  • La conteneurisation logicielle
  • Le mouvement DevOps

Il est un des projets qui symbolise et supporte techniquement ces transformations. D’où son omniprésence dans les discussions informatiques actuellement.

Le Cloud

  • Au delà du flou dans l’emploi de ce terme, le cloud est un mouvement de réorganisation technique et économique de l’informatique.
  • On retourne à la consommation de “temps de calcul” et de services après une “aire du Personnal Computer”.
  • Pour organiser cela on définit trois niveaux à la fois techniques et économiques de l’informatique:
    • Software as a Service: location de services à travers internet pour les usagers finaux
    • Plateform as a Service: location d’un environnement d’exécution logiciel flexible à destination des développeurs
    • Infrastructure as a Service: location de resources “matérielles” à la demande pour installer des logiciels sans avoir à maintenir un data center.

Conteneurisation

La conteneurisation est permise par l’isolation au niveau du noyau du système d’exploitation du serveur : les processus sont isolés dans des namespaces au niveau du noyau. Cette innovation permet de simuler l’isolation sans ajouter une couche de virtualisation comme pour les machines virtuelles.

Ainsi les conteneurs permettent d’avoir des performances proche d’une application traditionnelle tournant directement sur le système d’exploitation hote et ainsi d’optimiser les ressources.

Les images de conteneurs sont aussi beaucoup plus légers qu’une image de VM ce qui permet de

Les technologies de conteneurisation permettent donc de faire des boîtes isolées avec les logiciels pour apporter l’uniformisation du déploiement:

  • Un façon standard de packager un logiciel (basée sur le)
  • Cela permet d’assembler de grosses applications comme des legos
  • Cela réduit la complexité grâce:
    • à l’intégration de toutes les dépendance déjà dans la boîte
    • au principe d’immutabilité qui implique de jeter les boîtes ( automatiser pour lutter contre la culture prudence). Rend l’infra prédictible.

Les conteneurs sont souvent comparés à l’innovation du porte conteneur pour le transport de marchandise.

Le mouvement DevOps

  • Dépasser l’opposition culturelle et de métier entre les développeurs et les administrateurs système.
  • Intégrer tout le monde dans une seule équipe et …
  • Calquer les rythmes de travail sur l’organisation agile du développement logiciel
  • Rapprocher techniquement la gestion de l’infrastructure du développement avec l’infrastructure as code.
    • Concrètement on écrit des fichiers de code pour gérer les éléments d’infra
    • l’état de l’infrastructure est plus claire et documentée par le code
    • la complexité est plus gérable car tout est déclaré et modifiable au fur et à mesure de façon centralisée
    • l’usage de git et des branches/tags pour la gestion de l’évolution d’infrastructure

Objectifs du DevOps

  • Rapidité (velocity) de déploiement logiciel (organisation agile du développement et livraison jusqu’à plusieurs fois par jour)
    • Implique l’automatisation du déploiement et ce qu’on appelle la CI/CD c’est à dire une infrastructure de déploiement continu à partir de code.
  • Passage à l’échelle (horizontal scaling) des logiciels et des équipes de développement (nécessaire pour les entreprises du cloud qui doivent servir pleins d’utilisateurs)
  • Meilleure organisation des équipes
    • meilleure compréhension globale du logiciel et de son installation de production car le savoir est mieux partagé
    • organisation des équipes par thématique métier plutôt que par spécialité technique (l’équipe scale mieux)

Apports techniques de Kubernetes pour le DevOps

  • Abstraction et standardisation des infrastructures:
  • Langage descriptif et incrémental: on décrit ce qu’on veut plutôt que la logique complexe pour l’atteindre
  • Logique opérationnelle intégrée dans l’orchestrateur: la responsabilité des l’état du cluster est laissé au controlleur k8s ce qui simplifie le travail

On peut alors espérer fluidifier la gestion des défis techniques d’un grosse application et atteindre plus ou moins la livraison logicielle continue (CD de CI/CD)

Architecture logicielle optimale pour Kubernetes

Kubernetes est très versatile et permet d’installer des logiciels traditionnels “monolithiques” (gros backends situés sur une seule machine).

Cependant aux vues des transformations humaines et techniques précédentes, l’organisation de Kubernetes prend vraiment sens pour le développement d’applications microservices:

  • des applications avec de nombreux de “petits” services.
  • chaque service a des problématiques très limitées (gestion des factures = un logiciel qui fait que ça)
  • les services communiquent par le réseaux selon différents modes/API (REST, gRPC, job queues, GraphQL)

Les microservices permettent justement le DevOps car:

  • ils peuvent être déployés séparéments
  • une petite équipe gère chaque service ou groupe thématique de services

Nous y reviendrons pour expliquer l’usage des ressources Kubernetes.

Objets fondamentaux de Kubernetes

  • Les pods Kubernetes servent à grouper des conteneurs fortement couplés en unités d’application
  • Les deployments sont une abstraction pour créer ou mettre à jour (ex : scaler) des groupes de pods.
  • Enfin, les services sont des points d’accès réseau qui permettent aux différents workloads (deployments) de communiquer entre eux et avec l’extérieur.

Au delà de ces trois éléments, l’écosystème d’objets de Kubernetes est vaste et complexe

Kubernetes entre Cloud et auto-hébergement

Un des intérêts principaux de Kubernetes est de fournir un modèle de Plateform as a Service (PaaS) suffisamment versatile qui permet l’interopérabilité entre des fournisseurs de clouds différents et des solutions auto-hébergées (on premise).

Cependant cette interopérabilité n’est pas automatique (pour les cas complexes) car Kubernetes permet beaucoup de variations. Concrètement il existe des variations entre les installations possibles de Kubernetes

Distributions et “flavours” de Kubernetes

Kubernetes est avant tout un ensemble de standards qui peuvent avoir des implémentations concurrentes. Il existe beaucoup de variétés (flavours) de Kubernetes, implémentant concrètement les solutions techniques derrière tout ce que Kubernetes ne fait que définir : solutions réseau, stockage (distribué ou non), loadbalancing, service de reverse proxy (Ingress), autoscaling de cluster (ajout de nouvelles VM au cluster automatiquement), monitoring…

Il est très possible de monter un cluster Kubernetes en dehors de ces fournisseurs, mais cela demande de faire des choix (ou bien une solution opinionated ouverte comme Rancher) et une relative maîtrise d’un nombre varié de sujets (bases de données, solutions de loadbalancing, redondance du stockage…).

C’est là un tradeoff de kubernetes : tout est ouvert et standardisé, mais devant la (relative) complexité et connaissance nécessaire pour mettre en place sa propre solution (de stockage distribué par exemple) il est souvent préférable de louer un cluster chez un fournisseur quitte à retomber dans un certain vendor lock-in (enfermement propriétaire).

Quelques variantes connues de Kubernetes:

  • Google Kubernetes Engine (GKE) (Google Cloud Plateform): L’écosystème Kubernetes développé par Google. Très populaire car très flexible tout en étant l’implémentation de référence de Kubernetes.
  • Azure Kubernetes Services (AKS) (Microsoft Azure): Un écosystème Kubernetes axé sur l’intégration avec les services du cloud Azure (stockage, registry, réseau, monitoring, services de calcul, loadbalancing, bases de données…).
  • Elastic Kubernetes Services (EKS) (Amazon Web Services): Un écosystème Kubernetes assez standard à la sauce Amazon axé sur l’intégration avec le cloud Amazon (la gestion de l’accès, des loadbalancers ou du scaling notamment, le stockage avec Amazon EBS, etc.).
  • Rancher: Un écosystème Kubernetes très complet, assez opinionated et entièrement open-source, non lié à un fournisseur de cloud. Inclut l’installation de stack de monitoring (Prometheus), de logging, de réseau mesh (Istio) via une interface web agréable. Rancher maintient aussi de nombreuses solutions open source, comme par exemple Longhorn pour le stockage distribué.
  • K3S: Un écosystème Kubernetes fait par l’entreprise Rancher et axé sur la légèreté. Il remplace etcd par une base de données Postgres, utilise Traefik pour l’ingress et Klipper pour le loadbalancing.
  • Openshift : Une version de Kubernetes configurée et optimisée par Red Hat pour être utilisée dans son écosystème. Tout est intégré donc plus guidé, avec l’inconvénient d’être un peu captif·ve de l’écosystème et des services vendus par Red Hat.

02 - Cours - Mettre en place un cluster Kubernetes

Architecture de Kubernetes - Partie 1

Kubernetes master
  • Le Kubernetes master est responsable du maintien de l’état souhaité pour votre cluster. Lorsque vous interagissez avec Kubernetes, par exemple en utilisant l’interface en ligne de commande kubectl, vous communiquez avec le master Kubernetes de votre cluster.

  • Le “master” fait référence à un ensemble de processus gérant l’état du cluster. Le master peut également être répliqué pour la disponibilité et la redondance.

Noeuds Kubernetes

Les nœuds d’un cluster sont les machines (serveurs physiques, machines virtuelles, etc.) qui exécutent vos applications et vos workflows. Le master node Kubernetes contrôle chaque noeud; vous interagirez rarement directement avec les nœuds.

  • Pour utiliser Kubernetes, vous utilisez les objets de l’API Kubernetes pour décrire l’état souhaité de votre cluster: quelles applications ou autres processus que vous souhaitez exécuter, quelles images de conteneur elles utilisent, le nombre de réplicas, les ressources réseau et disque que vous mettez à disposition, et plus encore.

  • Vous définissez l’état souhaité en créant des objets à l’aide de l’API Kubernetes, généralement via l’interface en ligne de commande, kubectl. Vous pouvez également utiliser l’API Kubernetes directement pour interagir avec le cluster et définir ou modifier l’état souhaité.

  • Une fois que vous avez défini l’état souhaité, le plan de contrôle Kubernetes (control plane) permet de faire en sorte que l’état actuel du cluster corresponde à l’état souhaité. Pour ce faire, Kubernetes effectue automatiquement diverses tâches, telles que le démarrage ou le redémarrage de conteneurs, la mise à jour du nombre de replicas d’une application donnée, etc.

Le Kubernetes Control Plane

  • Le control plane Kubernetes comprend un ensemble de processus en cours d’exécution sur votre cluster:

    • Le master Kubernetes est un ensemble de trois processus qui s’exécutent sur un seul nœud de votre cluster, désigné comme nœud maître (master node en anglais). Ces processus sont:

      • kube-apiserver: expose l’API pour parler au cluster
      • kube-controller-manager: basé sur une boucle qui controlle en permanence l’état des resources et essaie de le corriger s’il n’est plus conforme.
      • kube-scheduler: monitore les resources des différents workers, décide et cartographie ou doivent être créé les conteneur(Pods)
    • Chaque nœud (master et worker) de votre cluster exécute deux processus : kubelet, qui communique avec le Kubernetes master et controle la création et l’état des pods sur son noeud. kube-proxy, un proxy réseau reflétant les services réseau Kubernetes sur chaque nœud.

Les différentes parties du control plane Kubernetes, telles que les processus kube-controller-manager et kubelet, déterminent la manière dont Kubernetes communique avec votre cluster.

Le control plane conserve un enregistrement de tous les objets Kubernetes du système et exécute des boucles de contrôle continues pour gérer l’état de ces objets. À tout moment, les boucles de contrôle du control plane répondent aux modifications du cluster et permettent de faire en sorte que l’état réel de tous les objets du système corresponde à l’état souhaité que vous avez fourni.

Par exemple, lorsque vous utilisez l’API Kubernetes pour créer un objet Deployment, vous fournissez un nouvel état souhaité pour le système. Le control plane Kubernetes enregistre la création de cet objet et exécute vos instructions en lançant les applications requises et en les planifiant vers des nœuds de cluster, afin que l’état actuel du cluster corresponde à l’état souhaité.

Le client kubectl

…Permet depuis sa machine de travail de contrôler le cluster avec une ligne de commande qui ressemble un peu à celle de Docker (cf. TP1 et TP2):

  • Lister les ressources
  • Créer et supprimer les ressources
  • Gérer les droits d’accès
  • etc.

Cet utilitaire s’installe avec un gestionnaire de paquet classique mais est souvent fourni directement par une distribution de développement de kubernetes.

Nous l’installerons avec snap dans le TP1.

Pour se connecter, kubectl a besoin de l’adresse de l’API Kubernetes, d’un nom d’utilisateur et d’un certificat.

  • Ces informations sont fournies sous forme d’un fichier YAML appelé kubeconfig
  • Comme nous le verrons en TP ces informations sont généralement fournies directement par le fournisseur d’un cluster k8s (provider ou k8s de dev)

Le fichier kubeconfig par défaut se trouve sur Linux à l’emplacement ~/.kube/config.

On peut aussi préciser la configuration au runtime comme ceci: kubectl --kubeconfig=fichier_kubeconfig.yaml <commandes_k8s>

Le même fichier kubeconfig peut stocker plusieurs configurations dans un fichier YAML :

Exemple :

apiVersion: v1

clusters:
- cluster:
    certificate-authority: /home/jacky/.minikube/ca.crt
    server: https://172.17.0.2:8443
  name: minikube
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURKekNDQWcrZ0F3SUJBZ0lDQm5Vd0RRWUpLb1pJaHZjTkFRRUxCUUF3TXpFVk1CTUdBMVVFQ2hNTVJHbG4KYVhSaGJFOWpaV0Z1TVJvd0dBWURWUVFERXhGck9<clipped>3SCsxYmtGOHcxdWI5eHYyemdXU1F3NTdtdz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
    server: https://5ba26bee-00f1-4088-ae11-22b6dd058c6e.k8s.ondigitalocean.com
  name: do-lon1-k8s-tp-cluster

contexts:
- context:
    cluster: minikube
    user: minikube
  name: minikube
- context:
    cluster: do-lon1-k8s-tp-cluster
    user: do-lon1-k8s-tp-cluster-admin
  name: do-lon1-k8s-tp-cluster
current-context: do-lon1-k8s-tp-cluster

kind: Config
preferences: {}

users:
- name: do-lon1-k8s-tp-cluster-admin
  user:
      token: 8b2d33e45b980c8642105ec827f41ad343e8185f6b4526a481e312822d634aa4
- name: minikube
  user:
    client-certificate: /home/jacky/.minikube/profiles/minikube/client.crt
    client-key: /home/jacky/.minikube/profiles/minikube/client.key

Ce fichier déclare 2 clusters (un local, un distant), 2 contextes et 2 users.

Installation de développement

Pour installer un cluster de développement :

  • solution officielle : Minikube, tourne dans Docker par défaut (ou dans des VMs)
  • solution très pratique et vanilla: kind
  • avec Docker Desktop depuis peu (dans une VM aussi)
  • un cluster léger avec k3s, de Rancher (simple et utilisable en production/edge)

Commander un cluster en tant que service (managed cluster) dans le cloud

Tous les principaux provider de cloud fournissent depuis plus ou moins longtemps des solutions de cluster gérées par eux :

  • Google Cloud Plateform avec Google Kubernetes Engine (GKE) : très populaire car très flexible et l’implémentation de référence de Kubernetes.
  • AWS avec EKS : Kubernetes assez standard mais à la sauce Amazon pour la gestion de l’accès, des loadbalancers ou du scaling.
  • Azure avec AKS : Kubernetes assez standard mais à la sauce Amazon pour la gestion de l’accès, des loadbalancers ou du scaling.
  • DigitalOcean ou Scaleway : un peu moins de fonctions mais plus simple à appréhender

Pour sa qualité on recommande souvent Google GKE qui est plus ancien avec un bonne UX. Mais il s’agit surtout de faciliter l’intégration avec l’existant:

  • Si vous utilisez déjà AWS ou Azure

Installer un cluster de production on premise : l’outil officiel kubeadm

kubeadm est un utilitaire aider générer les certificats et les configurations spéciques pour le control plane et connecter les noeuds au control plane. Il permet également d’effectuer des taches de maintenant comme la mise à jour progressive (rolling) de chaque noeud du cluster.

  • Installer le dæmon Kubelet sur tous les noeuds
  • Installer l’outil de gestion de cluster kubeadm sur un noeud master
  • Générer les bons certificats avec kubeadm
  • Installer un réseau CNI k8s comme flannel (d’autres sont possible et le choix vous revient)
  • Déployer la base de données etcd avec kubeadm
  • Connecter les nœuds worker au master.

L’installation est décrite dans la documentation officielle

Opérer et maintenir un cluster de production Kubernetes “à la main” est très complexe et une tâche à ne pas prendre à la légère. De nombreux éléments doivent être installés et géré par les opérateurs.

  • Mise à jour et passage de version de kubernetes qui doit être fait très régulièrement car une version n’est supportée que 2 ans.
  • Choix d’une configuration réseau et de sécurité adaptée.
  • Installation probable de système de stockage distribué comme Ceph à maintenir également dans le temps
  • Etc.

Kubespray

https://kubespray.io/#/

En réalité utiliser kubeadm directement en ligne de commande n’est pas la meilleure approche car cela ne respecte pas l’infrastructure as code et rend plus périlleux la maintenance/maj du cluster par la suite.

Le projet kubespray est un installer de cluster kubernetes utilisant Ansible et kubeadm. C’est probablement l’une des méthodes les plus populaires pour véritablement gérer un cluster de production on premise.

Mais la encore il s’agit de ne pas sous-estimer la complexité de la maintenance (comme avec kubeadm).

Installer un cluster complètement à la main pour s’exercer

On peut également installer Kubernetes de façon encore plus manuelle pour mieux comprendre ses rouages et composants. Ce type d’installation est décrite par exemple ici : Kubernetes the hard way.

Remarque sur les clusters hybrides

Il est possible de connecter plusieurs clusters ensembles dans le cloud chez plusieurs fournisseurs

03 - TP1 - Installation et configuration de Kubernetes

Au cours de nos TPs nous allons passer rapidement en revue deux manières de mettre en place Kubernetes :

  • Un cluster de développement avec minikube
  • Un cluster managed loué chez un provider (Scaleway, DigitalOcean, Azure ou Google Cloud)

Nous allons d’abord passer par la première option.

Découverte de Kubernetes

Installer le client CLI kubectl

kubectl est le point d’entré universel pour contrôler tous les type de cluster kubernetes. C’est un client en ligne de commande qui communique en REST avec l’API d’un cluster.

Nous allons explorer kubectl au fur et à mesure des TPs. Cependant à noter que :

  • kubectl peut gérer plusieurs clusters/configurations et switcher entre ces configurations
  • kubectl est nécessaire pour le client graphique Lens que nous utiliserons plus tard.

La méthode d’installation importe peu. Pour installer kubectl sur Ubuntu nous ferons simplement: sudo snap install kubectl --classic.

  • Faites kubectl version pour afficher la version du client kubectl.
Bash completion

Pour permettre à kubectl de compléter le nom des commandes et ressources avec <Tab> il est utile d’installer l’autocomplétion pour Bash :

sudo apt install bash-completion

source <(kubectl completion bash)

echo "source <(kubectl completion bash)" >> ${HOME}/.bashrc

Vous pouvez désormais appuyer sur <Tab> pour compléter vos commandes kubectl, c’est très utile !

Installation de k3s

K3s est une distribution de Kubernetes orientée vers la création de petits clusters de production notamment pour l’informatique embarquée et l’Edge computing. Elle a la caractéristique de rassembler les différents composants d’un cluster kubernetes en un seul “binaire” pouvant s’exécuter en mode master (noeud du control plane) ou agent (noeud de calcul).

Avec K3s, il est possible d’installer un petit cluster d’un seul noeud en une commande ce que nous allons faire ici:

  • Lancez dans un terminal la commande suivante: curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--disable=traefik" sh -

La configuration kubectl pour notre nouveau cluster k3s est dans le fichier /etc/rancher/k3s/k3s.yaml et accessible en lecture uniquement par root. Pour se connecter au cluster on peut donc faire (parmis d’autre méthodes pour gérer la kubeconfig):

  • Créer le répertoire ~/.kube
  • Copie de la conf sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
  • Changer les permission sudo chown $USER ~/.kube/k3s.yaml
  • activer cette configuration pour kubectl avec une variable d’environnement: export KUBECONFIG=~/.kube/k3s.yaml
  • Tester la configuration avec kubectl get nodes qui devrait renvoyer quelque chose proche de:
NAME                 STATUS   ROLES                  AGE   VERSION
vnc-stagiaire-...   Ready    control-plane,master   10m   v1.21.7+k3s1

Explorons notre cluster k8s

Notre cluster k8s est plein d’objets divers, organisés entre eux de façon dynamique pour décrire des applications, tâches de calcul, services et droits d’accès. La première étape consiste à explorer un peu le cluster :

  • Listez les nodes pour récupérer le nom de l’unique node (kubectl get nodes) puis affichez ses caractéristiques avec kubectl describe node/<nom du node>.

La commande get est générique et peut être utilisée pour récupérer la liste de tous les types de ressources.

De même, la commande describe peut s’appliquer à tout objet k8s. On doit cependant préfixer le nom de l’objet par son type (ex : node/<nom du node> ou nodes <nom du node>) car k8s ne peut pas deviner ce que l’on cherche quand plusieurs ressources ont le même nom.

  • Pour afficher tous les types de ressources à la fois que l’on utilise : kubectl get all
NAME                 TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.96.0.1   <none>        443/TCP   2m34s

Il semble qu’il n’y a qu’une ressource dans notre cluster. Il s’agit du service d’API Kubernetes, pour qu’on puisse communiquer avec le cluster.

En réalité il y en a généralement d’autres cachés dans les autres namespaces. En effet les éléments internes de Kubernetes tournent eux-mêmes sous forme de services et de daemons Kubernetes. Les namespaces sont des groupes qui servent à isoler les ressources de façon logique et en termes de droits (avec le Role-Based Access Control (RBAC) de Kubernetes).

Pour vérifier cela on peut :

  • Afficher les namespaces : kubectl get namespaces

Un cluster Kubernetes a généralement un namespace appelé default dans lequel les commandes sont lancées et les ressources créées si on ne précise rien. Il a également aussi un namespace kube-system dans lequel résident les processus et ressources système de k8s. Pour préciser le namespace on peut rajouter l’argument -n à la plupart des commandes k8s.

  • Pour lister les ressources liées au kubectl get all -n kube-system.

  • Ou encore : kubectl get all --all-namespaces (peut être abrégé en kubectl get all -A) qui permet d’afficher le contenu de tous les namespaces en même temps.

  • Pour avoir des informations sur un namespace : kubectl describe namespace/kube-system

Déployer une application en CLI

Nous allons maintenant déployer une première application conteneurisée. Le déploiement est un peu plus complexe qu’avec Docker, en particulier car il est séparé en plusieurs objets et plus configurable.

  • Pour créer un déploiement en ligne de commande (par opposition au mode déclaratif que nous verrons plus loin), on peut lancer par exemple: kubectl create deployment demonstration --image=monachus/rancher-demo.

Cette commande crée un objet de type deployment. Nous pourvons étudier ce deployment avec la commande kubectl describe deployment/demonstration.

  • Notez la liste des événements sur ce déploiement en bas de la description.

  • De la même façon que dans la partie précédente, listez les pods avec kubectl. Combien y en a-t-il ?

  • Agrandissons ce déploiement avec kubectl scale deployment demonstration --replicas=5

  • kubectl describe deployment/demonstration permet de constater que le service est bien passé à 5 replicas.

    • Observez à nouveau la liste des évènements, le scaling y est enregistré…
    • Listez les pods pour constater

A ce stade impossible d’afficher l’application : le déploiement n’est pas encore accessible de l’extérieur du cluster. Pour régler cela nous devons l’exposer grace à un service :

  • kubectl expose deployment demonstration --type=NodePort --port=8080 --name=demonstration-service

  • Affichons la liste des services pour voir le résultat: kubectl get services

Un service permet de créer un point d’accès unique exposant notre déploiement. Ici nous utilisons le type Nodeport car nous voulons que le service soit accessible de l’extérieur par l’intermédiaire d’un forwarding de port.

Une méthode pour accéder à un service (quel que soit sont type) en mode développement est de forwarder le traffic par l’intermédiaire de kubectl (et des composants kube-proxy installés sur chaque noeuds du cluster).

  • Pour cela on peut par exemple lancer: kubectl port-forward svc/demonstration-service 8080:8080 --address 127.0.0.1
  • Vous pouvez désormais accéder à votre app via via kubectl sur: http://localhost:8080. Quelle différence avec l’exposition précédente via minikube ?

=> Un seul conteneur s’affiche. En effet kubectl port-forward sert à créer une connexion de developpement/debug qui pointe toujours vers le même pod en arrière plan.

Pour exposer cette application en production sur un véritable cluster, nous devrions plutôt avoir recours à un service de type un LoadBalancer. Nous y reviendrons dans le cours sur les objets kubernetes.

Simplifier les lignes de commande k8s

  • Pour gagner du temps on dans les commandes Kubernetes on peut définir un alias: alias kc='kubectl' (à mettre dans votre .bash_profile en faisant echo "alias kc='kubectl'" >> ~/.bash_profile, puis en faisant source ~/.bash_profile).

  • Vous pouvez ensuite remplacer kubectl par kc dans les commandes.

  • Également pour gagner du temps en ligne de commande, la plupart des mots-clés de type Kubernetes peuvent être abrégés :

    • services devient svc
    • deployments devient deploy
    • etc.

La liste complète : https://blog.heptio.com/kubectl-resource-short-names-heptioprotip-c8eff9fb7202

  • Essayez d’afficher les serviceaccounts (users) et les namespaces avec une commande courte.

Une 2e installation : Mettre en place un cluster K8s managé chez le provider de cloud Scaleway

Je vais louer pour vous montrer un cluster kubernetes managé. Vous pouvez également louez le votre si vous préférez en créant un compte chez ce provider de cloud.

La création prend environ 5 minutes.

  • Sur la page décrivant votre cluster, un gros bouton en bas de la page vous incite à télécharger ce même fichier kubeconfig (Download Kubeconfig).

Ce fichier contient la configuration kubectl adaptée pour la connexion à notre cluster.

  • Listez les contextes avec kubectl config get-contexts et affichez les contexte courant avec kubectl config current-context.

  • Changez de contexte avec kubectl config use-context <nom_contexte>.

  • Testons quelle connexion nous utilisons avec avec kubectl get nodes.

  • Observons les derniers évènements arrivés à notre cluster avec kubectl get events --watch.

Au délà de la ligne de commande…

Accéder à la dashboard Kubernetes

Le moyen le plus classique pour avoir une vue d’ensemble des ressources d’un cluster est d’utiliser la Dashboard officielle. Cette Dashboard est généralement installée par défaut lorsqu’on loue un cluster chez un provider.

On peut aussi l’installer dans minikube ou k3s.

=> Démonstration

Installer Lens

Lens est une interface graphique (un client “lourd”) pour Kubernetes. Elle se connecte en utilisant kubectl et la configuration ~/.kube/config par défaut et nous permettra d’accéder à un dashboard puissant et agréable à utiliser.

Vous pouvez l’installer en lançant ces commandes :

## Ajouter OpenLens
curl -LO https://github.com/MuhammedKalkan/OpenLens/releases/download/v6.5.2-366/OpenLens-6.5.2-366.x86_64.AppImage
sudo chmod +x OpenLens-6.5.2-366.x86_64.AppImage
  • Lancez l’application OpenLens grâce à ./OpenLens-6.5.2-366.x86_64.AppImage
  • Explorons ensemble les ressources dans les différentes rubriques et namespaces

04 - Cours - Objets Kubernetes - Partie 1

L’API et les Objets Kubernetes

Utiliser Kubernetes consiste à déclarer des objets grâce à l’API Kubernetes pour décrire l’état souhaité d’un cluster : quelles applications ou autres processus exécuter, quelles images elles utilisent, le nombre de replicas, les ressources réseau et disque que vous mettez à disposition, etc.

On définit des objets généralement via l’interface en ligne de commande et kubectl de deux façons :

  • en lançant une commande kubectl run <conteneur> ..., kubectl expose ...
  • en décrivant un objet dans un fichier YAML ou JSON et en le passant au client kubectl apply -f monpod.yml

Vous pouvez également écrire des programmes qui utilisent directement l’API Kubernetes pour interagir avec le cluster et définir ou modifier l’état souhaité. Kubernetes est complètement automatisable !

La commande apply

Kubernetes encourage le principe de l’infrastructure-as-code : il est recommandé d’utiliser une description YAML et versionnée des objets et configurations Kubernetes plutôt que la CLI.

Pour cela la commande de base est kubectl apply -f object.yaml.

La commande inverse kubectl delete -f object.yaml permet de détruire un objet précédement appliqué dans le cluster à partir de sa description.

Lorsqu’on vient d’appliquer une description on peut l’afficher dans le terminal avec kubectl apply -f myobj.yaml view-last-applied

Globalement Kubernetes garde un historique de toutes les transformations des objets : on peut explorer, par exemple avec la commande kubectl rollout history deployment.

Parenthèse : Le YAML

Kubernetes décrit ses ressources en YAML. A quoi ça ressemble, YAML ?

- marché:
    lieu: Marché de la Place
    jour: jeudi
    horaire:
      unité: "heure"
      min: 12
      max: 20
    fruits:
      - nom: pomme
        couleur: "verte"
        pesticide: avec

      - nom: poires
        couleur: jaune
        pesticide: sans
    légumes:
      - courgettes
      - salade
      - potiron

Syntaxe

  • Alignement ! (2 espaces !!)

  • ALIGNEMENT !! (comme en python)

  • ALIGNEMENT !!! (le défaut du YAML, pas de correcteur syntaxique automatique, c’est bête mais vous y perdrez forcément du temps !)

  • des listes (tirets)

  • des paires clé: valeur

  • Un peu comme du JSON, avec cette grosse différence que le JSON se fiche de l’alignement et met des accolades et des points-virgules

  • les extensions Kubernetes et YAML dans VSCode vous aident à repérer des erreurs

Syntaxe de base d’une description YAML Kubernetes

Les description YAML permettent de décrire de façon lisible et manipulable de nombreuses caractéristiques des ressources Kubernetes (un peu comme un Compose file par rapport à la CLI Docker).

Exemple

Création d’un service simple :

kind: Service
apiVersion: v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
spec:
  ports:
    - port: 443
      targetPort: 8443
  selector:
    k8s-app: kubernetes-dashboard
  type: NodePort

Remarques de syntaxe :

  • Toutes les descriptions doivent commencer par spécifier la version d’API (minimale) selon laquelle les objets sont censés être créés
  • Il faut également préciser le type d’objet avec kind
  • Le nom dans metadata:\n name: value est également obligatoire.
  • On rajoute généralement une description longue démarrant par spec:

Description de plusieurs ressources

  • On peut mettre plusieurs ressources à la suite dans un fichier k8s : cela permet de décrire une installation complexe en un seul fichier

  • L’ordre n’importe pas car les ressources sont décrites déclarativement c’est-à-dire que:

    • Les dépendances entre les ressources sont déclarées
    • Le control plane de Kubernetes se charge de planifier l’ordre correct de création en fonction des dépendances (pods avant le déploiement, rôle avec l’utilisateur lié au rôle)
    • On préfère cependant les mettre dans un ordre logique pour que les humains puissent les lire.
  • On peut sauter des lignes dans le YAML et rendre plus lisible les descriptions

  • On sépare les différents objets par ---

Objets de base

Les namespaces

Tous les objets Kubernetes sont rangés dans différents espaces de travail isolés appelés namespaces.

Cette isolation permet 3 choses :

  • ne voir que ce qui concerne une tâche particulière (ne réfléchir que sur une seule chose lorsqu’on opère sur un cluster)
  • créer des limites de ressources (CPU, RAM, etc.) pour le namespace
  • définir des rôles et permissions sur le namespace qui s’appliquent à toutes les ressources à l’intérieur.

Lorsqu’on lit ou créé des objets sans préciser le namespace, ces objets sont liés au namespace default.

Pour utiliser un namespace autre que default avec kubectl il faut :

  • le préciser avec l’option -n : kubectl get pods -n kube-system
  • créer une nouvelle configuration dans la kubeconfig pour changer le namespace par defaut.

Kubernetes gère lui-même ses composants internes sous forme de pods et services.

  • Si vous ne trouvez pas un objet, essayez de lancer la commande kubectl avec l’option -A ou --all-namespaces

Les Pods

Un Pod est l’unité d’exécution de base d’une application Kubernetes que vous créez ou déployez. Un Pod représente des process en cours d’exécution dans votre Cluster.

Un Pod encapsule un conteneur (ou souvent plusieurs conteneurs), des ressources de stockage, une IP réseau unique, et des options qui contrôlent comment le ou les conteneurs doivent s’exécuter (ex: restart policy). Cette collection de conteneurs et volumes tournent dans le même environnement d’exécution mais les processus sont isolés.

Un Pod représente une unité de déploiement : un petit nombre de conteneurs qui sont étroitement liés et qui partagent :

  • les mêmes ressources de calcul
  • des volumes communs
  • la même IP donc le même nom de domaine
  • peuvent se parler sur localhost
  • peuvent se parler en IPC
  • ont un nom différent et des logs différents

Chaque Pod est destiné à exécuter une instance unique d’un workload donné. Si vous désirez mettre à l’échelle votre workload, vous devez multiplier le nombre de Pods avec un déploiement.

Pour plus de détail sur la philosophie des pods, vous pouvez consulter ce bon article.

Kubernetes fournit un ensemble de commande pour débugger des conteneurs :

  • kubectl logs <pod-name> -c <conteneur_name> (le nom du conteneur est inutile si un seul)
  • kubectl exec -it <pod-name> -c <conteneur_name> -- bash
  • kubectl attach -it <pod-name>

Enfin, pour debugger la sortie réseau d’un programme on peut rapidement forwarder un port depuis un pods vers l’extérieur du cluster :

  • kubectl port-forward <pod-name> <port_interne>:<port_externe>
  • C’est une commande de debug seulement : pour exposer correctement des processus k8s, il faut créer un service, par exemple avec NodePort.

Pour copier un fichier dans un pod on peut utiliser: kubectl cp <pod-name>:</path/to/remote/file> </path/to/local/file>

Pour monitorer rapidement les ressources consommées par un ensemble de processus il existe les commande kubectl top nodes et kubectl top pods

Un manifeste de Pod

rancher-demo-pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: rancher-demo-pod
spec:
  containers:
    - image: monachus/rancher-demo:latest
      name: rancher-demo-container
      ports:
        - containerPort: 8080
          name: http
          protocol: TCP
    - image: redis
      name: redis-container
      ports:
        - containerPort: 6379
          name: http
          protocol: TCP

Rappel sur quelques concepts

Haute disponibilité

  • Faire en sorte qu’un service ait un “uptime” élevé.

On veut que le service soit tout le temps accessible même lorsque certaines ressources manquent :

  • elles tombent en panne
  • elles sont sorties du service pour mise à jour, maintenance ou modification

Pour cela on doit avoir des ressources multiples…

  • Plusieurs serveurs
  • Plusieurs versions des données
  • Plusieurs accès réseau

Il faut que les ressources disponibles prennent automatiquement le relais des ressources indisponibles. Pour cela on utilise en particulier:

  • des “load balancers” : aiguillages réseau intelligents
  • des “healthchecks” : une vérification de la santé des applications

Nous allons voir que Kubernetes intègre automatiquement les principes de load balancing et de healthcheck dans l’orchestration de conteneurs

Répartition de charge (load balancing)

  • Un load balancer : une sorte d'“aiguillage” de trafic réseau, typiquement HTTP(S) ou TCP.
  • Un aiguillage intelligent qui se renseigne sur plusieurs critères avant de choisir la direction.

Cas d’usage :

  • Éviter la surcharge : les requêtes sont réparties sur différents backends pour éviter de les saturer.

L’objectif est de permettre la haute disponibilité : on veut que notre service soit toujours disponible, même en période de panne/maintenance.

  • Donc on va dupliquer chaque partie de notre service et mettre les différentes instances derrière un load balancer.

  • Le load balancer va vérifier pour chaque backend s’il est disponible (healthcheck) avant de rediriger le trafic.

  • Répartition géographique : en fonction de la provenance des requêtes on va rediriger vers un datacenter adapté (+ proche).

Healthchecks

Fournir à l’application une façon d’indiquer qu’elle est disponible, c’est-à-dire :

  • qu’elle est démarrée (liveness)
  • qu’elle peut répondre aux requêtes (readiness).

Application microservices

  • Une application composée de nombreux petits services communiquant via le réseau. Le calcul pour répondre à une requête est décomposé en différente parties distribuées entre les services. Par exemple:

  • un service est responsable de la gestion des clients et un autre de la gestion des commandes.

  • Ce mode de développement implique souvent des architectures complexes pour être mis en oeuvre et kubernetes est pensé pour faciliter leur gestion à grande échelle.

  • Imaginez devoir relancer manuellement des services vitaux pour une application en hébergeant des centaines d’instances : c’est en particulier à ce moment que kubernetes devient indispensable.

2 exemples d’application microservices:

L’architecture découplée des services Kubernetes

Comme nous l’avons vu dans le TP1, déployer une application dans kubernetes demande plusieurs étapes. En réalité en plus des pods l’ensemble de la gestion d’un service applicatif se décompose dans Kubernetes en 3 à 4 objets articulés entre eux:

  • replicatset
  • deployment
  • service
  • (ingress)

Les Deployments (deploy)

Les déploiements sont les objets effectivement créés manuellement lorsqu’on déploie une application. Ce sont des objets de plus haut niveau que les pods et replicaset et les pilote pour gérer un déploiement applicatif.

Les poupées russes Kubernetes : un Deployment contient un ReplicaSet, qui contient des Pods, qui contiennent des conteneurs

Si c’est nécessaire d’avoir ces trois types de ressources c’est parce que Kubernetes respecte un principe de découplage des responsabilités.

La responsabilité d’un déploiement est de gérer la coexistence et le tracking de versions multiples d’une application et d’effectuer des montées de version automatiques en haute disponibilité en suivant une RolloutStrategy (CF. TP optionnel).

Ainsi lors des changements de version, un seul deployment gère automatiquement deux replicasets contenant chacun une version de l’application : le découplage est nécessaire.

Un deployment implique la création d’un ensemble de Pods désignés par une étiquette label et regroupé dans un Replicaset.

Exemple :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.7.9
          ports:
            - containerPort: 80
  • Pour les afficher : kubectl get deployments

  • La commande kubectl run sert à créer un deployment à partir d’un modèle. Il vaut mieux utilisez apply -f.

Les ReplicaSets (rs)

Dans notre modèle, les ReplicaSet servent à gérer et sont responsables pour:

  • la réplication (avoir le bon nombre d’instances et le scaling)

  • la santé et le redémarrage automatique des pods de l’application (Self-Healing)

  • kubectl get rs pour afficher la liste des replicas.

En général on ne les manipule pas directement (c’est déconseillé) même s’il est possible de les modifier et de les créer avec un fichier de ressource. Pour créer des groupes de conteneurs on utilise soit un Deployment soit d’autres formes de workloads (DaemonSet, StatefulSet, Job) adaptés à d’autres cas.

Les Services

Dans Kubernetes, un service est un objet qui :

  • Désigne un ensemble de pods (grâce à des tags) généralement géré par un déploiement.
  • Fournit un endpoint réseau pour les requêtes à destination de ces pods.
  • Configure une politique permettant d’y accéder depuis l’intérieur ou l’extérieur du cluster.

L’ensemble des pods ciblés par un service est déterminé par un selector.

Par exemple, considérons un backend de traitement d’image (stateless, c’est-à-dire ici sans base de données) qui s’exécute avec 3 replicas. Ces replicas sont interchangeables et les frontends ne se soucient pas du backend qu’ils utilisent. Bien que les pods réels qui composent l’ensemble backend puissent changer, les clients frontends ne devraient pas avoir besoin de le savoir, pas plus qu’ils ne doivent suivre eux-mêmes l’état de l’ensemble des backends.

L’abstraction du service permet ce découplage : les clients frontend s’addressent à une seule IP avec un seul port dès qu’ils ont besoin d’avoir recours à un backend. Les backends vont recevoir la requête du frontend aléatoirement.

Les Services sont de trois types principaux :

  • ClusterIP: expose le service sur une IP interne au cluster. Les autres pods peuvent alors accéder au service de l’intérieur du cluster, mais il n’est pas l’extérieur.

  • NodePort: expose le service depuis l’IP de chacun des noeuds du cluster en ouvrant un port directement sur le nœud, entre 30000 et 32767. Cela permet d’accéder aux pods internes répliqués. Comme l’IP est stable on peut faire pointer un DNS ou Loadbalancer classique dessus.

Crédits à Ahmet Alp Balkan pour les schémas

  • LoadBalancer: expose le service en externe à l’aide d’un Loadbalancer de fournisseur de cloud. Les services NodePort et ClusterIP, vers lesquels le Loadbalancer est dirigé sont automatiquement créés.

Crédits Ahmet Alp Balkan

Les autres types de Workloads Kubernetes

En plus du déploiement d’un application, Il existe pleins d’autre raisons de créer un ensemble de Pods:

  • Le DaemonSet: Faire tourner un agent ou démon sur chaque nœud, par exemple pour des besoins de monitoring, ou pour configurer le réseau sur chacun des nœuds.
  • Le Job : Effectuer une tache unique de durée limitée et ponctuelle, par exemple de nettoyage d’un volume ou la préparation initiale d’une application, etc.
  • Le CronJob : Effectuer une tache unique de durée limitée et récurrente, par exemple de backup ou de régénération de certificat, etc.

De plus même pour faire tourner une application, les déploiements ne sont pas toujours suffisants. En effet ils sont peu adaptés à des applications statefull comme les bases de données de toutes sortes qui ont besoin de persister des données critiques. Pour celà on utilise un StatefulSet que nous verrons par la suite.

Étant donné les similitudes entre les DaemonSets, les StatefulSets et les Deployments, il est important de comprendre un peu précisément quand les utiliser.

Les Deployments (liés à des ReplicaSets) doivent être utilisés :

  • lorsque votre application est complètement découplée du nœud
  • que vous pouvez en exécuter plusieurs copies sur un nœud donné sans considération particulière
  • que l’ordre de création des replicas et le nom des pods n’est pas important
  • lorsqu’on fait des opérations stateless

Les DaemonSets doivent être utilisés :

  • lorsqu’au moins une copie de votre application doit être exécutée sur tous les nœuds du cluster (ou sur un sous-ensemble de ces nœuds).

Les StatefulSets doivent être utilisés :

  • lorsque l’ordre de création des replicas et le nom des pods est important
  • lorsqu’on fait des opérations stateful (écrire dans une base de données)

Jobs

Les jobs sont utiles pour les choses que vous ne voulez faire qu’une seule fois, comme les migrations de bases de données ou les travaux par lots. Si vous exécutez une migration en tant que Pod dans un deployment:

  • Dès que la migration se finit le processus du pod s’arrête.
  • Le replicaset qui détecte que l'“application” s’est arrêter va tenter de la redémarrer en recréant le pod.
  • Votre tâche de migration de base de données se déroulera donc en boucle, en repeuplant continuellement la base de données.

CronJobs

Comme des jobs, mais se lancent à un intervalle régulier, comme les cron sur les systèmes unix.

05 - TP 2 - Déployer en utilisant des fichiers ressource et Lens

Dans ce court TP nous allons redéployer notre application demonstration du TP1 mais cette fois en utilisant kubectl apply -f et en visualisant le résultat dans Lens.

N’hésitez pas aussi à observer les derniers évènements arrivés à votre cluster avec kubectl get events --watch.

  • Changez de contexte pour k3s avec kubectl config use-context k3s ou kubectl config use-context default
  • Chargez également la configuration de k3s dans Lens en cliquant à nouveau sur plus et en selectionnant k3s ou default
  • Commencez par supprimer les ressources demonstration et demonstration-service du TP1
  • Créez un dossier TP2_deploy_using_files_and_Lens sur le bureau de la machine distante et ouvrez le avec VSCode.

Nous allons d’abord déployer notre application comme un simple Pod (non recommandé mais montré ici pour l’exercice).

  • Créez un fichier demo-pod.yaml avec à l’intérieur le code d’exemple suivant :

rancher-demo-pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: rancher-demo-pod
spec:
  containers:
    - image: monachus/rancher-demo:latest
      name: rancher-demo-container
      ports:
        - containerPort: 8080
          name: http
          protocol: TCP
    - image: redis
      name: redis-container
      ports:
        - containerPort: 6379
          name: http
          protocol: TCP
  • Appliquez le ficher avec kubectl apply -f <fichier>
  • Constatez dans Lens dans la partie pods que les deux conteneurs du pod sont bien démarrés (deux petits carrés vert à droite de la ligne du pod)
  • Modifiez le nom du pod dans la description précédente et réappliquez la configuration. Kubernetes mets à jour le nom.
  • Modifier le nom du conteneur rancher-demo et réappliquez la configuration. Que se passe-t-il ?

=> Kubernetes refuse d’appliquer le nouveau nom de conteneur car un pod est largement immutable. Pour changer d’une quelquonque façon les conteneurs du pod il faut supprimer (kubectl delete -f <fichier>) et recréer le pod. Mais ce travail de mise à jour devrais être géré par un déploiement pour automatiser et pour garantir la haute disponibilité de notre application demonstration.

Kubernetes fournit un ensemble de commande pour débugger des conteneurs :

  • kubectl logs <pod-name> -c <conteneur_name> (le nom du conteneur est inutile si un seul)

  • kubectl exec -it <pod-name> -c <conteneur_name> -- bash

  • kubectl attach -it <pod-name>

  • Explorez le pod avec la commande kubectl exec -it <pod-name> -c <conteneur_name> -- bash écrite plus haut.

  • Supprimez le pod.

Avec un déploiement (méthode à utiliser)

  • Créez un fichier demo-deploy.yaml avec à l’intérieur le code suivant à compléter:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demonstration
  labels:
    nom-app: demonstration
    partie: objet-deploiement
spec:
  selector:
    matchLabels:
      nom-app: demonstration
      partie: les-petits-pods-demo
  strategy:
    type: Recreate
  replicas: 1
  template:
    metadata:
      labels:
        nom-app: demonstration
        partie: les-petits-pods-demo
    spec:
      containers:
        - image: <image>
          name: <name>
          ports:
            - containerPort: <port>
              name: demo-http
  • Appliquez ce nouvel objet avec kubectl.
  • Inspectez le déploiement dans Lens.
  • Changez le nom d’un conteneur et réappliquez: Cette fois le déploiement se charge créer un nouveau pod avec les bonnes caractéristiques et de supprimer l’ancien.
  • Changez le nombre de réplicats.

Ajoutons un service en mode NodePort

  • Créez un fichier demo-svc.yaml avec à l’intérieur le code suivant à compléter:
apiVersion: v1
kind: Service
metadata:
  name: demo-service
  labels:
    nom-app: demonstration
    partie: le-fameux-service-demo
spec:
  ports:
    - port: <port>
  selector:
    nom-app: demonstration
    partie: les-petits-pods-demo
  type: NodePort
  • Appliquez ce nouvel objet avec kubectl.
  • Inspectez le service dans Lens.
  • Visitez votre application avec l’Internal ip du noeud (à trouver dans les information du node) et le nodeport (port 3xxxx) associé au service, le nombre de réplicat devrait apparaître.
  • Pour tester, changez le label du selector dans le service (lignes nom-app: demonstration et partie: les-petits-pods-demo à remplacer dans le fichier ) et réappliquez.
  • Constatez que l’application n’est plus accessible dans le navigateur. Pourquoi ?
  • Allez voir la section endpoints dans lens, constatez que quand l’étiquette est la bonne la liste des IPs des pods est présente et après la modification du selector la liste est vide (None)

=> Les services kubernetes redirigent le trafic basés sur les étiquettes (labels) appliquées sur les pods du cluster. Il faut donc de même éviter d’utiliser deux fois le même label pour des parties différentes de l’application.

Solution

Le dépôt Git de la correction de ce TP est accessible ici : git clone -b correction_k8s_tp2 https://github.com/Uptime-Formation/corrections_tp.git

06 - Rappels Docker

Les Dockerfiles

Les volumes et les conteneurs

Pour un exemple docker que nous allons réutiliser dans le TP3 vous pouvez cloner le code suivant: git clone -b correction_k8s_tp2 https://github.com/Uptime-Formation/corrections_tp.git

07 - TP 3 - Déployer des conteneurs de A à Z

Récupérez le projet de base en clonant la correction du TP2: git clone -b exercice https://github.com/Uptime-Formation/tp3-k8s.git tp3. On peut ouvrir une fenêtre VSCode directement dans le dossier qui nous intéresse avec : code tp3.

Ce TP va consister à créer des objets Kubernetes pour déployer une application microservices (plutôt simple) : monsterstack. Elle est composée :

  • d’un front-end en Flask (Python) appelé monstericon,
  • d’un service de backend qui génère des images (un avatar de monstre correspondant à une chaîne de caractères) appelé dnmonster
  • et d’un datastore redis servant de cache pour les images de monstericon

Nous allons également utiliser le builder kubernetes skaffold pour déployer l’application en mode développement : l’image du frontend monstericon sera construite à partir du code source présent dans le dossier app et automatiquement déployée dans minikube.

Etudions le code et testons avec docker compose

  • Monstericon est une application web python (flask) qui propose un petit formulaire et lance une requete sur le backend pour chercher une image et l’afficher.
  • Monstericon est construit à partir du Dockerfile présent dans le dossier TP3.
  • Le fichier docker-compose.yml est utile pour faire tourner les trois services de l’application dans docker rapidement (plus simple que kubernetes)

Pour lancer l’application il suffit :

  1. d’installer Docker avec : curl https://get.docker.com | sudo sh -
  2. puis d’exécuter : sudo docker compose up -d
  3. on peut afficher l’install avec sudo docker compose ps et les logs avec sudo docker compose logs -f

Passons maintenant à Kubernetes.

Utiliser Kompose (facultatif)

Explorer avec Kompose comment on peut traduire un fichier docker-compose.yml en ressources Kubernetes (ce sont les instructions à la page suivante : https://kubernetes.io/fr/docs/tasks/configure-pod-container/translate-compose-kubernetes/).

D’abord, installons Kompose :

# Linux
curl -L https://github.com/kubernetes/kompose/releases/download/v1.26.1/kompose-linux-amd64 -o kompose

chmod +x kompose
sudo mv ./kompose /usr/local/bin/kompose

Puis, utilisons la commande kompose convert et observons les fichiers générés. On peut ensuite faire kubectl apply avec les ressources créées à partir du fichier Compose.

Déploiements pour le backend d’image dnmonster et le datastore redis

Maintenant nous allons également créer un déploiement pour dnmonster:

  • créez dnmonster.yaml dans le dossier k8s-deploy-dev et collez-y le code suivant :

dnmonster.yaml :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dnmonster
  labels:
    app: monsterstack
spec:
  selector:
    matchLabels:
      app: monsterstack
      partie: dnmonster
  strategy:
    type: Recreate
  replicas: 5
  template:
    metadata:
      labels:
        app: monsterstack
        partie: dnmonster
    spec:
      containers:
        - image: amouat/dnmonster:1.0
          name: dnmonster
          ports:
            - containerPort: 8080
              name: dnmonster
  • Ensuite, configurons un deuxième deployment redis.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis
  labels:
    app: monsterstack
spec:
  selector:
    matchLabels:
      app: monsterstack
      partie: redis
  strategy:
    type: Recreate
  replicas: 1
  template:
    metadata:
      labels:
        app: monsterstack
        partie: redis
    spec:
      containers:
        - image: redis:latest
          name: redis
          ports:
            - containerPort: 6379
              name: redis
  • Appliquez ces ressources avec kubectl apply -f k8s-deploy-dev/ et vérifiez dans Lens que les 5 + 1 réplicats sont bien lancés.

Déploiement du frontend monstericon

Ajoutez au fichier monstericon.yml du dossier k8s-deploy-dev le code suivant:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: monstericon
  labels:
    app: monsterstack
spec:
  selector:
    matchLabels:
      app: monsterstack
      partie: monstericon
  strategy:
    type: Recreate
  replicas: 3
  template:
    metadata:
      labels:
        app: monsterstack
        partie: monstericon
    spec:
      containers:
        - name: monstericon
          image: monstericon
          ports:
            - containerPort: 5000

Skaffold (optionnel)

L’image monstericon de ce déploiement n’existe pas sur le Docker Hub, et notre Kubernetes doit pouvoir accéder à la nouvelle version de l’image construite à partir du Dockerfile. Nous allons utiliser skaffold pour cela. Il y a plusieurs possibilités :

  • utiliser minikube : minikube a la capacité de se connecter au registry de notre installation Docker locale
  • sur k3s ou sur un cluster cloud : pousser à chaque itération notre image sur un registry distant (Docker Hub)
    • pour ce faire, il faut éditer le fichier skaffold.yaml et le fichier de Deployment correspondant pour remplacer le nom de l’image monstericon pour faire référence à l’adresse à laquelle on souhaite pousser l’image sur le registry distant (ex: docker.io/MON_COMPTE_DOCKER_HUB/monstericon)
    • il est possible qu’il faille ajouter au même niveau que artifacts: dans le fichier skaffold.yaml ceci :
  local:
    push: true
  • heureusement le mécanisme de layers des images Docker ne nous oblige à uploader que les layers modifiés de notre image à chaque build

  • (plus long) configurer un registry local (en Docker ou en Kubernetes) auquel Skaffold et Kubernetes peuvent accéder

    • c’est plus long car il faut simplement configurer les certificats HTTPS ou expliciter que l’on peut utiliser un registry non sécurisé (HTTP)
    • ensuite il suffit de déployer un registry tout simple (l’image officielle registry:2) ou plus avancé (Harbour par exemple)
  • (plus avancé) utiliser Kaniko, un programme de Google qui permet de builder directement dans le cluster Kubernetes : https://skaffold.dev/docs/pipeline-stages/builders/docker/#dockerfile-in-cluster-with-kaniko

  • Observons le fichier skaffold.yaml

  • Lancez skaffold run pour construire et déployer l’application automatiquement (skaffold utilise ici le registry docker local et kubectl)

Une image pour monstericon

L’image monstericon de ce déploiement n’existe pas sur le Docker Hub, et notre Kubernetes doit pouvoir accéder à la nouvelle version de l’image construite à partir du Dockerfile.

Sans un outil comme Skaffold, nous sommes bloqué·es : il faut construire l’image à la main et la pousser dans un registry joignable par notre install.

Voici l’image déjà poussée par le formateur : docker.io/uptimeformation/monstericon

Santé du service avec les Probes

  • Ajoutons des healthchecks au conteneur dans le pod avec la syntaxe suivante (le mot-clé livenessProbe doit être à la hauteur du i de image:) :
livenessProbe:
  tcpSocket: # si le socket est ouvert c'est que l'application est démarrée
    port: 5000
  initialDelaySeconds: 5 # wait before firt probe
  timeoutSeconds: 1 # timeout for the request
  periodSeconds: 10 # probe every 10 sec
  failureThreshold: 3 # fail maximum 3 times
readinessProbe:
  httpGet:
    path: /healthz # si l'application répond positivement sur sa route /healthz c'est qu'elle est prête pour le traffic
    port: 5000
    httpHeaders:
      - name: Accept
        value: application/json
  initialDelaySeconds: 5
  timeoutSeconds: 1
  periodSeconds: 10
  failureThreshold: 3

La livenessProbe est un test qui s’assure que l’application est bien en train de tourner. S’il n’est pas rempli le pod est automatiquement supprimé et recréé en attendant que le test fonctionne.

Ainsi, k8s sera capable de savoir si notre conteneur applicatif fonctionne bien, quand le redémarrer. C’est une bonne pratique pour que le replicaset Kubernetes sache quand redémarrer un pod et garantir que notre application se répare elle même (self-healing).

Cependant une application peut être en train de tourner mais indisponible pour cause de surcharge ou de mise à jour par exemple. Dans ce cas on voudrait que le pod ne soit pas détruit mais que le traffic évite l’instance indisponible pour être renvoyé vers un autre backend ready.

La readinessProbe est un test qui s’assure que l’application est prête à répondre aux requêtes en train de tourner. S’il n’est pas rempli le pod est marqué comme non prêt à recevoir des requêtes et le service évitera de lui en envoyer.

Configuration d’une application avec des variables d’environnement simples

  • Notre application monstericon peut être configurée en mode DEV ou PROD. Pour cela elle attend une variable d’environnement CONTEXT pour lui indiquer si elle doit se lancer en mode PROD ou en mode DEV. Ici nous mettons l’environnement DEV en ajoutant (aligné avec la livenessProbe):
env:
  - name: CONTEXT
    value: DEV

Ajouter des indications de ressource nécessaires pour garantir la qualité de service

  • Ajoutons aussi des contraintes sur l’usage du CPU et de la RAM, en ajoutant à la même hauteur que env: :
resources:
  requests:
    cpu: "100m" # 10% de proc
    memory: "50Mi"
  limits:
    cpu: "300m" # 30% de proc
    memory: "200Mi"

Nos pods auront alors la garantie de disposer d’un dixième de CPU (100/1000) et de 50 mégaoctets de RAM. Ce type d’indications permet de remplir au maximum les ressources de notre cluster tout en garantissant qu’aucune application ne prend toute les ressources à cause d’un fuite mémoire etc.

  • Relancer skaffold run pour appliquer les modifications.
  • Avec kubectl describe deployment monstericon, lisons les résultats de notre readinessProbe, ainsi que comment s’est passée la stratégie de déploiement type: Recreate.

Exposer notre stack avec des services

Les services K8s sont des endpoints réseaux qui balancent le trafic automatiquement vers un ensemble de pods désignés par certains labels. Ils sont un peu la pierre angulaire des applications microservices qui sont composées de plusieurs sous parties elles même répliquées.

Pour créer un objet Service, utilisons le code suivant, à compléter :

apiVersion: v1
kind: Service
metadata:
  name: <nom_service>
  labels:
    app: monsterstack
spec:
  ports:
    - port: <port>
  selector:
    app: <app_selector>
    partie: <tier_selector>
  type: <type>
---

Ajoutez le code précédent au début de chaque fichier déploiement. Complétez pour chaque partie de notre application :

  • le nom du service (name: dans metadata:) par le nom de notre programme. En particulier, il faudra forcément appeler les services redis et dnmonster comme ça car cela permet à Kubernetes de créer les entrées DNS correspondantes. Le pod monstericon pourra ainsi les joindre en demandant à Kubernetes l’IP derrière dnmonster et redis.
  • nom de la partie par le nom de notre programme (monstericon, dnmonster et redis)
  • le port par le port du service
  • les selectors app et partie par ceux du pod correspondant.

Le type sera : ClusterIP pour dnmonster et redis, car ce sont des services qui n’ont à être accédés qu’en interne, et LoadBalancer pour monstericon.

  • Appliquez à nouveau avec skaffold run.
  • Listez les services avec kubectl get services.
  • Visitez votre application dans le navigateur avec minikube service monstericon.
  • Supprimez l’application avec skaffold delete.

Ajoutons un ingress (~ reverse proxy) pour exposer notre application en http

  • Pour Minikube : Installons le contrôleur Ingress Nginx avec minikube addons enable ingress.

  • Pour les autres types de cluster (cloud ou k3s), lire la documentation sur les prérequis pour les objets Ingress et installez l’ingress controller appelé ingress-nginx : https://kubernetes.io/docs/concepts/services-networking/ingress/#prerequisites. Si besoin, aidez-vous du TP suivant sur l’utilisation de Helm.

  • Avant de continuer, vérifiez l’installation du contrôleur Ingress Nginx avec kubectl get svc -n ingress-nginx ingress-nginx-controller : le service ingress-nginx-controller devrait avoir une IP externe.

Il s’agit d’une implémentation de reverse proxy dynamique (car ciblant et s’adaptant directement aux objets services k8s) basée sur nginx configurée pour s’interfacer avec un cluster k8s.

  • Repassez le service monstericon en mode ClusterIP. Le service n’est plus accessible sur un port. Nous allons utiliser l’ingress à la place pour afficher la page.

  • Ajoutez également l’objet Ingress suivant dans le fichier monster-ingress.yaml :

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: monster-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
    - host: monsterstack.local # à changer si envie/besoin
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: monstericon
                port:
                  number: 5000
  • Ajoutez ce fichier avec skaffold run.

  • Récupérez l’ip de minikube avec minikube ip, (ou alors allez observer l’objet Ingress dans Lens dans la section Networking. Sur cette ligne, récupérez l’ip de minikube en 192.x.x.x.).

  • Ajoutez la ligne <ip-minikube> monsterstack.local au fichier /etc/hosts avec sudo nano /etc/hosts puis CRTL+S et CTRL+X pour sauver et quitter.

  • Visitez la page http://monsterstack.local pour constater que notre Ingress (reverse proxy) est bien fonctionnel.

Solution

Le dépôt Git de la correction de ce TP est accessible ici : git clone -b tp3 https://github.com/Uptime-Formation/corrections_tp.git

08 - Cours - Le réseau dans Kubernetes

Les solutions réseau dans Kubernetes ne sont pas standard. Il existe plusieurs façons d’implémenter le réseau.

Rappel, les objets Services

Les Services sont de trois types principaux :

  • ClusterIP: expose le service sur une IP interne au cluster appelée ClusterIP. Les autres pods peuvent alors accéder au service mais pas l’extérieur.

  • NodePort: expose le service depuis l’IP publique de chacun des noeuds du cluster en ouvrant port directement sur le nœud, entre 30000 et 32767. Cela permet d’accéder aux pods internes répliqués. Comme l’IP est stable on peut faire pointer un DNS ou Loadbalancer classique dessus.

    • Dans la pratique, on utilise très peu ce type de service.

Crédits à Ahmet Alp Balkan pour les schémas

  • LoadBalancer: expose le service en externe à l’aide d’un Loadbalancer de fournisseur de cloud. Les services NodePort et ClusterIP, vers lesquels le Loadbalancer est dirigé sont automatiquement créés.
    • Dans la pratique, on utilise que ponctuellement ce type de service, pour du HTTP/s on ne va pas exposer notre service (ce sera un service de type ClusterIP) et on va utiliser à la place un objet Ingress (voir ci-dessous).

Crédits Ahmet Alp Balkan

Fournir des services LoadBalancer on premise avec MetalLB

Dans un cluster managé provenant d’un fournisseur de cloud, la création d’un objet Service Lodbalancer entraine le provisionning d’une nouvelle machine de loadbalancing à l’extérieur du cluster avec une IPv4 publique grâce à l’offre d’IaaS du provideur (impliquant des frais supplémentaires).

Cette intégration n’existe pas par défaut dans les clusters de dev comme minikube ou les cluster on premise (le service restera pending et fonctionnera comme un NodePort). Le projet MetalLB cherche à y remédier en vous permettant d’installer un loadbalancer directement dans votre cluster en utilisant une connexion IP classique ou BGP pour la haute disponibilité.

Les objets Ingresses

Crédits Ahmet Alp Balkan

Un Ingress est un objet pour gérer dynamiquement le reverse proxy HTTP/HTTPS dans Kubernetes. Documentation: https://kubernetes.io/docs/concepts/services-networking/ingress/#what-is-ingress

Exemple de syntaxe d’un ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-wildcard-host
spec:
  rules:
  - host: "domain1.bar.com"
    http:
      paths:
      - pathType: Prefix
        path: "/bar"
        backend:
          service:
            name: service1
            port:
              number: 80
      - pathType: Prefix
        path: "/foo"
        backend:
          service:
            name: service2
            port:
              number: 80
  - host: "domain2.foo.com"
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: service3
            port:
              number: 80

Pour pouvoir créer des objets ingress il est d’abord nécessaire d’installer un ingress controller dans le cluster:

  • Il s’agit d’un déploiement conteneurisé d’un logiciel de reverse proxy (comme nginx) et intégré avec l’API de kubernetes
  • Le controlleur agit donc au niveau du protocole HTTP et doit lui-même être exposé (port 80 et 443) à l’extérieur, généralement via un service de type LoadBalancer.
  • Le controleur redirige ensuite vers différents services (généralement configurés en ClusterIP) qui à leur tour redirigent vers différents ports sur les pods selon l’URL de la requête.

Il existe plusieurs variantes d'ingress controller:

  • Un ingress basé sur Nginx plus ou moins officiel à Kubernetes et très utilisé: https://kubernetes.github.io/ingress-nginx/
  • Un ingress Traefik optimisé pour k8s.
  • il en existe d’autres : celui de payant l’entreprise Nginx, Contour, HAProxy…

Chaque provider de cloud et flavour de kubernetes est légèrement différent au niveau de la configuration du controlleur ce qui peut être déroutant au départ:

  • minikube permet d’activer l’ingress nginx simplement
  • autre example: k3s est fourni avec traefik configuré par défaut
  • On peut installer plusieurs ingress controllers correspondant à plusieurs IngressClasses

Comparaison des controlleurs: https://medium.com/flant-com/comparing-ingress-controllers-for-kubernetes-9b397483b46b

Gestion dynamique des certificats à l’aide de certmanager

Certmanager est une application kubernetes (un operator) plus ou moins officielle capable de générer automatiquement des certificats TLS/HTTPS pour nos ingresses.

Exemple de syntaxe d’un ingress utilisant certmanager:

apiVersion: networking.k8s.io/v1 
kind: Ingress
metadata:
  name: kuard
  annotations:
    kubernetes.io/ingress.class: "nginx"    
    cert-manager.io/issuer: "letsencrypt-prod"
spec:
  tls:
  - hosts:
    - example.example.com
    secretName: quickstart-example-tls
  rules:
  - host: example.example.com
    http:
      paths:
      - path: /
        pathType: Exact
        backend:
          service:
            name: kuard
            port:
              number: 80

Le mesh networking et les service meshes

Un service mesh est un type d’outil réseau pour connecter un ensemble de pods, généralement les parties d’une application microservices de façon encore plus intégrée que ne le permet Kubernetes.

En effet opérer une application composée de nombreux services fortement couplés discutant sur le réseau implique des besoins particuliers en terme de routage des requêtes, sécurité et monitoring qui nécessite l’installation d’outils fortement dynamique autour des nos conteneurs.

Un exemple de service mesh est https://istio.io qui, en ajoutant en conteneur “sidecar” à chacun des pods à supervisés, ajoute à notre application microservice un ensemble de fonctionnalités d’intégration très puissant.

CNI (container network interface) : Les implémentations du réseau Kubernetes

Beaucoup de solutions de réseau qui se concurrencent, demandant un comparatif un peu fastidieux.

  • plusieurs solutions très robustes
  • diffèrent sur l’implémentation : BGP, réseau overlay ou non (encapsulation VXLAN, IPinIP, autre)
  • toutes ne permettent pas d’appliquer des NetworkPolicies : l’isolement et la sécurité réseau
  • peuvent parfois s’hybrider entre elles (Canal = Calico + Flannel)
  • ces implémentations sont souvent concrètement des DaemonSets : des pods qui tournent dans chacun des nodes de Kubernetes

  • Calico, Flannel, Weave ou Cilium sont très employées et souvent proposées en option par les fournisseurs de cloud

  • Cilium a la particularité d’utiliser la technologie eBPF de Linux qui permet une sécurité et une rapidité accrue

Comparaisons :

Les network policies : des firewalls dans le cluster

Crédits Ahmet Alp Balkan

Par défaut, les pods ne sont pas isolés au niveau réseau : ils acceptent le trafic de n’importe quelle source.

Les pods deviennent isolés en ayant une NetworkPolicy qui les sélectionne. Une fois qu’une NetworkPolicy (dans un certain namespace) inclut un pod particulier, ce pod rejettera toutes les connexions qui ne sont pas autorisées par cette NetworkPolicy.

Ressources sur le réseau

Vidéos

Des vidéos assez complètes sur le réseau, faites par Calico :

Sur MetalLB, les autres vidéos de la chaîne sont très bien :

09 - TP 4 - Déployer Wordpress Avec une base de donnée persistante

Déployer Wordpress et MySQL avec du stockage et des Secrets

Nous allons suivre ce tutoriel pas à pas : https://kubernetes.io/docs/tutorials/stateful-application/mysql-wordpress-persistent-volume/

Il faut :

  • Créez un projet TP4.
  • Créer la kustomization.yaml avec le générateur de secret.
  • Copier les 2 fichiers dans le projet.
  • Les ajouter comme resources à la kustomization.yaml.

Commentons un peu le contenu des deux fichier mysql-deployment.yaml et wordpress-deployment.yaml.

  • Vérifier que le stockage et le secret ont bien fonctionnés.
  • Exposez et visitez le service avec minikube service wordpress. Faite la configuration de base de wordpress.

Observer le déploiement du secret à l’intérieur des pods

  • Entrez dans le pod de mysql grâce au terminal de Lens.
  • Cherchez la variable d’environnement MYSQL_ROOT_PASSWORD à l’aide des commande env | grep MYSQL. Le conteneur mysql a utilisé cette variable accessible de lui seul pour se configurer.

Observez la persistence

  • Supprimez et recréer les deux déploiements (mais pas le total). En rechargeant le site on constate que les données ont été conservées.

  • Allez observer la section stockage dans Lens. Commentons ensemble.

  • Supprimer tout avec kubectl delete -k .. Que s’est-il passé ? (côté storage)

En l’état les PersistentVolumes générés par la combinaise du PersistentVolumeClaim et de la StorageClass de minikube sont également supprimés en même tant que les PVC. Les données sont donc perdues et au chargement du site on doit relancer l’installation.

Pour éviter cela il faut que la storageClass standard soit configurée avec une Reclaim Policy à retain (conserver) et non delete. Cependant minikube dans docker ne permet pas simplement de faire une storage class en mode retain (à cause d’un bug semble-t-il). Nous allons donc créer manuellement des volumes avec une storageClass retain.

  • Créez deux volumes en cliquant sur le + > create resource en bas à gauche de Lens et collez le code suivant:
---
kind: PersistentVolume
apiVersion: v1
metadata:
  name: wordpress-mysql-pv
  labels:
    type: local
spec:
  storageClassName: manual
  capacity:
    storage: 100Mi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/mnt/mysql-data"
---
kind: PersistentVolume
apiVersion: v1
metadata:
  name: wordpress-pv
  labels:
    type: local
spec:
  storageClassName: manual
  capacity:
    storage: 100Mi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/mnt/wp-data"
  • Modifiez les PersistentVolumeClaims(PVC) des deploiements wordpress et mysql pour passer le storage à 100Mi et ajouter storageClassName: manual dans la spec: de chaque PVC.

  • Recréez les ressources avec apply. Les volumes devraient se connecter à nos conteneurs mysql et wordpress.

Essayons avec Scaleway

10 - Cours - Objets Kubernetes Partie 2.

Le stockage dans Kubernetes

Les Volumes Kubernetes

Comme dans Docker, Kubernetes fournit la possibilité de monter des volumes virtuels dans les conteneurs de nos pod. On liste séparément les volumes de notre pod puis on monte un ou plusieurs points de montage dans les différents conteneurs Exemple:

apiVersion: v1
kind: Pod
metadata:
  name: test-pod
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /test
      name: test-volume
  volumes:
  - name: test-volume
    hostPath:
      # chemin du dossier sur l'hôte
      path: /data
      # ce champ est optionnel
      type: Directory

La problématique des volumes et du stockage est plus compliquée dans kubernetes que dans docker car k8s cherche à répondre à de nombreux cas d’usages. doc officielle. Il y a donc de nombeux types de volumes kubernetes correspondants à des usages de base et aux solutions proposées par les principaux fournisseurs de cloud.

Mentionnons quelques d’usage de base des volumes:

  • hostPath: monte un dossier du noeud ou est plannifié le pod à l’intérieur du conteneur.
  • local: comme hostPath mais conscient de la situation physique du volume sur le noeud et à combiner avec les placements de pods avec nodeAffinity
  • emptyDir: un dossier temporaire qui est supprimé en même temps que le pod
  • configMap: pour monter des fichiers de configurations provenant du cluster à l’intérieur des pods
  • secret: pour monter un secret (configuration) provenant du cluster à l’intérieur des pods
  • cephfs: monter un volume ceph provenant d’un ceph installé sur le cluster
  • etc.

En plus de la gestion manuelle des volumes avec les option précédentes, kubernetes permet de provisionner dynamiquement du stockage en utilisant des plugins de création de volume grâce à 3 types d’objets: StorageClass PersistentVolume et PersistentVolumeClaim.

Les types de stockage avec les StorageClasses

Le stockage dynamique dans Kubernetes est fourni à travers des types de stockage appelés StorageClasses :

  • dans le cloud, ce sont les différentes offres de volumes du fournisseur,
  • dans un cluster auto-hébergé c’est par exemple des opérateurs de stockage comme rook.io ou longhorn(Rancher).

doc officielle

Demander des volumes et les liers aux pods :PersistentVolumes et PersistentVolumeClaims

Quand un conteneur a besoin d’un volume, il crée une PersistentVolumeClaim : une demande de volume (persistant). Si un des objets StorageClass est en capacité de le fournir, alors un PersistentVolume est créé et lié à ce conteneur : il devient disponible en tant que volume monté dans le conteneur.

  • les StorageClasses fournissent du stockage
  • les conteneurs demandent du volume avec les PersistentVolumeClaims
  • les StorageClasses répondent aux PersistentVolumeClaims en créant des objets PersistentVolumes : le conteneur peut accéder à son volume.

doc officielle

Le provisionning de volume peut être manuelle (on crée un objet PersistentVolume ou non la PersistentVolumeClaim mène directement à la création d’un volume persistant si possible)

Des déploiements plus stables et précautionneux : les StatefulSets

L’objet StatefulSet est relativement récent dans Kubernetes.

On utilise les Statefulsets pour répliquer un ensemble de pods dont l’état est important : par exemple, des pods dont le rôle est d’être une base de données, manipulant des données sur un disque.

Un objet StatefulSet représente un ensemble de pods dotés d’identités uniques et de noms d’hôtes stables. Quand on supprime un StatefulSet, par défaut les volumes liés ne sont pas supprimés.

Les StatefulSets utilisent un nom en commun suivi de numéros qui se suivent. Par exemple, un StatefulSet nommé web comporte des pods nommés web-0, web-1 et web-2. Par défaut, les pods StatefulSet sont déployés dans l’ordre et arrêtés dans l’ordre inverse (web-2, web-1 puis web-0).

En général, on utilise des StatefulSets quand on veut :

  • des identifiants réseau stables et uniques
  • du stockage stable et persistant
  • des déploiements et du scaling contrôlés et dans un ordre défini
  • des rolling updates dans un ordre défini et automatisées

Article récapitulatif des fonctionnalités de base pour applications stateful: https://medium.com/capital-one-tech/conquering-statefulness-on-kubernetes-26336d5f4f17

Paramétrer ses Pods

Les ConfigMaps

D’après les recommandations de développement 12factor, la configuration de nos programmes doit venir de l’environnement. L’environnement est ici Kubernetes.

Les objets ConfigMaps permettent d’injecter dans des pods des ensemble clés/valeur de configuration en tant que volumes/fichiers de configuration ou variables d’environnement.

les Secrets

Les Secrets se manipulent comme des objets ConfigMaps, mais ils sont chiffrés et faits pour stocker des mots de passe, des clés privées, des certificats, des tokens, ou tout autre élément de config dont la confidentialité doit être préservée. Un secret se créé avec l’API Kubernetes, puis c’est au pod de demander à y avoir accès.

Il y a 3 façons de donner un accès à un secret :

  • le secret est un fichier que l’on monte en tant que volume dans un conteneur (pas nécessairement disponible à l’ensemble du pod). Il est possible de ne jamais écrire ce secret sur le disque (volume tmpfs).
  • le secret est une variable d’environnement du conteneur.

Pour définir qui et quelle app a accès à quel secret, on peut utiliser les fonctionnalités “RBAC” de Kubernetes.

Lier utilisateurs et autorisations: Le Role-Based Access Control (RBAC)

Kubernetes intègre depuis quelques versions un système de permissions fines sur les ressources et les namespaces. Il fonctionne en liant des ensembles de permissions appelées Roles à des identités/comptes humains appelés User ou des comptes de services pour vos programmes appelés ServiceAccount.

Exemple de comment générer un certificat à créer un nouvel utilisateur dans minikube: https://docs.bitnami.com/tutorials/configure-rbac-in-your-kubernetes-cluster/

Roles et ClusterRoles + bindings

Une role est un objet qui décrit un ensemble d’actions permises sur certaines ressources et s’applique sur un seul namespace. Pour prendre un exemple concret, voici la description d’un roles qui authorise la lecture, création et modification de pods et de services dans le namespace par défaut:

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: default
  name: pod-and-services
rules:
- apiGroups: [""]
  resources: ["pods", "services"]
  verbs: ["create", "delete", "get", "list", "patch", "update", "watch", "proxy"]
  • Un role est une liste de règles rules

  • Les rules sont décrites à l’aide de 8 verbes différents qui sont ceux présent dans le role d’exemple au dessus qu’ont associe à une liste d’objets.

  • Le role ne fait rien par lui même : il doit être appliqué à une identité ie un User ou ServiceAccount.

  • Classiquement on crée des Roles comme admin ou monitoring qui désignent un ensemble de permission consistante pour une tâche donnée.

  • Notre rôle exemple est limité au namespace default. Pour créer des permissions valable pour tout le cluster on utilise à la place un objet appelé un ClusterRole qui fonctionne de la même façon mais indépendamment des namespace.

  • Les Roles et ClusterRoles sont ensuite appliqués aux ServicesAccounts à l’aide respectivement de RoleBinding et ClusterRoleBinding comme l’exemple suivant:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: default
  name: pods-and-services
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: alice
- apiGroup: rbac.authorization.k8s.io
  kind: Group
  name: mydevs
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: pod-and-services

En plus des rôles que vous pouvez créer pour les utilisateur·ices et processus de votre cluster, il existe déjà dans kubernetes un ensemble de ClusterRoles prédéfinis qui sont affichables avec :

kubectl get clusterroles

La plupart de ces rôles intégrés sont destinés au kube-system, c’est-à-dire aux processus internes du cluster.

Cependant quatre rôles génériques existent aussi par défaut :

  • Le rôle cluster-admin fournit un accès complet à l’ensemble du cluster.
  • Le rôle admin fournit un accès complet à un espace de noms précis.
  • Le rôle edit permet à un·e utilisateur·ice de modifier des choses dans un espace de noms.
  • Le rôle view permet l’accès en lecture seule à un espace de noms.

La commande kubectl auth can-i <verb> <type_de_resource> permet de déterminer selon le profil utilisé (défini dans votre kubeconfig) les permissions actuelles de l’user sur les objets Kubernetes.

11 - Cours - Helm, le gestionnaire de paquets Kubernetes et les Opérateurs

Nous avons vu que dans Kubernetes la configuration de nos services / applications se fait généralement via de multiples fichiers YAML.

Les kustomizations permettent de rassembler ces descriptions en dossier de code et ont pas mal d’avantages mais on a vite besoin de quelque chose de plus puissant.

  • Pour s’adapter à plein de paramétrages différents de notre application
  • Pour éviter la répétition de code

C’est donc “trop” déclaratif en quelque sorte, et il faut se concentrer sur les quelques propriétés que l’on souhaite créer ou modifier,

Helm

Pour pallier ce problème, il existe un utilitaire appelé Helm, qui produit les fichiers de déploiement que l’on souhaite.

Helm est le package manager recommandé par Kubernetes, il utilise les fonctionnalités de templating du langage Go.

Helm permet donc de déployer des applications / stacks complètes en utilisant un système de templating et de dépendances, ce qui permet d’éviter la duplication et d’avoir ainsi une arborescence cohérente pour nos fichiers de configuration.

Mais Helm propose également :

  • la possibilité de mettre les Charts dans un répertoire distant (Git, disque local ou partagé…), et donc de distribuer ces Charts publiquement.
  • un système facilitant les Updates et Rollbacks de vos applications.

Il existe des sortes de stores d’applications Kubernetes packagées avec Helm, le plus gros d’entre eux est Kubeapps Hub, maintenu par l’entreprise Bitnami qui fournit de nombreuses Charts assez robustes.

Si vous connaissez Ansible, un chart Helm est un peu l’équivalent d’un rôle Ansible dans l’écosystème Kubernetes.

Concepts

Les quelques concepts centraux de Helm :

  • Un package Kubernetes est appelé Chart dans Helm.

  • Un Chart contient un lot d’informations nécessaires pour créer une application Kubernetes :

    • la Config contient les informations dynamiques concernant la configuration d’une Chart
    • Une Release est une instance existante sur le cluster, combinée avec une Config spécifique.

Quelques commandes Helm:

Voici quelques commandes de bases pour Helm :

  • helm repo add bitnami https://charts.bitnami.com/bitnami: ajouter un repo contenant des charts

  • helm search repo bitnami : rechercher un chart en particulier

  • helm install my-release my-chart --values=myvalues.yaml : permet d’installer le chart my-chart avec le nom my-release et les valeurs de variable contenues dans myvalues.yaml (elles écrasent les variables par défaut)

  • helm upgrade my-release my-chart : permet de mettre à jour notre release avec une nouvelle version.

  • helm ls: Permet de lister les Charts installés sur votre Cluster

  • helm delete my-release: Permet de désinstaller la release my-release de Kubernetes

La configuration d’un Chart: des templates d’objets Kubernetes

Visitons un exemple de Chart : minecraft

On constate que Helm rassemble des fichiers de descriptions d’objets k8s avec des variables (moteur de templates de Go) à l’intérieur, ce qui permet de factoriser le code et de gérer puissamment la différence entre les versions.

Kubernetes API et extension par APIgroups

Tous les types de resources Kubernetes correspondent à un morceau (un sous arbre) d’API REST de Kubernetes. Ces chemins d’API pour chaque ressources sont classés par groupe qu’on appelle des apiGroups:

  • On peut lister les resources et leur groupes d’API avec la commande kubectl api-resources -o wide.
  • Ces groups correspondent aux préfixes indiqué dans la section apiVersion des descriptions de ressources.
  • Ces groupes d’API sont versionnés sémantiquement et classés en alpha beta et stable. beta indique déjà un bon niveau de stabilité et d’utilisabilité et beaucoup de ressources officielles de kubernetes ne sont pas encore en api stable. Exemple: les CronJobs viennent de se stabiliser au début 2021.
  • N’importe qui peut développer ses propres types de resources appelées CustomResourceDefinition (voir ci dessous) et créer un apiGroup pour les ranger.

Documentation: https://kubernetes.io/docs/reference/using-api/

Operators et Custom Resources Definitions (CRD)

Un opérateur est :

  • un morceau de logique opérationnelle de votre infrastructure (par exemple: la mise à jour votre logiciel de base de donnée stateful comme cassandra ou elasticsearch) …
  • … implémenté dans kubernetes par un/plusieurs conteneur(s) “controller” …
  • … controllé grâce à une extension de l’API Kubernetes sous forme de nouveaux type d’objets kubernetes personnalisés (de haut niveau) appelés CustomResourcesDefinition
  • … qui crée et supprime des resources de base Kubernetes comme résultat concrêt.

Les opérateurs sont un sujet le plus méta de Kubernetes et sont très à la mode depuis leur démocratisation par Red Hat pour la gestion automatique de base de données.

  • Il peuvent être développés avec un framework Go ou Ansible
  • Ils sont généralement répertoriés sur le site: https://operatorhub.io/

Exemples :

  • L’opérateur Prometheus permet d’automatiser le monitoring d’un cluster et ses opérations de maintenance.
  • la chart officielle de la suite Elastic (ELK) définit des objets de type elasticsearch
  • KubeVirt permet de rajouter des objets de type VM pour les piloter depuis Kubernetes
  • Azure propose des objets correspondant à ses ressources du cloud Azure, pour pouvoir créer et paramétrer des ressources Azure directement via la logique de Kubernetes.

Limites des opérateurs

Il est possible de développer soit même des opérateurs mais il s’agit de développement complexes qui devraient être entrepris par les développeurs du logiciel et qui sont surtout utiles pour des applications distribuées et stateful. Les opérateurs n’ont pas forcément vocation à remplacer les Charts Helm comme on l’entend parfois.

Voir : https://thenewstack.io/kubernetes-when-to-use-and-when-to-avoid-the-operator-pattern/

12 - TP 5 - Déployer Wordpress avec Helm et ArgoCD

Helm est un “gestionnaire de paquet” ou vu autrement un “outil de templating avancé” pour k8s qui permet d’installer des applications sans faire des copier-coller pénibles de YAML :

  • Pas de duplication de code
  • Possibilité de créer du code générique et flexible avec pleins de paramètres pour le déploiement.
  • Des déploiements avancés avec plusieurs étapes

Inconvénient: Helm ajoute souvent de la complexité non nécessaire car les Charts sur internet sont très paramétrables pour de multiples cas d’usages (plein de code qui n’est utile que dans des situations spécifiques).

Helm ne dispense pas de maîtriser l’administration de son cluster.

Installer Helm

  • Pour installer Helm sur Ubuntu, utilisez : sudo snap install helm --classic

Autocomplete

helm completion bash | sudo tee /etc/bash_completion.d/helm et relancez votre terminal.

Utiliser un chart Helm pour installer Wordpress

  • Cherchez Wordpress sur https://artifacthub.io/.

  • Prenez la version de Bitnami et ajoutez le dépôt avec la commande indiquée dans “Install” : helm repo add bitnami https://charts.bitnami.com/bitnami

  • Installer une “release” wordpress-tp de cette application (ce chart) avec helm install wordpress-tp bitnami/wordpress

  • Suivez les instructions affichées dans le terminal pour trouver l’IP et afficher le login et password de notre installation. Si l’IP n’est pas accessible, il faut également lancer kubectl port-foward service wordpress-tp <port_de_votre_choix>:80.

  • Notre Wordpress est prêt. Connectez-vous-y avec les identifiants affichés (il faut passer les commandes indiquées pour récupérer le mot de passe stocké dans un secret k8s).

Vous pouvez constater que l’objet Service est par default Loadbalancer ce qui n’est pas très pertinent. Un chart prend de nombreux paramètres de configuration qui sont toujours listés dans le fichier values.yaml à la racine du Chart.

On peut écraser certains de ces paramètres dans un nouveau fichier par exemple myvalues.yaml et installer la release avec l’option --values=myvalues.yaml.

  • Désinstallez Wordpress avec helm uninstall wordpress-tp

Utiliser la fonction template de Helm pour étudier les ressources d’un Chart

  • Visitez le code des charts de votre choix en clonant le répertoire Git des Charts officielles Bitnami et en l’explorant avec VSCode :
git clone https://github.com/bitnami/charts/ --depth 1
code charts/bitnami/wordpress
  • Regardez en particulier les fichiers templates et le fichier de paramètres values.yaml.

  • Comment modifier l’username wordpress à l’installation ? il faut donner comme paramètres le yaml suivant:

wordpressUsername: <votrenom>
  • Nous allons paramétrer plus encore l’installation. Créez un dossier TP5 avec à l’intérieur un fichier values.yaml contenant:
wordpressUsername: <stagiaire> # replace
wordpressBlogName: Kubernetes example blog

replicaCount: 1

service:
  type: ClusterIP

ingress:
  enabled: true
  ingressClassName: nginx
  hostname: wordpress.<stagiaire>.formation.dopl.uk # replace with your hostname pointing on the cluster ingress loadbalancer IP

Si vous avez activé certmanager (voir TP optionnel) vous pouvez remplacer la clé ingress avec :


ingress:
  enabled: true
  hostname: wordpress.<stagiaire>.formation.dopl.uk # replace with your hostname pointing on the cluster ingress loadbalancer IP
  tls: true
  certManager: true
  ingressClassName: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    kubernetes.io/ingress.class: nginx
  • En utilisant ces paramètres, plutôt que d’installer le chart, nous allons faire le rendu (templating) des fichiers ressource générés par le chart: helm template wordpress-tp bitnami/wordpress --values=values.yaml > wordpress-tp-manifests.yaml.

On peut maintenant lire dans ce fichier les objets kubernetes déployés par le chart et ainsi apprendre de nouvelles techniques et syntaxes. En le parcourant on peut constater que la plupart des objets abordés pendant cette formation y sont présent plus certains autres.

Installer ArgoCD

Voir le TP Gitlab et ArgoCD.

Facultatif : A la main, avec MySQL, des init containers et des ConfigMaps

TP optionnel - Exposer une application en HTTPS via certmanager et un ingress nginx

  1. Installer cert-manager avec la commande kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.6.1/cert-manager.yaml

Créer un certificat Letsencrypt pour notre cluster k3s: version Traefik

  1. Objet de type ClusterIssuer à créer pour configurer Let’s Encrypt :
apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
  #   name: letsencrypt-staging
  name: letsencrypt-prod
spec:
  acme:
    email: cto@doxx.fr
    privateKeySecretRef:
      name: prod-issuer-account-key
    #   name: staging-issuer-account-key
    server: https://acme-v02.api.letsencrypt.org/directory
    # server: https://acme-staging-v02.api.letsencrypt.org/directory
    http01: {}
    solvers:
      - http01:
          ingress:
            class: traefik
        selector: {}
  1. Si ce n’est pas fait, installer un Ingress Controller (un reverse proxy) avec Helm, ici nous installons Traefik mais ça peut être Nginx (si vous prenez Nginx, il faudra modifier un peu l’objet Ingress plus bas et l’avant-dernière ligne de l’objet ClusterIssuer) :
helm repo add traefik https://helm.traefik.io/traefik
helm repo update
helm install traefik traefik/traefik
  1. Créer un objet Ingress en adaptant celui donné dans le tutoriel (il faudra qu’il soit lié à un Service existant, lui-même lié à un objet Deployment existant) :
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: monster-ingress
  annotations:
    traefik.ingress.kubernetes.io/router.tls: "true"
    kubernetes.io/ingress.class: traefik
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts:
        - monster.hadrien.lab.doxx.fr
      secretName: monster-hadrien-lab-doxx-fr
  rules:
    - host: monster.hadrien.lab.doxx.fr
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: monstericon
                port:
                  number: 5000

NB : si vous n’arrivez pas à obtenir de certificat HTTPS, modifiez l’objet ClusterIssuer pour obtenir un certificat depuis les serveurs staging de Let’s Encrypt : Let’s Encrypt limite très fortement le nombre de certificats installables sur les mêmes domaines et sous-domaines.

Créer un certificat Letsencrypt pour notre cluster k3s: version Nginx

TP opt. - Le RBAC

Les rôles et le RBAC

  1. Configurer Minikube pour activer RBAC.
minikube start --extra-config=apiserver.Authorization.Mode=RBAC

kubectl create clusterrolebinding add-on-cluster-admin --clusterrole=cluster-admin --serviceaccount=kube-system:default
  1. Créer trois connexions à minikube dans ~/.kube/config :
  • une en mode cluster-admin,
  • une en mode admin sur un namespace
  • et une en mode user avec un rolebinding
  1. En switchant de contexte à chaque fois, lancer la commande kubectl auth can-i pour différents cas et observer la différence

Ressources

TP optionnel - Installer un registry privé d'images dans votre cluster

https://www.linuxtechi.com/setup-private-docker-registry-kubernetes/

Pour une solution plus avancée que le simple conteneur registry voir par exemple:

-> le registry gitlab (sur gitlab.com ou on premise) -> https://goharbor.io/ -> registry avancé avec analyse de vulnérabilité des images

TP opt. - CI/CD avec Gitlab et ArgoCD

Installation d’un cluster avec argoCD

ArgoCD est un outil de GitOps extrêment pratique et puissant mais il nécessite d’être installé dans un cluster public (avec un IP publique) et avec un certificat HTTPS pour être utilisé correctement.

Qu’est-ce que le GitOps: https://www.objectif-libre.com/fr/blog/2019/12/17/gitops-tour-horizon-pratiques-outils/

Vos serveurs VNC qui sont aussi désormais des clusters k3s ont déjà plusieurs sous-domaines configurés: <votrelogin>.<soudomaine>.dopl.uk et *.<votrelogin>.<soudomaine>.dopl.uk. Le sous domaine argocd.<login>.<soudomaine>.dopl.uk pointe donc déjà sur le serveur (Wildcard DNS).

Ce nom de domaine va nous permettre de générer un certificat HTTPS pour notre application web argoCD grâce à un ingress nginx, le cert-manager de k8s et letsencrypt (challenge HTTP101).

Installer le ingress NGINX dans k3s

  • Installer l’ingress nginx avec la commande: kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.1.0/deploy/static/provider/cloud/deploy.yaml (pour autres méthodes ou problèmes voir : https://kubernetes.github.io/ingress-nginx/deploy/)

  • Vérifiez l’installation avec kubectl get svc -n ingress-nginx ingress-nginx-controller : le service ingress-nginx-controller devrait avoir une IP externe.

Installer Cert-manager dans k3s

  • Pour installer cert-manager lancez : kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.6.1/cert-manager.yaml

  • Il faut maintenant créer une ressource de type ClusterIssuer pour pourvoir émettre (to issue) des certificats.

  • Créez une ressource comme suit (soit dans Lens avec + soit dans un fichier à appliquer ensuite avec kubectl apply -f):

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    # You must replace this email address with your own.
    # Let's Encrypt will use this to contact you about expiring
    # certificates, and issues related to your account.
    email: cto@doxx.fr
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      # Secret resource that will be used to store the account's private key.
      name: letsencrypt-prod-account-key
    # Add a single challenge solver, HTTP01 using nginx
    solvers:
    - http01:
        ingress:
          class: nginx

Installer Argocd

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: argocd-server-ingress
  namespace: argocd
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    kubernetes.io/ingress.class: nginx
    kubernetes.io/tls-acme: "true"
    nginx.ingress.kubernetes.io/ssl-passthrough: "true"
    # If you encounter a redirect loop or are getting a 307 response code 
    # then you need to force the nginx ingress to connect to the backend using HTTPS.
    #
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
spec:
  tls:
  - hosts:
    - argocd.<yoursubdomain>
    secretName: argocd-secret # do not change, this is provided by Argo CD
  rules:
  - host: argocd.<yoursubdomain>
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: argocd-server
            port:
              number: 443
  • Créez et appliquez cette ressource Ingress.

  • Vérifiez dans Lens que l’ingress a bien généré un certificat (cela peut prendre jusqu’à 2 minutes)

  • Chargez la page argocd.<votre sous domaine> dans un navigateur. exp argocd.stagiaire1.docker.dopl.uk

  • Pour se connecter utilisez le login admin et récupérez le mot de passe admin en allant chercher le secret argocd-initial-admin-secret dans Lens (Config > Secrets avec le namespace argocd activé).

Récupérer le corrigé du TP et le pousser sur Gitlab

  • Récupérer le corrigé à compléter du TP CICD gitlab argocd avec git clone -b k8s_gitlab_argocd_correction https://github.com/Uptime-Formation/corrections_tp.git k8s_gitlab_argocd_correction

  • Ouvrez le projet dans VSCode

  • Créer un nouveau projet vide sur gitlab

  • Remplacez dans tout le projet, les occurences de <sousdomain>.dopl.uk par votre sous domaine par exemple stagiaire1.docker.dopl.uk

  • Remplacez également partout gitlab.com/e-lie/cicd_gitlab_argocd_corrections par l’url de votre dépot Gitlab (sans le https:// ou git@).

  • Poussez ce projet dans la branche k8s_gitlab_argocd_correction du dépot créé précédement:

git remote add gitlab <votre dépot gitlab>
git push gitlab
  • Observons le fichier .gitlab-ci.yml.

  • Allons voir le pipeline dans l’interface CI/CD de gitlab. Les deux premier stages du pipeline devraient s’être bien déroulés.

Déploiement de l’application dans argoCD

Expliquons un peu le reste du projet projet.

  • Créez un token de déploiement dans Gitlab > Settings > Repository > Deploy Tokens. Ce token va nous permettre de donner l’authorisation à ArgoCD de lire le dépôt gitlab (facultatif si le dépôt est public cela ne devrait pas être nécessaire). Complétez ensuite 2 fois le token dans le fichier k8s/argocd-apps.yaml comme suit : https://<nom_token>:<motdepasse_token>@gitlab.com/<votre depo>.git dans les deux sections repoURL: des deux applications.

  • Créer les deux applications monstericon-dev et monstericon-prod dans argocd avec kubectl apply -f k8s/argocd-apps.yaml.

  • Allons voir dans l’interface d’ArgoCD pour vérifier que les applications se déploient bien sauf le conteneur monstericon dont l’image n’a pas encore été buildée avec le bon tag. Pour cela il va falloir que notre pipeline s’execute complètement.

Les deux étapes de déploiement (dev et prod) du pipeline nécessitent de pousser automatiquement le code du projet à nouveau pour déclencher le redéploiement automatique dans ArgoCD (en mode pull depuis gitlab). Pour cela nous avons besoin de créer également un token utilisateur:

  • Allez dans Gitlab > User Settings (en haut à droite dans votre profil) > Access Tokens et créer un token avec read_repository write_repository read_registry write_registry activés. Sauvegardez le token dans un fichier.

  • Allez dans Gitlab > Settings > CI/CD > Variables pour créer deux variables de pipelines: CI_USERNAME contenant votre nom d’utilisateur gitlab et CI_PUSH_TOKEN contenant le token précédent. Ces variables de pipelines nous permettent de garder le token secret dans gitlab et de l’ajouter automatiquement aux pipeline pour pouvoir autoriser la connexion au dépot depuis le pipeline (git push).

  • Nous allons maintenant tester si le pipeline s’exécute correctement en commitant et poussant à nouveau le code avec git push gitlab.

  • Debuggons les pipelines s’ils sont en échec.

  • Allons voir dans ArgoCD pour voir si l’application dev a été déployée correctement. Regardez la section events et logs des pods si nécessaire.

  • Une fois l’application dev complètement healthy (des coeurs verts partout). On peut visiter l’application en mode dev à l’adresse https://monster-dev.<votre_sous_domaine>.

  • On peut ensuite déclencer le stage deploy-prod manuellement dans le pipeline, vérifier que l’application est healthy dans ArgoCD (debugger sinon) puis visiter https://monster.<votre_sous_domaine>.

Idées d’amélioration

  • Déplacer le code de déploiement dans un autre dépôt que le code d’infrastructure. Le pipeline de devra cloner le dépôt d’infrastructure, templater avec kustomize la bonne version de l’image dans le bon environnement. Pousser le code d’infrastructure sur le dépôt d’infrastructure. Corriger l’application ArgoCD pour monitorer le dépôt d’infrastructure.

  • Mutualiser le code de déploiement k8s avec des overlays kustomize

  • Utiliser une stragégie de blue/green ou A/B déploiement avec Argo Rollouts ou Istio avec vérification de réussite du déploiement et rollback en cas d’échec.

  • Ajouter plus d’étapes réalistes de CI/CD en se basant par exemple sur le livre GitOps suivant.

  • Gérer la création des ressources gitlab automatiquement avec Terraform et gérer les secrets (tokens gitlab) consciencieusement.

Bibliographie

  • 2021 - GitOps and Kubernetes Continuous Deployment with Argo CD, Jenkins X, and Flux

  • Billy Yuen, Alexander Matyushentsev, Todd Ekenstam, Jesse Suen (z-lib.org)

Bibliographie

Livres

Chez oreilly:

  • Cloud Native DevOps with Kubernetes (la philosophie et les enjeux du choix de kubernetes avec des exemples techniques)
  • Kubernetes Up and Running (les bases mais déjà compliqué)
  • Kubernetes Best Practices (problématiques avancés et bonnes pratiques de résolution)

Ressources

Réseau

Vidéos sur le réseau

Des vidéos assez complètes sur le réseau, faites par Calico :

Sur MetalLB, les autres vidéos de la chaîne sont très bien :

Stockage

Sécurité de Kubernetes

Gestion de secrets

TP optionnel - Stratégies de déploiement et monitoring

Installer Prometheus pour monitorer le cluster Minikube

Pour comprendre les stratégies de déploiement et mise à jour d’application dans Kubernetes (deployment and rollout strategies) nous allons installer puis mettre à jour une application d’exemple et observer comment sont gérées les requêtes vers notre application en fonction de la stratégie de déploiement choisie.

Pour cette observation on peut utiliser un outil de monitoring. Nous utiliserons ce TP comme prétexte pour installer une des stack les plus populaires et intégrée avec kubernetes : Prometheus et Grafana. Prometheus est un projet de la Cloud Native Computing Foundation.

Prometheus est un serveur de métriques c’est à dire qu’il enregistre des informations précises (de petite taille) sur différents aspects d’un système informatique et ce de façon périodique en effectuant généralement des requêtes vers les composants du système (metrics scraping).

Installer Prometheus avec Helm

Installez Helm si ce n’est pas déjà fait. Sur Ubuntu : sudo snap install helm --classic

  • Créons un namespace pour prometheus et grafana : kubectl create namespace monitoring

  • Ajoutez le dépot de chart Prometheus et kube-state-metrics: helm repo add prometheus-community https://prometheus-community.github.io/helm-charts puis helm repo add kube-state-metrics https://kubernetes.github.io/kube-state-metrics puis mise à jours des dépots helm helm repo update.

  • Installez ensuite le chart prometheus :

helm install \
  --namespace=monitoring \
  --version=13.2.1 \
  --set=service.type=NodePort \
  prometheus \
  prometheus-community/prometheus

kube-state-metrics et le monitoring du cluster

Le chart officiel installe par défaut en plus de Prometheus, kube-state-metrics qui est une intégration automatique de kubernetes et prometheus.

Une fois le chart installé vous pouvez visualisez les informations dans Lens, dans la premiere section du menu de gauche Cluster.

Déployer notre application d’exemple (goprom) et la connecter à prometheus

Nous allons installer une petite application d’exemple en go.

  • Téléchargez le code de l’application et de son déploiement depuis github: git clone https://github.com/e-lie/k8s-deployment-strategies

Nous allons d’abord construire l’image docker de l’application à partir des sources. Cette image doit être stockée dans le registry de minikube pour pouvoir être ensuite déployée dans le cluster. En mode développement Minikube s’interface de façon très fluide avec la ligne de commande Docker grace à quelques variable d’environnement : minikube docker-env

  • Changez le contexte de docker cli pour pointer vers minikube avec eval et la commande précédente.
réponse:
  • Allez dans le dossier goprom_app et “construisez” l’image docker de l’application avec le tag uptimeformation/goprom.
réponse:
  • Allez dans le dossier de la première stratégie recreate et ouvrez le fichier app-v1.yml. Notez que image: est à uptimeformation/goprom et qu’un paramètre imagePullPolicy est défini à Never. Ainsi l’image sera récupéré dans le registry local du docker de minikube ou sont stockées les images buildées localement plutôt que récupéré depuis un registry distant.

  • Appliquez ce déploiement kubernetes:

réponse:

Observons notre application et son déploiement kubernetes

  • Explorez le fichier de code go de l’application main.go ainsi que le fichier de déploiement app-v1.yml. Quelles sont les routes http exposées par l’application ?
réponse:
  • Faites un forwarding de port Minikube pour accéder au service goprom dans votre navigateur.
réponse:
  • Faites un forwarding de port pour accéder au service goprom-metrics dans votre navigateur (c’est sur la route /metrics). Quelles informations récupère-t-on sur cette route ?
réponse:
  • Pour tester le service prometheus-server nous avons besoin de le mettre en mode NodePort (et non ClusterIP par défaut). Modifiez le service dans Lens pour changer son type.

  • Exposez le service avec Minikube (n’oubliez pas de préciser le namespace monitoring).

  • Vérifiez que prometheus récupère bien les métriques de l’application avec la requête PromQL : sum(rate(http_requests_total{app="goprom"}[5m])) by (version).

  • Quelle est la section des fichiers de déploiement qui indique à prometheus ou récupérer les métriques ?

réponse:

Installer et configurer Grafana pour visualiser les requêtes

Grafana est une interface de dashboard de monitoring facilement intégrable avec Prometheus. Elle va nous permettre d’afficher un histogramme en temps réel du nombre de requêtes vers l’application.

Créez un secret Kubernetes pour stocker le loging admin de grafana.

cat <<EOF | kubectl apply -n monitoring -f -
apiVersion: v1
kind: Secret
metadata:
  namespace: monitoring
  name: grafana-auth
type: Opaque
data:
  admin-user: $(echo -n "admin" | base64 -w0)
  admin-password: $(echo -n "admin" | base64 -w0)
EOF

Ensuite, installez le chart Grafana en précisant quelques paramètres:

helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
helm install \
  --namespace=monitoring \
  --version=6.1.17 \
  --set=admin.existingSecret=grafana-auth \
  --set=service.type=NodePort \
  --set=service.nodePort=32001 \
  grafana \
  grafana/grafana

Maintenant Grafana est installé vous pouvez y acccéder en forwardant le port du service grace à Minikube:

$ minikube service grafana

Pour vous connectez utilisez, username: admin, password: admin.

Il faut ensuite connecter Grafana à Prometheus, pour ce faire ajoutez une DataSource:

Name: prometheus
Type: Prometheus
Url: http://prometheus-server
Access: Server

Créer une dashboard avec un Graphe. Utilisez la requête prometheus (champ query suivante):

sum(rate(http_requests_total{app="goprom"}[5m])) by (version)

Pour avoir un meilleur aperçu de la version de l’application accédée au fur et à mesure du déploiement, ajoutez {{version}} dans le champ legend.

Observer un basculement de version

Ce TP est basé sur l’article suivant: https://blog.container-solutions.com/kubernetes-deployment-strategies

Maintenant que l’environnement a été configuré :

  • Lisez l’article.
  • Vous pouvez testez les différentes stratégies de déploiement en lisant leur README.md.
  • En résumé, pour les plus simple, on peut:
    • appliquer le fichier app-v1.yml pour une stratégie.
    • lançer la commande suivante pour effectuer des requêtes régulières sur l’application: service=$(minikube service goprom --url) ; while sleep 0.1; do curl "$service"; done
    • Dans un second terminal (pendant que les requêtes tournent) appliquer le fichier app-v2.yml correspondant.
    • Observez la réponse aux requêtes dans le terminal ou avec un graphique adapté dans graphana (Il faut configurer correctement le graphique pour observer de façon lisible la transition entre v1 et v2). Un aperçu en image des histogrammes du nombre de requêtes en fonction des versions 1 et 2 est disponible dans chaque dossier de stratégie.
    • supprimez le déploiement+service avec delete -f ou dans Lens.

Par exemple pour la stratégie recreate le graphique donne:

Facultatif : Installer Istio pour des scénarios plus avancés

Pour des scénarios plus avancés de déploiement, on a besoin d’utiliser un service mesh. Un des plus connus est Istio.

  1. Sur k3s, supprimer la release Helm de Traefik pour remplacer le Ingress Controller Traefik par Istio.
  2. Installer Istio, créer du trafic vers l’ingress de l’exemple et afficher le graphe de résultat dans le dashboard Istio : https://istio.io/latest/docs/setup/getting-started/
  3. Utiliser ces deux ressources pour appliquer une stratégie de déploiement de type A/B testing poussée :

TP fil rouge DevOps

Introduction

L’objectif de ce TP est de faire la démonstration pratique de la plupart des éléments techniques appris durant le cursus DevOps.

L’activité de DevOps dans une équipe est une activité de support au développement et d’automatisation des divers éléments pratiques nécessaire au bon fonctionnement d’une application. Elle est par nature intégrative.

Ce TP consiste donc logiquement à rassembler les aspects pratiques (éléments vus en TP) découverts dans les modules du cursus et de les combiner autour d’une infrastrure Kubernetes pour réaliser en particulier une CI/CD de notre application utilisant Jenkins.

Attention :

  • Toutes les parties ne sont pas forcément obligatoire. L’appréciation sera globale. Les bonus sont des idées de personnalisation à réaliser si vous avez le temps et le courage.

  • Ce sujet de TP est loin d’être simple :

    • N’hésitez pas à demander de l’aide aux formateurs.
    • Collaborez et partagez la compréhension des enjeux dans le groupe.
  • Le sujet est succeptible d’évoluer au fur et à mesure en fonction de vos retours et demandes d’information.

  • Les parties de la fin du cursus (Jenkins et peut-être le Monitoring et/ou AWS et/ou Ansible) seront ajoutées par la suite.

  • N’oubliez pas de vous reposer pendant les vacances !!

Rendu

Le rendu du TP est à effectuer par groupe.

Pour chaque groupe les éléments suivant devront être présentés lors de la présentation finale du cursus:

  • Une présentation décrivant les différents élements de l’infrastructure et leurs objectifs ainsi que les choix réalisés lors de la réalisation.

  • On peut se servir de diapositives afin d’avoir un support oral. L’idée est de voir la gestion du temps, l’expression orale et évidemment le côté technique. Et attention, à la répartition de parole dans le groupe, chacun doit occuper sa place.

  • La qualité des diapositives est notée également.

  • La présentation dure 20mn, 10mn de plus de questions du jury, 5 mn de délibération du jury sans les stagiaires et 5 mn de compte rendu au groupe de la part du jury.

  • Pas de rapport écrit à part les diapositives.

Objectifs

  • Mettre en œuvre un système d’intégration continue et de déploiement DevOps

  • Construire une image capable de servir à l’application

  • Automatiser la construction d’images

  • Mettre à jour et déployer automatiquement des images

  • Une installation fonctionnelle de l’infrastructure et de l’application du TP installé sur cette infrastructure telle que décrite dans l’énoncé suivant.

  • Deux dépots de code sur Github ou Gitlab contenant pour le premier le code d’infrastructure et pour le second l’application à déployer sur l’infrastructure.

0 - Vagrant et Virtualbox: créer une machine virtuelle avec du code

Une infrastructure est généralement composée de machines virtuelles pour la flexibilité, qu’elles soient louées chez un provider de cloud comme Amazon Web Service ou créées à l’aide d’un hyperviseur comme Virtualbox (ou VMWare ou Proxmox etc).

Dans ce TP nous allons utiliser Virtualbox pour créer un ou plusieurs serveurs (selon vos préférences, voir bonus kubernetes installation dans la suite). Pour respecter les bonnes pratiques de l’infrastructure as code et pouvoir partager et reproduire l’installation nous aimerions créer ces machines virtuelles à l’aide de code descriptif. L’outil adapté pour cela s’appelle Vagrant.

curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
sudo apt-get update && sudo apt-get install vagrant
  • Créez un dossier pour votre code d’infrastructure par exemple tp_fil_rouge_infra et ajoutez à l’intérieur un fichier Vagrantfile contenant le code suivant:
Vagrant.configure("2") do |config|
    config.vm.provider :virtualbox do |v|
      v.memory = 2048
      v.cpus = 2
    end

    config.vm.define :master do |master|
      # Vagrant va récupérer une machine de base ubuntu 20.04 (focal) depuis cette plateforme https://app.vagrantup.com/boxes/search
      master.vm.box = "ubuntu/focal64"
      master.vm.hostname = "master"
      master.vm.network :private_network, ip: "10.10.0.1"
    end
  end
  • Entrainez vous à allumer, éteindre, détruire la machine et vous y connecter en ssh en suivant ce tutoriel: https://les-enovateurs.com/vagrant-creation-machines-virtuelles/. (pensez également à utiliser vagrant --help ou vagrant <commande> --help pour découvrir les possibilités de la ligne de commande vagrant).

Remarques pratiques sur Vagrant :

  • Toutes les machines vagrant ont automatiquement un utilisateur vagrant.
  • Vagrant partage automatiquement le dossier dans lequel est le Vagrantfile à l’intérieur de la VM dans le dossier /vagrant. Les scripts et autres fichiers sont donc directement accessibles dans la VM.

1 - Application Web Python et Linux

En vous aidant du tutorial suivant (jusqu’à la partie 5, avant la partie certbot): https://www.digitalocean.com/community/tutorials/how-to-serve-flask-applications-with-gunicorn-and-nginx-on-ubuntu-20-04-fr

  • Installez dans la machine virtuelle Vagrant précédente une application web flask (par exemple celle proposée dans le tutoriel).

  • Rassemblez les étapes d’installation dans un script shell (à ajouter dans le dossier d’infra).

  • Vérifiez que votre script d’installation fonctionne en détruisant et recréant la machine virtuelle (vagrant destroy) puis en lançant le script en ssh.

  • (facultatif) Vous pouvez même ajouter le script directement au Vagrantfile, après la ligne master.vm.network :private_network, ip: "10.10.0.1" avec la syntaxe suivante (cf. la documentation):

      master.vm.provision :shell, privileged: false, inline: <<-SHELL
      commande1
      commande2
      etc
  SHELL

Idée de bonus

2 - Git

Versionner le code de l’application précédente avec Git. Créer un dépôt sur Github ou Gitlab.

  • Un-e membre du groupe crée le dépôt et ajoute ses collègues à l’application en leur donnant le status de maintainer.
  • Poussez le code avec une branche develop, une branche main (production).
  • Chaque membre du groupe créé une branche à son nom et s’efforce de ne plus pousser sur develop ou main dans le futur mais en utilisant sa branche.
  • Le code sera ensuite mergé dans la branche develop et/ou main.

Répétez les étapes précédentes en créant un dépôt pour le code d’infrastructure.

Ces deux dépôts serviront pour la présentation finale de votre code.

Idées de bonus

  • Écrire à l’avance des issues (au fur et a mesure plutôt que toutes au départ) pour décrire les prochaines étapes à réaliser.

  • Utilisez pour la suite du TP des branches pour les issues.

    • Les merger dans main sans passer par develop (les feature branches remplacent la branche develop) en effectuant des pull request Github ou merge requests Gitlab.
  • Utilisez le wiki Github ou Gitlab du dépôt d’infrastructure pour documenter votre infrastructure et servir de support à la présentation finale.

3 - Docker

En s’aidant du TP2 et TP4 du module Docker, et de votre script d’installation existant :

  • Dockeriser une application flask simple (par exemple celle de la partie précédent ou celle du TP Docker à la place) en écrivant un Dockerfile.
  • (facultatif) Ajoutez un fichier docker-compose.yml. pour lancer l’application.
  • Ajoutez les fichiers créées à votre dépôt d’application.

Idée de bonus

4 - Kubernetes installation

En suivant/vous inspirant des TP kubernetes et de la partie 0.

  • En repartant du Vagrantfile de la partie 0 : utilisez la commande master.vm.provision comme indiqué dans la partie 0 ci-dessus pour installer k3s avec la commande curl -sfL https://get.k3s.io | sh -.

  • (facultatif) Trouvez comment supprimer l’ingress Traefik de k3s et installez à la place un ingress nginx plus classique (pour pouvoir exposer l’application web à l’extérieur).

  • (facultatif) Installez cert-manager comme dans le TP avec un générateur de certificat auto-signé : https://cert-manager.io/docs/configuration/selfsigned/

  • Versionnez le Vagrantfile et les fichiers d’installation Kubernetes dans le dépôt d’infrastructure.

Idées de bonus

  • Installez un repository d’image docker simple en vous aidant de tutoriels sur Internet et de l’image registry:2, ou bien de solutions plus avancées
  • Créez un cluster de 3 noeuds k3s avec Vagrant et k3sup.

5 - Kubernetes déploiement de l’application

  • Déployez une application flask dans le cluster en vous inspirant du TP déployer une application de A à Z.
  • Versionnez les fichiers d’installation dans un dossier k8s du dépôt d’application.

Idée de bonus

  • Déployer en plus l’application flask avec une base de donnée externe (voir chapitre 19 du mega tutorial). Installez MySQL à l’aide d’un chart Helm.

Contenu intégral

Exporter les supports en pdf

Pour exporter correctement les TPs et autres pages de ce site au format pdf, utilisez la fonction imprimer de Google Chrome ou Firefox (vous pouvez aussi activer le Mode Lecture de Firefox en cliquant Affichage > Passer en Mode Lecture) en ouvrant la page suivante : Contenu intégral.