Académique Documents
Professionnel Documents
Culture Documents
Retour au site
https://supports.uptime-formation.fr/all_content/ 1/210
14/11/2022 17:46 Exporter tout le contenu
1 - Manipulation des conteneurs
TP 1 - Installer Docker et jouer avec
2 - Images et conteneurs
QCM
TP 2 - Images et conteneurs
3 - Volumes et réseaux
TP 3 - Réseaux
TP 3bis - Volumes
4 - Créer une application multiconteneur
TP 4 - Créer une application multiconteneur
5 - Orchestration et clustering
TP 5 - Orchestration et clustering
Conclusion
TP 6 (bonus) - Intégration continue avec Gitlab
TP 7 (bonus) - Docker et les reverse proxies
QCM Docker
Bibliographie
01 - Cours - Présentation de Kubernetes
02 - Cours - Mettre en place un cluster Kubernetes
03 - TP1 - Installation et configuration de Kubernetes
04 - Cours - Objets Kubernetes - Partie 1
05 - TP 2 - Déployer en utilisant des fichiers ressource et Lens
06 - Rappels Docker
07 - TP 3 - Déployer des conteneurs de A à Z
08 - Cours - Le réseau dans Kubernetes
09 - TP 4 - Déployer Wordpress Avec une base de donnée persistante
10 - Cours - Objets Kubernetes Partie 2.
11 - Cours - Helm, le gestionnaire de paquets Kubernetes et les Opérateurs
12 - TP 5 - Déployer Wordpress avec Helm et ArgoCD
TP optionnel - Exposer une application en HTTPS via certmanager et un ingress nginx
TP opt. - Le RBAC
TP optionnel - Installer un registry privé d'images dans votre cluster
TP opt. - CI/CD avec Gitlab et ArgoCD
Bibliographie
1 - Découverte de l'écosystème Elastic
1 - Installation
2 - Elasticsearch
2 - Elasticsearch - Exercices
3 - Kibana
3 - Kibana - Exercices
4 - Beats
4 - Beats - Exercices
5 - Logstash
5 - Logstash - Exercices bonus
6 - Conclusion
6 - Elastic APM dans Flask - Exercice bonus
7 - Bibliographie
API Elasticsearch memento - Version 7.14 de l'API
TP optionnel - Stratégies de déploiement et monitoring
Environnement de travail
Pour cette formation en distanciel nous utiliserons les outils suivants:
L’espace de travail Teams fournis pour la formation dans lequel a lieu les conférences Videos/Audio. Il permet également de faire des remarques
et partager des fichier entre nous.
Ce site internet qui contient tout le contenu de la formation. Le site internet est maintenu à jour tout au long de la formation (pensez à recharger
les pages régulièrement) vous pourrez l’imprimer en PDF à la fin.
Pour toute la partie pratique nous utiliserons des serveurs distants accessibles en mode graphique(VNC) via le site guacamole.formation.dopl.uk
Serveurs distants
L’intérêt des serveurs distant est double:
L’environnement est fiable et identique pour tout le monde ce qui évitera les bugs spécifiques en fonction des environnements de chacun.
Ils facilite le travail à distance car tous les stagiaires peuvent voir l’écran du formateur et inversement le formateur peut intervenir rapidement sur
les machines de tous les stagiaires.
Connexion à Guacamole
Connectez-vous à guacamole.dopl.uk avec comme login votre prenomnom (sans point ni tirets) et comme mot de passe devops101.
Ouvrez chacune des connexions dans un nouvel onglet ou une nouvelle fenêtre.
Vous pouvez désormais suivre les actions du formateur et les reproduire de votre côté sur le serveur.
Pour la sauvegarde de votre code nous créerons un dépôt git sur github au cours de la formation.
Introduction
Introduction
https://supports.uptime-formation.fr/all_content/ 2/210
14/11/2022 17:46 Exporter tout le contenu
DevOps
Le nouveau paradigme de l’informatique
Introduction DevOps
A propos de moi
A propos de vous
Attentes ?
Début du cursus :
Est-ce que ça vous plait ?
Quels modules avez vous déjà fait ?
Le mouvement DevOps
Le DevOps est avant tout le nom d’un mouvement de transformation professionnelle et technique de l’informatique.
Ce mouvement se structure autour des solutions humaines (organisation de l’entreprise et des équipes) et techniques (nouvelles technologies de
rupture) apportées pour répondre aux défis que sont:
L’agrandissement rapide face à la demande des services logiciels et infrastructures les supportant.
La célérité de déploiement demandée par le développement agile (cycles journaliers de développement).
Difficultées à organiser des équipes hétérogènes de grande taille et qui s’agrandissent très vite selon le modèle des startups.et
Du côté humain:
Application des process de management agile aux opérations et la gestion des infrastructures (pour les synchroniser avec le développement).
Remplacement des procédés d’opérations humaines complexes et spécifiques par des opérations automatiques et mieux standardisées.
Réconciliation de deux cultures divergentes (Dev et Ops) rapprochant en pratique les deux métiers du développeur et de l’administrateur système.
Du côté technique:
L’agilité en informatique
Traditionnellement la qualité logicielle provient :
Lenteur de livraison du logiciel (une version par an ?) donc aussi difficulté de fixer les bugs et problèmes de sécurité a temps
Le Travail du développeur est dominé par des process formels : ennuyeux et abstrait
difficulté commerciale : comment répondre à la concurence s’il faut 3 ans pour lancer un produit logiciel.
La célérité est : la rapidité (itérative) non pas seulement dans le développement du logiciel mais plus largement dans la livraison du service au
client:
Exemple : Netflix ou Spotify ou Facebook etc. déploient une nouvelle version mineure de leur logiciel par jour.
Lorsque la concurrence peut déployer des innovations en continu il devient central de pouvoir le faire.
Dans un DSI (département de service informatique) on organise ces activités d’admin sys en opérations:
On a un planning d’opération avec les priorités du moment et les trucs moins urgents
On prépare chaque opération au minimum quelques jours à l’avance.
https://supports.uptime-formation.fr/all_content/ 3/210
14/11/2022 17:46 Exporter tout le contenu
On suit un protocole pour pas oublier des étapes de l’opération (pas oublier de faire une sauvegarde avant par exemple)
La difficulté principale pour les Ops c’est qu’un système informatique est:
Un système très complexe qu’il est quasi impossible de complètement visualiser dans sa tête.
Les évènements qui se passe sur la machines sont instantanés et invisibles
L'état actuel de la machine n’est pas ou peu explicite (combien d’utilisateur, machine pas connectée au réseau par exemple.)
Les interractions entre des problèmes peu graves peuvent entrainer des erreurs critiques en cascades.
On peut donc constater que les opérations traditionnelles implique une culture de la prudence
On s’organise à l’avance.
On vérifie plusieurs fois chaque chose.
On ne fait pas confiance au code que nous donnent les développeurs.
On suit des procédures pour limiter les risques.
On surveille l’état du système (on parle de monitoring)
Et on reçoit même des SMS la nuit si ya un problème :S
Bilan
Peuvent pas aller trop vite car il faut marcher sur des oeufs.
Les Ops veulent pas déployer de nouvelles versions trop souvent car ça fait plein de boulot et ils prennent des risques (bugs / incompatilibités).
Quand c’est mal organisé ou qu’on va trop vite il y a des catastrophes possibles.
Du côté des développeurs avec l’agilité on a déjà depuis des années une façon d’automatiser pleins d’opérations sur le code à chaque fois qu’on valide
une modification.
Le principe central du DevOps est d’automatiser également les opérations de déploiement et de maintenance en se basant sur le même modèle.
Mais pour que ça fonctionne il faut résoudre des défi techniques nouveau => innovations
Infrastructure as a Service (IaaS): on commande du linux, du réseau et des loadbalancer etc. à la demande
https://supports.uptime-formation.fr/all_content/ 4/210
14/11/2022 17:46 Exporter tout le contenu
Exemple: Amazon Web Services, DigitalOcean, Azure etc
Plateforme as a Service (PaaS): on commande directement un environnement PHP ou NodeJS pour notre application
Software as a service (SaaS): des services web à la demande pour des utilisateurs finaux
On peut dire que chaque couche (d’abstraction) de l’informatique est commandable à la demande.
Il s’agit comme son nom l’indique de gérer les infrastructures en tant que code c’est-à-dire des fichiers textes avec une logique algorithmique/de
données et suivis grâce à un gestionnaire de version (git).
Le problème identifié que cherche a résoudre l’IaC est un écheveau de difficulées pratiques rencontrée dans l’administration système traditionnelle:
1. Connaissance limité de l’état courant d’un système lorsqu’on fait de l'administration ad-hoc (manuelle avec des commandes unix/dos).
2. Faible reproductibilité des systèmes et donc difficultée/lenteur du passage à l’échelle (horizontal scaling).
Multiplier les serveurs identiques est difficile si leur état est le résultat d’un processus manuel partiellement documenté.
Difficulté à reproduire/simuler l’état précis de l’infrastructure de production dans les contextes de tests logiciels.
3. Difficultés du travail collaboratif dans de grandes équipes avec plusieurs culture (Dev vs Ops) lorsque les rythmes et les modes de travail
diffèrent
Notre programme
Docker : les conteneurs et l’infra as code
Ansible : couteau suisse de l’infra as code
Kubernetes : infrastructure de conteneurs (iac et cloud)
Jenkins : CI/CD pour intégrer ensemble le dev et les opérations
Slides de cours
1. Introduction à Linux
2. Linux administration
3. Linux admin avancée et réseau
4. Shell scripting
Feuilles d’exercices
1. Feuille d’exercices 1 partie 1
2. Feuille d’exercices 1 partie 2
3. Feuille d’exercices 2 admin et réseau
4. Feuille d’exercices 3 scripting
https://supports.uptime-formation.fr/all_content/ 5/210
14/11/2022 17:46 Exporter tout le contenu
formationLinux
7. Processus
7.1 : Faire sleep 30 puis Ctrl+Z et bg pour mettre la commande en arrière plan. Constater que vous pouvez de nouveau taper des commandes.
Faire jobs dans les secondes qui suivent et constater que la commande est toujours ‘Running’. Appuyer sur ‘Entrée’ régulièrement pendant les
secondes qui suivent jusqu’à ce que ‘Done’ s’affiche.
7.2 : Faire sleep 30 puis Ctrl+Z et bg pour mettre la commande en arrière plan. Constatez que vous pouvez de nouveau taper des commandes.
Avant que la commande ne se termine (au bout des 30 secondes), faire fg pour récupérer la main sur le sleep puis faites Ctrl+C.
7.3 : Faire sleep 30 & et constater que vous pouvez taper des commandes. Noter aussi que la console a affiché le PID du programme
correspondant au sleep. Utiliser ce PID pour tuer le programme avec kill <PID>.
7.4 : Lancer sleep 700000 (par exemple) puis faire ps -ef dans un autre terminal et constater que le processus est bien listé (vous pouvez aussi
utiliser ‘top’ et les fleches)
7.5 : Dans le ps -ef utiliser précédemment, vous pouvez trouver le PPID (parent PID) à côté du PID. Vous pouvez regarder dans le reste de la
liste pour checher le programme correspondant à ce PPID (apriori il s’apelle ‘bash’).
7.6 - Connaissant le PID du parent, utiliser kill <PID> ou kill -9 <PID> pour tuer le shell. Vous devriez constater que la session est détruite.
7.7 - Lancez screen et dedans, sleep 30. Faites Ctrl+A puis D pour détacher la session. Eventuellement screen -list pour lister les sessions
screen. Depuis un autre terminal, faites screen -r et constatez que vous récupérez bien le shell ou vous aviez lancé sleep 30.
7.8 - Avec ps -ef ou ps -ef --forest, vous devriez voir le processus sleep 30 (ou relancez le si il a déjà terminé). Son parent devrait être un
bash. Après avoir trouvé le PID de ce parent, faites kill -9 <PID> pour tuer cette session. Vous deviez constater que la session est détruite.
7.9 - Lancer top : celui tout en haut consomme actuellement le plus de CPU. Si vous appuyez sur Shift+M, les programmes seront maintenant trié
par utilisation de la RAM.
7.10 - Lancer la commande openssl speed -multi 4, puis relancer top : vous devriez voir que 4 processus consomment maintenant beaucoup de
CPU
7.11 - Lancer nice -n 19 ls /bin : vous devriez voir que cette commande est trèèèès longue.
7.12 - Identifier le PID des différent process openssl. Utiliser renice +10 <PID> pour changer leur niceness. En utilisant top, la colonnee ‘NI’
devrait maintenant indiquer une niceness différente de 0. Après avoir réduit la niceness de ces processus, relancer nice -n 19 ls /bin devrait
être un peu plus rapide.
7.13 - pkill openssl
8.2 : Ajouter la ligne précédente en bas de ~/.bashrc à l’aide de nano puis recharger avec source ~/.bashrc
8.3 : Même chose que 8.2 mais en rajoutant une ligne comme :
echo "May the source be with you
8.4 : Ouvrir /root/.bashrc avec nano (et possiblement sudo) puis rajouter une ligne pour modifier le PS1, comme :
PS1="[\033[01;31m\u on \h\033[0m:\033[01;34m\w\033[0m] \n> "
https://supports.uptime-formation.fr/all_content/ 6/210
14/11/2022 17:46 Exporter tout le contenu
8.5 : Taper ll pour tester si la commande existe. Si elle n’existe pas, rajouter alias ll='ls -l' dans le bashrc puis le recharger avec source. Pour
s’assurer que ls utiliser --color=auto par défaut, taper alias ls.
8.6 : Taper alias suls='sudo ls -la' (par exemple) et alias sucat='sudo cat'. Tester en tant que padawan de taper suls /root et sucat
/etc/shadow (ou d’autres dossiers / fichiers accessibles uniquement par root).
8.7 : alias r2d2="sudo su r2d2" puis tester de taper r2d2.
8.8 : Se renseigner sur Internet ;P (question un peu ‘extra’)
8.9 : alias ls="echo 'G pas envie'"
touch /tmp/chat
chmod +w /tmp/chat
puis faire echo "beep boop" >> /tmp/chat depuis d’autres terminaux (attention, il y a deux chevrons !).
Il est possible de créer l’alias say qui parle dans le chat avec :
9.12 :
alias esquecestleweekend='date | grep "^Sat \|^Sun " >/dev/null && echo "Cest le weekend" && echo "Cest le weekend" || echo "Il faut encore taff
9.13 : Les lignes vides correspondent à ^$ (début de ligne suivi de fin de ligne) donc : cat /etc/login.defs | grep -v "^$" Pour enlever
également les commentaires, on utilise un “ou” dans grep (\|), ce qui donne : cat /etc/login.defs | grep -v "^$\|^#"
9.14 : dpkg-query --status vim | grep -q 'Status: install ok installed' && echo Oui || echo Non
9.15 : grep -nr "daemon" /etc/
9.16 : Similaire à la question 9.14 : who | grep -q r2d2 && echo "R2d2 est là !" || echo "Mais que fais r2d2 ?!"
9.17 : ps -ef | grep -v "UID" | awk '{print $1}' | sort | uniq -c
9.18 : cat loginattempts.log | awk '{print $9}' | sort | uniq -c | sort -n
9.19 : Il s’agit d’un exercice un peu avancé avec plusieurs solutions possibles (qui ne sont pas trop robuste, mais peuvent dépanner). En voici une
qui envoie les adresses des images dans un fichier img.list :
curl yoloswag.team \
| grep "\[img\]" \
> img.list
1.21 - En utilisant gzip, produisez une version compressée de l’archive de la question précédente
gzip monhome.tar
# comparé à avant
1.23 - En fouillant dans les options de tar, trouvez un moyen de lister le contenu de l’archive
tar -tvf monhome.tar
1.24 - Créez un dossier test_extract dans /tmp/, déplacez l’archive dans ce dossier puis décompressez-là dedans.
mkdir /tmp/test_extract
mv monhome.tar.gz /tmp/test_extract
cd /tmp/test_extract
https://supports.uptime-formation.fr/all_content/ 7/210
14/11/2022 17:46 Exporter tout le contenu
1.25 - TODO
1.26 - Il existe des fichiers de logs g-zippés comme /var/log/apt/history.log.1.gz (si vous avez fait quelques commandes avec apt). Il contient
l’historique des opérations récentes effectuées avec apt. Ou encore : /var/log/dmesg.1.gz (si votre machine a déjà démarré plusieurs fois) qui
contient les historiques de démarrage du système. On peut faire find /var/log -name "*.gz" pour trouver tous les fichiers de log zippés. Si l’on
utilise uniquement cat /var/log/apt/history.log.1.gz, le résultat n’est pas lisible car il s’agit d’un flux binaire. Il est néanmoins possible de le
dézipper “à la volée” en pipant le résultat dans gzip :
Ou bien il existe une commande zcat qui fait cette opération directement :
zcat /var/log/apt/history.log.1.gz
DISTRIB=$(cat /etc/os-release \
| grep PRETTY_NAME \
| tr -d '"')
| wc -l)
UPTIME=$(uptime --pretty)
IP_LOCALE=$(ip a | grep "inet " | grep -v "127.0.0.1" | awk '{print $2}' | awk -F/ '{print $1}')
RED="\033[31m"
GREEN="\033[32m"
PURPLE="\033[35m"
2 - Paramétrabilité, interactivité
2.2 (add)
#!/bin/bash
RESULTAT=$(($1 + $2))
echo $RESULTAT
### 2.3 (age)
#!/bin/bash
CURRENT_YEAR=$(date +%Y)
read YEAR
AGE=$(($CURRENT_YEAR-$YEAR))
2.4 (check_user)
#!/bin/bash
USER="$1"
Fichiers
Comprendre la notion de chemin relatif et de chemin absolu pour désigner un fichier
Où sont stockés les fichiers de configuration
Où sont stockés les programmes
Où sont stockés les logs
Où sont les répertoires utilisateurs
Savoir afficher ou éditer un fichier depuis la ligne de commande
Users
Processus
Voir les processus qui tournent actuellement
Savoir identifier le proprietaire d’un process et son PID
Comprendre les relations de parentés entre process
Savoir tuer un process
Comprendre qu’il existe un process “originel” nommé “init”
Installation de Linux
Gestionnaire de paquet
Réseau (IP)
Comprendre que l’acheminement des paquets sur le réseau se fait par des routeurs qui discutent entre eux pour optimiser les trajets
Comprendre la notion de réseau local
Savoir qu’il y a l’IPv4 … mais aussi l’IPv6 !
Savoir identifier son IPv4 locale
Savoir identifier son IPv4 globale
Savoir pinger une autre machine
Réseau (TCP)
Comprendre que TCP est une couche réseau qui permet d’introduire de la fiabilité dans les communications à l’aide d’accusé de réceptions
Comprendre qu’il est nécessaire d’introduire la notion de port (en plus de l’IP) pour spécifier entre quelles programmes se fait une
communication TCP
Savoir lister les process qui écoutent sur un port
Savoir ce qu’est un firewall et de quoi il protège
Réseau (DNS)
Réseau (web)
Comprendre ce qu’il se passe au niveau réseau lorsqu’on visite une page web (résolution DNS, établissement d’une communication TCP, envoi
d’une requête GET /)
Savoir télécharger des pages web ou fichiers sur le web avec wget ou curl
Notions de sécurité
Connaître les bonnes pratiques de base : tenir son serveur raisonnablement à jour, utiliser des mots (ou phrases) de passes raisonnablement forts
https://supports.uptime-formation.fr/all_content/ 9/210
14/11/2022 17:46 Exporter tout le contenu
Comprendre pourquoi il ne faut pas faire chmod 777, ou chmod +r, notamment sur des fichiers contenant des informations privées (données
personnelles) ou critique (mot de passe de base de donnée)
Comprendre qu’un serveur sur internet est sujet à des attaques automatiques, et qu’il est possible de mettre en place des contre-mesures
Comprendre le principe de la cryptographie asymétrique : notion de clef publique, clef privée
(idéalement : comprendre la notion d’authenticité et de signature cryptographique)
Infrastructure
Savoir qu’il est possible d’acheter des serveurs (VPS) en ligne (infrastructure as a service)
SSH
Services
Savoir ce qu’est un service (au sens de l’administration système)
Savoir lancer / arrêter / redémarrer un service
Savoir afficher l’état d’un service
Savoir trouver et lire les logs d’un service
Comprendre que le déploiement d’une application implique généralement d’installer et configurer un écosystème de services qui travaillent
ensemble : serveur web, “l’app”, et le serveur de base de donnée
Commandes “avancées”
Comprendre la notion de code de retour, d’entrée standard, sortie standard et erreur standard
Savoir rediriger les sorties des commandes
Savoir enchainer des commandes (;, &&, ||, |)
Savoir utiliser grep, awk, cut, sort, uniq, wc, … pour filtrer ou faire des calculs sur les sorties des commandes
Scripting bash
Git et Gitlab
Git
Collaborer dans des dépôts de code efficacement.
Git 1 - Introduction
Git 1 - Exercices
Git 2 - Explorer un dépôt
Git 2 - Exercices
Git 3 - Les branches
Git 3 - Exercices
Git 4 - Forges Git
Git 4 - Exercices
Git 5 - Les pipelines avec Gitlab CI
Git 5 - Exercices
Memento des commandes
Bibliographie
QCM
Git 1 - Introduction
GIT = Des dépôts de code à partager
Comment gérer du code logiciel ?
Plusieurs difficultés :
Comme on l’a vu chaque lettre compte : une erreur = un bug qui peut être grave et nous faire perdre plusieurs heures
Mémoire : comment savoir ou l’on en était quand on revient sur le projet d’il y a deux mois
https://supports.uptime-formation.fr/all_content/ 10/210
14/11/2022 17:46 Exporter tout le contenu
Comment partager nos modifications ?
Comment faire si deux personnes travaillent sur le même fichier => conflits
3. Version du logiciel :
Le développement est un travail itératif = contruction petit à petit => plein de versions !
On veut ajouter une nouvelle fonctionnalité à un logiciel, mais continuer à distribuer l’ancienne version et l’améliorer.
On veut créer une version de test pour que des utilisateur·trices avancé·es trouvent des bugs
2. Permet de stocker plusieurs versions des mêmes fichiers et passer d’une version à l’autre.
Un peu comme la fonctionnalité “Historique” de Google Docs ou de Framapad en beaucoup plus avancé.
3. Permet de suivre qui a fait quelle modification, partager les modifications avec les autres, régler les conflits d’édition
Git !
git est un petit programme en ligne de commande. Qui fait tout ce dont on vient de parler :
https://supports.uptime-formation.fr/all_content/ 11/210
14/11/2022 17:46 Exporter tout le contenu
Pour la petite histoire, Git a été inventé en 2005 par Linus Torvalds, le créateur de Linux, pour garder la trace des propositions de modification du code
de Linux !
Écosystème Git :
⚠️A ne pas confondre !!!
le programme git : le gestionnaire de version = le coeur de l’écosystème => en ligne de commande
les interfaces graphique de git : VSCode et ses extensions, tig, meld, GitKraken, etc.
Pour faciliter l’utilisation de git et visualiser plus facilement
communique avec git sans le remplacer (ne fait que traduire vos clics de souris en commandes Git)
les forges logicielles basées sur Git comme github ou framagit:
des plateformes web pour accéder au dépôt de code (dossier) / mettre son code sur les internets.
faciliter la collaboration sur un projet
tester et déployer le code automatiquement comme dans la démarche DevOps (plus avancé)
On va utiliser les trois car c’est nécessaires pour bien comprendre comment on travaille avec git sur un projet.
On va utiliser :
git en ligne de commande souvent : il faut absolument connaître les fonctions de base pour travailler sur un projet de code aujourd’hui
VSCode : un éditeur de texte qui a des fonctions pratiques pour visualiser les modifications git et l’historique d’un projet, afficher les conflits
d’édition.
Gitlab sur l’instance framagit.org : une forge logicielle open-source. On va l’utiliser pour collaborer sur du code existant. Framagit est l’instance
de l’association Framasoft qui milite pour le libre et un Internet décentralisé.
On va utiliser les commandes de base durant les prochains jours pour se familiariser avec le fonctionnement normal.
En entreprise on utilise tout le temps git avec une routine simple. On y reviendra.
Même les ingénieur·es avec de l’expérience se trompent dans le comportement d’une commande Git et ne connaissent pas forcément les
fonctions avancées.
git init crée un dépôt dans ce dossier (transforme un dossier simple en un dossier avec git d’initialisé)
git add permet de suivre certains fichiers (dire à git qu’il faut inclure la version actuelle de ce fichier dans git)
git commit permet de valider vos modifications pour créer ce qu’on appelle un commit, c’est-à-dire une étape validée du code.
git status et git log permettent de suivre l’état du dépôt et la liste des commits.
Le commit
Un commit est composé :
d’un instantané du code auquel on se réfère via un identifiant unique (une empreinte ou un hash)
de référence par rapport à un (ou des) commit parent (ce qui nous permet de faire des branches de l'arbre de Git)
Idéalement, lorsque vous faites un commit, le code devrait être dans un état à peu près cohérent et le commit devrait rassembler des
modifications qui ont du sens pour atteindre un objectif (par exemple : résoudre un bug, rajouter une fonctionnalité, modifier de la
documentation…)
Les commits sont des étapes du développement du logiciel. Lire la liste de ces étapes devrait permettre à un·e developpeur / développeuse de
comprendre l’évolution du code.
un commit est toujours une référence à une version précise de l’ensemble du code par rapport à l’arbre Git, c’est n’est pas juste des ajouts et
des suppressions par rapport au code du commit précédent
https://supports.uptime-formation.fr/all_content/ 12/210
14/11/2022 17:46 Exporter tout le contenu
arbres :
l’espace de travail : ce sont les fichiers qu’il y a réellement dans votre dossier
l'index ou staging : un espace où l’on prépare son futur commit
et enfin l’arbre des commits définitif, avec HEAD qui fait référence au tout dernier commit
Premiers exercices
https://supports.uptime-formation.fr/all_content/ 13/210
14/11/2022 17:46 Exporter tout le contenu
Git 1 - Exercices
Créer un projet git
Durant ces exercices nous allons utiliser Git en ligne de commande (sans interface graphique) : l’objectif est de pratiquer les différentes commandes de
base git
Installer Git
git est souvent déjà installé sur Linux. Mais si ce n’est pas le cas, il suffit d’installer le paquet git, par exemple avec apt install git.
Initialiser le dépôt
Chargez ce dossier avec VSCode. Si VSCode n’est pas installé : snap install --classic code
Créez un nouveau fichier Python dans ce dossier appelé multiplication.py. Copiez-y le code suivant :
Pour le moment Git ne versionne aucun fichier du dépôt comme le confirme la commande git status.
Utilisez git add <nom_fichier> sur le fichier. Puis faites à nouveau git status. Le fichier est passé à l’état suivi (tracked).
Créez un nouveau fichier et écrivez quelque chose à l’intérieur (ou copiez un fichier situé en dehors de ce dossier vers ce dossier).
Faites git status à nouveau. Que s’est-il passé ?
Lancez le script multiplication.py pour vérifier
Pour créer un commit on utilise la commande git commit -m "<message_de_commit>" (commit signifie s’engager alors réfléchissez avant de
lancer cette commande !). Utilisons le message "Ceci est mon premier commit" pour le premier commit d’un dépôt. Valider la version courante.
Lancez un git status pour voir l’état du dépôt. Que constate-t-on ?
Lancez git log pour observer votre premier commit.
Utiliser git add avec l’option -A pour ajouter tous les fichiers actuels de votre projet.
Supprimer un fichier
Oh non ! Vous avez ajouté le dossier __pycache__ dans votre commit précédent 🙃
Ce ne serait pas correct de pousser sur Internet votre code en l’état !
Ignorer un fichier
Maintenant que nous avons supprimé ce dossier nous voulons éviter de l’ajouter accidentellement à un commit à l’avenir. Nous allons ignorer ce
dossier.
Le problème avec la suppression de __pycache__ de la partie précédente est qu’elle n’affecte que le dernier commit. Le dossier inutile __pycache__
encombre encore l’historique de notre dépôt.
Explorer la fenêtre git graph en cliquant sur Git Graph en haut à gauche de la fenêtre des fichiers.
Utilisez git reset avec HEAD~2 pour revenir deux commits en arrière (nous parlerons de HEAD plus tard).
Faites git status. Normalement vous devriez avoir un seul fichier non suivi .gitignore. Git vient de réinitialiser les ajouts des deux commits
précédents.
Constatez dans Git Graph que seul reste le premier commit qui est toujours là.
Exercices supplémentaires
“1: Séquence d’introduction et Montée en puissance” sur Learn Git branching
gitexercises.fracz.com
1. https://gitexercises.fracz.com/exercise/master
2. https://gitexercises.fracz.com/exercise/commit-one-file
3. https://gitexercises.fracz.com/exercise/commit-one-file-staged
4. https://gitexercises.fracz.com/exercise/ignore-them
5. https://gitexercises.fracz.com/exercise/remove-ignored
git clone <url dépot> puis cd <dépôt> pour aller dans le dossier du dépôt.
https://supports.uptime-formation.fr/all_content/ 15/210
14/11/2022 17:46 Exporter tout le contenu
ou bien https://github.com/spring-projects/spring-petclinic et cd spring-petclinic
ou encore https://github.com/miguelgrinberg/microblog et cd microblog
git checkout <commit num> pour vous déplacer au niveau d’un commit : le code dans le dépôt change.
git diff <commit_1> <commit_2> pour voir ce qui a changé entre deux commits.
Plus pratique : apt install tig et tig pour explorer chaque commit ou alors utilisez VSCode et GitLens
Un dépôt Git téléchargé depuis Internet peut être privé : il faut alors se connecter avant à son compte (en HTTP ou SSH) pour le télécharger. Quand on
veut modifier le dépôt distant (ajouter des commits), il faut de toute façon se connecter à un compte.
Un dépôt git permet d’avoir plusieurs historiques en parallèle qu’on appelle des branches. Un dépôt git ressemble à un arbre.
La branche principale s’appelle master dans git (par convention), parfois main.
https://supports.uptime-formation.fr/all_content/ 16/210
14/11/2022 17:46 Exporter tout le contenu
Ça commence à devenir compliqué ! Mais on va souvent travailler avec seulement deux branches 😌
Dans git, HEAD désigne un curseur qui indique dans quel état est le dépôt actuellement.
par défaut HEAD pointe sur le dernier commit de la branche (master s’il n’y en a qu’une).
remonter le temps cela signifie déplacer HEAD.
git reflog affiche l’historique des déplacements de HEAD.
C’est le cas de VSCode, en particulier avec les extensions Git Graph et GitLens.
Il s’agit d’un dépôt exemple d’une application de microblogging (comme Twitter) codée en Python avec le framework Flask.
Explorer le dépôt
https://supports.uptime-formation.fr/all_content/ 17/210
14/11/2022 17:46 Exporter tout le contenu
Installez ce qui est nécessaire pour l’application avec les commandes :
source ~/.bashrc
flask db init
flask db upgrade
Utilisez l’application en visitant l’adresse http://localhost:5000/, puis créez-vous un compte et postez un message.
Plutôt que d’utiliser la version finale de l’application, remontons l’historique du dépôt pour retrouver un état plus simple de l’application.
Utilisez la commande git blame sur le fichier app/main/routes.py. Cette commande est très utile quand on travaille à plusieurs car elle permet de
savoir à qui s’adresser lorsqu’on cherche à comprendre le code ou qu’on a trouvé un bug.
Installez tig qui est un utilitaire pour explorer un dépôt depuis le terminal.
Installez les extensions VSCode suivantes pour explorer le dépôt depuis VSCode :
GitLens
Git Graph.
À l’aide de tig ou des extensions VSCode cherchez le premier commit de l’historique qui ne fasse pas référence à Redis : c’est le commit de la
version v0.21 avant la version v0.22
Déplacez vous au niveau de ce commit avec git checkout <num_commit>. Votre dépôt est en mode “HEAD détaché” c’est à dire que le pointeur
HEAD se balade le long de l’historique.
C’est un état anormal dans lequel il ne faut généralement pas modifier le code. Il est très facile de se
perdre dans un dépôt git (le cas échéant utilisez git reflog pour bien comprendre les opérations qui vous ont amené dans l’état courant).
On observe que la fonctionnalité d’export de posts qui était cassée n’existe plus
Utilisez git reflog pour observer les déplacement de votre pointeur HEAD.
Nous allons expérimenter de réinitialiser (violemment) le projet au début de son historique avec git reset --hard. Réinitialisez au niveau du
commit identifié précédemment. Constatez sur GitLens ou Git Graph que les commits on été effacés et les fichiers également (sans le --hard les
commits auraient disparu mais les fichiers et leur contenus auraient été gardés et désindexés comme dans la partie 1).
La commande précédente a effacé toutes les modifications du dépôt des 106 derniers commits. Faites bien attention avec cette commande git
reset --hard ! Dans notre cas ce n’est pas un problème car ces commits sont disponibles sur le serveur. Pour récupérer les commits effacés
utilisez git pull. pull va récupérer les modifications depuis le serveur.
Créez une nouvelle branche avec git checkout -b <nom branche> appelez-la about-page.
Trouvez comment ajouter une page A propos à l’application Flask (indice : il faut ajouter une route, un template et un lien dans le menu).
Solution :
Une fois vos modifications ajoutées, faites simplement git diff. Cette fonction affiche en vert le code que vous venez d’ajouter, et en rouge celui
que vous avez retiré, si jamais.
Ajoutez les fichiers modifiés (git add) et committez toutes ces nouvelles modifications.
Maintenant que les modifications sont engagées (commitées) refaites git diff. Que se passe-t-il ?
Trouvez comment faire pour comparer avec un autre commit pris au hasard en utilisant git diff.
Utilisez git reset HEAD~1 pour annuler le dernier commit puis refaites-le en utilisant l’interface graphique de VSCode.
Exercices supplémentaires
“Déplacer le travail + Un assortiment + Sujets avancés” sur Learn Git branching
gitexercises.fracz.com
https://gitexercises.fracz.com/exercise/fix-typo
https://gitexercises.fracz.com/exercise/commit-lost
https://supports.uptime-formation.fr/all_content/ 18/210
14/11/2022 17:46 Exporter tout le contenu
Théoriquement, une branche n’est qu’un pointeur vers un commit, une sorte de raccourci vers un commit particulier, qui est mise à jour à chaque
fois que l’on crée un nouveau commit sur telle branche.
Les tags
Les tags sont comme des raccourcis vers un commit précis.
En général on ne les modifie pas après les avoir créés.
Ils servent souvent pour faire référence au commit précis qui définit la version du code.
Cycles de développement
Il existe plusieurs méthodes d’organisation dans Git par rapport à l’utilité des branches
parfois il y a une branche stable et une branche development qui représente une version plus beta de l’application
il y a souvent des branches pour chaque fonctionnalité ajoutée, appelées feature branch
git-flow, le workflow le plus ancien, un peu trop complexe
https://supports.uptime-formation.fr/all_content/ 19/210
14/11/2022 17:46 Exporter tout le contenu
Merge et rebase
Parfois il faut donc utiliser quelques commandes plus avancées de Git pour expliquer aux gens lisant l’historique Git quand on a voulu raconter que :
Réécrire l’historique
L’historique Git, c’est un peu raconter une histoire de comment on est arrivé à ce bout de code, ajouté pour telle fonctionnalité à telle version du
logiciel.
Le rebase interactif
Le rebase interactif est un outil un peu compliqué à manipuler, qui nous permet de réécrire l’historique d’une branche en choisissant quels commits
on va fusionner ensemble, effacer, ou réordonner. C’est la commande git rebase -i
L’article suivant, extrêmement riche, est une référence à laquelle on peut revenir en cas de doute sur le choix de merge ou de rebase :
Bien utiliser Git
merge et rebase, par Delicious Insights
Git 3 - Exercices
Les branches
Maîtriser les commandes Git
Learn Git branching
Merge
Les fusions de branche peuvent s’effectuer en local sur la machine ou sur la forge logicielle.
https://supports.uptime-formation.fr/all_content/ 20/210
14/11/2022 17:46 Exporter tout le contenu
Prendre le TP microblog et localiser la branche qui ajoute une page “A propos”. Faire un merge de cette branche avec master en local.
Rebase
Prendre le TP microblog et localiser la branche qui ajoute une page “A propos”.
Faire un rebase de cette branche sur master ou sur la branche de votre choix.
Faire un rebase de cette branche (ou d’une autre) sur master en mode interactif.
Exercices supplémentaires
1. https://gitexercises.fracz.com/exercise/chase-branch
2. https://gitexercises.fracz.com/exercise/change-branch-history
3. https://gitexercises.fracz.com/exercise/merge-conflict
4. https://gitexercises.fracz.com/exercise/save-your-work
5. https://gitexercises.fracz.com/exercise/fix-old-typo
6. https://gitexercises.fracz.com/exercise/commit-parts
7. https://gitexercises.fracz.com/exercise/pick-your-features
Interactive rebase
1. https://gitexercises.fracz.com/exercise/split-commit
2. https://gitexercises.fracz.com/exercise/too-many-commits
3. https://gitexercises.fracz.com/exercise/rebase-complex
4. https://gitexercises.fracz.com/exercise/invalid-order
Bisect (avancé)
https://gitexercises.fracz.com/exercise/find-bug
La forge logicielle
Github.com
… est une forge logicielle en forme de réseau social.
Gitlab
… est une forge logicielle concurrente, et qui est open source : on peut en installer sa propre instance (ex: framagit.org). La plus grosse
instance Gitlab est gitlab.com.
git fetch : récupérer la dernière version du dépôt distant (sans rien changer à son dépôt local)
git pull : récupérer la dernière version de la branche actuelle depuis le dépôt distant (bouge le HEAD)
git push : envoyer la dernière version locale de la branche actuelle jusqu’au dépôt distant (bouge le HEAD distant, en d’autres termes modifie
origin/HEAD)
CI/CD
L’intégration continue : s’assurer automatiquement de la qualité du code, à chaque commit poussé sur une forge.
Le déploiement continu : déployer
automatiquement une nouvelle version du code quand un commit est poussé sur une forge (sur la branche master ou deploy en général).
Git 4 - Exercices
Développer de façon collaborative avec la forge logicielle Gitlab
Créer un compte sur Gitlab et pousser un projet
https://supports.uptime-formation.fr/all_content/ 21/210
14/11/2022 17:46 Exporter tout le contenu
Sauf si nous décidons ensemble d’un autre projet, nous allons contribuer sur ce projet : https://gitlab.com/ketsapiwiq/exercice-gitlab-workflow
Rendez-vous dans le dossier du projet en terminal et suivez les instructions gitlab pour pousser votre dépôt existant
Workflow
Pour chaque ajout le code sera :
Ajouté dans une nouvelle branche.
Poussé sur un projet Gitlab partagé.
Le code sera revu par la personne qui n’a pas codé grâce à une merge request puis sera fusionnée (merged) dans la branche master.
La personne qui n’a pas codé récupère la dernière version du code grâce à git pull
Merge
Les fusions de branche peuvent s’effectuer en local sur la machine ou sur la forge logicielle.
Ressources
https://github.com/KTH-dESA/centralized-workflow-exercise
Des tests systématiques et automatisés pour ne pas se reposer sur la vérification humaine.
Un déploiement progressif en parallèle (Blue/Green) pour pouvoir automatiser le Rollback et être serein.
La CI/CD fait partie de l’approche DevOps dont fait aussi partie les concepts de cloud (Infrastructure-as-a-Service, IaaS), d’Infrastructure-as-
Code et les conteneurs.
Du coup on peut agrandir sans effort l’infrastructure de production pour délivrer une nouvelle version
Cloud et API
Dans le cloud, à la demande signifie que les vendeurs de cloud fournissent une API (REST généralement) Pour contrôler leur infrastructure.
https://supports.uptime-formation.fr/all_content/ 22/210
14/11/2022 17:46 Exporter tout le contenu
Une API est un ensemble de fonctions qu’on peut appeler en codant.
Une API REST (assez simple et très populaire depuis) est une API qui permet de discuter sur le web avec des informations décrite dans le format
JSON.
Infrastructure As Code
Avantages :
On peut multiplier les machines (une machine ou 100 machines identiques c’est pareil).
Git ! gérer les version de l’infrastructure et collaborer facilement comme avec du code.
Les conteneurs
La nature facile à déployer des conteneurs et l’intégration du principe d’Infrastructure-as-Code les rend indispensable dans de la CI/CD
(intégration continue et déploiement continu).
Les principaux outils de CI sont Gitlab, Jenkins, Github Actions, Travis CI…
Gitlab propose par défaut des runners préconfigurés qui utilisent des conteneurs Docker et tournent en général dans un cluster Kubernetes.
Gitlab propose aussi un registry d’images Docker, privé ou public, par projet.
Ressources
Essentiel :
La syntaxe Gitlab CI
https://supports.uptime-formation.fr/all_content/ 23/210
14/11/2022 17:46 Exporter tout le contenu
Documentation de référence de .gitlab-ci.yml : https://docs.gitlab.com/ee/ci/yaml/
Exemples
Exemple de pipeline :
build-job:
stage: build
script:
test-job1:
stage: test
script:
test-job2:
stage: test
script:
- echo "This job tests something, but takes more time than test-job1."
- echo "After the echo commands complete, it runs the sleep command for 20 seconds"
- echo "which simulates a test that runs 20 seconds longer than test-job1"
- sleep 20
deploy-prod:
stage: deploy
script:
stages:
- build
- test
build-code-job:
stage: build
script:
- echo "Check the ruby version, then build some Ruby project files:"
- ruby -v
- rake
test-code-job1:
stage: test
script:
- echo "If the files are built successfully, test some files with one command:"
- rake test1
test-code-job2:
stage: test
script:
- echo "If the files are built successfully, test other files with a different command:"
- rake test2
variables:
# This will suppress any download for dependencies and plugins or upload messages which would clutter the console log.
# `showDateTime` will show the passed time in milliseconds. You need to specify `--batch-mode` to make this work.
# `installAtEnd` and `deployAtEnd` are only effective with recent version of the corresponding plugins.
image: maven:3.3.9-jdk-8
cache:
paths:
- .m2/repository
# See https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html
.verify: &verify
stage: test
script:
variables:
- $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
<<: *verify
https://supports.uptime-formation.fr/all_content/ 24/210
14/11/2022 17:46 Exporter tout le contenu
# To deploy packages from CI, create a ci_settings.xml file
deploy:jdk8:
stage: deploy
script:
- if [ ! -f ci_settings.xml ];
then echo "CI settings missing\! If deploying to GitLab Maven Repository, please see https://docs.gitlab.com/ee/user/packages/maven_repo
fi
only:
variables:
- $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
Git 5 - Exercices
A l’aide des ressources suivantes, créez une pipeline (un fichier .gitlab-ci.yml) qui :
Ressources
Documentation
Get started with GitLab CI/CD : https://docs.gitlab.com/ee/ci/quick_start/
Documentation de référence de .gitlab-ci.yml : https://docs.gitlab.com/ee/ci/yaml/
Vidéos
Tutoriels
TP Docker : Gitlab CI
Code Refinery :
https://coderefinery.github.io/testing/continuous-integration/
https://coderefinery.github.io/testing/full-cycle-ci/
https://tsi-ccdoc.readthedocs.io/en/master/ResOps/2019/Gitlab.html
Commande Effet
git init <directory> Créer un dépôt git vide dans le répertoire spécifié ou initialise le répertoire courant en tant que dépôt git.
git config --global user.name <name> Définir le nom de l’auteur/autrice à utiliser pour les nouveaux commits.
git status Afficher l’état du dépôt et la liste des fichiers inclus ou non pour le prochain commit.
git add <dossier> Inclure (stage) tous les changement dans <dossier> pour le commit
git add <fichier> Inclure les changement du <fichier> pour le commit
git add -A Inclure tous les changements pour le commit
git rm <fichier> Enlever (unstage) <fichier> du prochain commit.
git diff Afficher les lignes modifiées depuis le dernier commit.
Valider les modifications sélectionnées (staged) pour créer un nouveau commit avec le message
git commit -m "<message>"
<message>.
git log ou git log --oneline --all --
Afficher l’historique des commits
graph
git remote add <name> <url> Ajouter une connexion de votre dépôt courant à un dépôt sur un serveur.
git push Pousser les nouveau commits sur le serveur (principal).
Commande Effet
git clone <url> Cloner en local un dépot depuis l’adresse <url> généralement un serveur ou un forge.
git pull Récupérer les dernières modification (#réflexe).
git log --oneline Afficher l’historique avec une ligne par commit.
tig Un outil plus sympa que git log pour explorer l’historique.
git diff HEAD <num_commit> Affiche la différence entre le commit actuel (HEAD) et le commit <num_commit>.
git diff HEAD HEAD~1 Affiche la différence entre le commit actuel (HEAD) et le précédent (HEAD~1).
https://supports.uptime-formation.fr/all_content/ 25/210
14/11/2022 17:46 Exporter tout le contenu
Commande Effet
Charge la version du code au niveau du commit <num_commit>. La “tête” se déplace au niveau de ce commit
git checkout <num_commit>
(HEAD détachée).
git checkout master ou
Positionne HEAD au niveau du dernier commit de la branche.
<nom_branch>
git reflog Affiche une liste des dernières positions de HEAD. (quand on est perdu !!! )
Commande Effet
git branch Affiche la liste des branches
git checkout <nom_branche> Basculer sur la branche <nom_branche>
git checkout -b <nom_branche> Créer une nouvelle branche et basculer dessus
git diff <branche_1> <branche_2> Comparer deux branches pour voir les différences
git merge <nom_branche> Fusionner la branche <nom_branche> avec la branche courante.
Commande Effet
git commit --amend Ajouter des modifications au commit précédent pour le corriger ou simplement changer le message du commit précédent.
git reset <commit> Réinitialiser le HEAD au commit indiqué en gardant les modifications.
git reset --hard
Réinitialiser le HEAD au commit indiqué en perdant les modifications.
<commit>
(plus complexe) Reconstruire l’historique de la branche courant à partir d’une autre branche en résolvant les conflits à
git rebase <branche>
chaque commit
Lexique git
Concept Explication
Un commit Une version validée du code avec un auteur / une autrice, un message et un identifiant unique.
Une branche Une suite de commits avec un nom contenant une version du logiciel.
HEAD Le commit actuellement sélectionné dans le dépôt.
remote Un dépôt git sur un serveur par exemple la forge framagit.
origin Le nom du remote par défaut.
master La branche par défaut, généralement la branche principale.
Bibliographie
Liens
Ressources
Documentation
Théorie
Points précis
Bien utiliser Git merge et rebase (en français)
4 branching workflows for Git
Git Submodules vs Git Subtrees
Extensions VSCode
GitLens
Git Graph
Exercices
https://learngitbranching.js.org/?locale=fr_FR
https://gitexercises.fracz.com/
https://www.w3schools.com/git/exercise.asp
https://supports.uptime-formation.fr/all_content/ 26/210
14/11/2022 17:46 Exporter tout le contenu
QCM
Initiation à Git : questionnaire
Entourez la ou LES bonnes réponses.
Question 1
Question 2
A. git add
B. git checkout
C. git status
D. git branch
Question 3
Qu’est-ce que HEAD ?
Question 4
Question 5
Git permet de :
Question 6
A. Dans Gitlens.
B. Dans un dossier invisible .git pour chaque dépôt.
C. Dans le dossier /etc.
Question 7
Question 8
A. master
B. feature
C. main
Question 9
Question 10
https://supports.uptime-formation.fr/all_content/ 27/210
14/11/2022 17:46 Exporter tout le contenu
Comment savoir quelles modifications ont été apportées lors du dernier commit ?
Question 11
Une merge request sert à :
A. Créer une discussion avec des collègues sur le code d’une fonction.
B. Faciliter la vérification collaborative du code.
C. Corriger automatiquement le code de votre nouvelle fonction.
D. Tester automatiquement le code ajouté.
Python
Bienvenue !
Vous trouverez sur ce site les supports de formations pour un module Python réalisé pour une POE à Rouen en juillet 2021.
Vous pouvez imprimer son contenu (page par page) en PDF à l’aide de la fonction d’impression de chromium qui donne de bons résultats.
Sans plus attendre vous pouvez démarrer par la page de mise en place de outils pour la formation
Introduction
Plan
Partie 1 : Notions de “base”
Variables, fonctions
Structures de contrôle (conditions, boucles)
Structures de données (listes, dictionnaires, …)
Debugging avec pdb/ipdb
Fichiers
Exceptions
Librairies
Bonne pratiques
Classes et méthodes
Héritage et Polymorphisme
Encapsulation
Stockage de données?
Flask
Bonus
Regexp
Méthode
Alternance entre :
Des explications théoriques sur une notion donnée et présentation de syntaxes Python
Exercices pratiques que nous ferons ensemble pas à pas.
Cuisiner de l’information
Langage de programmation
Comme un vrai langage !
Le langage Python
Nous récapitulerons en conclusion les caractéristiques du langage, ses avantages et ses inconvénients.
Python history
« … In December 1989, I was looking for a “hobby” programming project that would keep me occupied during the week around
Christmas. My office … would be closed, but I had a home computer, and not much else on my hands.
I decided to write an interpreter for
the new scripting language I had been thinking about lately: a descendant of ABC that would appeal to Unix/C hackers.
I chose Python as a
working title for the project, being in a slightly irreverent mood (and a big fan of Monty Python’s Flying Circus). »
— Guido van Rossum
https://supports.uptime-formation.fr/all_content/ 29/210
14/11/2022 17:46 Exporter tout le contenu
https://supports.uptime-formation.fr/all_content/ 30/210
14/11/2022 17:46 Exporter tout le contenu
Remarque Meme
Explorez !
Développement Logiciel
Jusqu’ici nous avons parlé du langage python et de la façon dont il permet d’exprimer un programme. Il s’agit donc de programmation.
Mais l’activité de coder va au delà de l’expression d’une logique dans un langage. Il s’agit d’organiser la production d’un ensemble d’élément de
programme, un logiciel et pour cela on parle plutôt de développement logiciel.
Algorithme vs Langage
Pour trouver un algorithme il vaut mieux dessiner et écrire sur un papier ce que l’on cherche à faire !
Architecture
Attitude du développeur
Il est important avoir des connaissances fondamentales (ce que je vous raconte ici notamment) et des connaissances techniques.
Il faut également avoir la recherche web facile pour pouvoir faire le tri dans la junglede tétails techniques.
github
stackoverflow.
Communauté
Bonne nouvelle le Python est un écosystème informatique plutôt sain: culture libriste et passion de l’informatique dans la communauté python.
N’hésitez pas à aller rencontrer d’autre développeurs.
Le sujet est très vaste, le métier de développeur est long à intégrer. Il faut “passer plusieurs couches de peintures”.
Nous allons parcourir pas mal de
distance (en profondeur) et vous pourrez (devriez ?) creuser en largeur par la suite grâce aux références indiquées.
https://supports.uptime-formation.fr/all_content/ 31/210
14/11/2022 17:46 Exporter tout le contenu
Demander et afficher des informations
Syntaxe Description
print("message") Affiche “message” dans la console
v = input("message") Demande une valeur et la stocke dans v
Calculs
Syntaxe Description
a + b Addition de a b
a - b Soustraction de a et b
a / b Division de a par b
a * b Multiplication de a par b
a % b Modulo (reste de division) de a par b
a ** b Exponentiation de a par b
Toutes ces opérations peuvent être appliquées directement sur une variable via
la syntaxe du type a += b (additionner b à a et directement modifier la
valeur
de a avec le résultat).
Syntaxe Description
type(v) Renvoie le type de v
int(v) Converti v en entier
float(v) Converti v en float
str(v) Converti v en string
Chaînes de caractères
Syntaxe Description
chaine1 + chaine2 Concatène les chaînes de caractères chaine1 et chaine2
chaine[n:m] Retourne les caractères de chaine depuis la position n à m
chaine * n Retourne chaine concaténée n fois avec elle-meme
len(chaine) Retourne la longueur de chaine
chaine.replace(a, b) Renvoie chaine avec les occurences de a remplacées par b
chaine.split(c) Créé une liste à partir de chaine en la séparant par rapport au caractère c
chaine.strip() “Nettoie” chaine en supprimant les espaces et \n au début et à la fin
\n Représentation du caractère ‘nouvelle ligne’
Fonctions
def ma_fonction(toto, tutu=3):
return une_valeur
Cette fonction :
Conditions
if condition:
instruction1
instruction2
elif autre_condition:
instruction3
elif encore_une_autre_condition:
instruction4
else:
instruction5
instruction6
Opérateurs de conditions
Syntaxe Description
a == b Egalité entre a et b
a != b Différence entre a et b
a > b a supérieur (strictement) à b
a >= b a supérieur ou égal à b
a < b a inférieur (strictement) à b
a <= b a inférieur ou égal à b
cond1 and cond2 cond1 et cond2
cond1 or cond2 cond1 ou cond2
not cond négation de la condition cond
a in b a est dans b (chaîne, liste, set..)
https://supports.uptime-formation.fr/all_content/ 32/210
14/11/2022 17:46 Exporter tout le contenu
Inline ifs
Exception, assertions
try:
instruction1
instruction2
except FirstExceptionTime:
instruction3
except Exception as e:
def une_fonction(n):
assert isinstance(n, int) and is_prime(n), "Cette fonction fonctionne seulement pour des entiers premiers !"
Boucles
Syntaxe Description
for i in range(0, 10) Itère sur i de 0 à 9
for element in iterable Itère sur tous les elements de iterable (liste, set, dict, …)
for key, value in d.items() Itère sur toutes les clefs, valeurs du dictionnaire d
while condition Répète un jeu d’instruction tant que condition est vraie
break Quitte immédiatement une boucle
continue Passe immédiatement à l’itération suivante d’une boucle
Structures de données
Syntaxe Description
L = ["a", 2, 3.14 ] Liste (suite ordonnée d’éléments)
S = { "a", "b", 3 } Ensemble (éléments unique, désordonné)
D = { "a": 2, "b": 4 } Dictionnaire (ensemble de clé-valeurs, avec clés uniques)
T = (1,2,3) Tuple (suite d’élément non-mutables)
Syntaxe Description
L[i] i-eme element d’une liste ou d’une tuple
L[i:] Liste de tous les éléments à partir du i-eme
L[i] = e Remplace le i-eme element par e dans une liste
L.append(e) Ajoute e à la fin de la liste L
S.add(e) Ajoute e dans le set S
L.insert(i, e) Insère e à la position i dans la liste L
chaine.join(L) Produit une string à partir de L en intercallant la string chaine entre les elements
Fichiers
content = f.readlines()
f.write(content)
(Le mode 'a' (append) au lieu de 'w' permet d’ouvrir le fichier pour ajouter
du contenu à la fin plutôt que de le ré-écrire)
Installation
https://supports.uptime-formation.fr/all_content/ 33/210
14/11/2022 17:46 Exporter tout le contenu
Sur linux installer le paquet python3 (généralement déjà installé parce que linux utilise beaucoup python)
Sur Windows installer depuis python.org ou depuis un outil comme chocolatey
Sur MacOs déjà installé mais pour gérer un version plus à jours on peut le faire manuellement depuis python.org ou avec homebrew.
Python 2 vs Python 3
Python 2 existe depuis 2000
Python 3 existe depuis 2008
Fin de vie de Python 2 en 2020
… mais encore la version par défaut dans de nombreux système … (c.f. python --version)
Généralement il faut lancer python3 explicitement ! (et non python) pour utiliser python3
Différences principales
print "toto" ne fonctionnera pas en Python 3 (utiliser print("toto")
Nommage des paquets debian (python-* vs python3-*)
Gestion de l’encodage
range, xrange
Disponibilité des librairies ?
ou implicitement (shebang)
#!/usr/bin/env python3
print("Hello, world!")
$ chmod +x hello.py
$ ./hello.py
Démarrer VS Code
print("Hello World!")
Créez un fichier hello.py directement dans la console (par exemple via nano hello.py) et mettez dedans :
#!/usr/bin/env python3
print("Hello, world!")
En interactif
$ python3
$ ipython3
Principaux avantages:
Inconvénients:
Moins standard
à installer en plus de l’interpréteur python.
Il est simple ou départ et fortement extensible: à l’installation seules les fonctionnalités de base sont disponibles
Éditeur de code avec coloration et raccourcis pratiques
Navigateur de fichier (pour manipuler une grande quantité de fichers et sous dossier sans sortir de l’éditeur)
Recherche et remplacement flexible avec des expressions régulières (très important pour trouver ce qu’on cherche et faire de refactoring)
https://supports.uptime-formation.fr/all_content/ 34/210
14/11/2022 17:46 Exporter tout le contenu
Terminal intégrée (On a plein d’outils de développement à utiliser dans le terminal)
Une interface git assez simple très bien faite (git on s’y perd facilement, une bonne interface aide à s’y retrouver)
Indépendamment du logiciel choisi on trouve en général toutes ces fonctionnalités dans un éditeur de code.
Observons un peu tout ça avec une démo de VSCode et récapitulons l’importance des ces fonctions.
Installez l’extension Python (et affichez la documentation si vous êtes curieux) en allant dans la section Extensions (Icone de gauche avec 4 carrés dont
un détaché)
Nous allons également utiliser git sérieusement donc nous allons installer une super extension git appelée Gitgraph pour pouvoir mieux explorer
l’historique d’un dépôt git.
Enfin vous pouvez installer d’autres extensions pour personnaliser l’éditeur comme l’extension VIM si vous aimez habituellement utiliser cet éditeur.
VSCode est développé par Microsoft et partiellement opensource (Le principal code est accessible mais pas tout)
VSCodium est la version opensource communautaire de VSCode mais certaines fonctions puissantes et pratiques sont seulement dans VSCode
(les environement distant Docker et SSH par exemple)
Un fork récent et complètement opensource de VSCode qui peut fonctionner directement dans le navigateur (Cf. gitpod.io). Moins mature.
Ces trois logiciels sont très proches et vous pouvez coder vos extensions (compatibles avec les 3) pour étendre ces éditeur.
Il me semble important pour choisir un outil de se demander si on possède l’outil ou si l’outil nous possède (plus ou moins les deux en général). Pour
pouvoir gérér la complexité du développement moderne on dépend de pas mal d’outils. Savoir choisir des outils ouverts et savoir utiliser également les
outils en ligne commande (git, pylint, etc cf. suite du cours) est très important pour ne pas s’enfermer dans un environnement limitant et possessif.
1. Les variables
1.1. Exemple
message = "Je connais la réponse à l'univers, la vie et le reste"
reponse = 6 * 7
print(message)
print(reponse)
1.2. Principe
https://supports.uptime-formation.fr/all_content/ 35/210
14/11/2022 17:46 Exporter tout le contenu
Nommage
6*7
print(6*7)
Faire un calcul et stocker le résultat dans une variable r pour le réutiliser plus tard
r = 6*7
Opérations mathématiques
2 + 3 # Addition
2 - 3 # Soustraction
2 * 3 # Multiplication
2 / 3 # Division
2 % 3 # Modulo
2 ** 3 # Exponentiation
x -= 3 # Équivalent à x = x - 3
x *= 3 # Équivalent à x = x * 3
x /= 3 # Équivalent à x = x / 3
x %= 3 # Équivalent à x = x % 3
x **= 3 # Équivalent à x = x ** 3
Types
42 # Entier / integer / int
https://supports.uptime-formation.fr/all_content/ 36/210
14/11/2022 17:46 Exporter tout le contenu
Conversion de type
int("3") -> 3
int(3.14) -> 3
int(True) -> 1
Interactivité basique
Dans un terminal il est possible de demander une information à l’utilisateur avec `input(“message”)
reponse = input("Combien font 6 fois 7 ?")
Ex.1.2 Interactivité
2. Chaînes de caractères
Entre simple quote (') ou double quotes ("). Par exemple: "hello"
print("hello") affiche le texte Hello
print(hello) affiche le contenu d’une variable qui s’apellerait Hello
Longueur
m = "Hello world"
len(m) # -> 11
Extraction
Multiplication
Concatenation
name = "Marius"
age = 28
"Je m'appelle " + name + " et j'ai " + str(age) + " ans"
Substitution
https://supports.uptime-formation.fr/all_content/ 37/210
14/11/2022 17:46 Exporter tout le contenu
\n est une syntaxe spéciale faisant référence au caractère “nouvelle ligne”
##########
# Python #
##########
3. Les fonctions
Principe
Donner un nom à un ensemble d’instructions pour créer de la modularité et de la sémantique
instruction1
instruction2
...
return resultat
On peut ensuite utiliser la fonction avec les arguments souhaitées et récupérer le resultat :
Calculs mathématiques
int("3.14") -> 3
print("un message")
Exemples concrets
def aire_triangle(base, hauteur):
def aire_disque(rayon):
rayon_carree = rayon ** 2
https://supports.uptime-formation.fr/all_content/ 38/210
14/11/2022 17:46 Exporter tout le contenu
def aire_disque(rayon):
rayon_carree = rayon ** 2
Éléments de syntaxe
def aire_disque(rayon):
rayon_carree = rayon ** 2
def, :
des instructions indentées !!
des arguments (ou pas!)
return (ou pas)
Les arguments
def aire_disque(rayon):
# [ ... ]
Une fonction est un traitement générique. On ne connait pas à l’avance la valeur précise qu’aura un argument, et généralement on appelle la
fonction pleins de fois avec des arguments différents…
En définissant la fonction, on travaille donc avec un argument “abstrait” nommé rayon
Le nom rayon en tant qu’argument de la fonction n’a de sens qu’a l’intérieur de cette fonction !
En utilisant la fonction, on fourni la valeur pour rayon, par exemple: aire_disque(6).
def aire_disque(rayon):
rayon_carree = rayon ** 2
# [ ... ]
Les variables créées dans la fonction sont locales: elles n’ont de sens qu’a l’intérieur de la fonction
Ceci dit, cela ne m’empêche pas d’avoir des variables aussi nommées rayon ou rayon_carree dans une autre fonction ou dans la portée globale
(mais ce ne sont pas les mêmes entités)
Le return
def aire_disque(rayon):
rayon_carree = rayon ** 2
Erreur classique:
def aire_disque(rayon):
rayon_carree = rayon ** 2
def aire_disque(rayon):
rayon_carree = rayon ** 2
“Bonne” solution
https://supports.uptime-formation.fr/all_content/ 39/210
14/11/2022 17:46 Exporter tout le contenu
def aire_disque(rayon):
rayon_carree = rayon ** 2
Ceci dit, il peut être tout à fait légitime de mettre des print dans une fonction, par exemple pour la débugger…!
A1 = aire_triangle(3, 5)
A2 = aire_triangle(4, hauteur=8)
A3 = aire_triangle(hauteur=6, base=2)
aire_triangle(base=3, hauteur=5)
que juste
aire_triangle(3, 5)
base = 3
hauteur = 5
A1 = aire_triangle(base=base, hauteur=hauteur)
Arguments optionnels
Les arguments peuvent être rendu optionnels si ils ont une valeur par défaut :
def distance(dx, dy=0, dz=0):
[...]
distance(5)
distance(2, 4)
distance(5, 8, 2)
distance(9, dy=5)
distance(0, dz=4)
Exemple réaliste
subprocess.Popen(args,
bufsize=0,
executable=None,
stdin=None,
stdout=None,
stderr=None,
preexec_fn=None,
close_fds=False,
shell=False,
cwd=None,
env=None,
universal_newlines=False,
startupinfo=None,
creationflags=0)
c.f. https://docs.python.org/2/library/subprocess.html#subprocess.Popen
Ex.3 Fonctions
3.1
Écrire une fonction annee_naissance qui prends en argument un age et retourne l’année de naissance (+/- 1) sachant que nous sommes en 2019. Par
exemple, annee_naissance(29) retounera l’entier 1990.
3.2
Ecrire une fonction centrer prend en argument une chaîne de caractère, et retourne une nouvelle chaîne centrée sur 40 caractères. Par exemple
print(centrer("Python")) affichera :
| Python |
Ajouter un argument optionnel pour gérer la largeur au lieu du 40 “codé en dur”. Par exemple print(centrer("Python", 20)) affichera :
| Python |
https://supports.uptime-formation.fr/all_content/ 40/210
14/11/2022 17:46 Exporter tout le contenu
####################
| Python |
####################
La principale instruction conditionnelle est, en python comme dans les autres langages impératifs, le if (Si condition alors …) assorti généralement du
else (Sinon faire …) et en python de la contraction elif de else if (Sinon, Si condition alors …)e
Syntaxe générale
if condition:
instruction1
instruction2
instruction3
instruction4
else:
instruction5
instruction6
Attention à l’indentation !
if condition:
instruction1
instruction2
Exemple
a = 0
if a > 0 :
elif a < 0 :
else:
Les conditions comme a > 0 sont en fait transformées en booléen lorsque la ligne est interprétée.
On aurait pu écrire :
a_est_positif = (a > 0)
if a_est_positif:
[...]
else:
[...]
angle != pi # Différence
print("x > 0 and x == 2:", x > 0 and x == 2) # vrai et vrai donne vrai
print("x > 0 and x == 1:", x > 0 and x == 2) # vrai et faux donne faux
print("x > 0 or not x == 1:", x > 0 or not x == 1) # vrai ou (non faux) donne vrai ou vrai donne vrai
Conditions “avancées”
Remarque: l’opérateur in est très utile et générale en Python: il sert à vérifier qu’un élément existe dans une collection. Par exemple si l’entier 2 est
présent dans une liste d’entier ou comme ici si un mot est présent dans une chaine de caractère.
‘Inline’ ifs
https://supports.uptime-formation.fr/all_content/ 41/210
14/11/2022 17:46 Exporter tout le contenu
parite = "pair" if n % 2 == 0 else "impair"
En python pour tester si une variable contient une valeur vide ou pas de valeur (c-à-d valeur None) on aime bien, par convention “pythonique”, écrire
simplement if variable: :
reste_division = a % 2
if reste_division:
print("a est pair parce que le reste de sa division par 2 est nul")
else:
if texte:
else:
print("pas de texte")
Remarque: dans notre dernier cas il n’est pas forcément important de savoir si texte est None ou une chaîne vide mais plutôt de savoir si on a
effectivement une valeur “significative” à afficher. C’est souvent le cas et c’est pour cela qu’on privilégie if variable pour simplifier la lecture du
code.
L’usage de if variable: comme précédemment est basé sur la truthiness ou vraisemblance de la variable. On dit que a est vraisemblable si la
conversion de a en booléen donne True : bool(3) donne True on dit que 3 est truthy, bool(None) donne False donc None est falsy.
TODO Nous verrons
dans la partie sur le Python Data Model que cela implique des choses pour nos classes de programmation orientée objet en python (en Résumé on veut
que if monObjet: soit capable de tester si l’objet est initialisé et utilisable)
Autrement dit en python on aime utiliser la vraisemblance implicite des
variables pour tester si leur valeur est significative/initialisée ou non.
Ex.4 Conditions
4.1 Reprendre la fonction annee_naissance et afficher un message d’erreur et sortir immédiatement de la fonction si l’argument fourni n’est pas un
nombre entre 0 et 130. Valider le comportement en appelant votre fonction avec comme argument -12, 158, None ou "toto".
4.2 Reprendre la fonction centrer de l’exercice 3.1 et gérer le cas où la largueur demandée est -1 : dans ce cas, ne pas centrer. Par exemple,
print(encadrer("Python", -1)) affichera :
##########
# Python #
##########
5. Les boucles
Répéter des opération est le coeur de la puissance de calcul des ordinateur. On peut pour cela utiliser des boucles ou des appels récusifs de fonctions.
Les deux boucles python sont while et for.
La boucle while
while <condition>: veut dire “tant que la condition est vraie répéter …”. C’est une boucle simple qui teste à chaque tour (avec une sorte de if) si on
doit continuer de boucler.
Exemple:
a = 0
while (a < 10) # On répète les deux instructions de la boucle tant que a est inférieur à 7
print(a)
On peut traduire la boucle Python for element in collection: en français par “Pour chaque élément de ma collection répéter …”. Nous avons donc
besoin d’une “collection” (en fait un iterateur) pour l’utiliser. Classiquement on peut utiliser une liste python pour cela:
print(entier)
Pour générer rapidement une liste d’entiers et ainsi faire un nombre défini de tours de boucle on utilise classiquement la fonction range()
print(range(10))
https://supports.uptime-formation.fr/all_content/ 42/210
14/11/2022 17:46 Exporter tout le contenu
for entier in range(2, 11, 2):
print(entier) # Afficher les 5 nombres pairs de 2 à 10 (le dernier paramètre indique d'avancer de 2 en 2)
continue et break
continue permet de passer immédiatement à l’itération suivante
for i in range(0,10):
if i % 2 == 0:
continue
if i == 7:
break
Ex.5 Boucles
5.1.1 : Écrire une fonction qui, pour un nombre donné, renvoie la table de multiplication. Dans un premier temps, on pourra se contenter d’afficher les
résultats. Par exemple print(table_du_7()) affichera:
7
14
21
...
70
Table du 7
----------
1 x 7 = 7
2 x 7 = 14
[..]
10 x 7 = 70
5.1.2 : Cette fois, passer le nombre en argument. La fonction devient par exemple table_multiplication(7)
5.1.3 : En appelant cette fonction plusieurs fois, afficher les tables de multiplication pour tous les nombres entre 1 et 10.
5.1.4 : Protéger l’accès à toute cette connaissance précieuse en demandant, au début du programme, un “mot de passe” jusqu’à ce que le bon mot de
passe soit donné.
5.2 : (Optionnel) Écrire une fonction qui permet de déterminer si un nombre est premier. Par exemple is_prime(3) renverra True, et is_prime(10)
renverra False.
(Optionnel)
5.3.1 : Jeu des allumettes
Le jeu des allumettes est un jeu pour deux joueurs, où n allumettes sont disposées, et chaque joueur peut prendre à tour de rôle 1, 2 ou 3 allumettes. Le
perdant est celui qui se retrouve obligé de prendre la dernière allumette.
Écrire une fonction afficher_allumettes capable d’afficher un nombre donné d’allumettes (donné en argument), par exemple avec le caractère |
Écrire une fonction choisir_nombre qui demande à l’utilisateur combien d’allumette il veut prendre. Cette fonction vérifiera que le choix est
valide (en entier qui est soit 1, 2 ou 3).
Commencer la construction d’une fonction partie_allumettes qui pour le moment, se contente de :
Initialiser le nombre d’allumette sur la table
Afficher des allumettes avec afficher_allumettes
Demander à l’utilisateur combien il veut prendre d’allumettes avec choisir_nombre
Propager ce choix sur le nombre d’allumette actuellement sur la table
Afficher le nouvel état avec afficher_allumettes
5.3.2 : (Optionnel) Modifier partie_allumettes pour gérer deux joueurs (1 et 2) et les faire jouer à tour de rôle jusqu’à ce qu’une condition de victoire
soit détectée (il reste moins d’une allumette…).
Reprendre le jeu précédent et le modifier pour introduire une “intelligence” artificielle qui soit capable de jouer en tant que 2ème joueur. (Par exemple,
une stratégie très simple consiste à prendre une allumette quoiqu’il arrive)
5.3.4 : (Optionnel)
Installer pylint3 avec:
pip3 install pylint3
https://supports.uptime-formation.fr/all_content/ 43/210
14/11/2022 17:46 Exporter tout le contenu
… Mieux vaut un programme cassé mais lisible (donc débuggable)
… qu’un programme qui marche mais incompréhensible (donc fragile et/ou qu’on ne saura pas faire évoluer)
… et donc qui va surtout faire perdre du temps aux futurs développeurs
Autrement dit : la lisibilité pour vous et vos collègues a énormément d’importance pour la maintenabilité et l’évolution d’un projet
Il faut tester et débugger au fur et à mesure, pas tout d’un seul coup !
Permet (entre autre) de définir des “break points” pour rentrer en interactif
Une fois en interactif, on peut inspecter les variables, tester des choses, …
l(ist) : affiche les lignes de code autour de code (ou continue le listing precedent)
s(tep into) : investiguer plus en détail la ligne en cours, possiblement en descendant dans les appels de fonction
w(here) : print the stack trace, c.a.d. les différents sous-appels de fonction dans lesquels on se trouve
tbreak : fixe un point d’arrêt temporaire qui sera retiré au premier passage.
pp <variable> : pretty-print d’une variable (par ex. une liste, un dict, ..)
Debug VSCode
Dans VSCode on peut fixer des breakpoints (points rouges) directement dans le code en cliquant sur la colonne de gauche de l’éditeur.
Il faut ensuite aller dans l’onglet debug et sélectionner une configuration de debug ou en créer une plus précise
(https://code.visualstudio.com/docs/python/python-tutorial)
Ensuite on lance le programme en mode debug et au moment de l’arrêt il est possible d’explorer les valeurs de toutes les variables du programme
(Démo)
Architecture : découper son programme en fonction qui chacune résolvent un sous-problème précis
Robustesse : garder ses fonctions autant que possibles indépendantes, limiter les effets de bords
si je répète plusieurs fois les mémes opérations, il peut être intéressant d’introduire une nouvelle fonction
si le contenu d’une variable ou d’une fonction change, peut-être qu’il faut modifier son nom
si je fais pleins de petites opérations bizarre, peut-être qu’il faut créer une fonction
Dropbox
https://supports.uptime-formation.fr/all_content/ 44/210
14/11/2022 17:46 Exporter tout le contenu
Atom
Eve online
Matplotlib
https://supports.uptime-formation.fr/all_content/ 45/210
14/11/2022 17:46 Exporter tout le contenu
Blender
OpenERP / Odoo
Tartiflette
https://supports.uptime-formation.fr/all_content/ 46/210
14/11/2022 17:46 Exporter tout le contenu
La célèbre suite de Fibonacci, liée au nombre d’or, est une suite d’entiers dans laquelle chaque terme est la somme des deux termes qui le précèdent.
Mais elle est également un exercice classique d’algorithmique.
Écrire une fonction fibonacci_rec_naive(n) qui calcule de façon récursive la suite de fibonacci.
Créez une autre fonction fibonacci_iter(n) qui calcule de façon iterative la suite de fibonacci.
A l’aide de la librairie timeit et de sa fonction timer (from timeit import default_timer as timer) qui renvoie le temps processeur courant,
mesurez le temps d’exécution des deux fonctions.
Écrire une fonction fibonacci_rec_liste(n) qui calcule récursivement la suite de fibonacci en utilisant une liste comme mémoire pour ne pas
recalculer les terme déjà calculés.
Bonus 1: Utilisons un décorateur de “caching” de fonction (from functools import lru_cache as cache) sur fibonacci_rec_naive(n) pour
l’optimiser sans changer le code.
Corrections 1
Exercice 1.1
result_1 = 567 * 72
result_2 = 33**4
result_3 = 98.2 / 6
result_4 = (7 * 9)**4 / 6
print(result_1)
print(result_2)
print(result_3)
print(result_4)
Exercice 1.2
annee = int(input('Quelle est votre année de naissance ?\n'))
print("#"*len(mot)+2+"\n"+)
Exercice 3: Fonctions
3.1
def annee_naissance(age):
print(annee_naissance(32))
3.2
def centrer(mot, largeur=80):
resultat = "|" + nb_espaces_gauche * " " + mot + nb_espaces_droite * " " + "|"
return resultat
print(centrer("Pikachu"))
print(len(centrer("Pikachu")) # 80
print(centrer("Pikachu", 40))
print(encadrer("Pikachu"))
print(encadrer("Pikachu", 37))
Exercice 4: Conditions
4.1
https://supports.uptime-formation.fr/all_content/ 47/210
14/11/2022 17:46 Exporter tout le contenu
def annee_naissance(age):
return 2021-age
4.2
resultat = "|" + nb_espaces_gauche * " " + mot + nb_espaces_droite * " " + "|"
return resultat
if largeur == -1:
largeur = len(mot) + 4
if caractere == '':
longueur_max = largeur - 4
mot = mot[:longueur_max]
print(encadrer("Pikachu", -1))
print(encadrer("Pikachu", 8, '@'))
Exercice 5
5.1.1
def table_du_7():
print("Table du 7")
print("----------")
for i in range(1,11):
print("7 x {} = {}".format(i,7*i))
5.1.2
def table_multiplication(nombre):
print("Table du {}".format(nombre))
print("----------")
for i in range(1,11):
print("{} x {} = {}".format(nombre,i,nombre*i))
5.1.3
for i in range(1,11):
table_multiplication(i)
5.1.4
mot_de_passe=input("Mot de passe?")
mot_de_passe=input("Mot de passe?")
for i in range(1,11):
table_multiplication(i)
5.2
def isprime(nombre):
for i in range(nombre-1,1,-1):
if nombre%i==0:
return False
return True
5.3
def afficher_allumettes(nombre_allumettes):
for i in range(nombre_allumettes):
print("|",end='')
print("")
def choisir_nombre():
correct=False
if choix in [1,2,3]:
correct=True
return choix
def jeu(allumettes):
joueur=1
afficher_allumettes(allumettes)
print("Joueur {}:".format(joueur))
choix=choisir_nombre()
print("Choose again")
print("Joueur {}:".format(joueur))
https://supports.uptime-formation.fr/all_content/ 48/210
14/11/2022 17:46 Exporter tout le contenu
choix=choisir_nombre()
if allumettes-choix== 1:
print("Joueur {} gagne".format(joueur))
allumettes-=choix
afficher_allumettes(allumettes)
else:
allumettes-=choix
joueur=3-joueur
def jeu_avec_ia(allumettes):
joueur=1
afficher_allumettes(allumettes)
if joueur==1:
print("Joueur {}:".format(joueur))
choix=choisir_nombre()
while allumettes-choix <= 0:
print("Choose again")
print("Joueur {}:".format(joueur))
choix=choisir_nombre()
if allumettes-choix== 1:
print("Joueur {} gagne".format(joueur))
allumettes-=choix
afficher_allumettes(allumettes)
else:
allumettes-=choix
if joueur==2:
if allumettes== 2:
print("IA gagne")
afficher_allumettes(allumettes-1)
return
else:
allumettes-=1
joueur=3-joueur
jeu(10)
jeu_avec_ia(10)
Exercice 6
from timeit import default_timer as timer
def fib_rec_naive(n):
"""
"""
if n == 0:
return 0
elif n == 1:
return 1
else:
@cache()
def fib_rec_naive_cache(n):
"""
un décorateur de memoïzation qui stocke l'état de la pile d'éxecution entre les appels
"""
if n == 0:
return 0
elif n == 1:
return 1
else:
liste_termes_calculés = [0,1]
def fib_rec_liste(n):
"""
"""
if n < len(liste_termes_calculés):
return liste_termes_calculés[n]
else:
liste_termes_calculés.append(fib_rec_liste(n-1) + fib_rec_liste(n-2))
return liste_termes_calculés[n]
def fib_iter(n):
"""
"""
ancien_terme, nouveau_terme = 0, 1
if n == 0:
return 0
for i in range(n-1):
return nouveau_terme
if __name__ == "__main__":
start = timer()
fib_rec_naive(35)
https://supports.uptime-formation.fr/all_content/ 49/210
14/11/2022 17:46 Exporter tout le contenu
stop = timer()
start = timer()
fib_rec_naive_cache(35)
stop = timer()
start = timer()
fib_rec_liste(35)
stop = timer()
start = timer()
fib_iter(35)
stop = timer()
start = timer()
fib_rec_naive(38)
stop = timer()
start = timer()
fib_rec_naive_cache(38)
stop = timer()
start = timer()
fib_rec_liste(38)
stop = timer()
start = timer()
fib_iter(38)
stop = timer()
Bonus 2
def fibonacci_generator():
a, b = 0, 1
yield a
yield b
while True:
a, b = (b, a+b)
yield b
for n in fibonacci_generator():
if n > 500:
break
print(n)
Exos 1
1. Calculs dans l’interpréteur
2. Interactivité
2. Chaînes de caractères
# Python #
##########
3. Fonctions
Ecrire une fonction centrer prend en argument une chaîne de caractère, et retourne une nouvelle chaîne centrée sur 40 caractères. Par exemple
print(centrer("Python")) affichera :
| Python |
Ajouter un argument optionnel pour gérer la largeur au lieu du 40 “codé en dur”. Par exemple print(centrer("Python", 20)) affichera :
https://supports.uptime-formation.fr/all_content/ 50/210
14/11/2022 17:46 Exporter tout le contenu
| Python |
####################
| Python |
####################
4. Conditions
Reprendre la fonction annee_naissance et afficher un message d’erreur et sortir immédiatement de la fonction si l’argument fourni n’est pas un
nombre entre 0 et 130. Valider le comportement en appelant votre fonction avec comme argument -12, 158, None ou "toto".
Reprendre la fonction centrer de l’exercice 4.1 et gérer le cas où la largueur demandée est -1 : dans ce cas, ne pas centrer. Par exemple,
print(encadrer("Python", -1)) affichera :
##########
# Python #
##########
5. Boucles
def afficher_allumettes(nombre_allumettes):
for i in range(nombre_allumettes):
print("|",end='')
print("")
def choisir_nombre():
correct=False
choix=int(input("Combien d'allumettes?"))
if choix in [1,2,3]:
correct=True
return choix
def jeu(allumettes):
afficher_allumettes(allumettes)
choix=choisir_nombre()
if allumettes-choix <= 0:
print("Choose again")
elif allumettes-choix== 1:
print("you loose")
allumettes-=choix
afficher_allumettes(allumettes)
else:
allumettes-=choix
jeu(10)
Écrire une fonction fibonacci_rec_naive(n) qui calcule de façon récursive la suite de fibonacci.
Créez une autre fonction fibonacci_iter(n) qui calcule de façon iterative la suite de fibonacci.
A l’aide de la librairie timeit et de sa fonction timer (from timeit import default_timer as timer) qui renvoie le temps processeur courant,
mesurez le temps d’exécution des deux fonctions.
Écrire une fonction fibonacci_rec_liste(n) qui calcule récursivement la suite de fibonacci en utilisant une liste comme mémoire pour ne pas
recalculer les terme déjà calculés.
Bonus 1: Utilisons un décorateur de “caching” de fonction (from functools import lru_cache as cache) sur fibonacci_rec_naive(n) pour
l’optimiser sans changer le code.
Les listes
Une collection d’éléments ordonnés référencé par un indice
https://supports.uptime-formation.fr/all_content/ 51/210
14/11/2022 17:46 Exporter tout le contenu
animaux_favoris = [ "girafe", "chenille", "lynx" ]
fibonnaci = [ 1, 1, 2, 3, 5, 8 ]
Longueur
len(animaux_favoris) -> 3
Tester qu’un élément est (ou n’est pas) dans une liste
"lynx" in animaux_favoris # -> True
Iteration
animaux_favoris[1] = "papillon"
animaux_favoris.append("coyote")
Insertion, concatenation
animaux_favoris.insert(1, "sanglier")
Exemple de manip classique : filtrer une liste pour en construire une nouvelle
animaux_favoris = [ "girafe", "chenille", "lynx" ]
animaux_starting_with_c = []
if animal.startswith("c"):
# Je l'ajoute à la liste
animaux_starting_with_B.append(animal)
["girafe"]
Les dictionnaires
Une collection non-ordonnée (apriori) de clefs a qui sont associées des valeurs
https://supports.uptime-formation.fr/all_content/ 52/210
14/11/2022 17:46 Exporter tout le contenu
phone_numbers["Charlie"] = "06 25 65 92 83"
"email": "alice@megacorp.eu" },
"email": "bob.peterson@havard.edu.uk" },
"email": "alice@megacorp.eu" },
"email": "bob.peterson@harvard.edu.uk" },
Les sets
Les sets sont des collections d’éléments unique et non-ordonnée
chat = set(["c", "h", "a", "t"]) # -> {'h', 'c', 'a', 't'}
chien = set(["c", "h", "i", "e", "n") # -> {'c', 'e', 'i', 'n', 'h'}
chat | chien # -> {'c', 't', 'e', 'a', 'i', 'n', 'h'}
Les tuples
Les tuples permettent de stocker des données de manière similaire à une liste, mais de manière non-mutable.
Generalement itérer sur un tuple n’a pas
vraiment de sens…
xyz[0] # -> 2
xyz[1] # -> 3
List/dict comprehensions
https://supports.uptime-formation.fr/all_content/ 53/210
14/11/2022 17:46 Exporter tout le contenu
Les “list/dict comprehensions” sont des syntaxes particulière permettant de rapidement construire des listes (ou dictionnaires) à partir d’autres
structures.
List/dict comprehensions
Les “list/dict comprehensions” sont des syntaxes particulière permettant de rapidement construire des listes (ou dictionnaires) à partir d’autres
structures.
Générateurs
(Pas vraiment une structure de données, mais c’est lié aux boucles …)
Une fonction qui renvoie des résultats “au fur et à mesure” qu’ils sont demandés …
Se comporte comme un itérateur
Peut ne jamais s’arrêter …!
Typiquement, évite de créer des listes intermédiaires
"chenille": 2, "cobra": 45
# [...]
def au_moins_un_metre(animaux):
output = []
output.append(animal)
return output
...
"chenille": 2, "cobra": 45
# [...]
def au_moins_un_metre(animaux):
yield animal
...
Un autre exemple
def factorielle():
n = 1
acc = 1
while True:
acc *= n
n += 1
yield acc
7.1 : Écrire une fonction qui retourne le plus grand élément d’une liste (ou d’un set) de nombres, et une autre fonction qui retourne le plus petit. Par
exemple, plus_grand([5, 9, 12, 6, -1, 4]) retournera 12.
https://supports.uptime-formation.fr/all_content/ 54/210
14/11/2022 17:46 Exporter tout le contenu
assert plus_grand([5, 9, 12, 6, -1, 4]) == 12
7.2 : Écrire une fonction qui retourne le mot le plus long parmis une liste de mot donnée en argument.
7.3 : Écrire une fonction qui calcule la somme d’une liste de nombres.
assert somme([3, 4, 5]) == 12
7.4 : Écrire une fonction qui prends en argument un chemin de fichier comme “/usr/bin/toto.py” et extrait le nom du fichier, c’est à dire “toto”. On
pourra utiliser la méthode chaine.split(caractere) des chaînes de caractère.
7.5.1 : Récuperer le dictionnaire d’exemple auprès du formateur (example_dict.py) et boucler sur ce dictionnaire pour afficher quelque chose comme:
Sebastian est né.e en 1979
...
example_dict=[{'name': 'Sebastian', 'email': 'Donec.felis.orci@consectetueripsumnunc.edu', 'country': '1979'}, {'name': 'Barclay', 'email': 'ali
7.5.2 : Transformer le programme précédent pour n’afficher que les personnes ayant une adresse mail finissant par .edu.
7.6 : Ecrire une fonction compte_lettres qui prends en argument une (grande) chaîne de caractère et retourne un dictionnaire avec un compte des
occurences des lettres. Par exemple compte_lettres("hello") retournera {"h":1, "l": 2, "o": 1, "e":1 }. Utiliser cette fonction sur Lorem Ipsum
(“Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt […]")
7.7 : Écrire une fonction qui retourne seulement les entiers pairs d’une liste
7.8 : Écrire une fonction qui permet de trier une liste (ou un set) d’entiers
[ [ 0, 1, 2, 3, 4 ],
[ 0, 2, 4, 6, 8 ],
[ 0, 3, 6, 9, 12 ],
[ 0, 4, 8, 12, 16 ] ]
7.10 : Réécrire la fonction somme du 8.2, mais cette fois sans utiliser de variable intermédiaire (utiliser la récursivité)
7.11 : Ecrire un générateur carre() qui genere la suite 1, 4, 9, 16, … Utiliser ce générateur pour afficher les carrés jusqu’à ce qu’une valeur dépasse
200.
7.12 : Ecrire un générateur fibonnaci qui genere la suite de fibonnaci 0, 1, 1, 2, 3, 5, 8, … Utiliser ce générateur pour afficher les valeurs jusqu’à ce
qu’elles dépassent 500.
Variables
x = "Toto"
x = 40
y = x + 2
Fonctions
def aire_triangle(base, hauteur):
return calcul
Indentation
Arguments (peuvent être optionnels si on spécifie une valeur par défaut)
Variables locales
return pour pouvoir récupérer un résultat depuis l’extérieur
Appel de fonction
Conditions
https://supports.uptime-formation.fr/all_content/ 55/210
14/11/2022 17:46 Exporter tout le contenu
def aire_triangle(base, hauteur):
return -1
return calcul
Indentation
Opérateurs (==, !=, <=, >=, and, or, not, in, …)
Mot clefs if, elif, else
breakfast.append("Coffee")
print(stuff)
"beurre": 100,
"chocolat": 150}
if liste_entiers == []:
return None
m = liste_entiers[0]
if m < entier:
m = entier
return m
resultat = []
if entier % 2 == 0:
resultat.append(entier)
return resultat
9. Erreurs et exceptions
En Python, lorsqu’une erreur se produit ou qu’un cas particulier empêche (a priori) la suite du déroulement normal d’un programme ou d’une fonction,
une exception est déclenchée
Exemple d’exceptions
Utiliser int() sur quelque chose qui ne peut pas être converti en entier
Tenter d’ouvrir un fichier qui n’existe pas ou qu’on ne peut pas lire
etc…
Une exception a un type (c’est un objet d’un classe d’exception -> cf. Partie 3):
Lorsqu’une exception interrompt le programme, l’interpréteur affiche la stacktrace (TraceBack) qui contient des informations pour comprendre
quand et pourquoi l’exception s’est produite.
Traceback (most recent call last):
File "coucou.py", line 3, in <module>
print(coucou)
https://supports.uptime-formation.fr/all_content/ 56/210
14/11/2022 17:46 Exporter tout le contenu
# python3 test_int.py
demander_nombre()
Souvent une exception est due à une entrée utilisateur incorrecte (comme ici) mais pas toujours.
raise
if liste_entiers == []:
Autre exemple:
def envoyer_mail(destinataire, sujet, contenu):
try/except
De manière générale dans un programme, il peut y’avoir beaucoup de manipulation dont on sait qu’elles peuvent échouer pour un nombre de raisons
trop grandes à lister …
En Python, il est courant d'« essayer » des opérations puis de gérer les
exceptions si elles surviennent.
Exemple
reponse = input("Entrez un entier svp !")
try:
n = int(reponse)
except:
Utilisation différente
reponse = input("Entrez un entier svp !")
try:
n = int(reponse)
except:
n = -1
while True:
try:
n = int(reponse)
break
except:
continue
https://supports.uptime-formation.fr/all_content/ 57/210
14/11/2022 17:46 Exporter tout le contenu
def can_be_converted_to_int(stuff):
try:
int(stuff)
except:
return False
return True
Assertions
Il est possible d’utiliser des assertions pour expliciter certaines hypothèses
faites pendant l’écriture du code. Si elles ne sont pas remplies, une
exception est déclenchée.
assert liste_entiers != [], "max() ne peut pas fonctionner sur une liste vide!"
assert isinstance(x, (int, float)), "Cette fonction ne prends que des int ou float en argument !"
assert isinstance(y, (int, float)), "Cette fonction ne prends que des int ou float en argument !"
def some_function(n):
assert n % 2 == 0, "Cette fonction ne prends que des entiers pairs en argument !"
[...]
assert trier([15, 8, 4, 42, 23, 16]) == [4, 8, 15, 16, 23, 42]
assert trier([]) == []
Cf. Chapitre 19
Je soupçonne fortemment que ma_liste puisse ne pas être une liste ou puisse être vide
resultat = None
else:
resultat = max(ma_liste)
try:
resultat = max(ma_liste)
except ValueError as e:
print("Warning : peut-etre que ma_liste n'etait pas une liste non-vide ?")
resultat = None
Soit j’assert le test pour laisser la fonction appelante le soin de gérer l’entrée correctement
Normalement ma_liste est une liste non-vide, sinon il y a un très gros problème avant dans le programme…
assert isinstance(ma_liste, list) and ma_liste != []
resultat = max(ma_liste)
https://supports.uptime-formation.fr/all_content/ 58/210
14/11/2022 17:46 Exporter tout le contenu
Dans ce cas la fonction
10. Fichiers
Lire “brutalement”
mon_fichier = open("/etc/passwd", "r")
contenu_du_fichier = mon_fichier.readlines()
mon_fichier.close()
print(ligne)
contenu_du_fichier = mon_fichier.readlines()
print(ligne)
Explications
Lire
f.readlines() renvoie une liste contenant les lignes une par une
f.read() renvoie une (grande) chaĩne contenant toutes les lignes concaténées
Attention, si je modifie la variable contenu_du_fichier … je ne modifie pas vraiment le fichier sur le disque ! Pour cela, il faut explicitement
demander à écrire dans le fichier.
Ecrire
En remplacant tout !
with open("/home/alex/test", "w") as f:
f.write("Plop")
À la suite (« append »)
with open("/home/alex/test", "a") as f:
f.write("Plop")
Fichiers et exceptions
try:
lines = f.readlines()
except:
Un autre exemple
try:
lines = f.readlines()
except PermissionError:
except FileNotFoundError:
https://supports.uptime-formation.fr/all_content/ 59/210
14/11/2022 17:46 Exporter tout le contenu
Ex.10 Fichiers
10.1 : Créer un fonction liste_users qui lit le fichier /etc/passwd et retourne la liste des utilisateurs ayant comme shell de login /bin/bash.
modele = """
Bonjour {prenom} !
Voici en pièce jointe les billets pour votre voyage en train vers {destination}.
"""
Ecrire une fonction generer_email qui remplace dans modele les chaines {prenom}et {destination} par des arguments fourni à la fonction, et enregistre
le résultat dans un fichier email_{prenom}.txt. Par exemple, generer_email("Alex", "Strasbourg") générera le texte et sauvegardera le résultat dans
email_Alex.txt.
10.3 : Écrire une fonction qui permet d’afficher un fichier sans les commentaires et les lignes vides. Spécifier le caractère qui symbolise le début d’un
commentaire en argument de la fonction. (Ou pourra utiliser la méthode strip() des chaînes de caractère pour identifier plus facilement les lignes
vides)
11. Librairies
L’une des puissances de python vient de l’écosystème de librairie disponibles.
Syntaxes d’import
import un_module # -> Importer tout un module
# du module
Exemple
import math
une_fonction(...)
Exemple
from math import sqrt, sin, cos
Exemple : json
Le JSON est un format de fichier qui permet de décrire des données numériques complexe et imbriquées pour le stocker ou le transférer. Il s’agit du
format de données dominant aujourd’hui sur le web. Il est utilisé dans tous les langages et Python intègre à l’installation une librairie pour le manipuler.
"mailman": {
"branch": "master",
"level": 2,
"state": "working",
"url": "https://github.com/yunohost-apps/mailman_ynh",
},
"mastodon": {
"branch": "master",
"level": 3,
"state": "inprogress",
"url": "https://github.com/YunoHost-Apps/mastodon_ynh",
La fonction principale de la librairie est loads() qui tranforme une chaîne de caractère au format JSON en dictionnaire.
import json
with open("applications.json") as f:
j = json.loads(f.read())
https://supports.uptime-formation.fr/all_content/ 60/210
14/11/2022 17:46 Exporter tout le contenu
r = requests.get("https://en.wikipedia.org/wiki/Python", timeout=30)
Exemple : csv
import csv
with open("table.csv") as f:
Exemple : sys
permet d’interagir / de s’interfacer avec le systeme (librairie système commune à toutes les plateforme)
Par exemple:
import sys
Exemple : os
ospermet d’interagir avec le système d’exploitation pour réaliser différent
type d’action… Certaines étant spécifiques à l’OS en question (Linux,
Windows,
…)
Quelques exemples :
import os
Exemple : argparse
Du vrai parsing d’argument en ligne de commande
(Un peu long à initialiser mais puissant)
Usage:
naval_fate.py --version
Options:
"""
if __name__ == '__main__':
arguments = docopt(__doc__)
print(arguments)
Ensuite python naval_fate.py ship new monbateau --speed=15 renvoie un dictionnaire d’arguments du type:
{'--drifting': False, 'mine': False,
'<y>': '150'}
https://supports.uptime-formation.fr/all_content/ 61/210
14/11/2022 17:46 Exporter tout le contenu
Exemple : subprocess
subprocess peut typiquement être utilisé pour lancer des commandes en parallèle du programme principal et
récupérer leur résultat.
out = subprocess.check_output(["echo", "Hello World!"])
Moar ?
Debian packages : python-*
Python package manager : pip
Exemples
JSON, XML, HTML, YAML, …
Regular expressions
Logging, Parsing d’options, …
Internationalisation
Templating
Plots, LDAP, …
Installer un paquet :
pip3 install <paquet>
Rechercher un paquet :
pip3 search <motclef>
Installer une liste de dépendances :
pip3 install -r requirements.txt
Lister les paquets installés
pip3 list, pip3 freeze
Les paquets installés sont dans /usr/lib/python*/dist-packages/
Virtualenv
Environnement virtuel
Isoler des paquets / dépendances pour utiliser des versions spécifiques
# La premiere fois :
source venv/bin/activate
# Installation de dependances
https://supports.uptime-formation.fr/all_content/ 62/210
14/11/2022 17:46 Exporter tout le contenu
# On développe, on teste, etc....
deactivate
https://matthewhorne.me/how-to-install-python-and-pip-on-windows-10/
Ex.11 Librairies
Les énoncés des exercices suivants peuvent être un peu plus ouverts que les précédents, et ont aussi pour objectifs de vous inciter à explorer la
documentation des librairies (ou Internet en général…) pour trouver les outils dont vous avez besoin. Il existe de nombreuse façon de résoudre chaque
exercice.
11.1.1 : Télécharger le fichier https://app.yunohost.org/apps.json (avec votre navigateur ou wget par exemple). Écrire une fonction qui lit ce fichier,
le charge en tant que données json. Écrire une autre fonction capable de filter le dictionnaire pour ne garder que les apps d’un level supérieur à n donné
en argument. Écrire une fonction similaire pour le status (working, inprogress, notworking).
11.1.2 : Améliorer le programme précédent pour récupérer la liste directement depuis le programme avec requests. (Ajoutez une instruction pour
s’assurer que le code du retour est bien 200 avant de continuer).
11.1.3 : Exporter le résultat d’un filtre (par exemple toutes les applications avec level >= 7) dans un fichier json.
11.1.4 : À l’aide de la librairie argparse, paramétrez le tri à l’aide d’un argument donné en ligne de commande. Par exemple: python3 filtre_apps.py
--level 7 exportera dans “result.json” seulement les apps level >= 7.
CSV
11.2.1 : Récupérer le fichier de données CSV auprès du formateur, le lire, et afficher le nom des personnes ayant moins de 24 ans. Pour ce faire, on
utilisera la librarie csv.
11.2.2 : Trier les personnes du fichier CSV par année de naissance et enregistrer une nouvelle version de ce fichier avec seulement le nom et l’année de
naissance. Pour trier, on pourra utiliser sorted et son argument key.
Random
11.3 : Écrire une fonction jets_de_des(N) qui simule N lancés de dés 6 et retourne le nombre d’occurence de chaque face dans un dictionnaire. Par
exemple : {1: 13, 2:16, 3:12, ... }. Calculer ensuite la frequence (nb_occurences / nb_lancés_total) pour chaque face. Testez avec un N grand et
en déduire si votre dé virtuel est pipé ou non.
11.4 : Écrire un fonction create_tmp_dir qui choisi un nombre au hasard entre 0 et 100000 puis créer le dossier /tmp/tmp-{lenombre} et retourne le nom
du dossier ainsi créé. On pourra utiliser la librairie random pour choisir un nom aléatoire, et os.system ou subprocess.check_call pour créer le dossier.
11.5.1 : Écrire une fonction qui permet de trouver récursivement dans un dossier tous les fichiers modifiés il y a moins de 5 minutes.
11.5.2 : À l’aide d’une deuxième fonction permettant d’afficher les n dernières lignes d’un fichier, afficher les 10 dernières lignes des fichiers
récemment modifiés dans /var/log
11.6 : Écrire une fonction qui récupère l’utilisation actuelle de la mémoire RAM via la commande free. La fonction retournera une utilisation en
pourcent.
11.7 : Écrire une fonction qui renvoie les 3 processus les plus gourmands actuellement en CPU, et les 3 processus les plus gourmands en RAM (avec
leur consommation actuelle, chacun en CPU et en RAM)
docs.python.org
devdocs.io
stack overflow …
doc strings !!
https://supports.uptime-formation.fr/all_content/ 63/210
14/11/2022 17:46 Exporter tout le contenu
commentaires, doc strings
gestionnaire de version
generation de doc automatique ?
Un programme est vivant et évolue. Mieux vaut un programme cassé mais lisible (donc débuggable) qu’un programme qui marche mais
incompréhensible (donc fragile et/ou qu’on ne saura pas faire évoluer)
Autrement dit : la lisibilité pour vous et vos collègues a énormément d’importance pour la maintenabilité et l’évolution du projet
Keep It Simple
Sémantique : utiliser des noms de variables et de fonctions concis et pertinents
Commentaires : lorsque c’est nécessaire, pour démystifier ce qu’il se passe
Modularité : découper son programme en fonctions qui chacune résolvent un sous-problème
Couplage faible : garder ses fonctions autant que possibles indépendantes, limiter les effets de bords
Prendre le temps de refactoriser quand nécessaire
si je répète plusieurs fois les mémes opérations, peut-être définir une nouvelle fonction
si le contenu d’une variable ou d’une fonction change, peut-être changer son nom
Ne pas abuser des principes précédents
trop d’abstractions tue l’abstraction
tout ça viens avec le temps et l’expérience
12.1 - Utiliser pip3 pour trouver quelle est le numéro de version du package requests installé
12.2 - Rechercher avec pip3 si les paquets flake8 et autopep8 existent. Installez-les.
12.3 - Utilisez flake8 sur un code que vous avez écrit récemment (disons d’au moins 30 ou 40 lignes !). Étudiez les erreurs et warnings rapportées par
flake, et essayer les corriger manuellement. Si certains warnings vous semblent trop aggressif, utiliser --ignore pour spécifier des codes d’erreurs à
ignorer.
12.4.1 - Sur un autre code relativement mal formatté, utiliser autopep8 pour tenter d’ajuster automatiquement le formattage du code. Sauvegarder la
sortie fournie par autopep8 dans un autre fichier “version 2” et comparer le fichier initial avec le fichier de sortie à l’aide de diff ou de git diff --no-
index file1 file2.
parser.add_argument("many", type=int)
args = parser.parse_args()
for i in range(args.many):
Puis on le remplit avec les informations sur les arguments avec add_argument.
On peut indiquer un argument positionnel, en le nommant juste, comme
optionnel, avec - ou –
parser.add_argument("many", type=int)
argparse traite les données en entrée comme des chaines de caractère si un type n’est pas précisé. On peut le préciser tout simplement
avec l’option
type=(nom_du_type)
On prend ensuite les arguments en entrée et on les parses avec parse_args:
args=parser.parse_args()
https://supports.uptime-formation.fr/all_content/ 64/210
14/11/2022 17:46 Exporter tout le contenu
On a ainsi nos différents arguments. Ici, args.many, args.who
Reprendre l’exemple précédent et ajouter - devant le nom des argument. Que se passe t’il?
Reprendre l’exemple, sauf que cette fois si l’utilisateur ne
rentre rien, le programme affiche 3 fois Hello john.
Notes: notre parser est en fait un objet, tout comme ici args. args.many et args.who sont ainsi les attributs de l’objet args.
Nous reviendrons sur la notion
d’objet plus tard.
///// A supprimer ////
import argparse
parser.add_argument("--many", type=int)
args = parser.parse_args()
for i in range(args.many):
Format très général pour structurer des informations dans un fichier texte
Défini et géré par le W3C (Consortium de standardisation et developpement du Web)
(X)HTML est un cas particulier de XML
~historique ?… à tendance à être remplacé par JSON, YAML, bases SQL / noSQL, …
<data>
<apps>
</apps>
</data>
du html:
<html>
<head>
<meta charset="UTF-8">
<script src="lib.js"></script>
</head>
<body>
</body>
</html>
Un documents LibreOffice
<?xml version="1.0" encoding="UTF-8"?>
<office:document-content office:version="1.2">
[...]
<office:body>
<office:text>
<text:p text:style-name="P1">
</text:p>
</office:text>
</office:body>
</office:document-content>
Un peu de vocabulaire
<html>
<head>
<meta charset="UTF-8">
<script src="lib.js"></script>
</head>
<body>
</body>
</html>
https://supports.uptime-formation.fr/all_content/ 65/210
14/11/2022 17:46 Exporter tout le contenu
Lecture et chargement initial de tout le document (peut être lourd pour les gros documents !)
Puis accès à tous les noeuds de l’arbre (~AST)
Approche classique et répandue (c.f. Javascript)
ElementTree
<html>
<head>
<meta charset="UTF-8">
<script src="lib.js"></script>
</head>
<body>
</body>
</html>
xml.tree.ElementTree
Parser / lire
from xml.etree import ElementTree as ET
root = ET.parse("monfichier.html")
body = root.find("body")
print(body[0].tag) # --> p
tous_les_p = body.findall("p")
Construire / ecrire
from xml.etree import ElementTree as ET
root = ET.parse("monfichier.html")
body = root.find("body")
root.write("monfichier_2.xml")
def clear_elem_and_ancestors(elem):
elem.clear()
del ancestor.getparent()[0]
https://supports.uptime-formation.fr/all_content/ 66/210
14/11/2022 17:46 Exporter tout le contenu
# [...] traiter l'element
clear_elem_and_ancestors(element)
Exercices Partie 2
Correction - Exercice 2
7.1
def retourner_plus_grand(liste):
max=liste[0]
if nombre>=a:
max=nombre
return max
7.2
def plus_grand_mot(liste):
plus_grand_mot=liste[0]
if len(mot)>=len(a):
plus_grand_mot=mot
return plus_grand_mot
7.3
def somme(liste):
total=0
total+=nombre
return total
7.4
def extraire_nom_fichier(path):
liste=path.split("/") #['usr,'bin,'toto.py']
nom_du_fichier=list[-1].split(".") #['toto','py']
return liste[0]
# En un seule ligne
def extraire_nom_fichier_une_ligne(path):
return path.split("/")[-1].split(".")[0]
7.5.1
example_dict=[{'name': 'Sebastian', 'email': 'Donec.felis.orci@consectetueripsumnunc.edu', 'country': '1979'}, {'name': 'Barclay', 'email': 'ali
def lire_dict(dict):
lire_dico(exemple_dict)
7.5.2
def lire_dict_edu(dict):
if element["email"].split(".")[-1] == 'edu':
read_dict_edu(example_dict)
7.6
def compte_lettres(phrase):
dict={}
# Si la clef existe (dans ce cas la lettre a déjà été rencontrée) alors on incremente sa valeur de 1.
if lettre in dict:
dict[lettre]+=1
else:
dict[lettre]=1
return dict
phrase="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt "
print(compte_lettres(phrase))
7.7
def retourne_pair(liste):
nouvel_liste=[]
if nombre%2==0:
nouvel_liste.append(element)
return nouvel_liste
liste_paire=range(11)
print(retourne_pair(liste_paire))
7.8
Cette algorithme de tri classique s’appelle le tri à bulle. Ce n’est pas le plus rapide, mais il est facilement compréhensible.
def tri_a_bulles(tableau):
for i in range(len(tableau),0,-1):
for j in range(i-1):
https://supports.uptime-formation.fr/all_content/ 67/210
14/11/2022 17:46 Exporter tout le contenu
if tableau[j+1]<tableau[j]:
tableau[j+1], tableau[j]=tableau[j],tableau[j+1]
return tableau
7.10
def somme_2(liste):
if liste:
#Litéralement ma somme vaut le dernier élément plus la somme de tous les autres éléments moins le dernier
return liste[-1]+somme_2(liste[:-1])
else:
return 0
7.11
def carre():
i=1
while True:
i+=1
yield i*i
for i in carre():
if i>200
break
print (i)
10.1
def liste_users():
read_file=file.readlines()
e=element.rstrip().split(':')
if e[-1]=="/bin/bash":
print(e[0])
liste_users()
10.2
def generer_email(prenom,nom):
modele = """
Bonjour {prenom} !
Voici en pièce jointe les billets pour votre voyage en train vers {destination}.
""".format(prenom=prenom,destination=nom)
f.write(modele)
generer_email("Sacha","Rennes")
10.3
def afficher_fichier_no_comment(file,char):
read_file=file_read.readlines()
print(read_file)
print(element,end='')
afficher_fichier_no_comment("test","#")
Écrire une fonction qui remplace un mot par un autre dans un fichier. On pourra pour cela se servir de une_chaine.replace("mot",
"nouveau_mot") qui renvoie une version modifiée de une_chaine en ayant remplacé “mot” par “nouveau mot”.
Télécharger le fichier https://app.yunohost.org/community.json (avec votre navigateur ou wget par exemple). Écrire une fonction qui lit ce
fichier, le charge en tant que données json et renvoie un dictionnaire Python. Écrire une autre fonction capable de filtrer le dictionnaire pour ne
garder que les apps d’un level supérieur ou égal à un level n donné en argument. Essayez votre fonction avec le niveau 8.
Améliorez le programme précédent pour récupérer la liste directement depuis le programme avec requests. Gérer les différentes exceptions qui
pourraient se produire (afficher un message en français) : syntaxe json incorrecte, erreur 404, time-out du serveur, erreur SSL
https://supports.uptime-formation.fr/all_content/ 68/210
14/11/2022 17:46 Exporter tout le contenu
En utilisant des commandes comme head -n 50 copyright.xml, analyser visuellement la structure du fichier d’après ses premières lignes.
Initialiser un itérateur destiné à itérer sur ce fichier, et en particulier sur les tags Title. Créer une boucle à partir de cet itérateur et afficher tous les
titres qui contiennent la chaîne "Pyth". On prendra soin de nettoyer les éléments trouvés avant de passer à chaque nouvelle itération sous
peine de remplir la RAM très vite !
Pour chaque titre trouvé, remonter au parent ‘Record’ pour trouver le ‘Holder Name’ correspondant à ce titre. S’aider du debug VSCode, ipython
et/ou ipdb pour tester et expérimenter en interactif.
Partie 3 - POO
Cours 3
13. POO - Classes, attributs et méthodes
L’orienté objet est un paradigme de programmation inventé dans les années 80 et popularisé dans les années 90. Aujourd’hui il est incontournable bien
qu’il commence aussi à être critiqué.
Il permet d’organiser un programme de façon standard et ainsi d’éviter des erreurs d’architectures comme le spaghetti code
Principe de base
Regrouper les variables et fonctions en entités (“objets”) cohérentes qui appartiennent à des classes
De cette façon on fabrique des sorte types de données spécifique à notre programme utilisables de façon pratique et consistante.
Exemple
Les cercles (classe)
ont un centre, un rayon, une couleur, une épaisseur de trait : ce sont des attributs.
On peut : déplacer le cercle, l’agrandir, calculer son aire, le dessiner sur l’écran : ce sont des méthodes.
Exemple en Python
class Cercle:
self.centre = centre
self.rayon = rayon
self.couleur = couleur
self.epaisseur = epaisseur
cercle1.deplacer(dy=2)
print(cercle1.centre)
__init__ est le constructeur C’est la fonction qui est appelée à la création de l’objet.
self correspond à l’objet en train d’être manipulé. Il doit être passé en paramètre de toutes les fonctions de la classe (les méthodes)
Les attributs sont les variables internes qui décrivent l’état et régisse le fonctionnement de l’objet.
self.centre, self.rayon, self.couleur, self.epaisseur sont ici les attributs. Si on lit littéralement la syntaxe python on comprend self.centre
comme “le centre de l’objet en cours(le cercle en cours)”
Toutes les fonctions incluses dans la classe sont appelées des méthodes.
https://supports.uptime-formation.fr/all_content/ 69/210
14/11/2022 17:46 Exporter tout le contenu
__init__ et deplacer sont des méthodes. Elles agissent généralement sur les attributs de l’objet mais pas nécessairement.
Les attributs et méthodes de la classe sont “dans” chaque instance d’objet (ici chaque cercle). On dit que la classe est un namespace (ou espace de
nom). Chaque variable centre est isolée dans son cercle et on peut donc réutiliser plusieurs fois le nom centre pour chaque cercle. Par contre pour y
accéder on doit préciser le cercle concerné avec la syntaxe cercle1.centre.
Attention à l’indentation !!
On commence généralement à définir des variables globales accessibles partout pour maintenir l’état de notre programme. Plusieurs fonctions viennent
modifier de façon concurrente ces variables globales (pensez au score dans un jeu par exemple) pouvant mener à des bugs complexes.
On arrive aussi à beaucoup de code dupliqué et il devient très difficile dans ce contexte de refactorer un programme:
On peut voir la programmation orientée objet comme une façon d’éviter le code spaghetti.
Intérets de la POO
La POO est critique pour garder un code structuré et compréhensible quand la complexité d’un projet augmente.
Cela permet ensuite de modifier le code à un seul endroit pour tout changer -> puissant.
Il s’agit plus d’un ideal que d’un principe. Il ne faut pas l’appliquer à outrance parfois un peu de répétition est mieux car plus simple.
Si on factorise tout en POO on arrive souvent à un code fortement coupler qui empêche le refactoring et le programme finit par devenir fragile.
À retenir
__init__ est le constructeur
__init__ et deplacer sont des méthodes
self correspond à l’objet en train d’être manipulé
Toutes les méthodes ont au moins self comme premier argument
On utilise les methodes en faisant un_objet.la_methode(...)
self.centre, self.rayon, self.couleur, self.epaisseur sont des attributs
On instancie un objet en faissant mon_objet = Classe(...)
Par exemple, les cercles, les carrés et les étoiles sont trois types de figures géométriques.
En tant que figure géométriques, elles ont toutes un centre, une couleur et une épaisseur utilisés pour le dessin. On peut les déplacer, et on peut calculer
leur aire.
L’héritage permet d’ordonner des objets proches en les apparentant pour s’y retrouver.
Il permet également de factoriser du code en repérant des comportements génériques utilisés dans plusieurs contextes et en les mettant dans un
parent commun.
class FigureGeometrique:
self.centre = centre
self.couleur = couleur
self.epaisseur = epaisseur
https://supports.uptime-formation.fr/all_content/ 70/210
14/11/2022 17:46 Exporter tout le contenu
def aire(self):
class Cercle(FigureGeometrique):
self.rayon = rayon
def aire(self):
class Carre(FigureGeometrique):
self.cote = cote
def aire(self):
return self.cote ** 2
cercle_rouge.deplacer(dy=2)
carre_vert.deplacer(dx=-3)
Les cercles et les carrés “descendent” ou “héritent” de la classe FigureGeometrique avec la syntaxe class Carre(FigureGeometrique).
La méthode deplacer de la classe mère est disponible automatiquement dans les classes filles
Ainsi pour factoriser du code on peut repèrer un comportement commun à plusieurs éléments de notre programme et on créé une classe mère avec une
méthode exprimant ce comportement de façon générique. Tous les classes fille pourront utiliser ce comportement. Si on le change plus tard il sera
changé dans tout le programme (puissant pour refactoriser le code)
Cependant il est rare qu’un comportement soit exactement identique entre deux classes. On veut souvent changer légèrement ce comportement selon la
classe utilisée. Pour cela on utilise le polymorphisme.
Polymorphisme
Surcharge de fonction
Dans le cas de l’aire de nos figures, chaque figure doit pouvoir calculer son aire mais le calcul est différent pour chaque type de figure concrête.
On définit une méthode abstraite aire dans la classe mère pour indiquer que chaque figure a une méthode aire. Comme une figure en général n’a
pas de calcul d’aire la méthode abstraite déclenche une exception (Utilisez ici NotImplementedError() qui est faite pour ça)
On redéfinit la méthode aire dans chaque classe fille. La méthode aire fille écrase ou surcharge celle de la classe mère et sera appelée à la place
de celle-ci dès qu’on veut l’aire d’une figure géométrique.
Souvent on veut quand même utiliser la méthode de la classe même pour faire une partie du travail (commun à toute les classes filles) et ensuite
spécialiser le travail en ajoutant des actions suplémentaires dans la méthode fille qui surcharge la méthode mère. A cause de la surcharge la méthode
mère n’est pas du tout appelée automatiquement donc il faut le faire “manuellement”.
on appelle d’abord le constructeur de la classe mère qui initialise centre, couleur et epaisseur avec super().__init__()
Puis on initialise cote qui est un attribut du carré (mais pas du cercle donc pas dans le constructeur général)
Classe Abstraite
Une classe abstraite est une classe dont on ne peut pas créer d’instance. Elle est simplement là pour définir un modèle minimal que toutes les classes
fille doivent suivre (et étendre).
En Python on créé généralement une classe abstraite en levant l’exception NotImplementedError dans le constructeur __init__.
On parle de polymorphisme quand on utilise la classe abstraite pour gérer uniformément plusieurs type d’objets de classe différente et qu’on laisse le
langage choisir le comportement en fonction du contexte.
Par exemple on peut faire une liste de FigureGeometrique de différents types et afficher les aire de chacune. Python devinera automatiquement quelle
méthode appeler :
print(forme.aire())
https://supports.uptime-formation.fr/all_content/ 71/210
14/11/2022 17:46 Exporter tout le contenu
Le polymorphisme est puissant car il permet d’économiser beaucoup de if et autre branchements:
On aurait pu écrire l’exemple précédent avec des if isinstance(figure, Cercle): par exemple mais cela aurait été beaucoup moins élégant.
À retenir
class Cercle(FigureGeometrique) fais hériter Cercle de FigureGeometrique
super().__init__(...) permet d’appeler le constructeur de la classe mère
Les classes filles disposent des méthodes de la classe mère mais peuvent les surcharger (c.f. exemple avec aire)
super().une_methode(...) permet d’appeler une_methode telle que définie dans la classe mère.
isinstance verifie l’heritage ! isinstance(cercle_rouge, FigureGeometrique) vaut True !
class FormeGeometrique():
nb_instances = 0
def __init__(self):
FormeGeometrique.nb_instances += 1
forme1 = FormeGeometrique()
forme2 = FormeGeometrique()
forme3 = FormeGeometrique()
print(FormeGeometrique.nb_instances)
# -> affiche 3
La méthode statique est complètement indépendante de la classe même si rangée à l’intérieur alors que la méthode de classe récupère implicitement sa
classe comme premier argument ce qui permet de construire des objet de la classe dans le corps de la méthode
self._string = astring
@classmethod
return x
L’encapsulation
Nous avons évoqué dans le cours 13 qu’un des intérêts de la POO est de sécuriser les variables dans un contexte isolé pour éviter qu’elles soient
accédées à tort et à travers par différents programmeurs ce qui a tendance à créer des bugs mythiques.
Pour éviter cela on essaye au maximum d’encapsuler les attributs et les méthodes internes qui servent à faire fonctionner une classe pour éviter que les
utilisateurs de la classe (ignorants son fonctionnement) puissent pas les appeler directement et “casser” le fonctionnement de la classe.
En Python les attributs et méthodes d’un objet sont “publiques” par défaut : on peut y accéder quand on veut et donc il faut donc une façon de pouvoir
interdire leur usage:
On utilise un underscore _ devant le nom de l’attribut ou méthode pout indiquer qu’il est privé et ne doit pas être utilisé.
En réalité l’attribut/méthode est toujours accessible, il s’agit d’une convention mais il faut la respecter !! Par défaut les editeurs de code vous
masqueront les elements privés lors de l’autocomplétion par exemple.
https://supports.uptime-formation.fr/all_content/ 72/210
14/11/2022 17:46 Exporter tout le contenu
Pour cela on créé des attributs privés et on définit des méthodes “publique”
On veut donc généralement pouvoir y donner accès à l’utilisateur de la classe selon certaines conditions.
Pour cela un définit une méthode d’accès (getter/accesseur) qui décrit comment récupérer la valeur ou une méthode de modification (setter/mutateur)
qui contrôle comment on peut modifier la valeur (et qui vous envoie balader si vous définissez un rayon négatif).
self.centre = centre
self._rayon = rayon
self._couleur = couleur
def get_couleur(self):
return self._couleur
self._rayon = rayon
cercle1.get_couleur()
cercle1.set_rayon(1)
cercle1.set_rayon(-1) # Erreur
Cependant en Python on ne fait généralement pas directement comme dans cet exemple !
# [ ... ]
@property
def aire(self):
self.montant_total = total
self.montant_deja_paye = 0
@property
def montant_restant_a_payer(self):
ma_facture = Facture(45)
ma_facture.montant_deja_paye += 7
La façon pythonique de faire des getters et setters en python est donc la suivante:
@property
def toto(self):
return self.__toto
@toto.setter
monobjet.toto = "nouvelle_valeur"
print(monobjet.toto)
Sauvegarde
import pickle
ma_facture = Facture(45)
pickle.dump(ma_facture, f)
Puis recuperation
import pickle
f = open("save.bin", "rb")
ma_facture = pickle.load(f)
CREATE TABLE members (username text, email text, memberSince date, balance real)
# Add a record
# Find records
conn = sqlite3.connect('example.db')
c = conn.cursor()
# Create a table
# Add a record
conn.commit()
conn.close()
db = ActiveAlchemy('sqlite:///members.db')
class Member(db.Model):
https://supports.uptime-formation.fr/all_content/ 74/210
14/11/2022 17:46 Exporter tout le contenu
# Supprimer toutes les tables (attention ! dans la vraie vie on fait des migrations...)
db.drop_all()
db.create_all()
db.session.add(alice)
db.session.add(bob)
db.session.add(camille)
db.session.commit()
active_members = Member.query()
.filter(Member.active == True)
.order_by(Member.memberSince)
print(user.name)
Exercices Partie 3
Correction 3.1 - Cercles et Cylindres
Dans cet exercice nous allons représenter des objets et calculs géométriques simples en coordonnées entières. Utilisez des annotations de types : int, -
> None, -> int et : Tuples[int ...] dès que possible. Testez régulièrement la consistance de ces types avec mypy fichier.py.
Implémenter une classe Cercle avec comme attributs un rayon rayon et les coordonnées x et y de son centre. Par exemple on pourra instancier un
cercle avec mon_cercle = Cercle(5, (3,1))
Dans la classe Cercle, implémenter une propriété aire dépendante du rayon qu’on peut appeler avec mon_cercle.aire.
Implémenter une classe Cylindre, fille de Cercle, qui est caractérisée par un rayon rayon, une hauteur hauteur et des coordonnées x, y et z. On
écrira le constructeur de Cylindre en appelant le constructeur de Cercle.
Dans la classe Cercle, implémenter une méthode intersect qui retourne True ou False suivant si deux cercles se touchent. Exemple d’utilisation :
c1.intersect(c2)
Surcharger la méthode intersect pour la classe Cylindre, en se basant sur le résultat de la méthode de la classe mère.
Correction
Correction 3.1
Dans un fichier carte.py, créer une classe Carte. Une carte dispose d’une valeur (1 à 10 puis VALET, DAME et ROI) et d’une couleur
(COEUR, PIQUE, CARREAU, TREFLE). Par exemple, on pourra créer des cartes en invoquant Carte(3, 'COEUR') et Carte('ROI', 'PIQUE').
Implémenter la méthode points pour la classe Carte, qui retourne un nombre entre 1 et 13 en fonction de la valeur de la carte. Valider ce
comportement depuis un fichier main.py qui importe la classe Carte.
Implémenter la méthode __repr__ pour la classe Carte, de sorte à ce que print(Carte(3, "COEUR")) affiche <Carte 3 de COEUR>.
c = Carte("DAME", "PIQUE")
print(c.couleur)
# Affiche PIQUE
print(c.points)
# Affiche 12
print(c)
Pour sécuriser l’usage ultérieur de notre jeu de carte on aimerait que les cartes ne puissent être crées et modifiées qu’avec des valeurs correctes (les 4
couleurs et 13 valeurs précisées)
Modifiez le constructeur pour valider que les données fournies sont valides. Sinon levez une exception (on utilise conventionnellement le type
d’exception ValueError pour cela ou un type d’exception personnalisé).
Modifiez également les paramètres couleur et valeur pour les rendre privés, puis créer des accesseurs et mutateurs qui permettent d’y accéder en
mode public et de valider les données à la modification.
https://supports.uptime-formation.fr/all_content/ 75/210
14/11/2022 17:46 Exporter tout le contenu
Correction 3.2 `carte.py`
Implémenter la méthode melanger pour la classe Paquet qui mélange l’ordre des cartes.
Implémenter la méthode couper qui prends un nombre aléatoire du dessus du paquet et les place en dessous.
Implémenter la méthode piocher qui retourne la Carte du dessus du paquet (eticla l’enlève du paquet)
1.0 : Implémenter la méthode distribuer qui prends en argument un nombre de carte et un nombre de joueurs (e.g. p.distribuer(joueurs=4,
cartes=5)), pioche des cartes pour chacun des joueurs à tour de rôle, et retourne les mains correspondantes.
p = Paquet()
p.melanger()
print(main_alice)
# affiche par exemple [<Carte 3 de PIQUE>, <Carte VALET de CARREAU>, <Carte 1 de trefle>]
print(p.pioche())
print(main_alice[1].points())
# affiche 11
Créer un fichier mydb.py qui se contente de créer une base db (instance de ActiveAlchemy) de type sqlite. Dans la suite, on importera l’objet db
depuis mydb.py dans les autres fichiers si besoin.
Créer un fichier models.py et créer dedans une classe (aussi appellé modèle) App. On se limitera aux attributs (aussi appellés champs / colonnes)
suivants :
un nom qui est une chaîne de caractère unique parmis toutes les App ;
un niveau qui est un entier (ou vide) ;
une adresse qui est une chaîne de caractère unique parmis toutes les App ;
Créer un fichier nuke_and_reinit.py dont le rôle est de détruire et réinitialiser les tables, puis de les remplir avec les données du fichier json. On
utilisera pour ce faire db.drop_all() et db.create_all(). Puis, itérer sur les données du fichier json pour créer les objets App correspondant.
Commiter les changements à l’aide de db.session.add et commit.
Créer un fichier analyze.py qui cherche et affiche le nom de toutes les App connue avec un niveau supérieur ou égal à n. En utilisant l’utilitaire
bash time (ou bien avec time.time() en python), comparer les performances de analyze.py avec un script python équivalent mais qui travaille à
partir du fichier community.json directement (en local, pas via requests.get)
mydb.py
db = ActiveAlchemy('sqlite:///apps.db')
models.py
class App(db.Model):
def __repr__(self):
nuke_and_reinit
import json
db.drop_all()
db.create_all()
with open("apps.json") as f:
apps_from_json = json.loads(f.read())
db.session.add(a)
https://supports.uptime-formation.fr/all_content/ 76/210
14/11/2022 17:46 Exporter tout le contenu
db.session.commit()
apps_level_3 = App.query().filter(App.level == 3)
print(app.name)
Implémenter une classe Cercle avec comme attributs un rayon rayon et les coordonnées x et y de son centre. Par exemple on pourra instancier un
cercle avec mon_cercle = Cercle(5, (3,1))
Dans la classe Cercle, implémenter une propriété aire dépendante du rayon qu’on peut appeler avec mon_cercle.aire.
Implémenter une classe Cylindre, fille de Cercle, qui est caractérisée par un rayon rayon, une hauteur hauteur et des coordonnées x, y et z. On
écrira le constructeur de Cylindre en appelant le constructeur de Cercle.
Surcharger la méthode aire pour la classe Cylindre, en se basant sur le résultat de la méthode de la classe mère.
Implémenter la méthode points pour la classe Carte, qui retourne un nombre entre 1 et 13 en fonction de la valeur de la carte. Valider ce
comportement depuis un fichier main.py qui importe la classe Carte.
Implémenter la méthode __repr__ pour la classe Carte, de sorte à ce que print(Carte(3, "COEUR")) affiche <Carte 3 de COEUR>.
c = Carte("DAME", "PIQUE")
print(c.couleur)
# Affiche PIQUE
print(c.points)
# Affiche 12
print(c)
Pour sécuriser l’usage ultérieur de notre jeu de carte on aimerait que les cartes ne puissent être crées et modifiées qu’avec des valeurs correctes (les 4
couleurs et 13 valeurs précisées)
Modifiez le constructeur pour valider que les données fournies sont valides. Sinon levez une exception (on utilise conventionnellement le type
d’exception ValueError pour cela ou un type d’exception personnalisé).
Modifiez également les paramètres couleur et valeur pour les rendre privés, puis créer des accesseurs et mutateurs qui permettent d’y accéder en
mode public et de valider les données à la modification.
Implémenter la méthode melanger pour la classe Paquet qui mélange l’ordre des cartes.
Implémenter la méthode couper qui prends un nombre aléatoire du dessus du paquet et les place en dessous.
Implémenter la méthode piocher qui retourne la Carte du dessus du paquet (et l’enlève du paquet)
1.0 : Implémenter la méthode distribuer qui prends en argument un nombre de carte et un nombre de joueurs (e.g. p.distribuer(joueurs=4,
cartes=5)), pioche des cartes pour chacun des joueurs à tour de rôle, et retourne les mains correspondantes.
Créer un fichier mydb.py qui se contente de créer une base db (instance de ActiveAlchemy) de type sqlite. Dans la suite, on importera l’objet db
depuis mydb.py dans les autres fichiers si besoin.
Créer un fichier models.py et créer dedans une classe (aussi appellé modèle) App. On se limitera aux attributs (aussi appellés champs / colonnes)
suivants :
un nom qui est une chaîne de caractère unique parmis toutes les App ;
https://supports.uptime-formation.fr/all_content/ 77/210
14/11/2022 17:46 Exporter tout le contenu
un niveau qui est un entier (ou vide) ;
une adresse qui est une chaîne de caractère unique parmis toutes les App ;
Créer un fichier nuke_and_reinit.py dont le rôle est de détruire et réinitialiser les tables, puis de les remplir avec les données du fichier json. On
utilisera pour ce faire db.drop_all() et db.create_all(). Puis, itérer sur les données du fichier json pour créer les objets App correspondant.
Commiter les changements à l’aide de db.session.add et commit.
Créer un fichier analyze.py qui cherche et affiche le nom de toutes les App connue avec un niveau supérieur ou égal à n. En utilisant l’utilitaire
bash time (ou bien avec time.time() en python), comparer les performances de analyze.py avec un script python équivalent mais qui travaille à
partir du fichier community.json directement (en local, pas via requests.get)
Cette API (application programming interface = série de fonctions qui décrivent ce qu’on peut faire) se compose d’attributs et méthodes “spéciales” qui
sont encadrées par des doubles underscores (__ ) comme __add__.
typecode = 'd'
self.x = float(x)
self.y = float(y)
On parle aussi dans ce cas de surcharge d’opérateur qui est un classique dans les langage de POO.
self.mesitems = list(collection)
return self.mesitems[indice]
Une fois qu’on a implémenté le minimum de l’interface on peut utiliser des fonctions python intégrées par exemple ici on peut faire directement
shuffle(MaCollectionEnnuyeuse('Diantre')) # -> Mélange les lettres de Diantre
En fait, on peut dire qu’être une liste en python c’est plus ou moins avoir les méthodes spéciales qui définissent la liste. Pareil pour le dictionnaire. Un
bon exemple de ce principe est l’itérable : tout objet qui peut renvoyer un iterateur avec __iter__ est utilisable dans une boucle for (puissant)
Une liste est itérable, ce qui veut dire qu’elle possède une fonction __iter__ qui renvoie un itérateur sur ses éléments.
Méthodes spéciales
https://supports.uptime-formation.fr/all_content/ 78/210
14/11/2022 17:46 Exporter tout le contenu
Il existe plein de méthodes spéciales pour implémenter toutes les syntaxes, comportements sympathiques, et fonctions de base incluses dans Python
(comme shuffle ou sort). Quelques autre:
__repr__ et __str__ : génère automatiquement une représentation de l’objet sous forme de chaîne de caractères (la première est une
représentation basique pour le debug, la deuxième prioritaire est pour une représentation plus élégante de l’objet) qui permet de faire un “joli”
print(mon_objet)
def __str__(self):
__eq__ : définir l’égalité entre deux objets. Très important pour faire des comparaison rapide et par exemple permettre de trier automatiquement
vos objets dans une liste. Etc
__bool__: Permet de convertir votre objet en booléen et ainsi de supporter des syntaxes comme
if mon_objet:
print("c'est bon")
else:
ETC…
Implémenter ces différentes fonctions d’API n’est pas obligation mais surtout utile pour construire du code (souvent de librairie) qui sera agréable à
utiliser pour les autre développeurs habitués à Python.
Design Patterns
En fait au delà de Python et de la POO, lorsqu’on construit des programmes on peut identifier des bonnes façon de résoudre des problèmes courants ou
qui on une forme courante qu’on retrouve souvent dans les programmes. On appelle ces méthodes/forme des Design Patterns.
Par exemple l’iterateur (Pattern Iterator) est un design pattern que le langage Python implémente à sa façon et qui propose une solution pratique au
parcours d’une collection d’objets.
Le Decorator est également un motif pour personnaliser le fonctionnement d’une fonction ou classe sans la modifier (et donc sans complexifier le code
principal) il est implémenté en python grace à une syntaxe spécifique du langage très utilisée (Cf juste après).
Ces “motifs de conception” logicielle proviennent d’un ouvrage éponyme, influent dans les années 90, du Gang of Four (Gof). En réalité c’est même
plus général que ce livre orienté POO car on peut identifier des Design Patterns dans des langages très différents par exemple fonctionnels.
Il existe pas mal d’autres Patterns non implémentés direactement dans le langage Python:
https://fr.wikipedia.org/wiki/Patron_de_conception
https://design-patterns.fr/introduction-aux-design-patterns
Décorateurs
Les décorateurs sont en Python des sortes d'“emballages” qu’on ajoute aux fonctions et au classes pour personnaliser leur comportement sans modifier
le code principal de la fonction. Concrètement les décorateurs sont des
En gros ça permet d’ajouter des prétraitements, des posttraitements et de modifier le comportement de la fonction elle
Un programme synchrone est un programme ou toutes les étapes de calculs sont éxecutées les unes à la suite des autres. Conséquence on attend la fin
de chaque opération avant de continuer et si une opération prend du temps l’utilisateur attend.
Un programme asynchrone est un programme qui execute diférentes étapes de calcul sans respecter l’ordre linéraire du programme. Par exemple deux
fonctions appelées en même temps et qui vont s’exécuter de façon concurrent (on les lance toutes les deux en même temps et elles se partagent les
ressources de calculs).
Pour executer des morceaux de calculs de façon concurrente il y a pas mal d’approches dont:
1. le multiprocessing : on lance plusieurs processus au niveau de l’os, un peu l’équivalent de plusieurs programme en parallèle. Ils peuvent se
répartir les multiples processeurs d’une machine ou d’un cluster. C’est intéressant pour les gros calcul mais pour faire plein de petites taches c’est
pas très intéressant car le changement de process prend du temps.
2. le multithreading : on lance un processus système avec plusieurs processus “virtuels” “légers” à l’intérieur. Les différents threads peuvent aussi
potentiellement utiliser plusieurs processeurs en même temps. Cependant le multithread est peu efficace en python (avec Cpython) à cause du
Global Interpreter Lock. On utilise peu les threads.
3. execution asynchrone dans un seul processus (asyncio basé sur une event loop): En gros les différents morceaux du code concurrents ne
s’exécutent pas “réellement” en même temps, ils se partagent le temps d’exécution d’un seul processus de calcul en se passant la main. Cette
approche n’utilise pas tous les processeurs disponibles mais est légère et facilement controlable.
Avant de choisir une solution il faut étudier son programme pour diagnostiquer le ralentissement.
Très couramment à cause de blocages au niveau des entrées/sortie (IO) lorsqu’on attend qu’un serveur (sur le réseau ou autre) ou un device (le
disque ou autre) réponde à une demande.
Parce que le calcul est très lourd et demande plein d’opérations processeur (CPU intensive) (courant mais plus rare dans les programmes réels)
https://supports.uptime-formation.fr/all_content/ 79/210
14/11/2022 17:46 Exporter tout le contenu
Dans le premier cas il faut utiliser l’execution asynchrone (solution 3.) en coroutine (fonction commençant par async def) avec asyncio.
Dans le deuxième cas il faut utiliser le multiprocessing (solution 1.) pour maximiser les processeurs utilisés avec concurrent.futures.
Exemple de asyncio:
import asyncio
print("Foo!")
print("Hello World!")
asyncio.run(hello_world())
se rappeler qu’une fonction async def peut se réveille périodiquement pour s’exécuter (le flux d’exécution est plus dur à imaginer)
Il faut aussi gérer la concurrence entre les coroutines (attendre un résultat dont on a besoin pour continuer le calcul d’une autre coroutine avec
await par exemple)
Exemple2 avec gather pour attendre et rassembler les résultat de plusieurs taches:
gather
import asyncio
print(f"n: {n}!")
await asyncio.gather(*tasks)
asyncio.run(main())
Enfin pour compléter l’approche asyncio avec du multiprocessing (au cas ou c’est le processeur qui bloque et que le programme est toujours lent) on
peut utiliser concurrent.futures et un Pool de Process (ProcessPoolExecutor).
import asyncio
import concurrent.futures
def blocking_io():
return f.read(100)
def cpu_bound():
# process pool.
loop = asyncio.get_running_loop()
asyncio.run(main())
https://supports.uptime-formation.fr/all_content/ 80/210
14/11/2022 17:46 Exporter tout le contenu
Ils servent à regrouper des ensembles de classes et fonctions apparentées.
Un module est ce qu’on importe grace à import ou from ... import ....
fichier mon_module.py:
ma_variable = 1
fichier mon_module2.py:
ma_variable = 2
fichier mon_programme_principal.py
import mon_module
import mon_module2
if __name__ == "__main__"
ma_variable = 3
print(mon_module.ma_variable) # -> 1
print(mon_module2.ma_variable) # -> 2
print(ma_variable) # -> 3
print(mon_module2.ma_fonction(ma_variable))
Les modules sont des namespaces pour leurs variables : mon_module.ma_variable != mon_module2.mavariables != mavariables
Les imports de modules sont transitifs : si on importe module2 qui importe module1 alors on a module1 disponible même si on a pas importé
directement module1.
Le code d’un module est exécuté au moment de l’import (si ya un print qui traine dans le corps d’un module ça risque de se voir…)
On ne s’y retrouve plus avec un seul module ou quelques fichiers à la racine du projet.
On ajoute des fichiers __init__.py dans chaque sous dossiers et ça fait un module
Exemple
Considérant les fichiers suivants :
├── main.py
└── mylib/
├── __init__.py
print(dire_bonjour)
└── mylib/
├── __init__.py
print(bonjour)
Si on a besoin de le distribuer ou simplement pour le séparer du reste du code peut ensuite transformer son package en une librairie installable grâce à
un outil nommée setuptools et/ou pip.
Une modification à un bout du programme peut casser un autre morceau si on y prend pas garde ! Par exemple si on a changer un nom de variable mais
pas partout dans le code. Le logiciel a l’air de fonctionner.
Lorsqu’on a un gros logiciel avec une base de code python énorme on ne peut pas facilement connaître tout le code. Même sur un logiciel plus limité on
ne peut pas penser à tout.
Comme un logiciel doit pouvoir être en permanence refactorisé pour resté efficace et propre on a vraiment besoin de tests pour tout logiciel d’une
certaine taille.
Si vous codez une librairie pour d’autres développeurs/utilisateurs, ces utilisateurs veulent un maximum de tests pour garantir que vous ne laisserait pas
des bugs dans la prochaine version et qu’ils peuvent faire confiance à votre code.
Écrire des bons test nécessite d’imaginer les cas limites de chaque fonction. Si on a oublié de gérer le cas argument = -1 par exemple au moment des
tests on peut le remarquer, le corriger et faire en sorte que le test garantisse que ce bug est évité.
Pour aider à coder le programme en réfléchissant à l’avance a ce que chaque fonction doit faire
Écrire des tests avant de coder, une pratique qu’on appelle le Test Driven Development
Intégration: tester l’application en largeur en appelant le programme ou certaines grosses partie dans un contexte plus ou moins réaliste. Pour
détecter les problèmes d’intgégration entre plusieurs parties du programme mais déclenche aussi les problèmes dans les fonctions.
Généralement les tests unitaires sont très rapides (on peut les lancer toutes les 5 minutes puisque ça prend 4 secondes)
Généralement les tests d'intégration sont plus lent puisqu’il faut initialiser toute l’application et son contexte avant de les lancer.
Dans mylib.py
def func(x):
return x + 1
Dans tests.py
def test_answer():
assert func(3) == 5
Lancer Pytest
En précisant le fichier de test un fichier: pytest tests.py ou python3 -m pytest tests.py si on utilise un environnement virtuel python.
En laissant pytest trouver tous les tests du projet : les commandes pytest ou python3 -m pytest parcourt tous les fichiers python du dossier et
considère comme des tests toutes les fonctions qui commencent par test_
(fixture = une fonction de préparation d’un contexte consistant pour les tests)
import os
import tempfile
import pytest
@pytest.fixture
def client():
with web_app.test_client() as client: # une application flask propose une méthode test_client() pour mettre en place un serveur web destiné
yield client # pour chaque test la fonction client() renvoie le client de test flask
return_value = client.get('/add/5/5')
assert b'5 + 5 = 10' in return_value.data
def test_compute_add_0_0(client):
return_value = client.get('/add/0/0')
assert b'0 + 0 = 0' in return_value.data
Ces deux tests s’éxecutent en montant un serveur web et en appelant la route (~page web) correspondante. On aurait pu également initialiser une base
de données pour le site web avant de lancer les tests avec une fixture par exemple bdd.
https://supports.uptime-formation.fr/all_content/ 82/210
14/11/2022 17:46 Exporter tout le contenu
Maintenant que l’attribut cartes n’est plus censé être accessible hors de la classe, nous avons besoin d’un nouvelle méthode pour accéder à une
carte du paquet depuis le programme principal. Implémentez la méthode spéciale __getitem__ pour pouvoir accéder à une carte avec
mon_paquet[position]. Tester la dans le programme principal.
Notre Paquet ressemble maintenant beaucoup à une véritable liste python. Essayez dans le main.py d’utiliser la méthode shuffle classique de
Python pour mélanger un paquet de carte : Il manque quelque chose.
Dans l’interpréteur (python3 ou ipython3) affichez la liste des méthode de la classe paquet en utilisant dir(). Les méthodes en python sont
assignées dynamiquement aux classes et peuvent être modifiées au fur et à mesure du programme. Ajoutons une méthode __setitem__
directement depuis l’interpréteur (démo). Affichez à nouveau le dictionnaire dir() de mon_paquet pour voir la nouvelle méthode ajoutée.
Ajoutez maintenant __setitem__ dans le code de Paquet. Supprimez et remplacez la méthode melanger par shuffle dans le code du projet.
Ajoutez à carte.py une classe IterateurDeCarte pour générer la suite des cartes à partir d’un objet carte.
1. D’abord créez la classe IterateurDeCarte qui prend en argument une Carte à la création et qui possède des méthodes __next__(self) qui
retourne la carte suivante dans l’ordre des cartes et __iter__ qui lui permet de se renvoyer lui même pour continuer l’itération.
2. Ajoutez une méthode __iter__ à la classe carte qui renvoie un itérateur basée sur la carte courante.
Ajoutez un paramètre facultatif carte_de_départ au contructeur de paquet pour commencer la génération du paquet à partie d’une carte du milieu
de la série de carte possible.
Modifiez le constructeur de la classe Carte pour qu’elle prenne en argument des valeurs et couleurs possibles qui ne soit pas les valeurs classique.
Testez cette fonctionnalité dans main.py en générant un jeu de “UNO” (sans les cartes “Joker” noire) à la place d’un jeu classique.
Modifiez l’itérateur de carte pour qu’il se base sur un générateur de carte infini utilisant les nombre de la suite de fibonacci et les quatre couleurs
du UNO. (Voir correction de fibonacci dans la partie 1)
https://supports.uptime-formation.fr/all_content/ 83/210
14/11/2022 17:46 Exporter tout le contenu
Récupérez avec git clone le projet de base à l’adresse https://github.com/e-lie/python202011-exercice-fancy-ops.git. Ouvrez le dans VSCode.
Créez un environnement virtuel python3 dans un dossier venv pour travailler de façon isolée des autres projets et de l’environnement python du
système: virtualenv -p python3 venv.
Activez l’environnement dans votre terminal courant : source ./venv/bin/activate (deactivate pour desactiver l’environnement).
Observer les fonctions de calculs présentes dans fancy_operations.py. Créez un script cli_calculator.py qui importe ces trois fonctions et les
utilise pour faire des calculs simples.
Essayez de debugger le script dans VSCode (normalement la configuration de debug est déjà présente dans car fournit dans le fichier
.vscode/launch.json du projet).
En vous inspirant du cours et de la documentation de docopt utilisez cette librairie pour faire en sorte que cli_calculator listops affiche la liste
des operations disponibles dans fancy_operations.py. On pourra pour cela ajouter dans fancy_operations.py un dictionnaire fancy_operations
répertoriant les operations au format { 'add': fancy_add, ... }.
Créez un dossier computation_libs pour la librairie à la racine du projet. À l’intérieur créer un sous dossier fancy_int_operations pour ranger
nos fonctions.
Déplacez et rangez les fonctions fancy_add, fancy_product et le dictionnaire fancy_operations à la racine de fancy_int_operations dans un
fichier __init__.py de façon à pouvoir les importer dans cli_calculator.py sous la forme from computation_libs.fancy_int_operations import
fancy_add, fancy_product, fancy_operations.
vector2d
`computation_libs/vector2d.py`
Documentez cette classe grâce à un doctype contenant le texte suivant A 2-dimensional vector class from the fluent python book chapter 9.
Gérer les mauvaises entrées utilisateurs grâce à un try: ... except:. On pourra afficher un message d’erreur tel que Bad operation or operand
(should be integers) et finir le script en erreur grâce à exit(1).
Ajoutez la librairie web flask aux dépendances du projet et installez la avec pip.
web_app = Flask(__name__)
@web_app.route('/')
def index():
Testez l’application avec flask run ou le lancement VSCode Webcalculator puis visitez http://localhost:5000 dans votre navigateur.
Maintenant que cette application minimale fonction une bonne pratique est d’en faire un package:
Créez un package web_app initialisant une application flask quand on l’importe avec le code :
web_app = Flask(__name__)
Créez un fichier routes.py dans le package avec notre route index et en important correctement les modules nécessaires.
Déplacez le dossier templates dans le package également et gardez dans web_calculator.py uniquement from web_app import web_app.
https://supports.uptime-formation.fr/all_content/ 84/210
14/11/2022 17:46 Exporter tout le contenu
Créez une seconde route def compute(operation, int_n, int_m): en mode GET avec comme url /<operation>/<int_n>/<int_m> qui
utilisez la librairie fancy_int_operations pour effectuer des opérations sur des entier int_n et int_m
utilise le template jinja operation.html pour afficher le résultat
on pourra bien sur debugger l’application dans VSCode ou avec ipdb pour bien comprendre l’exécution et trouver les erreurs.
Testez votre application dans le navigateur.
Pour utiliser la librairie computation_libs.fancy_int_operations nous avons du déplacer le package à l’intérieur de web_app pour le rendre accessible à
l’application web. Notre cli_calculator ne fonctionne plus du coup.
La bonne méthode pour travailler avec des packages indépendants consiste à créer un paquet pip “editable” à partir de notre package:
remettez computation_libs à la racine du projet.
ajoutez dans computation_libs un fichier de packaging setup.py utilisé par setuptools pour packer notre librairie.
mettez à l’intérieur:
Gérez les mauvaises entrées utilisateur avec un try: except: renvoyant le cas échéant vers le template invalid.html. Testez.
Correction:
La correction finale est dans la branche correction_finale du dépôt visible sur github ici
Concrètement, ceci peut correspondre à des éléments d’interface graphique, des capteurs de surveillances (informatique ou physique), des systemes de
logs, ou encore des comptes sur des médias sociaux lorsqu’ils postent de nouveaux messages.
Nous proposons d’appliquer ce patron de conception pour créer un système avec des journaux / chaines youtube (observables, qui publient des articles /
videos) auxquels peuvent souscrire des personnes.
Ajoutons une méthode publish à la classe Channel qui permet d’ajouter une vidéo à la liste de vidéo de la chaíne. Chaque vidéo correspondra
uniquement à un titre et une date de publication (gérée avec la librairie datetime). Lorsque la méthode publish est appellée, elle déclenche aussi
notifySubscribers.
La méthode actualiser de la classe User s’occupe de parcourir toutes les chaines auxquelles le compte est abonné, et de récupérer le titre des 3
vidéos les plus récentes parmis toutes ses chaines. Ces 3 titres (et le nom du channel associé!) sont ensuite écris dans
latest_videos_for_{username}.txt.
arte = Channel("ARTE")
alice = User("alice")
bob = User("bob")
charlie = User("charlie")
arte.subscribe(alice)
cestpassorcier.subscribe(alice)
cestpassorcier.subscribe(bob)
videodechat.subscribe(bob)
videodechat.subscribe(charlie)
https://supports.uptime-formation.fr/all_content/ 85/210
14/11/2022 17:46 Exporter tout le contenu
Maintenant que l’attribut cartes n’est plus censé être accessible hors de la classe, nous avons besoin d’un nouvelle méthode pour accéder à une
carte du paquet depuis le programme principal. Implémentez la méthode spéciale __getitem__ pour pouvoir accéder à une carte avec
mon_paquet[position]. Tester la dans le programme principal.
Notre Paquet ressemble maintenant beaucoup à une véritable liste python. Essayez dans le main.py d’utiliser la méthode shuffle classique de
Python pour mélanger un paquet de carte : Il manque quelque chose.
Dans l’interpréteur (python3 ou ipython3) affichez la liste des méthode de la classe paquet en utilisant dir(). Les méthodes en python sont
assignées dynamiquement aux classes et peuvent être modifiées au fur et à mesure du programme. Ajoutons une méthode __setitem__
directement depuis l’interpréteur (démo). Affichez à nouveau le dictionnaire dir() de mon_paquet pour voir la nouvelle méthode ajoutée.
Ajoutez maintenant __setitem__ dans le code de Paquet. Supprimez et remplacez la méthode melanger par shuffle dans le code du projet.
`carte.py`
`paquet.py`
`main.py`
Ajoutez à carte.py une classe IterateurDeCarte pour générer la suite des cartes à partir d’un objet carte.
1. D’abord créez la classe IterateurDeCarte qui prend en argument une Carte à la création et qui possède des méthodes __next__(self) qui
retourne la carte suivante dans l’ordre des cartes et __iter__ qui lui permet de se renvoyer lui même pour continuer l’itération.
2. Ajoutez une méthode __iter__ à la classe carte qui renvoie un itérateur basée sur la carte courante.
Ajoutez un paramètre facultatif carte_de_départ au contructeur de paquet pour commencer la génération du paquet à partie d’une carte du milieu
de la série de carte possible.
Modifiez le constructeur de la classe Carte pour qu’elle prenne en argument des valeurs et couleurs possibles qui ne soit pas les valeurs classique.
Testez cette fonctionnalité dans main.py en générant un jeu de “UNO” (sans les cartes “Joker” noire) à la place d’un jeu classique.
`carte.py`
`paquet.py`
`main.py`
Modifiez l’itérateur de carte pour qu’elle se base sur un générateur de carte aléatoire infini.
Concrètement, ceci peut correspondre à des éléments d’interface graphique, des capteurs de surveillances (informatique ou physique), des systemes de
logs, ou encore des comptes sur des médias sociaux lorsqu’ils postent de nouveaux messages.
Nous proposons d’appliquer ce patron de conception pour créer un système avec des journaux / chaines youtube (observables, qui publient des articles /
videos) auxquels peuvent souscrire des personnes.
Ajoutons une méthode publish à la classe Channel qui permet d’ajouter une vidéo à la liste de vidéo de la chaíne. Chaque vidéo correspondra
uniquement à un titre et une date de publication (gérée avec la librairie datetime). Lorsque la méthode publish est appellée, elle déclenche aussi
notifySubscribers.
La méthode actualiser de la classe User s’occupe de parcourir toutes les chaines auxquelles le compte est abonné, et de récupérer le titre des 3
vidéos les plus récentes parmis toutes ses chaines. Ces 3 titres (et le nom du channel associé!) sont ensuite écris dans
latest_videos_for_{username}.txt.
arte = Channel("ARTE")
alice = User("alice")
bob = User("bob")
charlie = User("charlie")
arte.subscribe(alice)
cestpassorcier.subscribe(alice)
cestpassorcier.subscribe(bob)
videodechat.subscribe(bob)
videodechat.subscribe(charlie)
correction
Introduction à Flask
Présentation
Une application web
Cross-platform
Mise à jour simple
Au niveau technique : distinction plus évidente entre le front et le back-end ?
Plus de possibilité et de flexibilité cosmétiques
Cons:
https://supports.uptime-formation.fr/all_content/ 87/210
14/11/2022 17:46 Exporter tout le contenu
Flask
Flask
En quelques mots
Vues gérées avec Jinja (moteur de template avec une syntaxe “à la Python”)
Controleurs gérés avec Werkzeug (une URL <-> une fonction)
Modèles gérées avec SQLAlchemy (ORM : une classe <-> une table SQL)
Pour des applications plus grosses, on préferera tout même Django qui est un framework plus complet (mais plus complexe) mais qui suis la même
logique
Virtualenv
Environnement virtuel
Isoler des paquets / dépendances pour utiliser des versions spécifiques
# La premiere fois :
source venv/bin/activate
# Installation de dependances
deactivate
source venv/bin/activate
app = Flask(__name__)
@app.route('/')
def hello_world():
Mon controleur hello_world() doit renvoyer du texte ou une “HTTP response” (par exemple, erreur 404, ou redirection, …)
$ export FLASK_APP=hello.py
$ flask run
* Running on http://127.0.0.1:5000/
ensuite, je visite:
app = Flask(__name__)
@app.route('/')
def hello_world():
@app.route('/python')
def python():
ensuite :
https://supports.uptime-formation.fr/all_content/ 88/210
14/11/2022 17:46 Exporter tout le contenu
Créer des vues avec Jinja
Un template ressemble à :
<html>
Bonjour {{ prenom }} !
{% endfor %}
</html>
prenom = "Sacha"
Rendu :
<html>
Bonjour Sacha !
</html>
En supposant que le template précédent soit situé dans templates/hello.html, je peux utiliser render_template dans mon controleur générer un rendu à
l’aide de mes données
@app.route('/')
def homepage():
return render_template('hello.html',
name="Sacha",
apps=apps)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///./db.sqlite'
db = SQLAlchemy()
db.init_app(app)
class App(db.Model):
id = db.Column(db.Integer, primary_key=True)
db.drop_all()
db.create_all()
Ecrire
# Creer et ajouter une app dans la database...
db.session.add(mailman)
db.session.commit()
Lire
# Trouver toutes les apps..
App.query.all()
App.query.filter_by(level=7).all()
App.query.filter_by(name="mailman").first()
https://supports.uptime-formation.fr/all_content/ 89/210
14/11/2022 17:46 Exporter tout le contenu
Dans un controleur
from flask import render_template
@app.route('/')
def homepage():
apps = App.query.all()
return render_template('hello.html',
prenom="Sacha",
apps=apps)
@app.route('/add', methods=['POST','GET'])
def add():
@app.route('/redirect')
return redirect('/')
L'architecture MVC
L’architecture MVC
Pour résumer:
D’abord un utilisateur envoie une requête pour voir une page en entrant une URL
Cette requête est reçue par le Controleur
Le Controleur utilise le modèle pour trouver toutes les données dont il a besoin
Puis envoie les données à la Vue qui rend une page web
https://supports.uptime-formation.fr/all_content/ 90/210
14/11/2022 17:46 Exporter tout le contenu
Bibliographie
Livres
Apprendre la programmation avec Python 3 (plutôt complet et orienté débutant)
Fluent Python (Ce que pythonique veut dire, comment utiliser Python proprement)
Serious Python (problématiques avancées de développement)
Tutoriels
Flask Mega Tutorial très long et développé : tutoriel pour coder une application web d’assez grande taille en Python de façon réaliste et illustrant
pleins de point du travail de développeur et d’architecture d’application Python :
Bases de données
Structuration en package
Testing
Distribution et déploiement de l’application
Articles
https://www.infoq.com/news/2012/05/DRY-code-duplication-coupling/
https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction
Sites de références
https://python.org
https://stackoverflow.com
https://devdocs.io
Évènements Python
PyconFr: https://www.pycon.fr/2020/
Cherchez des RDV python près de chez vous.
Madhu,Akash2017 - Security automation with Ansible 2 - Packt
Docker
Module 2
Docker
Créer et manipuler des conteneurs
Introduction DevOps
0 - Introduction à Docker
1 - Manipulation des conteneurs
TP 1 - Installer Docker et jouer avec
2 - Images et conteneurs
TP 2 - Images et conteneurs
3 - Volumes et réseaux
TP 3 - Réseaux
TP 3bis - Volumes
4 - Créer une application multiconteneur
TP 4 - Créer une application multiconteneur
5 - Orchestration et clustering
TP 5 - Orchestration et clustering
Conclusion
TP 6 (bonus) - Intégration continue avec Gitlab
TP 7 (bonus) - Docker et les reverse proxies
QCM Docker
Bibliographie
Introduction DevOps
A propos de moi
A propos de vous
Attentes ?
Début du cursus :
Est-ce que ça vous plait ?
Quels modules avez vous déjà fait ?
Le mouvement DevOps
Le DevOps est avant tout le nom d’un mouvement de transformation professionnelle et technique de l’informatique.
https://supports.uptime-formation.fr/all_content/ 91/210
14/11/2022 17:46 Exporter tout le contenu
Ce mouvement se structure autour des solutions humaines (organisation de l’entreprise et des équipes) et techniques (nouvelles technologies de
rupture) apportées pour répondre aux défis que sont:
L’agrandissement rapide face à la demande des services logiciels et infrastructures les supportant.
La célérité de déploiement demandée par le développement agile (cycles journaliers de développement).
Difficultées à organiser des équipes hétérogènes de grande taille et qui s’agrandissent très vite selon le modèle des startups.et
Du côté humain:
Application des process de management agile aux opérations et la gestion des infrastructures (pour les synchroniser avec le développement).
Remplacement des procédés d’opérations humaines complexes et spécifiques par des opérations automatiques et mieux standardisées.
Réconciliation de deux cultures divergentes (Dev et Ops) rapprochant en pratique les deux métiers du développeur et de l’administrateur système.
Du côté technique:
0 - Introduction à Docker
Modularisez et maîtrisez vos applications
Introduction
La métaphore docker : “box it, ship it”
Une abstraction qui ouvre de nouvelles possibilités pour la manipulation logicielle.
https://supports.uptime-formation.fr/all_content/ 92/210
14/11/2022 17:46 Exporter tout le contenu
conteneur : un découpage dans Linux pour séparer des ressources (accès à des dossiers spécifiques sur le disque, accès réseau).
Les deux technologies peuvent utiliser un système de quotas pour l’accès aux ressources matérielles (accès en lecture/écriture sur le disque, sollicitation
de la carte réseau, du processeur)
L’imaginer comme une “boîte” est donc une allégorie un peu trompeuse, car ce n’est pas de la virtualisation (= isolation au niveau matériel).
Implémenté principalement par le programme chroot [change root : changer de racine], présent dans les systèmes UNIX depuis longtemps (1979
!) :
“Comme tout est fichier, changer la racine d’un processus, c’est comme le faire changer de système”.
jail
jailest introduit par FreeBSD en 2002 pour compléter chroot et qui permet pour la première fois une isolation réelle (et sécurisée) des
processus.
chroot ne s’occupait que de l’isolation d’un process par rapport au système de fichiers :
En 2005, Sun introduit les conteneurs Solaris décrits comme un « chroot sous stéroïdes » : comme les jails de FreeBSD
groupes séparés auxquels on donne un nom, d’ensembles de choses sur lesquelles on colle une étiquette
on parle aussi de contextes
Pour Linux, ce concept est repris via la mise en place de namespaces Linux
Les conteneurs ne sont finalement que plein de fonctionnalités Linux saucissonnées ensemble de façon cohérente.
Les namespaces correspondent à autant de types de compartiments nécessaires dans l’architecture Linux pour isoler des processus.
Les namespaces PID : “fournit l’isolation pour l’allocation des identifiants de processus (PIDs), la liste des processus et de leurs détails. Tandis
que le nouvel espace de nom est isolé de ses adjacents, les processus dans son espace de nommage « parent » voient toujours tous les processus
dans les espaces de nommage enfants — quoique avec des numéros de PID différent.”
Network namespace : “isole le contrôleur de l’interface réseau (physique ou virtuel), les règles de pare-feu iptables, les tables de routage, etc.”
Mount namespace : “permet de créer différents modèles de systèmes de fichiers, ou de créer certains points de montage en lecture-seule”
User namespace : isolates the user IDs between namespaces (dernière pièce du puzzle)
https://supports.uptime-formation.fr/all_content/ 93/210
14/11/2022 17:46 Exporter tout le contenu
“UTS” namespace : permet de changer le nom d’hôte.
IPC namespace : isole la communication inter-processus entre les espaces de nommage.
usage de la mémoire
du disque
du réseau
des appels système
du processeur (CPU)
En 2005, Google commence le développement des cgroups : une façon de tagger les demandes de processeur et les appels systèmes pour les
grouper et les isoler.
:(){ : | :& }; :
Ceci est une fork bomb. Dans un conteneur non privilégié, on bloque tout Docker, voire tout le système sous-jacent, en l’empêchant de créer de
nouveaux processus.
Pour éviter cela il faudrait limiter la création de processus via une option kernel.
1 container
= 1 groupe de process Linux
les cgroups
le chroot
les namespaces.
Originellement, Docker était basé sur LXC. Il a depuis développé son propre assemblage de ces 3 mécanismes.
Docker et LXC
En 2013, Docker commence à proposer une meilleure finition et une interface simple qui facilite l’utilisation des conteneurs LXC.
Puis il propose aussi son cloud, le Docker Hub pour faciliter la gestion d’images toutes faites de conteneurs.
Au fur et à mesure, Docker abandonne le code de LXC (mais continue d’utiliser le chroot, les cgroups et namespaces).
Le code de base de Docker (notamment runC) est open source : l'Open Container Initiative vise à standardiser et rendre robuste l’utilisation de
containers.
Vitesse d’exécution.
Flexibilité sur les ressources (mémoire partagée).
Moins complexe que la virtualisation
Plus standard que les multiples hyperviseurs
notamment moins de bugs d’interaction entre l’hyperviseur et le noyau
même si une faille dans l’hyperviseur reste possible car l’isolation n’est pas qu’uniquement matérielle
se baser sur l'immutabilité : la configuration d’un conteneur n’est pas faite pour être modifiée après sa création.
avoir un cycle de vie court -> logique DevOps du “bétail vs. animal de compagnie”
rapproche le monde du développement des opérations (tout le monde utilise la même technologie)
Permet l’adoption plus large de la logique DevOps (notamment le concept d’infrastructure as code)
Infrastructure as Code
Résumé
on décrit en mode code un état du système. Avantages :
pas de dérive de la configuration et du système (immutabilité)
on peut connaître de façon fiable l’état des composants du système
on peut travailler en collaboration plus facilement (grâce à Git notamment)
on peut faire des tests
on facilite le déploiement de nouvelles instances
La simplicité d’usage et le travail de standardisation (un conteneur Docker est un conteneur OCI : format ouvert standardisé par l’Open
Container Initiative) lui garantissent légitimité et fiabilité
La logique du conteneur fonctionne, et la bonne documentation et l’écosystème aident !
LXC existe toujours et est très agréable à utiliser, notamment avec LXD (développé par Canonical, l’entreprise derrière Ubuntu).
Il a cependant un positionnement différent : faire des conteneurs pour faire tourner des OS Linux complets.
https://supports.uptime-formation.fr/all_content/ 95/210
14/11/2022 17:46 Exporter tout le contenu
Apache Mesos : un logiciel de gestion de cluster qui permet de se passer de Docker, mais propose quand même un support pour les conteneurs
OCI (Docker) depuis 2016.
Podman : une alternative à Docker qui utilise la même syntaxe que Docker pour faire tourner des conteneurs OCI (Docker) qui propose un mode
rootless et daemonless intéressant.
Un volume : un espace virtuel pour gérer le stockage d’un conteneur et le partage entre conteneurs.
un registry : un serveur ou stocker des artefacts docker c’est à dire des images versionnées.
un orchestrateur : un outil qui gère automatiquement le cycle de vie des conteneurs (création/suppression).
L’écosystème Docker
Docker Compose : Un outil pour décrire des applications multiconteneurs.
Docker Machine : Un outil pour gérer le déploiement Docker sur plusieurs machines depuis un hôte.
Docker Hub : Le service d’hébergement d’images proposé par Docker Inc. (le registry officiel)
L’environnement de développement
Docker Engine pour lancer des commandes docker
https://supports.uptime-formation.fr/all_content/ 96/210
14/11/2022 17:46 Exporter tout le contenu
Pour développer et déployer, il marche parfaitement sur MacOS et Windows mais avec une méthode de virtualisation :
virtualisation optimisée via un hyperviseur
ou virtualisation avec logiciel de virtualisation “classique” comme VMWare ou VirtualBox.
Fonctionne avec Windows Subsystem for Linux : c’est une VM Linux très bien intégrée à Windows
On peut l’installer avec le gestionnaire de paquets de l’OS mais cette version peut être trop ancienne.
Sur Ubuntu ou CentOS la méthode conseillée est d’utiliser les paquets fournis dans le dépôt officiel Docker (vous pouvez avoir des surprises
avec la version snap d’Ubuntu).
Il faut pour cela ajouter le dépôt et les signatures du répertoire de packages Docker.
Documentation Ubuntu : https://docs.docker.com/install/linux/docker-ce/ubuntu/
Docker possède à la fois un module pour lancer les
applications (runtime) et un outil de build d’application.
docker images
docker image ls
https://supports.uptime-formation.fr/all_content/ 97/210
14/11/2022 17:46 Exporter tout le contenu
Les conteneurs
Un conteneur est une instance en cours de fonctionnement (“vivante”) d’une image.
un conteneur en cours de fonctionnement est un processus (et ses processus enfants) qui tourne dans le Linux hôte (mais qui est isolé de
celui-ci)
Commandes Docker
Docker fonctionne avec des sous-commandes et propose de grandes quantités d’options pour chaque commande.
docker info # affiche plein d'information sur l'engine avec lequel vous êtes en contact
Docker Engine
Linux
Images
alpine hello-world
Commandes Docker
Le démarrage d’un conteneur est lié à une commande.
Pour utiliser une commande on peut simplement l’ajouter à la fin de la commande run.
https://supports.uptime-formation.fr/all_content/ 98/210
14/11/2022 17:46 Exporter tout le contenu
docker run debian echo 'attendre 10s' && sleep 10 # s'arrête après 10s
Plus précisément, un conteneur est lié à un système de fichiers (avec des dossiers /bin, /etc, /var, des exécutables, des fichiers…), et possède des
métadonnées (stockées en json quelque part par Docker)
Les utilisateurs Unix à l’intérieur du conteneur ont des UID et GID qui existent classiquement sur l’hôte mais ils peuvent correspondre à un
utilisateur Unix sans droits sur l’hôte si on utilise les user namespaces.
Introspection de conteneur
La commande docker exec permet d’exécuter une commande à l’intérieur du conteneur s’il est lancé.
Une utilisation typique est d’introspecter un conteneur en lançant bash (ou sh).
Dans ce contexte un élément qui a fait le succès de Docker est le Docker Hub : hub.docker.com
Il s’agit d’un répertoire public et souvent gratuit d’images (officielles ou non) pour des milliers d’applications pré-configurées.
Docker Hub:
On peut y chercher et trouver presque n’importe quel logiciel au format d’image Docker.
La partie compte est le compte de la personne qui a poussé ses images sur le Docker Hub. Les images Docker officielles (ubuntu par exemple) ne
sont pas liées à un compte : on peut écrire simplement ubuntu:focal.
On peut également y créer un compte gratuit pour pousser et distribuer ses propres images, ou installer son propre serveur de distribution d’images
privé ou public, appelé registry.
En résumé
https://supports.uptime-formation.fr/all_content/ 99/210
14/11/2022 17:46 Exporter tout le contenu
Pour accéder au copier-coller de Guacamole, il faut appuyer sur Ctrl+Alt+Shift et utiliser la zone de texte qui s’affiche (réappuyer sur
Ctrl+Alt+Shift pour revenir à la VM).
Pour installer Docker, suivez la documentation officielle pour installer Docker sur Ubuntu, depuis “Install using the repository” jusqu’aux deux
commandes sudo apt-get update et sudo apt-get install docker-ce docker-ce-cli containerd.io.
Docker nous propose aussi une installation en une ligne (one-liner), moins sécurisée : curl -sSL https://get.docker.com | sudo sh
Lancez sudo docker run hello-world. Bien lire le message renvoyé (le traduire sur Deepl si nécessaire). Que s’est-il passé ?
Il manque les droits pour exécuter Docker sans passer par sudo à chaque fois.
Pour les prochaines fois, Docker nous propose aussi une installation en une ligne (one-liner) : curl -sSL https://get.docker.com | sudo sh
Autocomplétion
Pour vous faciliter la vie, ajoutez le plugin autocomplete pour Docker et Docker Compose à bash en copiant les commandes suivantes :
Important: Vous pouvez désormais appuyer sur la touche pour utiliser l’autocomplétion quand vous écrivez des commandes Docker
docker info # affiche plein d'information sur l'engine avec lequel vous êtes en contact
Manipuler un conteneur
Commandes utiles : https://devhints.io/docker
Documentation docker run : https://docs.docker.com/engine/reference/run/
https://supports.uptime-formation.fr/all_content/ 100/210
14/11/2022 17:46 Exporter tout le contenu
Mentalité :
Il faut aussi prendre l’habitude de bien lire ce que la console indique après avoir
passé vos commandes.
Avec l’aide du support et de --help, et en notant sur une feuille ou dans un fichier texte les commandes utilisées :
Résultat :
Lancez un conteneur Debian (docker run puis les arguments nécessaires, cf. l’aide --help)n avec l’option “mode détaché” et la commande passée
au conteneur echo "Je suis le conteneur basé sur Debian". Rien n’apparaît. En effet en mode détaché la sortie standard n’est pas connectée au
terminal.
Lancez docker logs avec le nom ou l’id du conteneur. Vous devriez voir le résultat de la commande echo précédente.
Résultat :
Solution :
Solution :
NB: On peut désigner un conteneur soit par le nom qu’on lui a donné, soit par le nom généré automatiquement, soit par son empreinte (toutes ces
informations sont indiquées dans un docker ps ou docker ps -a). L’autocomplétion fonctionne avec les deux noms.
Trouvez comment vous débarrasser d’un conteneur récalcitrant (si nécessaire, relancez un conteneur avec la commande sleep 3600 en mode
détaché).
Solution :
Solution :
Le nom d’un conteneur doit être unique (à ne pas confondre avec le nom de l’image qui est le modèle utilisé à partir duquel est créé le conteneur).
Lancez un conteneur debian en mode interactif (options -i -t) avec la commande /bin/bash et le nom debian_interactif.
Explorer l’intérieur du conteneur : il ressemble à un OS Linux Debian normal.
https://supports.uptime-formation.fr/all_content/ 101/210
14/11/2022 17:46 Exporter tout le contenu
Lancez un conteneur Nginx. Notez que lorsque l’image est déjà téléchargée le lancement d’un conteneur est quasi instantané.
Ce conteneur n’est pas très utile, car on a oublié de configurer un port exposé sur localhost.
Trouvez un moyen d’accéder quand même au Nginx à partir de l’hôte Docker (indice : quelle adresse IP le conteneur possède-t-il ?).
Solution :
En visitant l’adresse et le port associé au conteneur Nginx, on doit voir apparaître des logs Nginx dans son terminal car on a lancé le conteneur en
mode attach.
Supprimez ce conteneur. NB : On doit arrêter un conteneur avant de le supprimer, sauf si on utilise l’option “-f”.
On peut lancer des logiciels plus ambitieux, comme par exemple Funkwhale, une sorte d’iTunes en web qui fait aussi réseau social :
Vous pouvez visiter ensuite ce conteneur Funkwhale sur le port 80 (après quelques secondes à suivre le lancement de l’application dans les logs) ! Mais
il n’y aura hélas pas de musique dedans :(
Attention à ne jamais lancer deux containers connectés au même port sur l’hôte, sinon cela échouera !
Supprimons ce conteneur :
docker rm -f funky_conteneur
Nous pouvons accéder au Wordpress, mais il n’a pas encore de base MySQL configurée. Ce serait un peu dommage de configurer cette base de données
à la main. Nous allons configurer cela à partir de variables d’environnement et d’un deuxième conteneur créé à partir de l’image mysql.
Depuis Ubuntu:
Il va falloir mettre ces deux conteneurs dans le même réseau (nous verrons plus tarde ce que cela implique), créons ce réseau :
docker network create wordpress
Utilisons des variables d’environnement pour préciser le mot de passe root, le nom de la base de données et le nom d’utilisateur de la base de
données (trouver la documentation sur le Docker Hub).
Résultat :
Faites de même avec la documentation sur le Docker Hub pour préconfigurer l’app Wordpress.
En plus des variables d’environnement, il va falloir le mettre dans le même réseau, et exposer un port
Solution :
visitez votre app Wordpress et terminez la configuration de l’application : si les deux conteneurs sont bien configurés, on ne devrait pas avoir à
configurer la connexion à la base de données
avec docker exec, visitez votre conteneur Wordpress. Pouvez-vous localiser le fichier wp-config.php ? Une fois localisé, utilisez docker cp pour
le copier sur l’hôte.
Faire du ménage
Il est temps de faire un petit docker stats pour découvrir l’utilisation du CPU et de la RAM de vos conteneurs !
Combinez cette commande avec docker rm pour supprimer tous les conteneurs arrêtés (indice : en Bash, une commande entre les parenthèses de
“$()” est exécutée avant et utilisée comme chaîne de caractère dans la commande principale)
Solution :
S’il y a encore des conteneurs qui tournent (docker ps), supprimez un des conteneurs restants en utilisant l’autocomplétion et l’option adéquate
https://supports.uptime-formation.fr/all_content/ 102/210
14/11/2022 17:46 Exporter tout le contenu
Décortiquer un conteneur
En utilisant la commande docker export votre_conteneur -o conteneur.tar, puis tar -C conteneur_decompresse -xvf conteneur.tar pour
décompresser un conteneur Docker, explorez (avec l’explorateur de fichiers par exemple) jusqu’à trouver l’exécutable principal contenu dans le
conteneur.
Portainer
Portainer est un portail web pour gérer une installation Docker via une interface graphique. Il va nous faciliter la vie.
-p 9000:9000 \
-v portainer_data:/data \
-v /var/run/docker.sock:/var/run/docker.sock \
portainer/portainer-ce
Remarque sur la commande précédente : pour que Portainer puisse fonctionner et contrôler Docker lui-même depuis l’intérieur du conteneur il est
nécessaire de lui donner accès au socket de l’API Docker de l’hôte grâce au paramètre --volume ci-dessus.
Visitez ensuite la page http://localhost:9000 ou l’adresse IP publique de votre serveur Docker sur le port 9000 pour accéder à l’interface.
Créez un conteneur.
2 - Images et conteneurs
Créer une image en utilisant un Dockerfile
Jusqu’ici nous avons utilisé des images toutes prêtes.
Une des fonctionnalités principales de Docker est de pouvoir facilement construire des images à partir d’un simple fichier texte : le Dockerfile.
En réalité c’est assez différent : il s’agit uniquement d’un système de fichier (par couches ou layers) et d’un manifeste JSON (des méta-données).
Les images sont créés en empilant de nouvelles couches sur une image existante grâce à un système de fichiers qui fait du union mount.
Chaque nouveau build génère une nouvelle image dans le répertoire des images (/var/lib/docker/images) (attention ça peut vite prendre
énormément de place)
On construit les images à partir d’un fichier Dockerfile en décrivant procéduralement (étape par étape) la construction.
Dockerfi les
Dockerfile: FROM alpine
RUN apk update && apk add nodejs
COPY . /app
WORKDIR /app
CMD ["node","index.js"]
WORKDIR /app
FROM alpine RUN apk COPY . /app CMD "node" "inde x.js"
+ + +
apk update host/index.js --> $ cd /app
apk add nodejs container/app $ node index.js
alpine
hello:v0.1
Exemple de Dockerfile :
https://supports.uptime-formation.fr/all_content/ 103/210
14/11/2022 17:46 Exporter tout le contenu
FROM debian:latest
généralement pour construire une image on se place directement dans le dossier avec le Dockerfile et les élements de contexte nécessaire
(programme, config, etc), le contexte est donc le caractère ., il est obligatoire de préciser un contexte.
Le Dockerfile est un fichier procédural qui permet de décrire l’installation d’un logiciel (la configuration d’un container) en enchaînant des
instructions Dockerfile (en MAJUSCULE).
Exemple:
FROM alpine:3.5
# upgrade pip
EXPOSE 5000
Instruction FROM
L’image de base à partir de laquelle est construite l’image actuelle.
Instruction RUN
Permet de lancer une commande shell (installation, configuration).
Instruction ADD
Permet d’ajouter des fichier depuis le contexte de build à l’intérieur du conteneur.
Généralement utilisé pour ajouter le code du logiciel en cours de développement et sa configuration au conteneur.
Instruction CMD
Généralement à la fin du Dockerfile : elle permet de préciser la commande par défaut lancée à la création d’une instance du conteneur avec
docker run. on l’utilise avec une liste de paramètres
Instruction ENTRYPOINT
Précise le programme de base avec lequel sera lancé la commande
ENTRYPOINT ["/usr/bin/python3"]
CMD et ENTRYPOINT
Ne surtout pas confondre avec RUN qui exécute une commande Bash uniquement pendant la construction de l’image.
Si l’on souhaite que notre container lance le même exécutable à chaque fois, alors on peut opter pour l’usage d'ENTRYPOINT en combination avec CMD.
Instruction ENV
https://supports.uptime-formation.fr/all_content/ 104/210
14/11/2022 17:46 Exporter tout le contenu
Une façon recommandée de configurer vos applications Docker est d’utiliser les variables d’environnement UNIX, ce qui permet une
configuration “au runtime”.
Instruction HEALTHCHECK
HEALTHCHECK permet de vérifier si l’app contenue dans un conteneur est en bonne santé.
Les variables
On peut utiliser des variables d’environnement dans les Dockerfiles. La syntaxe est ${...}.
Exemple :
FROM busybox
ENV FOO=/bar
Se référer au mode d’emploi pour la logique plus précise de fonctionnement des variables.
Documentation
Il existe de nombreuses autres instructions possibles très clairement décrites dans la documentation officielle :
https://docs.docker.com/engine/reference/builder/
Lancer la construction
La commande pour lancer la construction d’une image est :
Lors de la construction, Docker télécharge l’image de base. On constate plusieurs téléchargements en parallèle.
Il lance ensuite la série d’instructions du Dockerfile et indique un hash pour chaque étape.
On parle d'Union Filesystem car chaque couche (de fichiers) écrase la précédente.
docker image history <conteneur> permet d’afficher les layers, leur date de construction et taille respectives.
Au lancement d’un container, le Docker Engine rajoute une nouvelle couche de filesystem “normal” read/write par dessus la pile des couches de
l’image.
docker diff <container> permet d’observer les changements apportés au conteneur depuis le lancement.
Or, on construit souvent plusieurs dizaines de versions d’une application par jour (souvent automatiquement sur les serveurs d’intégration
continue).
https://supports.uptime-formation.fr/all_content/ 105/210
14/11/2022 17:46 Exporter tout le contenu
Le principe de Docker est justement d’avoir des images légères car on va créer beaucoup de conteneurs (un par instance d’application/service).
De plus on télécharge souvent les images depuis un registry, ce qui consomme de la bande passante.
La principale bonne pratique dans la construction d’images est de limiter leur taille au maximum.
Une image ubuntu complète pèse déjà presque une soixantaine de mégaoctets.
mais une image trop rudimentaire (busybox) est difficile à débugger et peu bloquer pour certaines tâches à cause de binaires ou de
bibliothèques logicielles qui manquent (compilation par exemple).
Souvent on utilise des images de base construites à partir de alpine qui est un bon compromis (6 mégaoctets seulement et un gestionnaire
de paquets apk).
Par exemple python3 est fourni en version python:alpine (99 Mo), python:3-slim (179 Mo) et python:latest (918 Mo).
Avec les multi-stage builds, on peut utiliser plusieurs instructions FROM dans un Dockerfile. Chaque instruction FROM utilise une base différente.
On
sélectionne ensuite les fichiers intéressants (des fichiers compilés par exemple) en les copiant d’un stage à un autre.
WORKDIR /go/src/github.com/alexellis/href-counter/
COPY app.go .
FROM alpine:latest
WORKDIR /root/
CMD ["./app"]
L’intérêt ensuite est que l’image est disponible préconfigurée pour construire ou mettre à jour une infrastructure, ou lancer plusieurs instances
(plusieurs containers) à partir de cette image.
C’est grâce à cette fonctionnalité que Docker peut être considéré comme un outil d'infrastructure as code.
On peut également prendre une sorte de snapshot du conteneur (de son système de fichiers, pas des processus en train de tourner) sous forme
d’image avec docker commit <image> et docker push.
On utilise alors docker login <adresse_repo> pour se logger au registry et le nom du registry dans les tags de l’image.
Exemples de registries :
Gitlab fournit un registry très intéressant car intégré dans leur workflow DevOps.
TP 2 - Images et conteneurs
Découverte d’une application web flask
Récupérez d’abord une application Flask exemple en la clonant :
https://supports.uptime-formation.fr/all_content/ 106/210
14/11/2022 17:46 Exporter tout le contenu
git clone https://github.com/uptime-formation/microblog/
Ouvrez VSCode avec le dossier microblog en tapant code microblog ou bien en lançant VSCode avec code puis en cliquant sur Open Folder.
Dans VSCode, vous pouvez faire Terminal > New Terminal pour obtenir un terminal en bas de l’écran.
Passons à Docker
Déployer une application Flask manuellement à chaque fois est relativement pénible. Pour que les dépendances de deux projets Python ne se perturbent
pas, il faut normalement utiliser un environnement virtuel virtualenv pour séparer ces deux apps.
Avec Docker, les projets sont déjà isolés dans des
conteneurs. Nous allons donc construire une image de conteneur pour empaqueter l’application et la manipuler plus facilement. Assurez-vous que
Docker est installé.
Pour connaître la liste des instructions des Dockerfiles et leur usage, se référer au manuel de référence sur les Dockerfiles.
Normalement, VSCode vous propose d’ajouter l’extension Docker. Il va nous faciliter la vie, installez-le. Une nouvelle icône apparaît dans la
barre latérale de gauche, vous pouvez y voir les images téléchargées et les conteneurs existants. L’extension ajoute aussi des informations utiles
aux instructions Dockerfile quand vous survolez un mot-clé avec la souris.
Ajoutez en haut du fichier : FROM ubuntu:latest Cette commande indique que notre image de base est la dernière version de la distribution
Ubuntu.
Le conteneur s’arrête immédiatement. En effet il ne contient aucune commande bloquante et nous n’avons précisé aucune commande au
lancement. Pour pouvoir observer le conteneur convenablement il fautdrait faire tourner quelque chose à l’intérieur. Ajoutez à la fin du fichier la
ligne :
CMD ["/bin/sleep", "3600"]
Cette ligne indique au conteneur d’attendre pendant 3600 secondes comme au TP précédent.
Nous allons maintenant rentrer dans le conteneur en ligne de commande pour observer. Utilisez la commande : docker exec -it
<id_du_conteneur> /bin/bash
Vous êtes maintenant dans le conteneur avec une invite de commande. Utilisez quelques commandes Linux pour le visiter rapidement (ls, cd…).
Il s’agit d’un Linux standard, mais il n’est pas conçu pour être utilisé comme un système complet, juste pour une application isolée. Il faut
maintenant ajouter notre application Flask à l’intérieur. Dans le Dockerfile supprimez la ligne CMD, puis ajoutez :
RUN apt-get update -y
Pour installer les dépendances python et configurer la variable d’environnement Flask ajoutez:
Ensuite, copions le code de l’application à l’intérieur du conteneur. Pour cela ajoutez les lignes :
COPY ./ /microblog
WORKDIR /microblog
Cette première ligne indique de copier tout le contenu du dossier courant sur l’hôte dans un dossier /microblog à l’intérieur du conteneur.
Nous n’avons
pas copié les requirements en même temps pour pouvoir tirer partie des fonctionnalités de cache de Docker, et ne pas avoir à retélécharger les
dépendances de l’application à chaque fois que l’on modifie le contenu de l’app.
Reconstruisez votre image. Observons que le build recommence à partir de l’instruction modifiée. Les layers précédents avaient été mis en
cache par le Docker Engine.
Enfin, ajoutons la section de démarrage à la fin du Dockerfile, c’est un script appelé boot.sh :
CMD ["./boot.sh"]
Reconstruisez l’image et lancez un conteneur basé sur l’image en ouvrant le port 5000 avec la commande : docker run -p 5000:5000 microblog
Lancez un deuxième container cette fois avec : docker run -d -p 5001:5000 microblog
Une deuxième instance de l’app est maintenant en fonctionnement et accessible à l’adresse localhost:5001
Docker Hub
https://supports.uptime-formation.fr/all_content/ 107/210
14/11/2022 17:46 Exporter tout le contenu
Avec docker login, docker tag et docker push, poussez l’image microblog sur le Docker Hub. Créez un compte sur le Docker Hub le cas
échéant.
Solution :
Améliorer le Dockerfile
Une image plus simple
A l’aide de l’image python:3.9-alpine et en remplaçant les instructions nécessaires (pas besoin d’installer python3-pip car ce programme est
désormais inclus dans l’image de base), repackagez l’app microblog en une image taggée microblog:slim ou microblog:light. Comparez la taille
entre les deux images ainsi construites.
Le serveur de développement Flask est bien pratique pour debugger en situation de développement, mais n’est pas adapté à la production.
Nous
pourrions créer deux images pour les deux situations mais ce serait aller contre l’impératif DevOps de rapprochement du dev et de la prod.
Pour démarrer l’application, nous avons fait appel à un script de boot boot.sh avec à l’intérieur :
#!/bin/bash
# ...
set -e
else
fi
Déclarez maintenant dans le Dockerfile la variable d’environnement CONTEXT avec comme valeur par défaut PROD.
Puis, grâce aux bons arguments allant avec docker run, lancez une instance de l’app en configuration PROD et une instance en environnement DEV
(joignables sur deux ports différents).
Avec docker ps ou en lisant les logs, vérifiez qu’il existe bien une différence dans le programme lancé.
Exposer le port
Ajoutons l’instruction EXPOSE 5000 pour indiquer à Docker que cette app est censée être accédée via son port 5000.
NB : Publier le port grâce à l’option -p port_de_l-hote:port_du_container reste nécessaire, l’instruction EXPOSE n’est là qu’à titre de
documentation de l’image.
Dockerfile amélioré
`Dockerfile` final :
L’instruction HEALTHCHECK
HEALTHCHECK permet de vérifier si l’app contenue dans un conteneur est en bonne santé.
Dans un nouveau dossier ou répertoire, créez un fichier Dockerfile dont le contenu est le suivant :
FROM python:alpine
WORKDIR /app
EXPOSE 5000
healthy = True
app = Flask(__name__)
@app.route('/health')
def health():
global healthy
if healthy:
else:
@app.route('/kill')
def kill():
global healthy
healthy = False
https://supports.uptime-formation.fr/all_content/ 108/210
14/11/2022 17:46 Exporter tout le contenu
if __name__ == "__main__":
app.run(host="0.0.0.0")
Observez bien le code Python et la ligne HEALTHCHECK du Dockerfile puis lancez l’app. A l’aide de docker ps, relevez où Docker indique la santé
de votre app.
Visitez l’URL /kill de votre app dans un navigateur. Refaites docker ps. Que s’est-il passé ?
Solution :
Inspectez la dernière image que vous venez de créez (docker image --help pour trouver la commande)
Visitons en root (sudo su) le dossier /var/lib/docker/ sur l’hôte. En particulier, image/overlay2/layerdb/sha256/ :
On y trouve une sorte de base de données de tous les layers d’images avec leurs ancêtres.
Il s’agit d’une arborescence.
Vous pouvez aussi utiliser la commande docker save votre_image -o image.tar, et utiliser tar -C image_decompressee/ -xvf image.tar pour
décompresser une image Docker puis explorer les différents layers de l’image.
Solution :
Doit-on utiliser la commande ENTRYPOINT ou la commande CMD ? Se référer au manuel de référence sur les Dockerfiles si besoin.
Solution :
L’instruction ENTRYPOINT et la gestion des entrées-sorties des programmes dans les Dockerfiles peut être un peu capricieuse et il faut parfois avoir
de bonnes notions de Bash et de Linux pour comprendre (et bien lire la documentation Docker).
On utilise parfois des conteneurs juste pour qu’ils s’exécutent une fois (pour récupérer le résultat dans la console, ou générer des fichiers). On
utilise alors l’option --rm pour les supprimer dès qu’ils s’arrêtent.
3 - Volumes et réseaux
Cycle de vie d’un conteneur
Un conteneur a un cycle de vie très court: il doit pouvoir être créé et supprimé rapidement même en contexte de production.
https://supports.uptime-formation.fr/all_content/ 109/210
14/11/2022 17:46 Exporter tout le contenu
Conséquences :
On a besoin de mécanismes d’autoconfiguration, en particuler réseau car les IP des différents conteneur changent tout le temps.
On ne peut pas garder les données persistantes dans le conteneur.
Solutions :
Des réseaux dynamiques par défaut automatiques (DHCP mais surtout DNS automatiques)
Des volumes (partagés ou non, distribués ou non) montés dans les conteneurs
Réseau
Gestion des ports réseaux (port mapping)
L’instruction EXPOSE dans le Dockerfile informe Docker que le conteneur écoute sur les ports réseau au lancement. L’instruction EXPOSE ne publie
pas les ports. C’est une sorte de documentation entre la personne qui construit les images et la personne qui lance le conteneur à propos
des ports que l’on souhaite publier.
Par défaut les conteneurs n’ouvrent donc pas de port même s’ils sont déclarés avec EXPOSE dans le Dockerfile.
Pour publier un port au lancement d’un conteneur, c’est l’option -p <port_host>:<port_guest> de docker run.
Bridge et overlay
Un réseau bridge est une façon de créer un pont entre deux carte réseaux pour construire un réseau à partir de deux.
Par défaut les réseaux docker fonctionne en bridge (le réseau de chaque conteneur est bridgé à un réseau virtuel docker)
par défaut les adresses sont en 172.0.0.0/8, typiquement chaque hôte définit le bloc d’IP 172.17.0.0/16 configuré avec DHCP.
Un réseau overlay est un réseau virtuel privé déployé par dessus un réseau existant (typiquement public). Pour par exemple faire un cloud multi-
datacenters.
Serveur DNS et DHCP intégré dans le “user-defined network” (c’est une solution IPAM)
Mais ne pas avoir peur d’aller voir comment on perçoit le réseau de l’intérieur. Nécessaire pour bien contrôler le réseau.
ingress : un loadbalancer automatiquement connecté aux nœuds d’un Swarm. Voir la doc sur les réseaux overlay.
Aujourd’hui il faut utiliser un réseau dédié créé par l’utilisateur (“user-defined bridge network”)
Plugins réseaux
Il existe :
Volumes
Les volumes Docker via la sous-commande volume
docker volume ls
docker volume inspect
docker volume prune
docker volume create
docker volume rm
https://supports.uptime-formation.fr/all_content/ 110/210
14/11/2022 17:46 Exporter tout le contenu
Bind mounting
Lorsqu’un répertoire hôte spécifique est utilisé dans un volume (la syntaxe -v HOST_DIR:CONTAINER_DIR), elle est souvent appelée bind mounting
(“montage lié”).
C’est quelque peu trompeur, car tous les volumes sont techniquement “bind mounted”. La particularité, c’est que le point de montage
sur l’hôte est explicite plutôt que caché dans un répertoire appartenant à Docker.
Exemple :
# Sur l'hôte
# Dans le conteneur
cd /data/
touch testfile
exit
# Sur l'hôte
ls /home/user/app/data:
Volumes nommés
L’autre technique est de créer d’abord un volume nommé avec :
docker volume create mon_volume
docker run -d -v mon_volume:/data redis
L’instruction VOLUME dans un Dockerfile permet de désigner les volumes qui devront être créés lors du lancement du conteneur. On précise ensuite avec
l’option -v de docker run à quoi connecter ces volumes. Si on ne le précise pas, Docker crée quand même un volume Docker au nom généré
aléatoirement, un volume “caché”.
Pour partager des données on peut monter le même volume dans plusieurs conteneurs.
Pour lancer un conteneur avec les volumes d’un autre conteneur déjà montés on peut utiliser --volumes-from <container>
Par défaut le driver de volume est local c’est-à-dire qu’un dossier est créé sur le disque de l’hôte.
--opt type=btrfs \
--opt device=/dev/sda2 \
monVolume
Plugins de volumes
On peut utiliser d’autres systèmes de stockage en installant de nouveau plugins de driver de volume. Par exemple, le plugin vieux/sshfs permet de
piloter un volume distant via SSH.
Exemples:
Ou via docker-compose :
volumes:
sshfsdata:
driver: vieux/sshfs:latest
driver_opts:
sshcmd: "username@server:/location/on/the/server"
allow_other: ""
Permissions
Un volume est créé avec les permissions du dossier préexistant.
FROM debian
VOLUME /data/graphite
USER graphite
Backups de volumes
Pour effectuer un backup la méthode recommandée est d’utiliser un conteneur suplémentaire dédié
qui accède au volume avec --volume-from
qui est identique aux autres et donc normalement avec les mêmes UID/GID/permissions.
https://supports.uptime-formation.fr/all_content/ 111/210
14/11/2022 17:46 Exporter tout le contenu
TP 3 - Réseaux
Portainer
Si vous aviez déjà créé le conteneur Portainer, vous pouvez le relancer en faisant docker start portainer, sinon créez-le comme suit :
-p 9000:9000 \
-v portainer_data:/data \
-v /var/run/docker.sock:/var/run/docker.sock \
portainer/portainer-ce
Docker implémente ces réseaux virtuels en créant des interfaces. Lancez la commande ip -br a de nouveau et comparez. Qu’est-ce qui a changé ?
Visitez la page de notre application. Qu’en pensez vous ? Moby est le nom de la mascotte Docker 🐳 😊. Faites un motif en cliquant.
Comment notre application se connecte-t-elle au conteneur redis ? Elle utilise ces instructions JS dans son fichier server.js:
var port = opts.redis_port || process.env.USE_REDIS_PORT || 6379;
En résumé par défaut, notre application se connecte sur l’hôte redis avec le port 6379
Exécutez (docker exec) la commande ping -c 3 redis à l’intérieur de notre conteneur applicatif (moby-counter donc). Quelle est l’adresse IP
affichée ?
docker exec moby-counter ping -c3 redis
De même, affichez le contenu des fichiers /etc/hosts du conteneur (c’est la commande cat couplée avec docker exec). Nous constatons que
Docker a automatiquement configuré l’IP externe du conteneur dans lequel on est avec l’identifiant du conteneur.
Pour s’en assurer, interrogeons le serveur DNS de notre réseau moby-network en lançant la commande nslookup redis grâce à docker exec :
docker exec moby-counter nslookup redis
Créez une deuxième instance de l’application dans ce réseau : docker run -d --name moby-counter2 --network moby-network2 -p 9090:80
russmckendrick/moby-counter
Lorsque vous pingez redis depuis cette nouvelle instance moby-counter2, qu’obtenez-vous ? Pourquoi ?
Vous ne pouvez pas avoir deux conteneurs avec les mêmes noms, comme nous l’avons déjà découvert.
Par contre, notre deuxième réseau fonctionne
complètement isolé de notre premier réseau, ce qui signifie que nous pouvons toujours utiliser le nom de domaine redis. Pour ce faire, nous devons
spécifier l’option --network-alias :
Créons un deuxième redis avec le même domaine: docker run -d --name redis2 --network moby-network2 --network-alias redis
redis:alpine
Lorsque vous pingez redis depuis cette nouvelle instance de l’application, quelle IP obtenez-vous ?
Lancez nslookup redis dans le conteneur moby-counter2 pour tester la résolution de DNS.
Vous pouvez retrouver la configuration du réseau et les conteneurs qui lui sont reliés avec docker network inspect moby-network2.
Notez la
section IPAM (IP Address Management).
Pour faire rapidement le ménage des conteneurs arrêtés lancez docker container prune.
De même docker network prune permet de faire le ménage des réseaux qui ne sont plus utilisés par aucun conteneur.
https://supports.uptime-formation.fr/all_content/ 112/210
14/11/2022 17:46 Exporter tout le contenu
TP 3bis - Volumes
Portainer
Si vous aviez déjà créé le conteneur Portainer, vous pouvez le relancer en faisant docker start portainer, sinon créez-le comme suit :
docker volume create portainer_data
-p 9000:9000 \
-v portainer_data:/data \
-v /var/run/docker.sock:/var/run/docker.sock \
portainer/portainer-ce
Remarque sur la commande précédente : pour que Portainer puisse fonctionner et contrôler Docker lui-même depuis l’intérieur du conteneur il est
nécessaire de lui donner accès au socket de l’API Docker de l’hôte grâce au paramètre --volume ci-dessus.
cd /dossier-conteneur/
touch test-depuis-conteneur
Après être sorti·e du conteneur, listons le contenu du dossier sur l’hôte avec la commande suivante ou avec le navigateur de fichiers d’Ubuntu :
ls /tmp/dossier-hote/
Le fichier test-depuis-conteneur a été crée par le conteneur au dossier que l’on avait connecté grâce à -v /tmp/dossier-hote:/dossier-conteneur
Tentez de créer un fichier depuis l’hôte dans ce dossier. Que se passe-t-il ? Que faut-il faire ? Pourquoi ?
Stoppez tous les conteneurs redis et moby-counter avec docker stop ou avec Portainer.
Supprimez les conteneurs arrêtés avec docker container prune
Lancez docker volume prune pour faire le ménage de volume éventuellement créés dans les TPs précédent
Lancez aussi docker network prune pour nettoyer les réseaux inutilisés
Volumes nommés
Lorsqu’un répertoire hôte spécifique est utilisé dans un volume (la syntaxe -v HOST_DIR:CONTAINER_DIR), elle est souvent appelée bind mounting.
C’est
quelque peu trompeur, car tous les volumes sont techniquement “bind mounted”. La différence, c’est que le point de montage est explicite plutôt que
caché dans un répertoire géré par Docker.
En effet, la bonne façon de créer des volumes consiste à les créer manuellement dans un premier temps (volumes nommés), puis d’y associer un
conteneur : docker volume create redis_data.
Puis, à l’aide de la documentation disponible sur le Docker Hub, trouvons le point de montage où connecter un conteneur Redis pour que ses
données persistent à la suppression du conteneur.
créons le conteneur Redis connecté à notre volume nommé (il faut remplacer __VOLUME__:__POINT_DE_MONTAGE__ par les bonnes informations) :
https://supports.uptime-formation.fr/all_content/ 113/210
14/11/2022 17:46 Exporter tout le contenu
recréons le conteneur redis, mais par erreur nous allons oublier de le connecter à un volume à la création :
supprimez le nouveau conteneur redis : docker stop redis puis docker rm redis
Visitez votre application dans le navigateur. Elle est maintenant déconnectée de son backend.
FROM alpine:3.5
RUN set -x \
gcc \
linux-headers \
make \
musl-dev \
tar \
&& rm redis.tar.gz \
&& rm -r /usr/src/redis \
VOLUME /data
WORKDIR /data
ENTRYPOINT ["docker-entrypoint.sh"]
EXPOSE 6379
CMD [ "redis-server" ]
Notez que, vers la fin du fichier, il y a une instruction VOLUME ; cela signifie que lorque notre conteneur a été lancé, un volume “caché” a effectivement
été créé par Docker.
Beaucoup de conteneurs Docker sont des applications stateful, c’est-à-dire qui stockent des données. Automatiquement ces conteneurs créent des
volument anonymes en arrière plan qu’il faut ensuite supprimer manuellement (avec rm ou prune).
Inspectez la liste des volumes (par exemple avec Portainer) pour retrouver l’identifiant du volume caché. Normalement il devrait y avoir un
volume portainer_data (si vous utilisez Portainer) et un volume anonyme avec un hash.
Créez un nouveau conteneur redis en le rattachant au volume redis “caché” que vous avez retrouvé (en copiant l’id du volume anonyme) :
docker
container run -d --name redis -v <volume_id>/_data:/data --network moby-network redis:alpine
Visitez la page de l’application. Normalement un motif de logos moby d’une précédente session devrait s’afficher (après un délai pouvant aller
jusqu’à plusieurs minutes)
Affichez le contenu du volume avec la commande : docker exec redis ls -lha /data
Lancez la fonction prune pour les conteneurs d’abord, puis pour les réseaux, et enfin pour les volumes.
Comme les réseaux et volumes n’étaient plus attachés à des conteneurs en fonctionnement, ils ont été supprimés.
Généralement, il faut faire beaucoup plus attention au prune de volumes (données à perdre) qu’au prune de conteneurs (rien à perdre car
immutable et en général dans le registry).
Pour que l’app Python soit au courant de l’emplacement de la base de données, ajoutez à votre Dockerfile une variable d’environnement
DATABASE_URL ainsi (cette variable est lue par le programme Python) :
ENV DATABASE_URL=sqlite:////data/app.db
Cela indique que l’on va demander à Python d’utiliser SQLite pour stocker la base de données comme un unique fichier au format .db (SQLite) dans un
dossier accessible par le conteneur. On a en fait indiqué à l’app Python que chemin de la base de données est :
/data/app.db
https://supports.uptime-formation.fr/all_content/ 114/210
14/11/2022 17:46 Exporter tout le contenu
Ajouter au Dockerfile une instruction VOLUME pour stocker la base de données SQLite de l’application.
Solution :
Créez un volume nommé appelé microblog_db, et lancez un conteneur l’utilisant, créez un compte et écrivez un message.
Vérifier que le volume nommé est bien utilisé en branchant un deuxième conteneur microblog utilisant le même volume nommé.
Indice :
Le read-only est nécessaire pour que les deux Redis n’écrivent pas de façon contradictoire dans la base de valeurs.
Ajoutez une deuxième instance de l’application dans le deuxième réseau connectée à ce nouveau Redis.
Visitez la deuxième application : vous devriez voir également le motif de moby apparaître.
Vous possédez tous les ingrédients pour packager l’app de votre choix désormais ! Récupérez une image de base, basez-vous sur un Dockerfile existant
s’il vous inspire, et lancez-vous !
Pour faciliter tout cela et dans l’optique d'Infrastructure as Code, Docker introduit un outil nommé docker-compose qui permet de décrire de
applications multiconteneurs grâce à des fichiers YAML.
Pour bien comprendre qu’il ne s’agit que de convertir des options de commande Docker en YAML, un site vous permet de convertir une
commande docker run en fichier Docker Compose : https://www.composerize.com/
Le “langage” de Docker Compose : la documentation du langage (DSL) des compose-files est essentielle.
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 !!! (le défaut du YAML, pas de correcteur syntaxique automatique, c’est bête mais vous y perdrez forcément quelques heures !
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 Docker et YAML dans VSCode vous aident à repérer des erreurs
https://supports.uptime-formation.fr/all_content/ 115/210
14/11/2022 17:46 Exporter tout le contenu
postgres:
image: postgres:10
environment:
POSTGRES_USER: rails_user
POSTGRES_PASSWORD: rails_password
POSTGRES_DB: rails_db
networks:
- back_end
redis:
image: redis:3.2-alpine
networks:
- back_end
rails:
build: .
depends_on:
- postgres
- redis
environment:
DATABASE_URL: "postgres://rails_user:rails_password@postgres:5432/rails_db"
REDIS_HOST: "redis:6379"
networks:
- front_end
- back_end
volumes:
- .:/app
nginx:
image: nginx:latest
networks:
- front_end
ports:
- 3000:80
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
networks:
front_end:
back_end:
Un deuxième exemple :
services:
wordpress:
depends_on:
- mysqlpourwordpress
environment:
- "WORDPRESS_DB_HOST=mysqlpourwordpress:3306"
- WORDPRESS_DB_PASSWORD=monwordpress
- WORDPRESS_DB_USER=wordpress
networks:
- wordpress
ports:
- "80:80"
image: wordpress
volumes:
- wordpress_config:/var/www/html/
mysqlpourwordpress:
image: "mysql:5.7"
environment:
- MYSQL_ROOT_PASSWORD=motdepasseroot
- MYSQL_DATABASE=wordpress
- MYSQL_USER=wordpress
- MYSQL_PASSWORD=monwordpress
networks:
- wordpress
volumes:
- wordpress_data:/var/lib/mysql/
networks:
wordpress:
volumes:
wordpress_config:
wordpress_data:
updémarre tous les conteneurs définis dans le fichier compose et agrège la sortie des logs. Normalement, vous voudrez utiliser l’argument -d
pour exécuter Compose en arrière-plan.
build reconstruit toutes les images créées à partir de Dockerfiles. La commande up ne construira pas une image à moins qu’elle n’existe pas,
donc utilisez cette commande à chaque fois que vous avez besoin de mettre à jour une image (quand vous avez édité un Dockerfile). On peut
aussi faire docker-compose up --build
ps fournit des informations sur le statut des conteneurs gérés par Compose.
https://supports.uptime-formation.fr/all_content/ 116/210
14/11/2022 17:46 Exporter tout le contenu
run fait tourner un conteneur pour exécuter une commande unique. Cela aura aussi pour effet de faire tourner tout conteneur décrit dans
depends_on, à moins que l’argument --no-deps ne soit donné.
logs affiche les logs. De façon générale la sortie des logs est colorée et agrégée pour les conteneurs gérés par Compose.
rm enlève les contenants à l’arrêt. N’oubliez pas d’utiliser l’argument -v pour supprimer tous les volumes gérés par Docker.
down détruit tous les conteneurs définis dans le fichier Compose, ainsi que les réseaux
il est aussi possible d’utiliser des variables d’environnement dans Docker Compose : se référer au mode d’emploi pour les subtilités de
fonctionnement
Il est possible de visualiser l’architecture d’un fichier Docker Compose en utilisant docker-compose-viz
Cet outil peut être utilisé dans un cadre d’intégration continue pour produire automatiquement la documentation pour une image en fonction du
code.
Pour vous faciliter la vie et si ce n’est pas déjà le cas, ajoutez le plugin autocomplete pour Docker et Docker Compose à bash en copiant les
commandes suivantes :
sudo apt update
Démarrez un nouveau projet dans VSCode (créez un dossier appelé identidock et chargez-le avec la fonction Add folder to workspace)
Dans un sous-dossier app, ajoutez une petite application python en créant ce fichier identidock.py :
import requests
import hashlib
import redis
import os
import logging
logging.basicConfig(level=LOGLEVEL)
app = Flask(__name__)
salt = "UNIQUE_SALT"
default_name = 'toi'
def mainpage():
name = default_name
if request.method == 'POST':
name = request.form['name']
name_hash = hashlib.sha256(salted_name.encode()).hexdigest()
header = '<html><head><title>Identidock</title></head><body>'
</form>
<p>Tu ressembles à ça :
<img src="/monster/{1}"/>
'''.format(name, name_hash)
footer = '</body></html>'
@app.route('/monster/<name>')
def get_identicon(name):
found_in_cache = False
try:
image = cache.get(name)
redis_unreachable = False
https://supports.uptime-formation.fr/all_content/ 117/210
14/11/2022 17:46 Exporter tout le contenu
if image is not None:
found_in_cache = True
except:
redis_unreachable = True
if not found_in_cache:
try:
if not redis_unreachable:
cache.set(name, image)
except:
abort(503)
if __name__ == '__main__':
uWSGI est un serveur python de production très adapté pour servir notre serveur intégré Flask, nous allons l’utiliser.
FROM python:3.7
WORKDIR /app
USER uwsgi
Observons le code du Dockerfile ensemble s’il n’est pas clair pour vous. Juste avant de lancer l’application, nous avons changé d’utilisateur avec
l’instruction USER, pourquoi ?.
Construire l’application, pour l’instant avec docker build, la lancer et vérifier avec docker exec, whoami et id l’utilisateur avec lequel tourne le
conteneur.
Réponse :
services:
identidock:
build: .
ports:
- "5000:5000"
Plusieurs remarques :
Lancez le service (pour le moment mono-conteneur) avec docker-compose up (cette commande sous-entend docker-compose build)
Ajoutons maintenant un deuxième conteneur. Nous allons tirer parti d’une image déjà créée qui permet de récupérer une “identicon”. Ajoutez à la
suite du fichier Compose (attention aux indentations !) :
dnmonster:
image: amouat/dnmonster:1.0
services:
identidock:
build: .
ports:
- "5000:5000"
dnmonster:
image: amouat/dnmonster:1.0
Enfin, nous déclarons aussi un réseau appelé identinet pour y mettre les deux conteneurs de notre application.
Il faut déclarer ce réseau à la fin du fichier (notez que l’on doit spécifier le driver réseau) :
networks:
identinet:
driver: bridge
https://supports.uptime-formation.fr/all_content/ 118/210
14/11/2022 17:46 Exporter tout le contenu
Il faut aussi mettre nos deux services identidock et dnmonster sur le même réseau en ajoutant deux fois ce bout de code où c’est nécessaire
(attention aux indentations !) :
networks:
- identinet
Ajoutons également un conteneur redis (attention aux indentations !). Cette base de données sert à mettre en cache les images et à ne pas les
recalculer à chaque fois.
redis:
image: redis
networks:
- identinet
docker-compose.yml final :
services:
identidock:
build: .
ports:
- "5000:5000"
networks:
- identinet
dnmonster:
image: amouat/dnmonster:1.0
networks:
- identinet
redis:
image: redis
networks:
- identinet
networks:
identinet:
driver: bridge
Lancez l’application et vérifiez que le cache fonctionne en cherchant les messages dans les logs de l’application.
N’hésitez pas à passer du temps à explorer les options et commandes de docker-compose, ainsi que la documentation officielle du langage des
Compose files.
Solution :
D’autres services
Exercices de google-fu
On se propose ici d’essayer de déployer plusieurs services pré-configurés comme Wordpress, Nextcloud, Sentry ou votre logiciel préféré.
Récupérez (et adaptez si besoin) à partir d’Internet un fichier docker-compose.yml permettant de lancer un pad HedgeDoc ou autre avec sa base de
données. Je vous conseille de toujours chercher dans la documentation officielle ou le repository officiel (souvent sur Github) en premier.
Si besoin, lisez les logs en quête bug et adaptez les variables d’environnement.
curl -L -O https://raw.githubusercontent.com/elastic/beats/7.10/deploy/docker/filebeat.docker.yml
Renommons cette configuration et rectifions qui possède ce fichier pour satisfaire une contrainte de sécurité de Filebeat :
mv filebeat.docker.yml filebeat.yml
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.5.0
environment:
- discovery.type=single-node
- xpack.security.enabled=false
networks:
- logging-network
https://supports.uptime-formation.fr/all_content/ 119/210
14/11/2022 17:46 Exporter tout le contenu
filebeat:
image: docker.elastic.co/beats/filebeat:7.5.0
user: root
depends_on:
- elasticsearch
volumes:
- ./filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /var/run/docker.sock:/var/run/docker.sock
networks:
- logging-network
environment:
- -strict.perms=false
kibana:
image: docker.elastic.co/kibana/kibana:7.5.0
depends_on:
- elasticsearch
ports:
- 5601:5601
networks:
- logging-network
networks:
logging-network:
driver: bridge
Il suffit ensuite de :
L’index nécessaire à Kibana est créé, vous pouvez vous rendre dans la partie Discover à gauche (l’icône boussole 🧭) pour lire vos logs.
Il est temps de faire un petit docker stats pour découvrir l’utilisation du CPU et de la RAM de vos conteneurs !
Dans la dernière version de l’app microblog, Elasticsearch est utilisé pour fournir une fonctionnalité de recherche puissante dans les posts de l’app.
Avec l’aide du tutoriel de Miguel Grinberg, écrivez le docker-compose.yml qui permet de lancer une stack entière pour microblog. Elle devra contenir un
conteneur microblog, un conteneur mysql, un conteneur elasticsearch et un conteneur kibana.
5 - Orchestration et clustering
Orchestration
Un des intérêts principaux de Docker et des conteneurs en général est de :
A partir d’une certaine échelle, il n’est plus question de gérer les serveurs et leurs conteneurs à la main.
Les nœuds d’un cluster sont les machines (serveurs physiques, machines virtuelles, etc.) qui font tourner vos applications (composées de conteneurs).
L’orchestration consiste à automatiser la création et la répartition des conteneurs à travers un cluster de serveurs. Cela peut permettre de :
Docker Swarm
Swarm est l'outil de clustering et d’orchestration natif de Docker (développé par Docker Inc.).
Il s’intègre très bien avec les autres commandes docker (on a même pas l’impression de faire du clustering).
Swarm utilise l’API standard du Docker Engine (sur le port 2376) et sa propre API de management Swarm (sur le port 2377).
Il a perdu un peu en popularité face à Kubernetes mais c’est très relatif (voir comparaison plus loin).
https://supports.uptime-formation.fr/all_content/ 120/210
14/11/2022 17:46 Exporter tout le contenu
Les nœuds managers sont en fait aussi des workers et font tourner des conteneurs, c’est leur rôles qui varient.
l’algorithme de choix est “spread”, c’est-à-dire qu’il répartit au maximum en remplissant tous les nœuds qui répondent aux contraintes
données.
les stacks : la distribution (en plusieurs exemplaires) d’un ensemble de conteneurs (app multiconteneurs) décrits dans un fichier Docker
Compose
services:
web:
image: username/repo
deploy:
replicas: 5
resources:
limits:
cpus: "0.1"
memory: 50M
restart_policy:
condition: on-failure
ports:
- "4000:80"
networks:
- webnet
networks:
webnet:
https://supports.uptime-formation.fr/all_content/ 121/210
14/11/2022 17:46 Exporter tout le contenu
Le mot-clé deploy est lié à l’usage de Swarm
options intéressantes :
update_config : pour pouvoir rollback si l’update fail
placement : pouvoir choisir le nœud sur lequel sera déployé le service
replicas : nombre d’exemplaires du conteneur
resources : contraintes d’utilisation de CPU ou de RAM sur le nœud
Sous-commandes Swarm
swarm init : Activer Swarm et devenir manager d’un cluster d’un seul nœud
swarm join : Rejoindre un cluster Swarm en tant que nœud manager ou worker
docker stack deploy : Déploie une stack (= fichier Docker compose) ou update une stack existante
docker stack services : Liste les services qui composent une stack
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.
Haute disponibilité : on veut que notre service soit toujours disponible, même en cas de panne (partielle) ou de 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é (+ ou - proche)
Permet de router automatiquement le trafic d’un service vers les nœuds qui l’hébergent et sont disponibles.
Pour héberger une production il suffit de rajouter un loadbalancer externe qui pointe vers un certain nombre de nœuds du cluster et le trafic sera
routé automatiquement à partir de l’un des nœuds.
Gérer les données sensibles dans Swarm avec les secrets Docker
https://supports.uptime-formation.fr/all_content/ 122/210
14/11/2022 17:46 Exporter tout le contenu
echo "This is a secret" | docker secret create my_secret_data
Docker Machine
C’est l’outil de gestion d’hôtes Docker
Il est capable de créer des serveurs Docker “à la volée”
Concrètement, docker-machine permet de créer automatiquement des machines avec le Docker Engine et ssh configuré et de gérer les
certificats TLS pour se connecter à l’API Docker des différents serveurs.
Il permet également de changer le contexte de la ligne de commande Docker pour basculer sur l’un ou l’autre serveur avec les variables
d’environnement adéquates.
Exemple :
--digitalocean-ssh-key-fingerprint 41:d9:ad:ba:e0:32:73:58:4f:09:28:15:f2:1d:ae:5c \
--digitalocean-access-token "a94008870c9745febbb2bb84b01d16b6bf837b4e0ce9b516dbcaf4e7d5ff2d6" \
hote-digitalocean
docker run -d nginx:latest créé ensuite un conteneur sur le droplet digitalocean précédemment créé.
Présentation de Kubernetes
Les pods Kubernetes servent à grouper des conteneurs en unités d’application (microservices ou non) fortement couplées (un peu comme les
stacks Swarm)
Les deployments sont une abstraction pour scaler ou mettre à jours des groupes de pods (un peu comme les tasks dans Swarm).
Présentation de Kubernetes
Une autre solution très à la mode depuis 4 ans. Un buzz word du DevOps en France :)
Au cœur du consortium Cloud Native Computing Foundation très influent dans le monde de l’informatique.
https://supports.uptime-formation.fr/all_content/ 123/210
14/11/2022 17:46 Exporter tout le contenu
Kubernetes a un flat network (un overlay de plus bas niveau que Swarm) : https://neuvector.com/network-security/kubernetes-networking/
Swarm est beaucoup plus simple à mettre en œuvre qu’une stack Kubernetes.
Swarm serait donc mieux pour les clusters moyen et Kubernetes pour les très gros
TP 5 - Orchestration et clustering
Introduction à Swarm
Initialisez Swarm avec docker swarm init.
Créer un service
A l’aide de la propriété deploy: de docker compose, créer un service en 5 exemplaires (replicas) à partir de l’image traefik/whoami accessible sur le
port 9999 et connecté au port 80 des 5 replicas.
Solution :
Accédez à votre service et actualisez plusieurs fois la page. Les informations affichées changent. Pourquoi ?
Lancez une commande service scale pour changer le nombre de replicas de votre service et observez le changement avec docker service ps
hello
La stack example-voting-app
Cloner l’application example-voting-app ici : https://github.com/dockersamples/example-voting-app
Lire le schéma d’architecture de l’app example-voting-app sur Github. A noter que le service worker existe en deux versions utilisant un langage
de programmation différent (Java ou .NET), et que tous les services possèdent des images pour conteneurs Windows et pour conteneurs Linux.
Ces versions peuvent être déployées de manière interchangeable et ne modifient pas le fonctionnement de l’application multi-conteneur. C’est
une démonstration de l’utilité du paradigme de la conteneurisation et de l’architecture dite “micro-service”.
Lire attentivement les fichiers docker-compose.yml, docker-compose-simple.yml, docker-stack-simple.yml et docker-stack.yml. Ce sont tous des
fichiers Docker Compose classiques avec différentes options liées à un déploiement via Swarm. Quelles options semblent spécifiques à Docker
Swarm ? Ces options permettent de configurer des fonctionnalités d'orchestration.
Dessiner rapidement le schéma d’architecture associé au fichier docker-compose-simple.yml, puis celui associé à docker-stack.yml en indiquant
bien à quel réseau quel service appartient.
Avec docker swarm init, transformer son installation Docker en une installation Docker compatible avec Swarm. Lisez attentivement le message
qui vous est renvoyé.
Déployer la stack du fichier docker-stack.yml : docker stack deploy --compose-file docker-stack.yml vote
docker stack ls indique 6 services pour la stack vote. Observer également l’output de docker stack ps vote et de docker stack services vote.
Qu’est-ce qu’un service dans la terminologie de Swarm ?
Accéder aux différents front-ends de la stack grâce aux informations contenues dans les commandes précédentes. Sur le front-end lié au vote,
actualiser plusieurs fois la page. Que signifie la ligne Processed by container ID […] ? Pourquoi varie-t-elle ?
Scaler la stack en ajoutant des replicas du front-end lié au vote avec l’aide de docker service --help. Accédez à ce front-end et vérifier que cela
a bien fonctionné en actualisant plusieurs fois.
Se grouper par 2 ou 3 pour créer un cluster à partir de vos VM respectives (il faut utiliser une commande Swarm pour récupérer les instructions
nécessaires).
Si grouper plusieurs des VM n’est pas possible, vous pouvez créer un cluster multi-nodes très simplement avec l’interface du site Play With
Docker, il faut s’y connecter avec vos identifiants Docker Hub.
Vous pouvez faire docker swarm --help pour obtenir des infos manquantes, ou faire docker swarm leave --force pour réinitialiser votre
configuration Docker Swarm si besoin.
https://supports.uptime-formation.fr/all_content/ 124/210
14/11/2022 17:46 Exporter tout le contenu
N’hésitez pas à regarder dans les logs avec systemctl status docker comment se passe l’élection du nœud leader, à partir du moment où vous
avez plus d’un manager.
Accédez au service depuis un node, et depuis l’autre. Actualisez plusieurs fois la page. Les informations affichées changent. Lesquelles, et
pourquoi ?
Ajouter dans le Compose file des instructions pour scaler différemment deux services (3 replicas pour le service front par exemple). N’oubliez
pas de redéployer votre Compose file.
puis spécifier quelques options d’orchestration exclusives à Docker Swarm : que fait mode: global ? N’oubliez pas de redéployer votre Compose
file.
Avec Portainer ou avec docker-swarm-visualizer, explorer le cluster ainsi créé (le fichier docker-stack.yml de l’app example-voting-app contient
déjà un exemplaire de docker-swarm-visualizer).
Trouver la commande pour déchoir et promouvoir l’un de vos nœuds de manager à worker et vice-versa.
Puis sortir un nœud du cluster (drain) : docker node update --availability drain <node-name>
Indice 1 :
Indice 2 :
Indice 3 :
Solution / explications :
Introduction à Kubernetes
Le fichier kube-deployment.yml de l’app example-voting-app décrit la même app pour un déploiement dans Kubernetes plutôt que dans Docker
Compose ou Docker Swarm. Tentez de retrouver quelques équivalences entre Docker Compose / Swarm et Kubernetes en lisant attentivement ce fichier
qui décrit un déploiement Kubernetes.
Vous pouvez désormais faire l’exercice 2 du TP 7 pour configurer un serveur web qui permet d’accéder à vos services Swarm via des domaines
spécifiques.
Conclusion
https://supports.uptime-formation.fr/all_content/ 125/210
14/11/2022 17:46 Exporter tout le contenu
https://github.com/nginx-proxy/nginx-proxy
https://github.com/nginx-proxy/acme-companion
https://supports.uptime-formation.fr/all_content/ 126/210
14/11/2022 17:46 Exporter tout le contenu
La sécurité de Docker c’est aussi celle de la chaîne de dépendance, des images, des packages installés dans celles-ci : on fait confiance à trop de
petites briques dont on ne vérifie pas la provenance ou la mise à jour
docker-socket-proxy : protéger la socket Docker quand on a besoin de la partager à des conteneurs comme Traefik ou Portainer
Limites de Docker
Stateful
les conteneurs stateless c’est bien beau mais avec une base de données, ça ne se gère pas magiquement du tout
quelques ressources sur le stateful avec Docker : https://container.training/swarm-selfpaced.yml.html#450
Volumes distribués
problème des volumes partagés / répliqués
domaine à part entière
Solution 1 : solutions applicatives robustes
pour MySQL/MariaDB : Galera
pour Postgres : on peut citer Citus ou pgpool, voir la comparaison de différentes solutions
Elasticsearch est distribué out-of-the-box
Solution 2 : volume drivers avec Docker
Flocker, Convoy, visent à intégrer une technologie de réplication
c’est un moyen, pas une solution : reste un outil pour configurer ce que l’on souhaite
DataOps
Doit se baser sur une solution applicative complète comme Kafka
avec Compose pour les différents services et Swarm pour le scaling
Solutions actuelles peut-être plus orientées Kubernetes
Ressources :
Pas-à-pas très détaillé d’une pipeline “ETL” https://medium.com/sfu-cspmp/building-data-pipeline-kafka-docker-4d2a6cfc92ca
dépôt lié : https://github.com/salcaino/sfucmpt733
Exemple avec Kafka : https://github.com/rogaha/data-processing-pipeline/blob/master/docker-compose.yml
Dockercraft : administrez vos containers dans Minecraft
Retours
Comment ça s’est passé ?
Difficulté : trop facile ? trop dur ? quoi en particulier ?
Vitesse : trop rapide ? trop lent ? lors de quoi en particulier ?
Attentes sur le contenu ? Les manipulations ?
Questions restées ouvertes ? Nouvelles questions ?
Envie d’utiliser Docker ? ou de le jeter à la poubelle ?
Ressources
La section Quick Start de la documentation Gitlab-CI
Vous pouvez trouver des exemples de CI dans la documentation Gitlab.
La section dédiée à docker build de la documentation Gitlab
La section de la documentation dédiée au Container Registry
Avec BitBucket
BitBucket propose aussi son outil de pipeline, à la différence qu’il n’a pas de registry intégré, le template par défaut propose donc de pousser son image
sur le registry Docker Hub.
Il suffit de créer un repo BitBucket puis d’y ajouter le template de CI Docker proposé (le template est caché derrière un bouton See more).
Ensuite, il faut ajouter des Repository variables avec ses identifiants Docker Hub. Dans le template, ce sont les variables DOCKERHUB_USERNAME,
DOCKERHUB_PASSWORD et DOCKERHUB_NAMESPACE (identique à l’username ici).
Ressources
https://support.atlassian.com/bitbucket-cloud/docs/run-docker-commands-in-bitbucket-pipelines/
Conclusion
Déployer notre container ou notre projet Docker Compose
Nous avons fait la partie CI (intégration continue). Une étape supplémentaire est nécessaire pour ajouter le déploiement continu de l’app (CD) : si
aucune étape précédente n’a échoué, la nouvelle version de l’app devra être déployée sur votre serveur, via une connexion SSH et rsync par exemple.
Il
faudra ajouter des variables secrètes au projet (clé SSH privée par exemple), cela se fait dans les options de Gitlab ou de BitBucket.
Avec l’aide de la documentation Traefik, ajoutez une section pour le reverse proxy Traefik pour dans un fichier Docker Compose de votre choix.
Solution :
Explorez le dashboard Traefik accessible sur le port indiqué dans le fichier Docker Compose.
faire en sorte que Traefik reçoive la requête quand on s’adresse à l’URL voulue (DNS + routage)
faire en sorte que Traefik sache vers quel conteneur rediriger le trafic reçu (et qu’il puisse le faire)
Ajouter des labels à l’app web que vous souhaitez desservir grâce à Traefik à partir de l’exemple de la doc Traefik, grâce aux labels ajoutés dans
le docker-compose.yml (attention à l’indentation).
Solution :
Avec l’aide de la documentation Traefik sur Let’s Encrypt et Docker Compose, configurez Traefik pour qu’il crée un certificat Let’s Encrypt pour
votre container.
Solution :
Solution :
QCM Docker
Entourez la bonne réponse
Question 1
Quelle est la principale différence entre une machine virtuelle (VM) et un conteneur ?
1. Un conteneur est une boîte qui contient un logiciel Windows alors qu’une VM fonctionne généralement sous Linux.
2. Un conteneur permet de faire des applications distribuées dans le cloud contrairement aux machines virtuelles.
3. Un conteneur partage le noyau du système hôte alors qu’une machine virtuelle virtualise son propre noyau indépendant.
Question 2
https://supports.uptime-formation.fr/all_content/ 128/210
14/11/2022 17:46 Exporter tout le contenu
En quoi Docker permet de faire de l'Infrastructure as Code ?
1. Comme Ansible, Docker se connecte en SSH à un Linux pour décrire des configurations.
2. Docker permet avec les Dockerfiles et les fichiers Compose de décrire l’installation d’un logiciel et sa configuration.
Question 3
Quels sont les principaux atouts de Docker ?
1. Il permet de rendre compatible tous les logiciels avec le cloud (AWS, etc.) et facilite l’IoT.
2. Il utilise le langage Go qui est de plus en plus populaire et accélère les logiciels qui l’utilise.
3. Il permet d’uniformiser les déploiements logiciels et facilite la construction d’application distribuées.
Question 4
Pour créer un conteneur Docker à partir du code d’un logiciel il faut d’abord :
1. Écrire un Dockerfile qui explique comment empaqueter le code puis construire l’image Docker avec docker build.
2. Créer un cluster avec docker-machine puis compiler le logiciel avec Docker Stack.
Question 5
Un volume Docker est :
Question 6
Indiquez la ou les affirmations vraies :
Question 7
Un Compose file ou fichier Compose permet :
Question 8
Indiquez la ou les affirmations vraies :
1. L’immutabilité, c’est-à-dire le fait de jeter et recréer un conteneur pour le changer plutôt que d’aller modifier l’intérieur.
2. Le cloud, c’est-à-dire la vente de plateforme et de logiciel “as a service”.
3. L’infrastructure-as-code, c’est-à-dire la description d’un état souhaité de l’infrastructure hébergeant application
Question 9
Indiquez la ou les affirmations vraies :
1. Docker est très pratique pour distribuer un logiciel mais tous les conteneurs doivent obligatoirement être exposés à Internet.
2. Docker utilise un cloud pour distribuer facilement des logiciels dans de nombreuses versions.
3. Docker est une catastrophe en terme de sécurité car les conteneurs sont peu isolés.
Question 10
Docker Swarm est :
Bibliographie
Docker
McKendrick, Gallagher 2017 Mastering Docker - Second Edition
Cheatsheet
https://devhints.io/docker
Ressources
DevOps
Krief - Learning DevOps - The complete guide (Azure Devops, Jenkins, Kubernetes, Terraform, Ansible, sécurité) - 2019
The DevOps Handbook
Kubernetes
Module 3
Kubernetes
Administrer des applications multiconteneurs complexes
https://supports.uptime-formation.fr/all_content/ 130/210
14/11/2022 17:46 Exporter tout le contenu
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.
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).
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
https://supports.uptime-formation.fr/all_content/ 131/210
14/11/2022 17:46 Exporter tout le contenu
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:
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)
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)
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:
Au delà de ces trois éléments, l’écosystème d’objets de Kubernetes est vaste et complexe
https://supports.uptime-formation.fr/all_content/ 132/210
14/11/2022 17:46 Exporter tout le contenu
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
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).
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.
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.
https://supports.uptime-formation.fr/all_content/ 133/210
14/11/2022 17:46 Exporter tout le contenu
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 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:
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):
Cet utilitaire s’installe avec un gestionnaire de paquet classique mais est souvent fourni directement par une distribution de développement de
kubernetes.
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)
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:
https://supports.uptime-formation.fr/all_content/ 134/210
14/11/2022 17:46 Exporter tout le contenu
certificate-authority: /home/jacky/.minikube/ca.crt
server: https://172.17.0.2:8443
name: minikube
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURKekNDQWcrZ0F3SUJBZ0lDQm5Vd0RRWUpLb1pJaHZjTkFRRUxCUUF3TXpFVk1CTUdBMVVF
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
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)
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:
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).
https://supports.uptime-formation.fr/all_content/ 135/210
14/11/2022 17:46 Exporter tout le contenu
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 :
La méthode d’installation importe peu. Pour installer kubectl sur Ubuntu nous ferons simplement: sudo snap install kubectl --classic.
Installer Minikube
Minikube est la version de développement de Kubernetes (en local) la plus répendue. Elle est maintenue par la cloud native foundation et très proche
de kubernetes upstream. Elle permet de simuler un ou plusieurs noeuds de cluster sous forme de conteneurs docker ou de machines virtuelles.
Nous utiliserons classiquement docker comme runtime pour minikube (les noeuds k8s seront des conteneurs simulant des serveurs). Ceci est, bien sur,
une configuration de développement. Elle se comporte cependant de façon très proche d’un véritable cluster.
Si Docker n’est pas installé, installer Docker avec la commande en une seule ligne : curl -fsSL https://get.docker.com | sh, puis ajoutez-vous
au groupe Docker avec sudo usermod -a -G docker <votrenom>, et faites sudo reboot pour que cela prenne effet.
Pour lancer le cluster faites simplement: minikube start (il est également possible de préciser le nombre de coeurs de calcul, la mémoire et et
d’autre paramètre pour adapter le cluster à nos besoins.)
Minikube configure automatiquement kubectl (dans le fichier ~/.kube/config) pour qu’on puisse se connecter au cluster de développement.
Affichez à nouveau la version kubectl version. Cette fois-ci la version de kubernetes qui tourne sur le cluster actif est également affichée. Idéalement
le client et le cluster devrait être dans la même version mineure par exemple 1.20.x.
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 :
Vous pouvez désormais appuyer sur <Tab> pour compléter vos commandes kubectl, c’est très utile !
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/minikube.
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/minikube ou
nodes minikube) 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
https://supports.uptime-formation.fr/all_content/ 136/210
14/11/2022 17:46 Exporter tout le contenu
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
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).
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.
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 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.
De la même façon que dans la partie précédente, listez les pods avec kubectl. Combien y en a-t-il ?
kubectl describe deployment/demonstration permet de constater que le service est bien passé à 5 replicas.
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 :
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.
Avec minikube ce forwarding de port doit être concrêtisé avec la commande minikube service demonstration-service. Normalement la page s’ouvre
automatiquement et nous voyons notre application.
Une autre 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 à service de type un LoadBalancer. Mais
minikube ne propose pas par défaut de loadbalancer. Nous y reviendrons dans le cours sur les objets kubernetes.
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).
Également pour gagner du temps en ligne de commande, la plupart des mots-clés de type Kubernetes peuvent être abrégés :
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.
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).
Avec K3s, il est possible d’installer un petit cluster d’un seul noeud en une commande ce que nous allons faire ici:
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):
Téléchargeons le fichiers de configuration scaleway fourni par le formateur ou à récupérer sur votre espace Scaleway. Enregistrez le par exemple
dans ~/.kube/scaleway.yaml.
Copiez le fichier de config k3s /etc/rancher/k3s/k3s.yaml dans ~/.kube: sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/ && sudo chown
stagiaire ~/.kube/k3s.yaml
Changez la variable d’environnement pour déclarer la config par défaut avec en plus nos deux nouvelles configs: export
KUBECONFIG=~/.kube/config:~/.kube/scaleway.yaml:~/.kube/k3s.yaml
Pour afficher la configuration fusionnée des fichiers et l’exporter comme configuration par défaut lancez: kubectl config view --flatten >
~/.kube/config.
Maintenant que nos trois configs sont fusionnées, observons l’organisation du fichier ~/.kube/config en particulier les éléments des listes YAML
de:
clusters
contexts
users
Listez les contextes avec kubectl config get-contexts et affichez les contexte courant avec kubectl config current-context.
Testons quelle connexion nous utilisons avec avec kubectl get nodes.
Observons les derniers évènements arrivés à notre cluster avec kubectl get events --watch.
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.
=> Démonstration
Installer Lens
https://supports.uptime-formation.fr/all_content/ 138/210
14/11/2022 17:46 Exporter tout le contenu
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.
## Install Lens
Argocd est une solution de “Continuous Delivery” dédiée au GitOps avec Kubernetes. Elle fourni une interface assez géniale pour détecter et monitorer
les ressources d’un cluster.
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.
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é:
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 !!! (le défaut du YAML, pas de correcteur syntaxique automatique, c’est bête mais vous y perdrez forcément du temps !)
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
https://supports.uptime-formation.fr/all_content/ 139/210
14/11/2022 17:46 Exporter tout le contenu
les extensions Kubernetes et YAML dans VSCode vous aident à repérer des erreurs
Exemple
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:
L’ordre n’importe pas car les ressources sont décrites déclarativement c’est-à-dire que:
On peut sauter des lignes dans le YAML et rendre plus lisible les descriptions
Objets de base
Les namespaces
Tous les objets Kubernetes sont rangés dans différents espaces de travail isolés appelés namespaces.
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.
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 :
https://supports.uptime-formation.fr/all_content/ 140/210
14/11/2022 17:46 Exporter tout le contenu
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.
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 :
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
On veut que le service soit tout le temps accessible même lorsque certaines ressources manquent :
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:
Nous allons voir que Kubernetes intègre automatiquement les principes de load balancing et de healthcheck dans l’orchestration de conteneurs
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
https://supports.uptime-formation.fr/all_content/ 141/210
14/11/2022 17:46 Exporter tout le contenu
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.
https://github.com/microservices-patterns/ftgo-application -> fonctionne avec le très bon livre Microservices pattern visible sur le readme.
https://github.com/GoogleCloudPlatform/microservices-demo -> Exemple d’application microservice de référence de Google pour 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 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 :
https://supports.uptime-formation.fr/all_content/ 142/210
14/11/2022 17:46 Exporter tout le contenu
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
La commande kubectl run sert à créer un deployment à partir d’un modèle. Il vaut mieux utilisez apply -f.
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
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.
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.
https://supports.uptime-formation.fr/all_content/ 143/210
14/11/2022 17:46 Exporter tout le contenu
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
https://supports.uptime-formation.fr/all_content/ 144/210
14/11/2022 17:46 Exporter tout le contenu
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.
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).
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:
CronJobs
Comme des jobs, mais se lancent à un intervalle régulier, comme les cron sur les systèmes unix.
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 du cours précédent de la partie Pods.
https://supports.uptime-formation.fr/all_content/ 145/210
14/11/2022 17:46 Exporter tout le contenu
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.
kubectl logs <pod-name> -c <conteneur_name> (le nom du conteneur est inutile si un seul)
Explorez le pod avec la commande kubectl exec -it <pod-name> -c <conteneur_name> -- bash écrite plus haut.
Supprimez le pod.
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
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
=> 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
https://supports.uptime-formation.fr/all_content/ 146/210
14/11/2022 17:46 Exporter tout le contenu
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
Cours
TP
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
Ce TP va consister à créer des objets Kubernetes pour déployer une application microservices (plutôt simple) : monsterstack.
Elle est composée :
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.
chmod +x 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.
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
https://supports.uptime-formation.fr/all_content/ 147/210
14/11/2022 17:46 Exporter tout le contenu
partie: dnmonster
spec:
containers:
- image: amouat/dnmonster:1.0
name: dnmonster
ports:
- containerPort: 8080
name: dnmonster
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 et vérifiez dans Lens que les 3 réplicats sont bien lancés.
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
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
Lancez skaffold run pour construire et déployer l’application automatiquement (skaffold utilise ici le registry docker local et kubectl)
https://supports.uptime-formation.fr/all_content/ 148/210
14/11/2022 17:46 Exporter tout le contenu
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:
port: 5000
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.
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
Ajoutons aussi des contraintes sur l’usage du CPU et de la RAM, en ajoutant à la même hauteur que env: :
resources:
requests:
memory: "50Mi"
limits:
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.
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.
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.
https://supports.uptime-formation.fr/all_content/ 149/210
14/11/2022 17:46 Exporter tout le contenu
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.
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.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: monster-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: monstericon
port:
number: 5000
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
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.
https://supports.uptime-formation.fr/all_content/ 150/210
14/11/2022 17:46 Exporter tout le contenu
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
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
https://supports.uptime-formation.fr/all_content/ 151/210
14/11/2022 17:46 Exporter tout le contenu
une connexion IP classique ou BGP pour la haute disponibilité.
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
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.
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:
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
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.
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 :
https://www.objectif-libre.com/fr/blog/2018/07/05/comparatif-solutions-reseaux-kubernetes/
https://rancher.com/blog/2019/2019-03-21-comparing-kubernetes-cni-providers-flannel-calico-canal-and-weave/
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.
https://supports.uptime-formation.fr/all_content/ 153/210
14/11/2022 17:46 Exporter tout le contenu
Determine best networking option - Project Calico
Vidéos
Des vidéos assez complètes sur le réseau, faites par Calico :
Il faut :
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.
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.
https://supports.uptime-formation.fr/all_content/ 154/210
14/11/2022 17:46 Exporter tout le contenu
Recréez les ressources avec apply. Les volumes devraient se connecter à nos conteneurs mysql et wordpress.
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: k8s.gcr.io/test-webserver
name: test-container
volumeMounts:
- mountPath: /test-pd
name: test-volume
volumes:
- name: test-volume
hostPath:
path: /data
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.
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.
Le stockage dynamique dans Kubernetes est fourni à travers des types de stockage appelés StorageClasses :
doc officielle
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.
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)
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).
https://supports.uptime-formation.fr/all_content/ 155/210
14/11/2022 17:46 Exporter tout le contenu
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
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.
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.
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/
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: default
name: pod-and-services
rules:
- apiGroups: [""]
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 role 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 :
https://supports.uptime-formation.fr/all_content/ 156/210
14/11/2022 17:46 Exporter tout le contenu
La plupart de ces rôles intégrés sont destinés au kube-system, c’est-à-dire aux processus internes du cluster.
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.
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.
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.
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
Un Chart contient un lot d’informations nécessaires pour créer une application Kubernetes :
helm repo add bitnami https://charts.bitnami.com/bitnami: ajouter un repo contenant des charts
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
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.
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.
https://supports.uptime-formation.fr/all_content/ 157/210
14/11/2022 17:46 Exporter tout le contenu
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/
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.
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.
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/
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).
Installer Helm
Pour installer Helm sur Ubuntu, utilisez : sudo snap install helm --classic
Autocomplete
https://supports.uptime-formation.fr/all_content/ 158/210
14/11/2022 17:46 Exporter tout le contenu
Cherchez Wordpress sur https://artifacthub.io/.
Prenez la version de Bitnami et ajoutez le dépôt avec la première commande à droite (ajouter le dépôt et déployer une release).
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. Dans minikube il faut
également lancer minikube service wordpress-tp.
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’utilisateur est par default user 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.
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/
code charts
Comment modifier l’username et le password wordpress à l’installation ? il faut donner comme paramètres le yaml suivant:
wordpressUsername: <votrenom>
wordpressPassword: <easytoguesspasswd>
Nous allons paramétrer plus encore l’installation. Créez un dossier TP5 avec à l’intérieur un fichier values.yaml contenant:
wordpressPassword: myunsecurepassword
replicaCount: 1
service:
type: ClusterIP
ingress:
enabled: true
hostname: wordpress.<stagiaire>.formation.dopl.uk # replace with your hostname pointing on the cluster ingress loadbalancer IP
tls: true
certManager: true
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
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: {}
3. 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) :
https://supports.uptime-formation.fr/all_content/ 159/210
14/11/2022 17:46 Exporter tout le contenu
helm repo add traefik https://helm.traefik.io/traefik
4. 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.
TP opt. - Le RBAC
Les rôles et le RBAC
1. Configurer Minikube pour activer RBAC.
3. En switchant de contexte à chaque fois, lancer la commande kubectl auth can-i pour différents cas et observer la différence
Ressources
https://medium.com/@HoussemDellai/rbac-with-kubernetes-in-minikube-4deed658ea7b
https://docs.bitnami.com/tutorials/configure-rbac-in-your-kubernetes-cluster/
Pour une solution plus avancée que le simple conteneur registry voir par exemple:
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).
https://supports.uptime-formation.fr/all_content/ 160/210
14/11/2022 17:46 Exporter tout le contenu
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.
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.
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
solvers:
- http01:
ingress:
class: nginx
Installer Argocd
Il faut maintenant créer l’ingress (reverse proxy) avec une configuration particulière que nous allons expliquer.
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"
# 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>
rules:
- host: argocd.<yoursubdomain>
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: argocd-server
port:
number: 443
Vérifiez dans Lens que l’ingress a bien généré un certificat (cela peut prendre jusqu’à 2 minutes)
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é).
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@).
https://supports.uptime-formation.fr/all_content/ 161/210
14/11/2022 17:46 Exporter tout le contenu
Poussez ce projet dans la branche k8s_gitlab_argocd_correction du dépot créé précédement:
Allons voir le pipeline dans l’interface CI/CD de gitlab. Les deux premier stages du pipeline devraient s’être bien déroulés.
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.
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.
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
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
Awesome Kubernetes
Tutoriels Bitnami pour pleins d’exemples d’installation de prod : https://docs.bitnami.com/tutorials/all
Bitnami Helm : https://github.com/bitnami/charts/tree/master/bitnami
BKPR : https://github.com/bitnami/kube-prod-runtime
Vitess : A database clustering system for horizontal scaling of MySQL : https://vitess.io
Rancher
Charts Helm : https://hub.kubeapps.com
https://supports.uptime-formation.fr/all_content/ 162/210
14/11/2022 17:46 Exporter tout le contenu
Stratégies de déploiement : https://blog.container-solutions.com/kubernetes-deployment-strategies
Réseau
Documentation officielle : https://kubernetes.io/fr/docs/concepts/services-networking/service/
Stockage
Rook et Ceph (fr)
Longhorn
Sécurité de Kubernetes
[xmco-actusecu-51-dossier_kubernetes (fr)](http://repository.root-me.org/Exploitation - Système/FR - xmco-actusecu-51-dossier_kubernetes.pdf)
[hacking_and_hardening_kubernetes_by_example_v2 (en)](http://repository.root-me.org/Exploitation - Système/EN -
hacking_and_hardening_kubernetes_by_example_v2.pdf)
[ht-w02_hacking_and_hardening_kubernetes (en)](http://repository.root-me.org/Exploitation - Système/EN - ht-
w02_hacking_and_hardening_kubernetes.pdf)
Gestion de secrets
https://argoproj.github.io/argo-cd/operator-manual/secret-management/
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).
Installez Helm si ce n’est pas déjà fait. Sur Ubuntu : sudo snap install helm --classic
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.
--namespace=monitoring \
--version=13.2.1 \
--set=service.type=NodePort \
prometheus \
prometheus-community/prometheus
https://supports.uptime-formation.fr/all_content/ 163/210
14/11/2022 17:46 Exporter tout le contenu
Une fois le chart installé vous pouvez visualisez les informations dans Lens, dans la premiere section du menu de gauche Cluster.
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.
réponse:
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.
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:
apiVersion: v1
kind: Secret
metadata:
namespace: monitoring
name: grafana-auth
type: Opaque
data:
EOF
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:
https://supports.uptime-formation.fr/all_content/ 164/210
14/11/2022 17:46 Exporter tout le contenu
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.
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.
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 :
https://istio.io/latest/docs/tasks/traffic-management/request-routing/
https://github.com/ContainerSolutions/k8s-deployment-strategies/tree/master/ab-testing
Ansible
Module 1
Ansible
Découvrir le couteau suisse de l’automatisation et de l’infrastructure as code.
Introduction DevOps
Cours 1 - Présentation
Cours 2 - Les playbooks Ansible, modules de base, variables et structures de contrôle
Cours 3 - Organiser un projet modulariser son code avec les roles
Cours 4 - Ansible en production, sécurité et Cloud
TP1 - Mise en place et Ansible ad-hoc
TP2 - Créer un playbook de déploiement d'application web flask
TP3 - Structurer le projet avec des roles
TP4 Ansible - Découvrir Jenkins et lancer des jobs Ansible
TP5 Bonus - Orchestration, Serveur de contrôle et Cloud
Bibliographie
Introduction DevOps
A propos de moi
Élie Gavoty
A propos de vous
Attentes ?
Début du cursus :
Est-ce que ça vous plait ?
https://supports.uptime-formation.fr/all_content/ 165/210
14/11/2022 17:46 Exporter tout le contenu
Quels modules avez vous déjà fait ?
Le mouvement DevOps
Le DevOps est avant tout le nom d’un mouvement de transformation professionnelle et technique de l’informatique.
Ce mouvement se structure autour des solutions humaines (organisation de l’entreprise et des équipes) et techniques (nouvelles technologies de
rupture) apportées pour répondre aux défis que sont:
L’agrandissement rapide face à la demande des services logiciels et infrastructures les supportant.
La célérité de déploiement demandée par le développement agile (cycles journaliers de développement).
Difficultées à organiser des équipes hétérogènes de grande taille et qui s’agrandissent très vite selon le modèle des startups.et
Du côté humain:
Application des process de management agile aux opérations et la gestion des infrastructures (pour les synchroniser avec le développement).
Remplacement des procédés d’opérations humaines complexes et spécifiques par des opérations automatiques et mieux standardisées.
Réconciliation de deux cultures divergentes (Dev et Ops) rapprochant en pratique les deux métiers du développeur et de l’administrateur système.
Du côté technique:
L’agilité en informatique
Traditionnellement la qualité logicielle provient :
Lenteur de livraison du logiciel (une version par an ?) donc aussi difficulté de fixer les bugs et problèmes de sécurité a temps
Le Travail du développeur est dominé par des process formels : ennuyeux et abstrait
difficulté commerciale : comment répondre à la concurence s’il faut 3 ans pour lancer un produit logiciel.
La célérité est : la rapidité (itérative) non pas seulement dans le développement du logiciel mais plus largement dans la livraison du service au
client:
Exemple : Netflix ou Spotify ou Facebook etc. déploient une nouvelle version mineure de leur logiciel par jour.
Lorsque la concurrence peut déployer des innovations en continu il devient central de pouvoir le faire.
Dans un DSI (département de service informatique) on organise ces activités d’admin sys en opérations:
On a un planning d’opération avec les priorités du moment et les trucs moins urgents
On prépare chaque opération au minimum quelques jours à l’avance.
On suit un protocole pour pas oublier des étapes de l’opération (pas oublier de faire une sauvegarde avant par exemple)
La difficulté principale pour les Ops c’est qu’un système informatique est:
Un système très complexe qu’il est quasi impossible de complètement visualiser dans sa tête.
Les évènements qui se passe sur la machines sont instantanés et invisibles
L'état actuel de la machine n’est pas ou peu explicite (combien d’utilisateur, machine pas connectée au réseau par exemple.)
Les interractions entre des problèmes peu graves peuvent entrainer des erreurs critiques en cascades.
On peut donc constater que les opérations traditionnelles implique une culture de la prudence
On s’organise à l’avance.
On vérifie plusieurs fois chaque chose.
On ne fait pas confiance au code que nous donnent les développeurs.
On suit des procédures pour limiter les risques.
On surveille l’état du système (on parle de monitoring)
Et on reçoit même des SMS la nuit si ya un problème :S
https://supports.uptime-formation.fr/all_content/ 166/210
14/11/2022 17:46 Exporter tout le contenu
Bilan
Peuvent pas aller trop vite car il faut marcher sur des oeufs.
Les Ops veulent pas déployer de nouvelles versions trop souvent car ça fait plein de boulot et ils prennent des risques (bugs / incompatilibités).
Quand c’est mal organisé ou qu’on va trop vite il y a des catastrophes possibles.
Du côté des développeurs avec l’agilité on a déjà depuis des années une façon d’automatiser pleins d’opérations sur le code à chaque fois qu’on valide
une modification.
Le principe central du DevOps est d’automatiser également les opérations de déploiement et de maintenance en se basant sur le même modèle.
Mais pour que ça fonctionne il faut résoudre des défi techniques nouveau => innovations
Infrastructure as a Service (IaaS): on commande du linux, du réseau et des loadbalancer etc. à la demande
Plateforme as a Service (PaaS): on commande directement un environnement PHP ou NodeJS pour notre application
Software as a service (SaaS): des services web à la demande pour des utilisateurs finaux
On peut dire que chaque couche (d’abstraction) de l’informatique est commandable à la demande.
https://supports.uptime-formation.fr/all_content/ 167/210
14/11/2022 17:46 Exporter tout le contenu
Un façon standard de packager un logiciel
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.
Le problème identifié que cherche a résoudre l’IaC est un écheveau de difficulées pratiques rencontrée dans l’administration système traditionnelle:
1. Connaissance limité de l’état courant d’un système lorsqu’on fait de l'administration ad-hoc (manuelle avec des commandes unix/dos).
2. Faible reproductibilité des systèmes et donc difficultée/lenteur du passage à l’échelle (horizontal scaling).
Multiplier les serveurs identiques est difficile si leur état est le résultat d’un processus manuel partiellement documenté.
Difficulté à reproduire/simuler l’état précis de l’infrastructure de production dans les contextes de tests logiciels.
3. Difficultés du travail collaboratif dans de grandes équipes avec plusieurs culture (Dev vs Ops) lorsque les rythmes et les modes de travail
diffèrent
Notre programme
Docker : les conteneurs et l’infra as code
Ansible : couteau suisse de l’infra as code
Kubernetes : infrastructure de conteneurs (iac et cloud)
Jenkins : CI/CD pour intégrer ensemble le dev et les opérations
Cours 1 - Présentation
Plan
Module 1 : Installer ansible, configurer la connexion et commandes ad hoc ansible
Installation
configurer ansible
/etc ou ansible.cfg
configuration de la connexion
connexion SSH et autres plugins de connection
versions de Python et d’Ansible
L’inventaire ansible
https://supports.uptime-formation.fr/all_content/ 168/210
14/11/2022 17:46 Exporter tout le contenu
modules de déploiement et configuration
handlers
contrôler le statut de retour des tâches
gestion de l’idempotence des commandes Unix
debugging de playbook
verbosite
directive de debug
gestion des erreurs à l’exécution
TP2: Écriture d’un playbook simple de déploiement d’une application web flask en python.
Les roles
TP3: Transformation de notre playbook en role et utilisation de roles ansible galaxy pour déployer une infrastructure multitiers.
Intégration d’Ansible
Orchestration
Sécurité
TP4: Refactoring de notre code pour effectuer un rolling upgrade et déploiement dans le cloud + AWX
Présentation d’Ansible
Ansible
https://supports.uptime-formation.fr/all_content/ 169/210
14/11/2022 17:46 Exporter tout le contenu
Ansible est un gestionnaire de configuration et un outil de déploiement et d’orchestration très populaire et central dans le monde de
l'infrastructure as code (IaC).
Il fait donc également partie de façon centrale du mouvement DevOps car il s’apparente à un véritable couteau suisse de l’automatisation des
infrastructures.
Histoire
Ansible a été créé en 2012 (plus récent que ses concurrents Puppet et Chef) autour d’une recherche de simplicité et du principe de configuration
agentless.
Très orienté linux/opensource et versatile il obtient rapidement un franc succès et s’avère être un couteau suisse très adapté à l’automatisation DevOps
et Cloud dans des environnements hétérogènes.
Red Hat rachète Ansible en 2015 et développe un certain nombre de produits autour (Ansible Tower, Ansible container avec Openshift).
Ansible est agentless c’est à dire qu’il ne nécessite aucun service/daemon spécifique sur les machines à configurer.
La simplicité d’Ansible provient également du fait qu’il s’appuie sur des technologies linux omniprésentes et devenues universelles.
ssh : connexion et authentification classique avec les comptes présents sur les machines.
python : multiplateforme, un classique sous linux, adapté à l’admin sys et à tous les usages.
De fait Ansible fonctionne efficacement sur toutes les distributions linux, debian, centos, ubuntu en particulier (et maintenant également sur Windows).
petit:
… un petit playbook (~script) fournit avec le code d’un logiciel pour déployer en mode test.
… la configuration d’une machine de travail personnelle.
etc.
moyen:
grand:
Ansible et Docker
Il permet de provisionner des machines avec docker ou kubernetes installé pour ensuite déployer des conteneurs.
Il permet une orchestration simple des conteneur avec le module docker_container.
Plus récemment avec l’arrivé d'Ansible container il est possible de construire et déployer des conteneurs docker avec du code ansible. Cette solution
fait partie de la stack Red Hat Openshift. Concrètement le langage ansible remplace (avantageusement ?) le langage Dockerfile pour la construction des
images Docker.
https://supports.uptime-formation.fr/all_content/ 170/210
14/11/2022 17:46 Exporter tout le contenu
version généralement plus ancienne (2.4 ou 2.6)
facile à mettre à jour avec le reste du système
Pour installer une version récente on il existe des dépots spécifique à ajouter: exemple sur ubuntu: sudo apt-add-repository --yes --
update ppa:ansible/ansible
Avec pip le gestionnaire de paquet du langage python: sudo pip3 install
installe la dernière version stable (2.8 actuellement)
commande d’upgrade spécifique sudo pip3 install ansible --upgrade
possibilité d’installer facilement une version de développement pour tester de nouvelles fonctionnalité ou anticiper les migrations.
Pour tester la connexion aux serveurs on utilise la commande ad hoc suivante. ansible all -m ping
Classées par groupe et sous groupes pour être désignables collectivement (exp executer telle opération sur)
La méthode connexion est précisée soit globalement soit pour chaque machine.
Des variables peuvent être définies pour chaque machine ou groupe pour contrôler dynamiquement par la suite la configuration ansible.
Classées par groupe et sous groupes pour être désignables collectivement (exp executer telle opération sur)
La méthode connexion est précisée soit globalement soit pour chaque machine.
Des variables peuvent être définies pour chaque machine ou groupe pour contrôler dynamiquement par la suite la configuration ansible.
Exemple :
[all:vars]
ansible_ssh_user=elie
ansible_python_interpreter=/usr/bin/python3
[awx_nodes]
[dbservers]
[appservers]
Les inventaires peuvent également être au format YAML (plus lisible mais pas toujours intuitif) ou JSON (pour les machines).
Configuration
Ansible se configure classiquement au niveau global dans le dossier /etc/ansible/ dans lequel on retrouve en autre l’inventaire par défaut et des
paramètre de configuration.
Ansible est très fortement configurable pour s’adapter à des environnement contraints.
Liste des paramètre de configuration:
Alternativement on peut configurer ansible par projet avec un fichier ansible.cfg présent à la racine. Toute commande ansible lancée à la racine du
projet récupère automatiquement cette configuration.
La commande ansible
version minimale :
ansible <groupe_machine> -m <module> -a <arguments_module>
ansible all -m ping: Permet de tester si les hotes sont joignables et ansible utilisable (SSH et python sont présents et configurés).
Ansible fonctionne grâce à des modules python téléversés sur sur l’hôte à configurer puis exécutés. Ces modules sont conçus pour être cohérents et
versatiles et rendre les tâches courantes d’administration plus simples.
Il en existe pour un peu toute les tâches raisonnablement courantes : un slogan Ansible “Batteries included” ! Plus de 1300 modules sont intégrés par
défaut.
ping: un module de test Ansible (pas seulement réseau comme la commande ping)
yum/apt: pour gérer les paquets sur les distributions basées respectivement sur Red Hat ou Debian.
systemd (ou plus générique service): gérer les services/daemons d’un système.
file: pour créer, supprimer, modifier, changer les permission de fichiers, dossier et liens.
exemple: le module file permet de gérer de nombreuses opérations avec un seul module en variant les arguments.
cela permet de garder les appel de modules très succints pour les taches par défaut
il est également possible de rendre des paramètres par défaut explicites pour augmenter la clarté du code.
Exemple et bonne pratique: toujours préciser state: present même si cette valeur est presque toujours le défaut implicite.
Commençons le TP1
La dimension incrémentale du code rend en particulier plus aisé de construire une infrastructure progressivement en la complexifiant au fur et à mesure
plutôt que de devoir tout plannifier à l’avance.
Syntaxe yaml
Les playbooks ansible sont écrits au format YAML.
YAML est basé sur les identations à base d’espaces (2 espaces par indentation en général). Comme le langage python.
C’est un format assez lisible et simple à écrire bien que les indentations soient parfois difficiles à lire.
C’est un format assez flexible avec des types liste et dictionnaires qui peuvent s’imbriquer.
Le YAML est assez proche du JSON (leur structures arborescentes typées sont isomorphes) mais plus facile à écrire.
A quoi ça ressemble ?
Une liste
- 1
- Poire
Un dictionnaire
clé1: valeur1
clé2: valeur2
clé3: 3
jour: dimanche
horaire:
unité: "heure"
min: 9
max: 14 # entier
- nom: pomme
couleur: "verte"
- nom: poires
couleur: jaune
pesticide: sans
- courgettes
- salade
- potiron
Pour mieux visualiser l’imbrication des dictionnaires et des listes en YAML on peut utiliser un convertisseur YAML -> JSON :
https://www.json2yaml.com/.
https://supports.uptime-formation.fr/all_content/ 172/210
14/11/2022 17:46 Exporter tout le contenu
{
"marché": {
"jour": "dimanche",
"horaire": {
"unité": "heure",
"min": 9,
"max": 14
},
"fruits": [
"nom": "pomme",
"couleur": "verte",
"pesticide": "avec"
},
"nom": "poires",
"couleur": "jaune",
"pesticide": "sans"
],
"légumes": [
"courgettes",
"salade",
"potiron"
Observez en particulier la syntaxe assez condensée de la liste “fruits” en YAML qui est une liste de dictionnaires.
- name: premier play # une liste de play (chaque play commence par un tiret)
become: yes
vars:
logfile_name: "auth.log"
var_files:
- mesvariables.yml
pre_tasks:
set_fact:
roles:
- flaskapp
tasks:
apt: name=nginx state=present # syntaxe concise proche des commandes ad hoc mais moins lisible
mode: 755
- import_tasks: mestaches.yml
handlers:
- systemd:
name: nginx
state: "reloaded"
hosts: dbservers
tasks:
...
Un play est un dictionnaire yaml qui décrit un ensemble de taches ordonnées en plusieurs sections. Un play commence par préciser sur quelles
machines il s’applique puis précise quelques paramètres faculatifs d’exécution comme become: yes pour l’élévation de privilège (section hosts).
La section hosts est obligatoire. Toutes les autres sections sont facultatives !
La section tasks est généralement la section principale car elle décrit les taches de configuration à appliquer.
La section tasks peut être remplacée ou complétée par une section roles et des sections pre_tasks post_tasks
Les handlers sont des tâches conditionnelles qui s’exécutent à la fin (post traitements conditionnels comme le redémarrage d’un service)
Ordre d’execution
1. pre_tasks
2. roles
3. tasks
4. post_tasks
5. handlers
Les roles ne sont pas des tâches à proprement parler mais un ensemble de tâches et ressources regroupées dans un module un peu comme une librairie
developpement. Cf. cours 3.
https://supports.uptime-formation.fr/all_content/ 173/210
14/11/2022 17:46 Exporter tout le contenu
bonnes pratiques de syntaxe
Pour valider la syntaxe il est possible d’installer et utiliser ansible-linter sur les fichiers YAML.
Imports et includes
Les deux instructions précédentes désignent un import statique qui est résolu avant l’exécution.
Au contraire, include_tasks permet d’intégrer une liste de tâche dynamiquement pendant l’exécution
Par exemple:
vars:
apps:
- app1
- app2
- app3
tasks:
- include_tasks: install_app.yml
Ce code indique à Ansible d’executer une série de tâches pour chaque application de la liste. On pourrait remplacer cette liste par une liste dynamique.
Comme le nombre d’import ne peut pas facilement être connu à l’avance on doit utiliser include_tasks.
Élévation de privilège
L’élévation de privilège est nécessaire lorsqu’on a besoin d’être root pour exécuter une commande ou plus généralement qu’on a besoin d’exécuter une
commande avec un utilisateur différent de celui utilisé pour la connexion on peut utiliser:
Au moment de l’exécution l’argument --become en ligne de commande avec ansible, ansible-console ou ansible-playbook.
au début du play (après hosts) : toutes les tâches seront executée avec cette élévation par défaut.
après n’importe quelle tâche : l’élévation concerne uniquement la tâche cible.
Pour executer une tâche avec un autre utilisateur que root (become simple) ou celui de connexion (sans become) on le précise en ajoutant à
become: yes, become_user: username
Variables Ansible
Ansible utilise en arrière plan un dictionnaire contenant de nombreuses variables.
Ce moteur permet de créer des valeurs dynamiques dans le code des playbooks, des roles, et des fichiers de configuration.
Les variables écrites au format {{ mavariable }} sont remplacées par leur valeur provenant du dictionnaire d’exécution d’Ansible.
Des filtres (fonctions de transformation) permettent de transformer la valeur des variables: exemple : {{ hostname | default('localhost') }}
(Voir plus bas)
https://supports.uptime-formation.fr/all_content/ 174/210
14/11/2022 17:46 Exporter tout le contenu
La section vars: du playbook.
Un fichier de variables appelé avec var_files:
L’inventaire : variables pour chaque machine ou pour le groupe.
Dans des dossier extension de l’inventaire group_vars, host_bars
Dans le dossier defaults des roles (cf partie sur les roles)
Dans une tache avec le module set_facts.
A runtime au moment d’appeler la CLI ansible avec --extra-vars "version=1.23.45 other_variable=foo"
Lorsque définies plusieurs fois, les variables ont des priorités en fonction de l’endroit de définition.
L’ordre de priorité est plutôt complexe:
https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable
En résumé la règle peut être exprimée comme suit: les variables de runtime sont prioritaires sur les variables dans un playbook qui sont prioritaires sur
les variables de l’inventaire qui sont prioritaires sur les variables par défaut d’un role.
Bonne pratique: limiter les redéfinitions de variables en cascade (au maximum une valeur par défaut, une valeur contextuelle et une valeur
runtime) pour éviter que le playbook soit trop complexe et difficilement compréhensible et donc maintenable.
Remarques de syntaxe
groups.all et groups['all'] sont deux syntaxes équivalentes pour désigner les éléments d’un dictionnaire.
variables spéciales
https://docs.ansible.com/ansible/latest/reference_appendices/special_variables.html
Pour explorer chacune de ces variables vous pouvez utiliser le module debug en mode adhoc ou dans un playbook:
ou encore:
Facts
Les facts sont des valeurs de variables récupérées au début de l’exécution durant l’étape gather_facts et qui décrivent l’état courant de chaque machine.
Par exemple, ansible_os_family est un fact/variable décrivant le type d’OS installé sur la machine. Elle n’existe qu’une fois les facts récupérés.
! Lors d’une commande adhoc ansible les facts ne sont pas récupérés : la variable ansible_os_family ne sera pas disponible.
La liste des facts peut être trouvée dans la documentation et dépend des plugins utilisés pour les récupérés:
https://docs.ansible.com/ansible/latest/user_guide/playbooks_vars_facts.html
systemd:
name: nginx
state: started
La directive loop:
Cette directive permet d’executer une tache plusieurs fois basée sur une liste de valeur:
https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html
exemple:
- hosts: localhost
tasks:
debug:
loop:
- message1
- message2
- message3
- hosts: localhost
vars:
messages:
- message1
- message2
https://supports.uptime-formation.fr/all_content/ 175/210
14/11/2022 17:46 Exporter tout le contenu
- message3
tasks:
debug:
loop_control:
loop_var: message
index_var: num
Cette fonctionnalité de boucle était anciennement accessible avec le mot clé with_items: qui est maintenant déprécié.
Filtres Jinja
Pour transformer la valeur des variables à la volée lors de leur appel on peut utiliser des filtres (jinja2) :
par exemple on peut fournir une valeur par défaut pour une variable avec filtre default: {{ hostname | default('localhost') }}
Un autre usage courant des filtres est de reformater et filtrer des listes et dictionnaires de paramètre. Ces syntaxes sont peut intuitives. Vous
pouvez vous entrainer en regardant ces tutoriels:
https://www.tailored.cloud/devops/how-to-filter-and-map-lists-in-ansible/
https://www.tailored.cloud/devops/advanced-list-operations-ansible/
Debugger un playbook.
Utiliser une tache avec le module debug : debug msg="{{ mavariable }}".
Utiliser la directive debugger: always ou on_failed à ajouter à la fin d’une tâche. L’exécution s’arrête alors après l’exécution de cette tâche et
propose un interpreteur de debug.
group_vars/
group2.yml
host_vars/
hostname2.yml
roles/
webtier/ # same kind of structure as "common" was above, done for the webtier role
monitoring/ # ""
fooapp/ # ""
Plusieurs remarques:
Chaque environnement (staging, production) dispose d’un inventaire ce qui permet de préciser à runtime quel environnement cibler avec l’option
--inventaire production.
Chaque groupe de serveurs (tier) dispose de son playbook
qui s’applique sur le groupe en question.
éventuellement définit quelques variables spécifiques (mais il vaut mieux les mettre dans l’inventaire ou les dossiers cf suite).
Idéalement contient un minimum de tâches et plutôt des roles (ie des tâches rangées dans une sorte de module)
Pour limiter la taille de l’inventaire principal on range les variables communes dans des dossiers group_vars et host_vars. On met à l’intérieur un
fichier <nom_du_groupe>.yml qui contient un dictionnaire de variables.
On cherche à modulariser au maximum la configuration dans des roles c’est à dire des modules rendus génériques et specifique à un objectif de
configuration.
Ce modèle d’organisation correspond plutôt à la configuration de base d’une infrastructure (playbooks à exécuter régulièrement) qu’à l’usage de
playbooks ponctuels comme pour le déploiement. Mais, bien sur, on peut ajouter un dossier playbooks ou operations pour certaines opérations
ponctuelles. (cf cours 4)
Si les modules de Ansible (complétés par les commandes bash) ne suffisent pas on peut développer ses propre modules ansible.
Il s’agit de programmes python plus ou moins complexes
On les range alors dans le dossier library du projet ou d’un role et on le précise éventuellement dans ansible.cfg.
Observons le role Common : il est utilisé ici pour rassembler les taches de base des communes à toutes les machines. Par exemple s’assurer que les
clés ssh de l’équipe sont présentes, que les dépots spécifiques sont présents etc.
https://supports.uptime-formation.fr/all_content/ 176/210
14/11/2022 17:46 Exporter tout le contenu
Roles Ansible
Objectif:
Découper les tâches de configuration en sous ensembles réutilisables (une suite d’étapes de configuration).
Ansible est une sorte de langage de programmation et l’intéret du code est de pouvoir créer des fonction regroupées en librairies et les composer.
Les roles sont les “librairies/fonction” ansible en quelque sorte.
Comme une fonction un role prend généralement des paramètres qui permettent de personnaliser son comportement.
Tout le nécessaire doit y être (fichiers de configurations, archives et binaires à déployer, modules personnels dans library etc.)
Remarque ne pas confondre modules et roles : file est un module geerlingguy.docker est un role. On doit écrire des roles pour coder
correctement en Ansible, on peut écrire des modules mais c’est largement facultatif car la plupart des actions existent déjà.
Dans la philosophie Ansible on recherche la généricité des roles. On cherche à ajouter des paramètres pour que le rôle s’adapte à différents
cas (comme notre playbook flask app).
Une bonne pratique: préfixer le nom des paramètres par le nom du role exemple docker_edition.
Cependant la généricité est nécessaire quand on veut distribuer le role ou construire des outils spécifiques qui serve à plus endroit de
l’infrastructure mais elle augmente la complexité.
Donc pour les roles internes on privilégie la simplicité.
Les roles contiennent idéalement un fichier README en décrire l’usage et un fichier meta/main.yml qui décrit la compatibilité et les
dépendanice en plus de la licence et l’auteur.
Il peuvent idéalement être versionnés dans des dépots à part et installé avec ansible-galaxy
roles/
tasks/ #
handlers/ #
files/ #
foo.sh # <-- script files for use with the script resource
vars/ #
defaults/ #
meta/ #
lookup_plugins/
On constate que les noms des sous dossiers correspondent souvent à des sections du playbook. En fait le principe de base est d’extraire les différentes
listes de taches ou de variables dans des sous-dossier
Remarque : les fichier de liste doivent nécessairement s’appeler main.yml" (pas très intuitif)
Remarque2 : main.yml peut en revanche importer d’autre fichiers aux noms personnalisés (exp role docker de geerlingguy)
Le dossier defaults contient les valeurs par défaut des paramètres du role. Ces valeurs ne sont jamais prioritaires (elles sont écrasées par
n’importe quelle redéfinition)
Le fichier meta/main.yml est facultatif mais conseillé et contient des informations sur le role
auteur
license
compatibilité
version
dépendances à d’autres roles.
Le dossier files contient les fichiers qui ne sont pas des templates (pour les module copy ou sync, script etc).
Ansible Galaxy
C’est le store de roles officiel d’Ansible : https://galaxy.ansible.com/
C’est également le nom d’une commande ansible-galaxy qui permet d’installer des roles et leurs dépendances depuis internet. Un sorte de gestionnaire
de paquet pour ansible.
Elle est utilisée généralement sour la forme ansible install -r roles/requirements.yml -p roles <nom_role> ou plus simplement ansible-galaxy
install <role> mais installe dans /etc/ansible/roles.
Tous les rôles ansible sont communautaires (pas de roles officiels) et généralement stockés sur github.
Mais on peut voir la popularité la qualité et les tests qui garantissement la plus ou moins grande fiabilité du role
Il existe des roles pour installer un peu n’importe quelle application serveur courante aujourd’hui. Passez du temps à explorer le web avant de
développer quelque chose avec Ansible
https://supports.uptime-formation.fr/all_content/ 177/210
14/11/2022 17:46 Exporter tout le contenu
Installer des roles avec requirements.yml
Conventionnellement on utilise un fichier requirements.yml situé dans roles pour décrire la liste des roles nécessaires à un projet.
- src: geerlingguy.repo-epel
- src: geerlingguy.haproxy
- src: geerlingguy.docke
- src: https://github.com/bennojoy/nginx
version: master
name: nginx_role
Une production Ansible est généralement un serveur spécial (parfois appelé un ansible master) depuis lequel le code peut être exécuté, ponctuellement
ou de préférence régulièrement (2x par jours par exemple).
Le serveur Ansible s’assure également que les exécutions sont correctement logguées et que les DevOps peuvent par la suite s’assurer que les
différentes exécutions se sont déroulées correctement et éventuellement lire les logs d’execution pour diagnostiquer les erreurs.
Un serveur master Linux simple pour executer Ansible en CLI ou en Cron : plus léger et versatile mais ne propose par de dashboard pour afficher
l’état de de l’infrastructure
Rundeck: une solution générique pour exécuter des Jobs d’infrastructure qui s’intègre plutôt correctement avec Ansible.
Jenkins: souvent associé à la CI/CD, Jenkins est en réalité un serveur générique pour exécuter des Jobs automatiquement et à la demande. Il
propose un plugin Ansible intéssant et permet de consulté les logs d’exécution et d’avoir une vue globale des dernières exécutions à travers des
dashboard. Il est très flexible mais assez complexe à configurer correctement.
Nous allons pour le dernier TP de ce module utiliser Jenkins pour exécuter Ansible. Ainsi nous pouvons découvrir un peu en avance
Jenkins qui est
complexe et important pour la fin du cursus.
Sécurité
Les problématiques de sécurité linux ne sont pas résolue magiquement par Ansible. Tous le travail de réflexion et de sécurisation reste identique mais
peut comme le reste être mieux controllé grace à l’approche déclarative de l’infrastructure as code.
Si cette problématique des liens entre Ansible et sécurité vous intéresse : Security automation with Ansible
Il est à noter tout de même qu’Ansible est généralement apprécié d’un point de vue sécurité car il n’augmente pas (vraiment) la surface d’attaque de vos
infrastructure : il est basé sur ssh qui est éprouvé et ne nécessite généralement pas de réorganisation des infrastructures.
Pour les cas plus spécifiques et si vous voulez éviter ssh, Ansible est relativement agnostique du mode de connexion grâce aux plugins de connexions
(voir ci-dessous).
Authentification et SSH
Un bonne pratique importante : changez le port de connexion ssh pour un port atypique. Ajoutez la variable ansible_ssh_port=17728 dans l’inventaire.
Il faut idéalement éviter de créer un seul compte ansible de connexion pour toutes les machines:
difficile à bouger
responsabilité des connexions pas auditable (auth.log + syslog)
Il faut utiliser comme nous avons fait dans les TP des logins ssh avec les utilisateurs humain réels des machines et des clés ssh. C’est à dire le même
modèle d’authentification que l’administration traditionnelle.
Pour afficher la liste des plugins disponible lancez ansible-doc -t connection -l.
Une autre connexion courante est ansible_connection=local qui permet de configurer la machine locale sans avoir besoin d’installer un serveur
ssh.
Citons également les connexions ansible_connexion=docker et ansible_connexion=lxd pour configurer des conteneurs linux ainsi que
ansible_connexion= pour les serveurs windows
Les questions de sécurités de la connexion se posent bien sur différemment selon le mode de connexion utilisés (port, authentification, etc.)
https://supports.uptime-formation.fr/all_content/ 178/210
14/11/2022 17:46 Exporter tout le contenu
Pour débugger les connexions et diagnotiquer leur sécurité on peut afficher les détails de chaque connection ansible avec le mode de verbosité
maximal (network) en utilisant le paramètre -vvvv.
Variables et secrets
Le principal risque de sécurité lié à Ansible comme avec Docker et l’IaC en général consiste à laisser trainer des secrets (mot de passe, identités de
clients, api token, secret de chiffrement / migration etc.) dans le code ou sur les serveurs (moins problématique).
Attention : les dépôt git peuvent cacher des secrets dans leur historique. Pour chercher et nettoyer un secret dans un dépôt l’outil le plus courant est
BFG : https://rtyley.github.io/bfg-repo-cleaner/
Ansible vault
Pour éviter de divulguer des secrets par inadvertance, il est possible de gérer les secrets avec des variables d’environnement ou avec un fichier variable
externe au projet qui échappera au versionning git, mais ce n’est pas idéal.
Ansible intègre un trousseau de secret appelé , Ansible Vault permet de chiffrer des valeurs variables par variables ou des fichiers complets.
Les
valeurs stockées dans le trousseaux sont déchiffrée à l’exécution après dévérouillage du trousseau.
Pour déchiffrer il est ensuite nécessaire d’ajouter l’option --ask-vault-pass au moment de l’exécution de ansible ou ansible-playbook
Il existe également un mode pour gérer plusieurs mots de passe associés à des identifiants.
Il existe de nombreuses solutions pour intégrer Ansible avec les principaux providers de cloud (modules ansible, plugins d’API, intégration avec d’autre
outils d’IaC Cloud comme Terraform ou Cloudformation).
Inventaires dynamiques
Les inventaires que nous avons utilisés jusqu’ici implique d’affecter à la main les adresses IP des différents noeuds de notre infrastructure. Cela devient
vite ingérable.
La solution ansible pour le pas gérer les IP et les groupes à la main est appelée inventaire dynamique ou inventory plugin. Un inventaire dynamique
est simplement un programme qui renvoie un JSON respectant le format d’inventaire JSON ansible, généralement en contactant l’api du cloud provider
ou une autre source.
$ ./inventory_terraform.py
"_meta": {
"hostvars": {
"balancer0": {
"ansible_host": "104.248.194.100"
},
"balancer1": {
"ansible_host": "104.248.204.222"
},
"awx0": {
"ansible_host": "104.248.204.202"
},
"appserver0": {
"ansible_host": "104.248.202.47"
},
"all": {
"children": [],
"hosts": [
"appserver0",
"awx0",
"balancer0",
"balancer1"
],
"vars": {}
},
"appservers": {
"children": [],
"hosts": [
"balancer0",
"balancer1"
],
"vars": {}
},
https://supports.uptime-formation.fr/all_content/ 179/210
14/11/2022 17:46 Exporter tout le contenu
"awxnodes": {
"children": [],
"hosts": [
"awx0"
],
"vars": {}
},
"balancers": {
"children": [],
"hosts": [
"appserver0"
],
"vars": {}
}%
On peut ensuite appeler ansible-playbook en utilisant ce programme plutôt qu’un fichier statique d’inventaire: ansible-playbook -i
inventory_terraform.py configuration.yml
ansible --version
=> 2.8.x
Traditionnellement lorsqu’on veut vérifier le bon fonctionnement d’une configuration on utilise ansible all -m ping. Que signifie-t-elle ?
Réponse :
Réponse :
Utilisez en plus l’option -vvv pour mettre en mode très verbeux. Ce mode est très efficace pour débugger lorsqu’une erreur inconnue se présente.
Que se passe-t-il avec l’inventaire ?
Réponse :
Testez l’installation avec la commande ansible en vous connectant à votre machine localhost et en utilisant le module ping.
Réponse :
Ajoutez la ligne hotelocal ansible_host=127.0.0.1 dans l’inventaire par défaut (le chemin est indiqué dans). Et pinguer hotelocal.
Réponse :
Facultatif :
Nous sommes maintenant prêts à créers des fichiers pour notre projet Ansible.
Découvrir Vagrant
Vagrant est un outil pour créer des VMs (ou conteneurs) à partir de code. Son objectif est de permettre la création d’environnement de développement /
DevOps reproductibles et partageables.
Pour utiliser Ansible nous avons justement besoin de machine vituelles à provisionner. Nous allons utiliser Vagrant et Virtualbox pour créer plusieurs
serveurs.
Installez Vagrant en ajoutant le dépôt ubuntu et utilisant apt (voir https://www.vagrantup.com/downloads pour d’autres installation):
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
https://supports.uptime-formation.fr/all_content/ 180/210
14/11/2022 17:46 Exporter tout le contenu
Ajoutez à l’intérieur un fichier Vagrantfile contenant le code suivant:
Vagrant.configure("2") do |config|
config.ssh.insert_key = false # to use the global unsecure key instead of one insecure key per VM
v.memory = 512
v.cpus = 1
end
# Vagrant va récupérer une machine de base ubuntu 20.04 (focal) depuis cette plateforme https://app.vagrantup.com/boxes/search
ubu1.vm.box = "ubuntu/focal64"
ubu1.vm.hostname = "ubu1"
end
# Vagrant va récupérer une machine de base ubuntu 20.04 (focal) depuis cette plateforme https://app.vagrantup.com/boxes/search
centos1.vm.box = "geerlingguy/centos7"
centos1.vm.hostname = "centos1"
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).
Pour voir toutes les machines en train de tourner utilisez vagrant global-status --prune
Toutes les machines vagrant (on parle de boxes vagrant) ont automatiquement un utilisateur vagrant qui a une clé SSH publiquement disponible
(ce n’est pas sécurisé mais utile pour le développement).
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 de votre projet sont donc directement accessibles dans la VM.
Essayez de vous connecter à ubu1 et centos1 en ssh avec l’option -i ~/.vagrant.d/insecure_private_key pour vérifier que la clé ssh est bien
configurée et vérifiez dans chaque machine que le sudo est configuré sans mot de passe avec sudo -i.
Un projet Ansible implique généralement une configuration Ansible locale décrite dans un fichier ansible.cfg. Ainsi, la configuration est versionnée
avec git en même temps que le code et l’infrastructure devient portable entre les ordinateurs des différents développeurs/DevOps.
[defaults]
inventory = ./inventory.cfg
roles_path = ./roles
host_key_checking = false # nécessaire pour les labs ou on créé et supprime des machines constamment avec des signatures SSH changées.
Créez le fichier d’inventaire inventory.cfg comme spécifié dans ansible.cfg et ajoutez à l’intérieur nos machines ubu1 et centos1 d’après ce
modèle:
ubu1 ansible_host=<ip_ubu1>
centos1 ansible_host=<ip_centos1>
[all:vars]
ansible_user=<user>
ansible_ssh_private_key_file=~/.vagrant.d/insecure_private_key
Réponse :
Réponse :
Créez un groupe adhoc_lab et ajoutez les deux machines ubu1 et centos1 dedans.
Réponse :
Réponse :
https://supports.uptime-formation.fr/all_content/ 181/210
14/11/2022 17:46 Exporter tout le contenu
Nous avons jusqu’à présent utilisé une connexion ssh par clé et précisé l’utilisateur de connexion dans le fichier ansible.cfg. Cependant on peut
aussi utiliser une connexion par mot de passe et préciser l’utilisateur et le mot de passe dans l’inventaire ou en lançant la commande.
En précisant les paramètres de connexion dans le playbook il et aussi possible d’avoir des modes de connexion (ssh, winrm, lxd, docker, etc) différents
pour chaque machine.
[all:vars]
ansible_user=vagrant
ansible_ssh_private_key_file=~/.vagrant.d/insecure_private_key
[ubuntu_hosts]
ubu1 ansible_host=<ip>
[centos_hosts]
centos1 ansible_host=<ip>
[adhoc_lab:children]
ubuntu_hosts
centos_hosts
Dans un inventaire ansible on commence toujours par créer les plus petits sous groupes puis on les rassemble en plus grands groupes.
Nous allons maintenant installer nginx sur les 2 machines. Il y a plusieurs façons d’installer des logiciels grâce à Ansible: en utilisant le gestionnaire de
paquets de la distribution ou un gestionnaire spécifique comme pip ou npm. Chaque méthode dispose d’un module ansible spécifique.
Si nous voulions installer nginx avec la même commande sur des machines centos et ubuntu à la fois impossible d’utiliser apt car centos utilise
yum. Pour éviter ce problème on peut utiliser le module package qui permet d’uniformiser l’installation (pour les cas simples).
Allez voir la documentation de ce module
utilisez --become pour devenir root avant d’exécuter la commande (cf élévation de privilège dans le cours2)
Utilisez le pour installer nginx
Réponse :
Réponse :
Réponse :
Utiliser le module systemd et l’option --check pour vérifier si le service nginx est démarré sur chacune des 2 machines. Normalement vous
constatez que le service est déjà démarré (par défaut) sur la machine ubuntu et non démarré sur la machine centos.
Réponse :
L’option --check à vérifier l’état des ressources sur les machines mais sans modifier la configuration`. Relancez la commande précédente pour le
vérifier. Normalement le retour de la commande est le même (l’ordre peu varier).
Enlevez le --check pour vous assurer que le service est démarré sur chacune des machines.
Visitez dans un navigateur l’ip d’un des hôtes pour voir la page d’accueil nginx.
le module shell utilise un module python qui appelle un shell pour lancer une commande.
fonctionne comme le lancement d’une commande shell mais utilise un module python.
le module raw.
Créez un fichier dans /tmp avec touch et l’un des modules précédents.
Relancez la commande. Le retour est toujours changed car ces modules ne sont pas idempotents.
Relancer l’un des modules shell ou command avec touch et l’option creates pour rendre l’opération idempotente. Ansible détecte alors que le
fichier témoin existe et n’exécute pas la commande.
https://supports.uptime-formation.fr/all_content/ 182/210
14/11/2022 17:46 Exporter tout le contenu
[defaults]
inventory = ./inventory.cfg
roles_path = ./roles
host_key_checking = false
Vagrant.configure("2") do |config|
config.ssh.insert_key = false # to use the global unsecure key instead of one insecure key per VM
v.memory = 512
v.cpus = 1
end
# Vagrant va récupérer une machine de base ubuntu 20.04 (focal) depuis cette plateforme https://app.vagrantup.com/boxes/search
app1.vm.box = "ubuntu/focal64"
app1.vm.hostname = "app1"
end
# Vagrant va récupérer une machine de base ubuntu 20.04 (focal) depuis cette plateforme https://app.vagrantup.com/boxes/search
app2.vm.box = "ubuntu/focal64"
app2.vm.hostname = "app2"
end
end
[appservers]
app1 ansible_host=10.10.10.11
app2 ansible_host=10.10.10.12
[all:vars]
ansible_user=vagrant
ansible_ssh_private_key_file=~/.vagrant.d/insecure_private_key
Facultatif :
N’hésitez pas consulter extensivement la documentation des modules avec leur exemple ou d’utiliser la commande de doc ansible-doc <module>
- hosts: <hotes_cible>
tasks:
- name: ping
ping:
Commençons par installer les dépendances de cette application. Tous nos serveurs d’application sont sur ubuntu. Nous pouvons donc utiliser le
module apt pour installer les dépendances. Il fournit plus d’option que le module package.
Avec le module apt installez les applications: python3-dev, python3-pip, python3-virtualenv, virtualenv, nginx, git. Donnez à cette tache le
nom: ensure basic dependencies are present. Ajoutez, pour devenir root, la directive become: yes au début du playbook.
apt:
name:
- python3-dev
- python3-pip
- python3-virtualenv
- virtualenv
- nginx
- git
state: present
Lancez ce playbook sans rien appliquer avec la commande ansible-playbook <nom_playbook> --check --diff. La partie --check indique à
Ansible de ne faire aucune modification. La partie --diff nous permet d’afficher ce qui changerait à l’application du playbook.
Relancez bien votre playbook à chaque tache : comme Ansible est idempotent il n’est pas grave en situation de développement d’interrompre
l’exécution du playbook et de reprendre l’exécution après un échec.
https://supports.uptime-formation.fr/all_content/ 183/210
14/11/2022 17:46 Exporter tout le contenu
Ajoutez une tâche systemd pour s’assurer que le service nginx est démarré.
systemd:
name: nginx
state: started
Ajoutez une tache pour créer un utilisateur flask et l’ajouter au groupe www-data. Utilisez bien le paramètre append: yes pour éviter de supprimer
des groupes à l’utilisateur.
user:
name: "flask"
state: present
append: yes # important pour ne pas supprimer les groupes d'un utilisateur existant
groups:
- "www-data"
Télécharger le code dans notre projet et le copier sur chaque serveur avec le module sync qui fait une copie rsync.
Utiliser le module git.
Nous allons utiliser la deuxième option (git) qui est plus cohérente pour le déploiement et la gestion des versions logicielles. Allez voir la
documentation comment utiliser ce module.
Utilisez le pour télécharger le code source de l’application (branche master) dans le dossier /home/flask/hello mais en désactivant la mise à jour
(au cas ou le code change).
git:
repo: "https://github.com/e-lie/flask_hello_ansible.git"
dest: /home/flask/hello
version: "master"
clone: yes
update: no
Lancez votre playbook et allez vérifier sur une machine en ssh que le code est bien téléchargé.
La liste de nos dépendances est listée dans le fichier requirements.txt à la racine du dossier d’application.
Nous voulons installer ces dépendances dans un dossier venv également à la racine de l’application.
Nous voulons installer ces dépendance en version python3 avec l’argument virtualenv_python: python3.
Avec ces informations et la documentation du module pip installez les dépendances de l’application.
pip:
requirements: /home/flask/hello/requirements.txt
virtualenv: /home/flask/hello/venv
virtualenv_python: python3
Créez une tache file qui change le propriétaire du dossier de façon récursive.
- name: Change permissions of app directory
file:
path: /home/flask/hello
state: directory
owner: "flask"
recurse: true
[Unit]
After=network.target
[Service]
User=flask
Group=www-data
WorkingDirectory=/home/flask/hello
Environment="PATH=/home/flask/hello/venv/bin"
https://supports.uptime-formation.fr/all_content/ 184/210
14/11/2022 17:46 Exporter tout le contenu
[Install]
WantedBy=multi-user.target
Pour gérer les fichier de configuration on utilise généralement le module template qui permet à partir d’un fichier modèle situé dans le projet ansible de
créer dynamiquement un fichier de configuration adapté sur la machine distante.
Créez un dossier templates, avec à l’intérieur le fichier app.service.j2 contenant le texte précédent.
Utilisez le module template pour le copier au bon endroit avec le nom hello.service.
Utilisez ensuite systemd pour démarrer ce service (state: restarted ici pour le cas ou le fichier à changé).
Configurer nginx
Comme précédemment créez un fichier de configuration hello.test.conf dans le dossier /etc/nginx/sites-available à partir du fichier modèle:
nginx.conf.j2
server {
listen 80;
server_name hello.test;
location / {
include proxy_params;
proxy_pass http://unix:/home/flask/hello/hello.sock;
Utilisez file pour créer un lien symbolique de ce fichier dans /etc/nginx/sites-enabled (avec l’option force:yes pour écraser le cas échéant).
Ajoutez hello.test dans votre fichier /etc/hosts pointant sur l’ip d’un des serveur d’application.
Correction intermédiaire
flaskhello_deploy.yml
Code de correction :
Facultatif :
app:
name: hello
user: flask
domain: hello.test
Relancez le playbook : toutes les tâches devraient renvoyer ok à part les “restart” car les valeurs sont identiques.
Facultatif :
Le dépot contient également les corrigés du TP3 et TP4 dans d’autre branches.
https://supports.uptime-formation.fr/all_content/ 185/210
14/11/2022 17:46 Exporter tout le contenu
On désire plutôt ne relancer/recharger le service que lorsque la configuration conrespondante a été modifiée. c’est l’objet des taches spéciales nommées
handlers.
Déplacez la tâche de redémarrage/reload de nginx dans cette section et mettez comme nom reload nginx.
template:
src: templates/nginx.conf.j2
...
handlers:
systemd:
name: "nginx"
state: reloaded
Identifiez dans le playbook précédent les tâches qui sont exactement communes aux deux installations.
!!! il s’agit des taches d’installation des dépendances apt et de vérification de l’état de nginx (démarré)
Créez un nouveau fichier deploy_app_tasks.yml et copier à l’intérieur la liste de toutes les autres taches mais sans les handlers que vous laisserez
à la fin du playbook.
!!! Il reste donc dans le playbook seulement les deux premières taches et les handlers, les autres taches (toutes celles qui contiennent des parties
variables) sont dans deploy_app_tasks.yml.
Ce nouveau fichier n’est pas à proprement parlé un playbook mais une liste de taches. utilisez include_tasks: pour importer cette liste de tâche à
l’endroit ou vous les avez supprimées.
Ajoutez une tâche debug: msg={{ app }} au début du playbook pour visualiser le contenu de la variable.
Ensuite remplacez la variable app par une liste flask_apps de deux dictionnaires (avec name, domain, user différents les deux dictionnaires et
repository et version identiques).
flask_apps:
- name: hello
domain: "hello.test"
user: "flask1"
version: master
repository: https://github.com/e-lie/flask_hello_ansible.git
- name: hello2
domain: "hello2.test"
user: "flask2"
version: master
repository: https://github.com/e-lie/flask_hello_ansible.git
Utilisez les directives loop et loop_control+loop_var sur la tâche include_tasks pour inclure les taches pour chacune des deux applications.
Créez le dossier group_vars et déplacez le dictionnaire flask_apps dans un fichier group_vars/appservers.yml. Comme son nom l’indique ce
dossier permet de définir les variables pour un groupe de serveurs dans un fichier externe.
Testez en relançant le playbook que le déplacement des variables est pris en compte correctement.
Correction
Pour la correction clonez le dépôt de base à l’adresse https://github.com/e-lie/ansible_tp_corrections.
Renommez le clone en tp2.
ouvrez le projet avec VSCode.
Activez la branche tp2_correction avec git checkout tp2_correction.
Le dépot contient également les corrigés du TP3 et TP4 dans d’autre branches.
Bonus
Pour ceux ou celles qui sont allé-es vite, vous pouvez tenter de créer une nouvelle version de votre playbook portable entre centos et ubuntu. Pour cela
utilisez la directive when: ansible_os_family == 'Debian' ou RedHat.
https://supports.uptime-formation.fr/all_content/ 186/210
14/11/2022 17:46 Exporter tout le contenu
Essayez de déployer une version plus complexe d’application flask avec une base de donnée mysql:
https://github.com/miguelgrinberg/microblog/tree/v0.17
Il s’agit de l’application construite au fur et à mesure dans un magnifique tutoriel python. Ce chapitre indique comment déployer l’application sur linux.
Cherchez sur https://galaxy.ansible.com/ le nom du role mysql de geerlingguy. Il s’agit de l’auteur d’un livre de référence “Ansible for DevOps”
et de nombreux roles de références.
Pour décrire les roles nécessaires pour notre projet il faut créer un fichier requirements.yml contenant la liste de ces roles. Ce fichier peut être
n’importe où mais il faut généralement le mettre directement dans le dossier roles (autre convention).
Ajoutez la ligne geerlingguy.* au fichier .gitignore pour ne pas ajouter les roles externes à votre dépot git.
Pour installer notre base de données, ajoutez un playbook dbservers.yml appliqué au groupe dbservers avec juste une section:
...
roles:
- <nom_role>
Faire un playbook configuration.yml qui importe juste les deux playbooks flaskapp_deploy.yml et dbservers.yml avec import_playbook.
flaskapp
├── defaults
├── handlers
├── tasks
└── templates
├── app.service.j2
└── nginx.conf.j2
Les templates et les listes de handlers/tasks sont a mettre dans les fichiers correspondants (voir plus bas)
Le fichier defaults/main.yml permet de définir des valeurs par défaut pour les variables du role. Mettez à l’intérieur une application par défaut:
flask_apps:
- name: defaultflask
domain: defaultflask.test
repository: https://github.com/e-lie/flask_hello_ansible.git
version: master
user: defaultflask
Ces valeurs seront écrasées par celles fournies dans le dossier group_vars (la liste de deux applications du TP2). Elle est présente pour éviter que le role
plante en l’absence de variable (valeurs de fallback).
Copiez les tâches (juste la liste de tiret sans l’intitulé de section tasks:) contenues dans le playbook appservers dans le fichier tasks/main.yml.
Déplacez vos deux fichiers de template dans le dossier templates du role (et non celui à la racine que vous pouvez supprimer).
Pour appeler notre nouveau role, supprimez les sections tasks: et handlers: du playbook appservers.yml et ajoutez à la place:
roles:
- flaskapp
Votre role est prêt : lancez appservers.yml et debuggez le résultat le cas échéant.
Facultatif: Ajouter un paramètre d’exécution à notre rôle pour mettre à jour l’application.
Facultatif :
Correction
Pour la correction clonez le dépôt de base à l’adresse https://github.com/e-lie/ansible_tp_corrections.
Renommez le clone en tp3.
https://supports.uptime-formation.fr/all_content/ 187/210
14/11/2022 17:46 Exporter tout le contenu
ouvrez le projet avec VSCode.
Activez la branche tp3_correction avec git checkout tp3_correction.
Bonus
Essayez différents exemples de projets de Geerlingguy accessibles sur github à l’adresse https://github.com/geerlingguy/ansible-for-devops.
version: "2"
services:
jenkins:
image: <image_blueocean>
user: root
ports:
- "<port_jenkins_mapping>"
volumes:
- jenkins_data:/var/jenkins
- /var/run/docker.sock:/var/run/docker.sock
volumes:
jenkins_data:
Pour le mapping de port choissez 8080 pour le port hote. Le port de jenkins est 8080.
les données de jenkins se retrouverons dans le dossier jenkins_data et survivrons à la destruction du conteneur. A l’intérieur du conteneur
le dossier data est ``
pour que jenkins puisse utiliser Docker il doit pouvoir accéder au socket docker de l’hôte qui permet de controller la runtime docker. Il faut
pour cela monter /var/run/docker.sock au même emplacement (/var/run/docker.sock) côté conteneur.
Après avoir complété le fichier et ajouté les 3 volumes, lancez jenkins avec docker-compose up -d.
Pour vérifier que le conteneur est correctement démarré utilisez la commande docker-compose logs
Quand le logiciel est prêt la commande précédente affiche des triple lignes d’étoiles *. Entre les deux est affiché un token du type:
05992d0046434653bd253e85643bae12. Copiez ce token.
Visitez l’adresse http://localhost:8080. Vous devriez voir une page jenkins s’afficher. Activez le compte administrateur avec le token
précédemment récupéré.
pipeline {
agent any
stages {
stage("Hello") {
steps {
Sauvegardez le pipeline. Retournez sur la page d’accueil de Jenkins et lancez votre tache.
Cliquez le sur le job qui se lance #1 ou #2 pour suivre son déroulement puis cliquez sur Console Output dans le menu de gauche.
Vous devriez voir quelque chose comme:
[Pipeline] node
[Pipeline] {
[Pipeline] stage
https://supports.uptime-formation.fr/all_content/ 188/210
14/11/2022 17:46 Exporter tout le contenu
[Pipeline] { (Hello)
[Pipeline] echo
Hello World
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
Finished: SUCCESS
L’interface un peu vieillissante que vous venez de visiter est celle de jenkins traditionnelle. Nous allons maintenant voir BlueOcean qui est plus
simple et élégante mais plus limitée.
Cliquez sur Open Blue Ocean.
Affichez simplement les logs de notre pipeline précédent.
Pour accéder directement à la page d’accueil visitez http://localhost:8080/blue.
Cliquez sur le job hello est relancez le. Un nouveau pipeline démarre qui s’exécute en une seconde.
Passons maintenant à un vrai pipeline de test. Pour cela nous devons d’abord avoir une application à tester et un jeu de tests à appliquer. Nous allons
comme dans les TPs précédent utiliser une application python flask.
Le serveur agent doit avoir installé en local tous les outils nécessaires pour exécuter la tâche/pipeline requise. Par exemple il faut Python installé
sur l’agent pour exécuter des tests en langage python ou dans notre cas Ansible pour exécuter des playbooks avec Jenkins.
Traditionnellement les agents Jenkins sont des serveurs complets et fixes qu’on créé indépendamment de Jenkins puis qu’on connecte au master
Jenkins. C’est la méthode que nous utiliserons ici.
Cependant, si Jenkins a été créé bien avant Docker et Kubernetes, il s’intègre bien depuis des années avec les environnement conteneurisés. Ainsi,
plutôt que d’installer à la main un serveur linux pour être notre agent on peut demander à Jenkins (grâce à ses plugin docker ou kubernetes) de
lancer automatiquement des conteneurs agents pour exécuter notre tâche/pipeline et les détruire à la fin du job. Pour cette méthode, voir le TP
Jenkins dans Kubernetes.
Vagrant.configure("2") do |config|
config.ssh.insert_key = false
v.memory = 512
v.cpus = 1
end
# Vagrant va récupérer une machine de base ubuntu 20.04 (focal) depuis cette plateforme https://app.vagrantup.com/boxes/search
jenkinsagent.vm.box = "ubuntu/focal64"
jenkinsagent.vm.hostname = "jenkinsagent"
SHELL
end
end
Il nous faut en plus nous assurer un minimum de configuration de ce serveur pour que Jenkins puisse fonctionner et que Ansible soit disponible.
Créer le serveur avec vagrant up. Pour exécuter plusieurs fois les étapes d’installation on pourra utiliser vagrant provision.
Allons voir la configuration des agents Jenkins : Administrer Jenkins > Gérer les noeuds. Remarques:
Le master Jenkins est lui même un noeud d’exécution. C’est sur lui que s’est exécuté notre Job hello world.
On veut ici ajouter un noeud permanent qui sera toujours disponible mais consommera toujours des resources. Avec les docker ou
kubernetes les noeuds sont temporaires et créé dans un “cloud” Jenkins.
Nous avons besoin de nous connecter en SSH au serveur agent. Pour cela il faut créer dans Jenkins un credential (identifiant) qui lui permettra de se
connecter. Les credentials peuvent être de pleins de type différents:
https://supports.uptime-formation.fr/all_content/ 189/210
14/11/2022 17:46 Exporter tout le contenu
Nous allons crée un credential de type user / clé ssh avec vagrant et sa clé privée unsecure (c’est une configuration de test, car cette clé et
publiquement disponible. En production, il faudrait ajouter un utilisateur et une nouvelle clé ssh “originale” au serveur agent)
Allez voir la configuration des credentials Jenkins : Administrer Jenkins > Manage Credentials > Jenkins > Identifiants globaux > Ajouter
des identifiants.
Complétez le formulaire comme suit (dans Private key > enter directly collez le texte de la clé privée présent dans
~/.vagrant.d/insecure_private_key):
Retournez dans la configuration des agents Jenkins : Administrer Jenkins > Gérer les noeuds.
Sauvegardez et vérifiez grace aux logs de Jenkins si tout s’est bien passé ou quelle partie corriger.
… soit d’un problème d’initialisation du programme agent jenkins sur le serveur agent.
Dans tableau de bord, créez un job ansible test de type Pipeline et ajoutez le code suivant comme description du pipeline:
pipeline {
stages {
steps {
sh "ansible --version"
Grace à l’indication du label qui est le même que dans la configuration de notre agent, Jenkins saura ou il doit exécuter ce job. En effet on a vite de
nombreux agents avec des configurations et des ressources différents qui faut pouvoir désigner.
Lancez le job (Lancer un build) et allez voir dans les logs si la version de Ansible est bien affichée.
Créer un pipeline d’exécution Ansible plus réaliste grâce au plugin Ansible de Jenkins
Allez dans Gestion des plugins > Disponibles puis cherchez et installez les plugins ansible et ansicolor.
pipeline {
stages {
stage('Test') {
steps {
// dont forget to install ansicolor plugin and activate it at in jenkins system parameters
Ce pipeline récupère un petit projet de code sur github avec un inventaire contenant les app1 et app2 du TP2 avec leurs ips 10.10.10.11-12 et un
playbook ping.yml qui ping les deux machines.
Bonus
Essayez de créer de nouveaux pipelines pour cloner et lancer le code du TP2 ou du TP3.
https://supports.uptime-formation.fr/all_content/ 190/210
14/11/2022 17:46 Exporter tout le contenu
Pour simplifier le démarrage, clonez le dépôt de base à l’adresse https://github.com/e-lie/ansible_tp_corrections.
Renommez le clone en tp4.
ouvrez le projet avec VSCode.
Activez la branche tp4_correction avec git checkout tp4_correction.
Utilisez la commande ansible-inventory --graph pour afficher l’arbre des groupes et machines de votre inventaire
Utilisez la de même pour récupérer l’ip du balancer0 (ou balancer1) avec : ansible-inventory --host=balancer0
Nous avons rajouté à notre infrastructure un loadbalancer installé à l’aide du fichier balancers.yml
Le playbook upgrade_apps.yml permet de mettre à jour l’application en respectant sa haute disponibilité. Il s’agit d’une opération
d’orchestration simple en les 3 serveurs de notre infrastructure.
Cette opération utilise en particulier serial qui permet de d’exécuter séquentiellement un play sur un fraction des serveurs d’un groupe (ici
1 à la fois parmis les 2).
Notez également l’usage de delegate qui permet d’exécuter une tache sur une autre machine que le groupe initialement ciblé. Cette
directive est au coeur des possibilités d’orchestration Ansible en ce qu’elle permet de contacter un autre serveur ( déplacement latéral et
non pas master -> node ) pour récupérer son état ou effectuer une modification avant de continuer l’exécution et donc de coordonner des
opérations.
notez également le playbook exclude_backend.yml qui permet de sortir un backend applicatif du pool. Il s’utilise avec des variables en
ligne de commande
Désactivez le noeud qui vient de vous servir la page en utilisant le playbook exclude_backend.yml:
Rechargez la page: vous constatez que c’est l’autre backend qui a pris le relais.
Explorer AWX
Identifiez vous sur awx avec le login admin et le mot de passe précédemment configuré.
Dans la section modèle de projet, importez votre projet. Un job d’import se lance. Si vous avez mis le fichier requirements.yml dans roles les
roles devraient être automatiquement installés.
Dans la section crédentials, créez un crédential de type machine. Dans la section clé privée copiez le contenu du fichier ~/.ssh/id_ssh_tp que
nous avons configuré comme clé ssh de nos machines. Ajoutez également la passphrase que vous avez configuré au moment de la création de
cette clé.
Créez une ressource inventaire. Créez simplement l’inventaire avec un nom au départ. Une fois créé vous pouvez aller dans la section source et
choisir de l’importer depuis le projet, sélectionnez inventory.cfg que nous avons configuré précédemment. Bien que nous utilisions AWX les ip
n’ont pas changé car AWX est en local et peut donc se connecter au reste de notre infrastructure LXD.
Pour tester tout cela vous pouvez lancez une tâche ad-hoc ping depuis la section inventaire en sélectionnant une machine et en cliquant sur le
bouton executer.
Allez dans la section modèle de job et créez un job en sélectionnant le playbook site.yml.
Exécutez ensuite le job en cliquant sur la fusée. Vous vous retrouvez sur la page de job de AWX. La sortie ressemble à celle de la commande
mais vous pouvez en plus explorer les taches exécutées en cliquant dessus.
Modifiez votre job, dans la section Plannifier configurer l’exécution du playbook site.yml toutes les 15 minutes.
https://supports.uptime-formation.fr/all_content/ 191/210
14/11/2022 17:46 Exporter tout le contenu
Bibliographie
Ansible
Jeff Geerling - Ansible for DevOps - Leanpub
Ratan2017 - Practical Network Automation: Leverage the power of Python and Ansible to optimize your network
Madhu, Akash2017 - Security automation with Ansible 2
https://iac.goffinet.org/ansible-network/
Cheatsheet
https://www.digitalocean.com/community/cheatsheets/how-to-use-ansible-cheat-sheet-guide
Suite Elastic
La suite Elastic
Base de données et centralisation de logs avancée
Rappel
L’informatique c’est complexe surtout lorsqu’on est pas familier avec l’environnement. Ça prend quelques temps pour être vraiment à l’aise.
Elasticsearch et sa stack c’est particulièrement compliqué ! J’ai essayé d’évité les détails inutiles.
Je peux oublier de préciser certaines choses donc arrêtez moi si ce que je dis n’est pas clair.
Kibana : Une interface web pratique pour chercher et analyser les données stockées.
https://supports.uptime-formation.fr/all_content/ 192/210
14/11/2022 17:46 Exporter tout le contenu
La suite Elastic
La suite Elastic, historiquement appelée “ELK”, est une combinaison de plusieurs produits de la société Elastic, qui développe des logiciels :
Elastic APM
APM est le petit dernier d’Elastic, axé sur le monitoring et le traçage des performances des applications.
L’écosystème Elastic
La société Elastic évolue assez vite et change souvent ses produits. Elle a un business model open core : les fonctionnalités de base sont gratuites et
open source, certaines ont une licence gratuites et source ouverte un peu spéciale et sont regroupées dans le “X-Pack” (les fonctionnalités Basic).
D’autres fonctionnalités “X-Pack” enfin nécessitent un abonnement “Gold” ou plus.
Le cœur des produits Elastic est composé de Elasticsearch, Kibana (les dashboards et le mode Discover), de Logstash et de Filebeat.
Des bagarres de licence ont conduit d’autres personnes à proposer un fork d’Elasticsearch : OpenSearch (anciennement OpenDistro for Elasticsearch)
qui est un fork de la suite Elastic, sponsorisé par Amazon et AWS.
Détails :
https://www.elastic.co/fr/subscriptions
Gérer une GRANDE quantité de logs sur une infrastructure (entre 5 et des centaines de machines)
Les explorer efficacement : un problème difficile vu la quantité (on y reviendra)
Un brique important pour avoir des applications distribuées avec un déploiement automatisé #Devops
En résumé
On va voir trois choses durant ces deux jours qui peuvent résumer l’intérêt d’ELK:
Les logs : pourquoi? comment ? ( quelle est la motivation de ELK )
Découvrir elasticsearch et comment chercher dans du texte ? ( la partie principale )
Qu’est-ce qu’une infra distribuée moderne ? pourquoi ELK c’est du devops ? ( fin )
La “hype” Elasticsearch
Indipensable à de plus en plus d’entreprises qui grossissent : pour augmenter le contrôle sur les infrastructures.
Un outil très versatile et bien fait qui permet de faire de jolis dashboards d’analyse et les gens adorent avoir des jolis dashboards
Utile pour faire des big data : c’est un peu le moteur de l’informatique actuelle. Tous les nouveaux services fonctionnent grace au traitement de
données.
Dashboards
Logs ?
Ça veut dire journaux (système) et bûches
Icone originale de Logstash:
Objectif 1: monitoring
Suivre et anticiper le fonctionnement d’un système:
suivre (et réparer) = zut j’ai une erreur : le service nginx a crashé sur mon infra
anticiper : le disque dur de cette machine sera bientôt plein il faut que je le change / le vide.
enquêter : par exemple sur les erreurs rares d’une application
Exemples : ces derniers mois est-ce que l’application a correctement répondu aux requêtes de mes utilisateurs ?
Infra distribuée
Vous en connaissez ?
Exemple d’investigation
Objectif:
Sur le serveur exemple.net, une page web a été supprimée. On veut savoir qui des trois administrateurs Alice, Bob ou Jack a fait cette modification.
1. On se connecte en SSH
2. en utilisant cat et grep par exemple :
`/var/log/nginx/access.log` :
`/var/log/auth.log` :
1 - Installation
TP 1 : installation d’Elasticsearch, Kibana et Filebeat
Installer Elasticsearch avec Ansible
1. Clonez le dépôt situé à cette adresse : https://github.com/Uptime-Formation/vagrant-ansible-elk
2. Créez les VM avec vagrant up (il faut installer Vagrant et VirtualBox avant si ce n’est pas fait)
3. Vagrant a de lui-même lancé ansible-playbook ping.yml, il teste donc qu’Ansible est bien configuré.
4. Lancez ansible-playbook setup_elastic.yml. Les requirements
sont installés ! Voyez les ok et changed apparaissant lorsque vous
lancez le
playbook : Ansible est verbeux, il informe de sa réussite.
Rappels Ansible :
https://supports.uptime-formation.fr/all_content/ 194/210
14/11/2022 17:46 Exporter tout le contenu
Ansible peut être rejoué plusieurs fois (il est idempotent)
Ansible garantit l’état de certains éléments du système lorsqu’on le
(re)joue
Ansible est (dès qu’on est un peu habitué-e) plus limpide que du bash
curl http://192.168.2.2:9200/_cat/nodes?pretty
curl -XGET http://192.168.2.2:9200/_cluster/state?pretty
curl -XGET http://192.168.2.2:9200/_cluster/health?pretty
Si tout est bien configuré vous devriez voir une liste de deux nœuds
signifiant que les deux elastic se « connaissent »
Installer Kibana
curl -L -O https://raw.githubusercontent.com/elastic/beats/7.10/deploy/docker/filebeat.docker.yml
Renommons cette configuration et rectifions qui possède ce fichier pour satisfaire une contrainte de sécurité de Filebeat :
mv filebeat.docker.yml filebeat.yml
`docker-compose.yml` :
2 - Elasticsearch
Elasticsearch
Elasticsearch est à la fois :
une base de données distribuée (plusieurs instances de la base de données sont connectées entre elles de manière à assurer de la redondance si un
des nœuds en vient à avoir des problèmes)
un moteur de recherche puissant, basé sur un autre logiciel appelé Apache Lucene
Le point commun des deux : Stocker des données de base pour une application.
SQL: On veut avoir un historique des achats et les documents afférents : on relie formellement
utilisateurs et les produits à travers un historique
d’achat.
https://supports.uptime-formation.fr/all_content/ 195/210
14/11/2022 17:46 Exporter tout le contenu
NoSQL: On stocke les factures comme des documents JSON.
Côté SQL:
ça donne trois tables
Côté NoSQL:
Dev tools
Pour exécuter directement des requêtes REST (on revient sur ce que c’est juste après)
C’est cette vue qu’on va principalement utiliser dans les premières parties.
Il faut que vous compreniez bien le principe d’une API REST JSON parce que c’est très répandu.
GET /
réponse:
{
"name": "ZEWiZLN",
"cluster_name": "elk_formation",
"cluster_uuid": "rGzTBgbXRyev62Ku4vTWFw",
"version": {
"number": "7.14.1",
...
},
Version de Elasticsearch
Version 7.14. C’est important car entre chaque version majeure (3, 4, 5, 6, 7) il y a des changements dans les fonctions (L’API)
La référence c’est la documentation: https://www.elastic.co/guide/en/elasticsearch/reference/7.14/index.html
Toutes les fonctions de elasticsearch y sont décrites et on peut choisir la version selon celle installée.
https://supports.uptime-formation.fr/all_content/ 196/210
14/11/2022 17:46 Exporter tout le contenu
Documents
chaque entrée dans un index avec son id
ici un livre ou un vol
un peu comme une ligne dans une table en sql
<DATA>
PUT /bibliotheque/_doc/1
Le BODY
est facultatif
est en JSON (JavaScript Objet Notation)
décrire des données complexes avec du texte
très répendu
pas trop dur à lire pour un humain
Syntaxe du JSON
{
"champ1": "valeur1",
"champ3_liste": [
"item1",
"item2",
"item3",
],
"souschamp1": "valeur1.1";
...
},
"champ5": "Pour échapper des \"guillemets\" et des \\n" // échappement pour " et \
<DATA>
pour les champs texte il prend le type text + keyword (on verra pourquoi après)
pour champs numériques il prend le type integer ou float
Mapping explicite
Pour avoir plus de contrôle sur les types de champs il vaut mieux décrire manuellement le schéma de données.
Au moins les premiers champs qu’on
connaît déjà.
(Il peut arriver qu’on ait pas au début d’un projet une idée de toutes les parties importantes. On peut raffiner le mapping au fur et à mesure)
Exemple:
GET /kibana_sample_data_flights/_mapping
Types texte
text : Pour stocker du texte de longueur arbitraire. Indexé en recherche fulltext. On y reviendra: ça veut dire que tous les mots du texte sont
recherchables.
keyword : Du texte généralement court pour décrire une caractéristique du document
Exemple: OriginWeather décrit la météo cloudy
Types nombre
integer : un nombre entier
float : nombre à virgule
Il existe d’autres types de nombres plus courts ou plus longs (donc plus gourmand en espace).
Type date
Comme on stocke souvent des évènements dans elasticsearch il y a presque toujours une ou plusieurs date dans un document.
En fait ce n’est pas
vraiment une date mais ce qu’on appelle un timestamp qui va jusqu’à la milliseconde.
Type geo_point
Pour stocker un point géographique. C’est une paire de nombres : latitude et longitude.
On verra ça un peu dans kibana plus tard.
La stack Elk fournit
plein d’outil pour stocker des données géolocalisés et les visualiser :
C’est un besoin courant. Exemple : savoir d’où viennent les requêtes sur votre
application pour connaître vos usagers.
Exercice II.2.2)
II.3) API REST et JSON ?
HTTP
Le protocole le plus connu pour la communication d’applications
protocole = requêtes et réponses formalisées entre deux logiciels
exemples:
navigateur <-> serveur apache
https://supports.uptime-formation.fr/all_content/ 198/210
14/11/2022 17:46 Exporter tout le contenu
kibana <-> elasticsearch
application web <-> mongoDB
En requête
En réponse
un fichier avec
un en tête nommé HEAD qui gère décrit la réponse avec des métadonnées
le HEAD contient notamment un code de réponse :
200 = OK
404 = non trouvé
un contenu nommé BODY
API REST
PUT /catalog/product/1
"sku": "SP000001",
"ISBN": "1785288997",
"price": 26.99
Devient :
Recherche exacte: On veut pouvoir trouver les documents rangés dans la catégorie litterature anglaise ou bandes déssinées SF.
Recherche en texte intégral ou fulltext : On veut pouvoir trouver les documents qui ont Lanfeust ou éthique dans leur titre.
Recherche exacte
Quand je cherche littérature anglaise je ne veux pas trouver les documents de littérature espagnole bien qu’il y ai le mot “littérature” en
commun.
Je veux que les termes correspondent précisément ou dit autrement je veux que littérature anglaise soit comme une seule étiquette, pas un texte.
C’est le fonctionnement d’une recherche classique dans une base de données SQL:
Recherche exacte 2
On utilise _search, query et term.
GET /<index>/_search
"query": {
"term": {
"<field>": "<value>"
https://supports.uptime-formation.fr/all_content/ 199/210
14/11/2022 17:46 Exporter tout le contenu
Recherche fulltext
Retrouver non pas l’ensemble des livres d’un genre mais un livre à partir d’une citation.
Pour cela on fait un index inversé qui permet une recherche fulltext.
Elasticsearch est spécialement fait pour ce type de recherche. Il le fait très efficacement et sur des milliards de lignes de texte.
exemple: github utilise elasticsearch pour indexer des milliers de dépôts de code.
GET /<index>/_search
"query": {
"match": {
"<field>": "<value>"
Un champ text est automatiquement indexé en mode fulltext: la méthode match fonctionne
un champ textuel créé implicitement est double : le champ principal en text + un sous champ keyword:
Exercice III.1)
III) Life inside a cluster
shard
dimensionner un cluster
haute disponibilité
endpoint switching
fallback automatique
Exercice:
Imaginons qu’on veuille chercher tous les avions qui ont décollé de New York sous la pluie depuis un mois et qui ont un prix moyen supérieur à 800$.
Par exemple pour créer une mesure du risque économique que le dérèglement climatique fait peser sur une companie ?
Plusieurs outils
des requêtes composées
tous les vols qui vérifie condition A ET condition B ET PAS condition C
des filtres de requêtes
garder que les vols dont le prix est entre 300 et 1000 €
des aggrégations de requêtes (somme, aggrégation géographique)
chercher en gros le chiffre d’affaire d’une companie : faire la somme des
trafifs de ses vols.
Repasser à Kibana
On pourrait tout faire avec l’API mais ce serait pas très fun et on s’arracherait vite les cheveux.
2 - Elasticsearch - Exercices
II.1) API JSON
0. Accéder à Kibana
Dev tools
2. Requêtes
https://supports.uptime-formation.fr/all_content/ 200/210
14/11/2022 17:46 Exporter tout le contenu
POST /mabibli/_doc/
"<fieldname>": "<value>",
...
le titre (title)
l’auteur (author)
le prix (price)
la première phrase de la description à mettre entre guillemets (description)
d’autres infos si vous voulez
<DATA>
Solution
Solution :
II.2.1
Exercice II.2.1) Gérer les documents dans Elasticsearch.
Dans la vue Devtools et à l’aide de votre feuille de mémo de l’API :
Solution
Solution :
Exercice II.2.2)
1. supprimer votre index
3. Décrivez en JSON les propriétés suivantes pour ce mapping en choisissant les types: title, description, author, price, ISBN/EAN, weight
Solution
Solution :
Exercice II.2.3)
Exercice II.3) Utiliser curl
1. connectez vous à l’infra en ssh:
1. taper curl --help, cherchez le nom de l’option longue correspondant à -d (un petit grep ?)
2. ajouter une suite à l’un de vos livres avec curl.
3. ajoutez une entrée genre de type keyword dans votre mapping et mettez à jour vos livres pour ajouter leur genre
4. utilisez curl pour télécharger une page de la documentation dans votre dossier personnel.
Solution
Solution :
https://supports.uptime-formation.fr/all_content/ 201/210
14/11/2022 17:46 Exporter tout le contenu
Exercice III.1)
Avec la vue Devtools:
1. Faire une recherche des avions où New apparaît dans le champ Dest. Que remarquez vous ?
Solution
Solution :
3 - Kibana
Kibana
Kibana est un outil très complet de visualisation (dashboards) et d’administration des données dans une base de données Elasticsearch.
Elle est toujours
connectée à un cluster (un ou plusieurs nœuds) Elasticsearch.
Vue Discover
Un exemple de données : des vols d’avions
Des évènements très similaires à des logs mais plus facile à imaginer :
une date
un lieu
des informations spécifiques
Avec des données de géolocalisation (ce qui est pas forcément le cas pour des logs)
Résultat:
Régler la Période
Combien de vols ?
depuis 24h ?
depuis une semaine ?
depuis 30 jours ?
Dashboard
C’est joli mais un peu complexe/flippant, n’est-ce pas ?
Exemple:
Destfull:“New” -> champ text -> OK
New chitose airport
Dest:“New” -> champ keyword -> 0 hits
Recherche exacte:
DestCityName: “New York” -> keyword pour la ville
Dest:" John F Kennedy International Airport" -> keyword pour
l’aéroport de destination
Exemple
Ajouter un filtre avec le bouton “+ Add a Filter”
Imaginons qu’on veuille chercher tous les avions qui ont décollé de New York sous la pluie depuis un mois et qui ont un prix moyen supérieur à 800$.
Par exemple pour créer une mesure du risque économique que le dérèglement climatique fait peser sur une companie ?
requêtes composées
Des requête avec des ET des OU et des NON :
Tous les vols qui concernent tel aéroport et qui contiennent le nom airways.
Filtres de requêtes
En partant des résultats d’une recherche fulltext :
On récupère les documents renvoyés par une requête (ce qu’elastic appel des hits) et on ne va en garder qu’une partie.
Garder que les vols dont le prix est entre 300 et 1000 €:
FlightDelayMin:[30 TO 50]
rajouter un filtre avec le bouton “+ Add a Filter”
Grouper les documents/évènements par thème et faire des calculs transformations sur ces groupes.
Pour calculer le prix moyen d’un ticket par compagnie par exemple :
On va aggréger les vols de chaque compagnie et calculer la moyenne des
prix des billets.
3 type d’aggrégations:
Bucket (faire des groupes)
Permet de voir une proportion ou un changement en un coup d’oeil (quand on sait de quoi ça parle).
Intérêts de la Dashboard
Vue globale pour comprendre rapidement les données
Tout est dynamique: vous pouvez ajouter un filtre et les informations se mettent à jour.
https://supports.uptime-formation.fr/all_content/ 203/210
14/11/2022 17:46 Exporter tout le contenu
3 - Kibana - Exercices
Exercices
Rappel : Kibana est une interface pour Elastic (soit on attaque direct Elastic soit on utilise les trucs pratiques de Kibana)
Solution :
Solution :
Solution :
Exercices supplémentaires
requête pour analyser une erreur dans le code
graphique sur le volume de connexion au cours de la journée
(difficile) trouver les correspondances possibles pour aller d’une ville A à une ville B entre telle et telle heure ?
4 - Beats
Beats
Beats est un programme designé pour être extrêmement léger et n’avoir qu’une seule mission : récupérer et envoyer des logs à un autre programme
qui s’assurera du traitement de ceux-ci : soit Logstash, soit directement Elasticsearch.
Les Beats pour lire les données depuis plusieurs machines. Les principales sont :
FileBeat : lire des fichiers de log pour les envoyer à Logstash ou directement à Elasticsearch
MetricBeat : récupérer des données d’usage système, du CPU, de la mémoire, etc.
Packetbeat : récupérer des données très poussées sur le réseau
d’autres existent mais sont moins importants
Logstash
Logstash est un couteau suisse puissant de récupération, de transformation et d’envoi de logs.
Contrairement à Kibana et Elasticsearch, Logstash peut
être utilisé de façon indépendante à Elasticsearch ou à Kibana.
Il est un peu difficile de comprendre la différence fondamentale entre Beats et Logstash au début, on peut retenir :
que Beats a beaucoup moins de fonctionnalités que Logstash, et qu’il n’a que quelques missions simples à remplir,
là où Logstash est un outil très complet pour récupérer, transformer et renvoyer des logs.
dès que l’on est restreint-e par les possibilités de Beats, on utilise souvent à la fois Beats et Logstash
4 - Beats - Exercices
Filebeat avec Nginx
Nous allons suivre la partie Filebeat (il faut descendre jusqu’à environ la moitié de la page) du tutoriel officiel Elastic pour monitorer les logs
access.log et error.log de Nginx :
https://www.elastic.co/fr/blog/how-to-monitor-nginx-web-servers-with-the-elastic-stack
Nous pouvons ensuite utiliser une commande spéciale pour ajouter des tableaux pré-configurés pour Nginx et Kibana avec la commande suivante :
sudo
./filebeat setup --dashboards
https://supports.uptime-formation.fr/all_content/ 204/210
14/11/2022 17:46 Exporter tout le contenu
Puis, envoyez le contenu des fichiers auth.log et syslog (logs système) à Elasticsearch grâce au module appelé system :
https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-module-system.html
5 - Logstash
Logstash
Logstash est un couteau suisse puissant de récupération, de transformation et d’envoi de logs.
Contrairement à Kibana et Elasticsearch, Logstash peut
être utilisé de façon indépendante à Elasticsearch ou à Kibana.
Logstash : récupère les logs pour les traiter avant de les envoyer dans Elasticsearch
formater des logs
transformer les données avant de les mettre dans Elasticsearch
les inputs, là où Logstash récupère ou reçoit ses données, (en général, c’est Beats)
les filters, la partie importante, celle où les données reçues sont transformées avant envoi
les outputs, là où on indique à Logstash où envoyer ses données (en général : vers Elasticsearch)
Exemples
Inputs
Filters
Dissect (basique, en fonction d’un séparateur) et Grok (avancé, expressions régulières) : découper des messages de logs non structurés en
plusieurs entrées différentes (par exemple découper chaque info d’une ligne de logs de Nginx dans des entrées différentes)
Pour Grok, il existe un site indispensable pour débugger ses filtres Grok : https://grokdebug.herokuapp.com/
Outputs
Elasticsearch
l’exécution d’une ligne de commande (par exemple iptables pour couper l’accès firewall à une IP après avoir détecté une attaque)
Il est un peu difficile de comprendre la différence fondamentale entre Beats et Logstash au début, on peut retenir :
que Beats a beaucoup moins de fonctionnalités que Logstash et est designé pour être très léger, et n’avoir que quelques missions simples à
remplir
là où Logstash est un outil très complet pour récupérer, transformer et renvoyer des logs.
-->
L’input Twitter
Avec une clé d’API Twitter (à demander éventuellement au formateur), configurer l’input Twitter de Logstash pour archiver des tweets dans
Elasticsearch.
https://www.elastic.co/guide/en/logstash/current/plugins-inputs-twitter.html
https://grokdebug.herokuapp.com/
https://www.elastic.co/guide/en/logstash/7.14/plugins-filters-dissect.html
6 - Conclusion
Haute disponibilité
Une application en haute disponibilité signifie qu’elle continue à fonctionner quand une partie arrête de fonctionner (dans le cadre d’Elasticsearch :
quand un nœud devient injoignable par exemple).
Les mécanismes de haute disponibilité d’un cluster commencent réellement à partir de 3 nœuds : il faut 2 nœuds restants pour continuer à
fonctionner sans le 3e nœud défectueux.
7 - Bibliographie
API Elasticsearch memento - Version 7.14 de l'API
Gérer les documents
Source : https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html
Créer un document
PUT /<index>/_doc/1
"champ1": "value1",
"champ2": "value2"
https://supports.uptime-formation.fr/all_content/ 206/210
14/11/2022 17:46 Exporter tout le contenu
}
ou
POST /<index>/_doc
"champ1": "value1",
"champ2": "value2"
Afficher un document:
GET /<index>/_doc/<num>/
"doc": {
"field": "value"
Supprimer un document
DELETE /<index>/_doc/<_id>
GET /_cat/indices?v
Create index
PUT /<index>
"settings": {
"number_of_shards": 1, // default 5
"number_of_replicas": 0 // default 1
PUT /<index>
"settings": {
"index": {
"number_of_shards": 1,
"number_of_replicas": 0
},
"mappings": {
"properties": {
"<property>": {
"type": "<datatype>"
Supprimer un index
DELETE /<index>
GET /<index>/_mapping
PUT /<index>/_mapping
"properties": {
"<new_fieldname>": {
"type": "<datatype>"
https://supports.uptime-formation.fr/all_content/ 207/210
14/11/2022 17:46 Exporter tout le contenu
}
POST /<index>/_update_by_query
"script": {
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.
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.
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 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.
Objectifs
Mettre en œuvre un système d’intégration continue et de déploiement DevOps
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.
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.
https://supports.uptime-formation.fr/all_content/ 208/210
14/11/2022 17:46 Exporter tout le contenu
Installez Vagrant en ajoutant le dépôt ubuntu et utilisant apt (voir https://www.vagrantup.com/downloads pour d’autres installation):
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|
v.memory = 2048
v.cpus = 2
end
# 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"
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).
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):
commande1
commande2
etc
SHELL
Idée de bonus
Sur votre serveur, installez/scriptez en plus de la précédente, l’application flask microblog du Flask Mega Tutorial avec une base de donnée
MySQL. Voir ce lien tutoriel : https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xvii-deployment-on-linux.
Personnalisez votre application Flask / Python avec une ou des pages en plus ou une fonctionnalité en plus
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.
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.
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 :
https://supports.uptime-formation.fr/all_content/ 209/210
14/11/2022 17:46 Exporter tout le contenu
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
Dockeriser l’application microblog avec une base de données MySQL à mettre dans un conteneur à part (voir le chapitre 19 du Flask Mega
Tutorial et les différentes branches du dépôt https://github.com/Uptime-Formation/microblog/).
(facultatif) Comme vu dans les TP, rajoutez une instruction HEALTHCHECK au Dockerfile pour tester si votre app va bien.
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/
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.
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.
https://supports.uptime-formation.fr/all_content/ 210/210