+ - 0:00:00
Notes for current slide
Notes for next slide

Docker, créer et administrer
vos conteneurs d'applications


Allez-y doucement avec le WiFi!
N'utilisez pas votre hotspot.
Ne chargez pas de vidéos, ne téléchargez pas de gros fichiers pendant la formation.


shared/title.md
1 / 801

Présentations

  • Bonjour, je suis:

    • 👨🏾‍🎓 djalal
  • Cet atelier se déroulera de 9h à 17h.

  • La pause déjeuner se fera entre 12h et 13h30.

    (avec 2 pauses café à 10h30 et 15h!)

  • N'hésitez pas à m'interrompre pour vos questions, à n'importe quel moment.

  • Surtout quand vous verrez des photos de conteneurs en plein écran!

logistics.md

2 / 801

Une brève introduction

  • Ce cours a été écrit à l'origine comme support à des ateliers en présentiel

  • Ce contenu est maintenu par Jérôme Petazzoni et de nombreux contributeurs

  • La traduction intégrale depuis l'anglais est réalisée et maintenue par @djalal

  • Vous pouvez aussi le parcourir à votre propre rythme.

  • Nous avons inclus autant d'information que possible dans les diapositives.

  • Nous vous recommandons d'avoir un instructeur pour vous aider...

  • ... sauf si vous êtes à l'aise avec la lecture minutieuse de la documentation Docker...

  • ... et avec la recherche de réponses dans les forums Docker, StackOverflow, et autres boutiques.

containers/intro.md

3 / 801

À propos de ces diapositives

4 / 801

À propos de ces diapositives

  • Coquilles? Erreurs? Questions? N'hésitez pas à passer la souris en bas de diapo...

👇 Essayez! Le code source sera affiché et vous pourrez l'ouvrir dans Github pour le consulter et le corriger.

shared/about-slides.md

5 / 801

Détails supplémentaires

  • Cette diapo a une petite loupe dans le coin en haut à gauche.

  • Cette loupe signifie que ces diapos apportent des détails supplémentaires.

  • Vous pouvez les zapper si:

    • vous êtes pressé(e);

    • vous êtes tout nouveau et vous craignez la surcharge cognitive;

    • vous ne souhaitez que l'essentiel des informations.

  • Vous pourrez toujours y revenir une autre fois, ils vous attendront ici ☺

shared/about-slides.md

6 / 801

Image separating from the next chapter

15 / 801

Docker vu d'hélicoptère

(automatically generated title slide)

16 / 801

Docker vu d'hélicoptère

Dans cette leçon, nous apprendrons:

  • Pourquoi les conteneurs (pitch d'ascenseur non-technique)

  • Pourquoi les conteneurs (version technique du pitch d'ascenseur)

  • Comment Docker nous aide à les construire, transporter et lancer

  • L'Histoire des conteneurs

On ne va pas (encore!) lancer Docker ou des conteneurs dans ce chapitre.

Pas de souci, nous y arriverons assez tôt!

containers/Docker_Overview.md

17 / 801

Pitch d'ascenseur

(pour votre manager, votre patron...)

containers/Docker_Overview.md

18 / 801

OK... Pourquoi ce buzz autour des conteneurs?

  • L'industrie logicielle a changé

  • Avant:

    • applications monolithiques
    • longs cycles de développements
    • environnement unique
    • montée en charge lente
  • Maintenant:

    • services découplés
    • améliorations rapides, itératives
    • environnements multiples
    • montée en charge rapide

containers/Docker_Overview.md

19 / 801

Déployer devient très compliqué

  • Nombreuses technos différentes:

    • langages
    • frameworks
    • bases de données
  • Nombreux environnements différents:

    • espaces de développement individuels
    • pre-production, QA, staging...
    • production: on-prem, cloud, hybride

containers/Docker_Overview.md

20 / 801

Le problème du déploiement

problem

containers/Docker_Overview.md

21 / 801

La matrice infernale

matrix

containers/Docker_Overview.md

22 / 801

Parallèle avec l'industrie du transport

history

containers/Docker_Overview.md

23 / 801

Conteneurs pour transport intermodal

shipping

containers/Docker_Overview.md

24 / 801

Un nouvel écosystème de transport

shipeco

containers/Docker_Overview.md

25 / 801

Un système de transport par conteneur pour les applications

shipapp

containers/Docker_Overview.md

26 / 801

Fin de la matrice infernale

elimatrix

containers/Docker_Overview.md

27 / 801

Pitch d'ascenseur

(pour vos collègues développeurs et admin. système)

containers/Docker_Overview.md

29 / 801

Echapper à l'enfer des dépendances

  1. Ecrire les instructions d'installation dans un fichier INSTALL.txt

  2. Avec ce fichier, écrire un script install.sh qui va marcher pour vous

  3. Traduire ce fichier en Dockerfile, le tester sur votre machine

  4. Si le Dockerfile passe sur votre machine, il passera n'importe où

  5. Réjouissez-vous, car vous êtes sauvé de l'enfer des dépendances et du "ça marche sur ma machine"

Plus jamais de "ça marchait en dev - c'est le problème des admins maintenant!"

containers/Docker_Overview.md

30 / 801

Intégrez des développeurs et contributeurs rapidement

  1. Ecrire les Dockerfiles pour les composants applicatifs

  2. Utiliser des images pré-générées du Docker Hub (mysql, redis, etc.)

  3. Décrire votre suite logicielle avec un fichier Compose

  4. Intégrer quelqu'un avec deux commandes:

git clone ...
docker-compose up

Avec ça, vous pouvez monter des environnements de développement, intégration ou QA en quelques minutes!

containers/Docker_Overview.md

31 / 801

Implémenter facilement une CI stable

  1. Montez un environnement de test avec un Dockerfile ou un fichier Compose

  2. Pour chaque lancement de test, montez un nouveau conteneur (ou une suite complète)

  3. Chaque test est lancé dans un environnement propre.

  4. Aucune pollution des précédents tests

Bien plus rapide et économique que de monter des VMs à chaque fois!

containers/Docker_Overview.md

32 / 801

Utiliser des images de conteneurs comme artefacts de build

  1. Générez votre appli à partir de Dockerfiles

  2. Stockez les images résultantes dans un dépôt

  3. Stockez-les pour toujours (ou aussi longtemps que nécessaire)

  4. Testez ces images en QA, CI ou intégration...

  5. Lancez les mêmes images en production

  6. Quelque chose est cassé? Repassez à l'image précédente.

  7. Diagnostic d'une ancienne régression? Une ancienne image est toujours là pour vous!

Les images contiennent toutes les bibliothèques, dépendances, etc. nécessaires au lancement de l'appli.

containers/Docker_Overview.md

33 / 801

Découplez la "plomberie" de la logique applicative

  1. Ecrivez votre code pour qu'il se connecte à des services nommés ("db", "api", etc.)

  2. Utilisez Compose pour démarrer votre suite

  3. Docker va fournir un DNS pour conteneur pour résoudre ces noms de services

  4. Vous pouvez maintenant monter en charge, ajouter des répartiteurs de charge, de la réplication... sans changer votre code.

Note: ce n'est pas couvert dans cet atelier d'introduction!

containers/Docker_Overview.md

34 / 801

Qu'a apporté Docker à la table?

(Docker avant/après)

containers/Docker_Overview.md

35 / 801

Formats et APIs, avant Docker

  • Aucun format d'échange standard.
    (Non, un fichier tarball n'est pas un format!)

  • Difficile d'utiliser des conteneurs pour les développeurs.
    (Quel est l'équivalent d'un docker run debian?)

  • En conséquence, ils restent cachés des utilisateurs finaux.

  • Aucun composant réutilisable, APIs ou outils.
    (Au mieux, abstractions de VMs, e.g libvirt)

Analogie:

  • Transporter des conteneurs n'est pas une question de boîte d'acier.
  • Ce sont des boîtes d'acier de taille standard, avec les mêmes crochets et trous.

containers/Docker_Overview.md

36 / 801

Formats et APIs, après Docker

  • Standardiser le format de conteneur, parce que les conteneurs n'étaient pas portables.

  • Rendre les conteneurs faciles à utiliser pour les développeurs.

  • Focus sur les composants réutilisables, APIs et l'écosystème d'outils standard.

  • Amélioration par rapport aux outils ad-hoc, interne et spécifique.

containers/Docker_Overview.md

37 / 801

Livraison, avant Docker

  • Déploiement par paquets: deb, rpm, gem, jar, homebrew...

  • Enfer des dépendances

  • "Ça marche chez moi."

  • Base de livraison souvent réalisée de zéro (debootstrap...) et fragile.

containers/Docker_Overview.md

38 / 801

Livraison, après Docker

  • Livrez des images de conteneurs avec toutes leurs dépendances.

  • De plus grosses images, mais sous-découpées en couches.

  • Ne livre que les couches qui ont changé.

  • Économise du disque, du réseau et de la mémoire.

containers/Docker_Overview.md

39 / 801

Exemple

Couches (Layers):

  • CentOS
  • JRE
  • Tomcat
  • Dépendances
  • Application JAR
  • Configuration

containers/Docker_Overview.md

40 / 801

Devs vs Ops, avant Docker

  • On va livrer un fichier tarball (ou un hash de commit) avec ses instructions.

  • Avec un environnement de dev très différent de la production.

  • Sauf que les admin. système n'ont pas toujours un environnement de test eux-mêmes...

  • ... et quand ils l'ont, il peut différer de celui des devs.

  • Donc les admin. système doivent déterminer les différences, et faire en sorte que ça marche...

  • ... ou le renvoyer aux développeurs.

  • Déployer du code est cause de friction et de délais.

containers/Docker_Overview.md

41 / 801

Devs vs Ops, après Docker

  • On va livrer une image de conteneur ou un fichier Compose.

  • Un admin. système pourra toujours lancer cette image de conteneur.

  • Un admin. système pourra toujours lancer ce fichier Compose.

  • Les admin. doivent toujours adapter la configuration à l'environnement de prod, mais ils ont au moins un point de référence.

  • L'outillage des admin. système permet d'utiliser la même image en dev et prod.

  • Les développeurs pourront plus facilement être mis en position de lancer les déploiements eux-mêmes.

containers/Docker_Overview.md

42 / 801

Image separating from the next chapter

43 / 801

Histoire des conteneurs ... et de Docker

(automatically generated title slide)

44 / 801

Histoire des conteneurs ... et de Docker

containers/Docker_History.md

45 / 801

Premières expérimentations

Cela fait très longtemps que les conteneurs existent.

(Voir cet excellent billet par Serge Hallyn pour plus de détails historiques.)

containers/Docker_History.md

46 / 801

L'Âge du VPS (jusqu'à 2007-2008)

lightcont

containers/Docker_History.md

47 / 801

Conteneurs = moins cher que les VMs

  • Utilisateurs: fournisseurs d'hébergement.

  • Audience hautement spécialisée avec une forte culture d'admin. système.

containers/Docker_History.md

48 / 801

Période PAAS (2008-2018)

heroku 2007

containers/Docker_History.md

49 / 801

Conteneurs = plus facile que les VMs

  • Je ne peux pas parler pour Heroku, mais les conteneurs étaient l'arme secrète de dotCloud (parmi d'autres).

  • dotCloud maintenait un PaaS, via un moteur de conteneur personnalisé.

  • Ce moteur était basé sur OpenVZ (et plus tard, LXC) et AUFS.

  • Tout a commencé (vers 2008) par un simple script Python.

  • En 2012, le moteur comptait plusieurs composants Python (env. 10)
    (et env. 100 micro-services!)

  • Fin 2012, dotCloud reconstruit le moteur de conteneur.

  • Le nom de code de ce projet est "Docker".

containers/Docker_History.md

50 / 801

Première version publique de Docker

  • Mars 2013, Pycon, Santa Clara:
    "Docker" est montré en public pour la première fois.

  • Il est publié avec une licence open source.

  • Réactions et retours très positifs!

  • L'équipe dotCloud est progressivement réaffectée au développement de Docker.

  • La même année, dotCloud change de nom pour s'appeler Docker.

  • En 2014, l'activité PaaS est revendue.

containers/Docker_History.md

51 / 801

Docker premiers jours (2013-2014)

containers/Docker_History.md

52 / 801

Premiers utilisateurs de Docker

  • Gestionnaires de PAAS (Flynn, Dokku, Tsuru, Deis...)

  • Utilisateurs de PAAS (ceux assez gros pour justifier la construction de leur propre PAAS)

  • Services d'intégration continue

  • développeurs, développeurs, développeurs

containers/Docker_History.md

53 / 801

Boucle de retours positifs

  • En 2013, la technologie sous-tenant les conteneurs (cgroups, namespaces, stockage copy-on-write, etc.)

  • La popularité croissante de Docker et des conteneurs a mis en lumière de nombreux bugs.

  • En conséquence, ces bugs sont corrigés, résultant dans une meilleure stabilité des conteneurs.

  • Aujourd'hui, tout fournisseur d'hébergement/cloud un peu sérieux peut lancer des conteneurs.

  • Les conteneurs sont devenus un super outil pour déployer/transporter des applis vers/depuis les environnements on-prem/cloud.

containers/Docker_History.md

54 / 801

Maturité (2015-2016)

containers/Docker_History.md

55 / 801

Docker devient un standard d'industrie

  • Docker atteint le jalon symbolique du 1.0

  • Docker est maintenant supporté par les systèmes existants comme Mesos ou Cloud Foundry.

  • Standardisation autour de l'OCI (Open Containers Initiative).

  • De nouveaux moteurs de conteneurs sont développés.

  • Création de la CNCF (Cloud Native Computing Foundation).

containers/Docker_History.md

56 / 801

Docker devient une plate-forme

  • Le moteur de conteneur initial est maintenant nommé "Docker Engine".

  • D'autres outils y sont ajoutés:

    • Docker Compose (anciennement "Fig")
    • Docker Machine
    • Docker Swarm
    • Kitematic
    • Docker Cloud (anciennement "Tutum")
    • Docker Datacenter
    • etc.
  • Docker Inc. lance ses offres commerciales.

containers/Docker_History.md

57 / 801

Image separating from the next chapter

58 / 801

Notre environnement de formation

(automatically generated title slide)

59 / 801

Notre environnement de formation

SSH terminal

containers/Training_Environment.md

60 / 801

Notre environnement de formation

  • Si vous assistez à un atelier ou un tutoriel:

    • une VM a été provisionnée pour chaque apprenant
  • Si vous suivez ou révisez ce cours tout seul, vous pouvez:

    • installer Docker en local (comme expliqué dans le chapitre "Installer Docker")

    • installer Docker sur une VM dans le cloud (par ex.)

    • utiliser https://www.play-with-docker.com/ pour lancer en un instant un environnement de formation

containers/Training_Environment.md

61 / 801

Notre VM Docker

Cette section suppose que vous suivez ce cours dans le cadre d'un tutoriel, une atelier ou une formation, où chaque apprenant reçoit une VM Docker individuelle.

  • La VM est créée juste avant la formation.

  • Elle restera allumée pendant toute la durée de la formation.

  • Elle sera détruite peu de temps après la formation.

  • Elle est fournie avec Docker et quelques autres outils utiles.

containers/Training_Environment.md

62 / 801

C'est quoi Docker?

  • "Installer Docker" signifie en vrai "Installer le Docker Engine et le client en ligne de commande".

  • Le Docker Engine est un daemon (un service tournant en tâche de fond).

  • Ce daemon gère les conteneurs, à la manière d'un hyperviseur qui gère ses VMs.

  • Nous dialoguons avec le Docker Engine par la Docker CLI (ligne de commande).

  • Docker CLI et Docker Engine communiquent via une API.

  • Il existe de nombreux autres programmes, et de composants client, pour exploiter cette API.

containers/Training_Environment.md

63 / 801

Pourquoi on ne lance pas Docker en local?

  • On va télécharger des images de conteneur et des paquets de distribution.

  • Cela pourrait quelque peu stresser la connexion WiFi locale, et nous ralentir.

  • Au lieu de ça, on préfère passer par une VM distante qui a une meilleure connectivité.

  • Dans de rares cas, installer Docker en local peut s'avérer tortueux:

    • pas d'accès au compte admin/root (poste géré par une DSI stricte)

    • CPU ou système d'exploitation 32 bits.

    • vieille version de l'OS (par ex. CentOS 6, OSX pré-Yosemite, Windows 7)

  • Il est meilleur de passer du temps à apprendre les conteneurs qu'à trifouiller l'installateur!

containers/Training_Environment.md

64 / 801

Se connecter à votre Machine Virtuelle

Vous avez besoin d'un client SSH.

  • Sur OS X, Linux et autres systèmes UNIX, ssh suffit:
$ ssh <login>@<ip-address>

containers/Training_Environment.md

65 / 801

Vérifier votre Machine Virtuelle

Une fois connecté(e), assurez-vous que la commande Docker de base fonctionne:

$ docker version
Client:
Version: 18.03.0-ce
API version: 1.37
Go version: go1.9.4
Git commit: 0520e24
Built: Wed Mar 21 23:10:06 2018
OS/Arch: linux/amd64
Experimental: false
Orchestrator: swarm
Server:
Engine:
Version: 18.03.0-ce
API version: 1.37 (minimum version 1.12)
Go version: go1.9.4
Git commit: 0520e24
Built: Wed Mar 21 23:08:35 2018
OS/Arch: linux/amd64
Experimental: false

Si ça ne marche pas, levez la main et on viendra vous aider!

containers/Training_Environment.md

66 / 801

Image separating from the next chapter

67 / 801

Nos premiers conteneurs

(automatically generated title slide)

68 / 801

Nos premiers conteneurs

Colorful plastic tubs

containers/First_Containers.md

69 / 801

Objectifs

À la fin de cette leçon, vous aurez:

  • vu Docker en action;

  • démarré vos premiers conteneurs.

containers/First_Containers.md

70 / 801

Hello World

Depuis votre environnement Docker, lancez juste la commande suivante:

$ docker run busybox echo hello world
hello world

(Si votre installation Docker est vierge, quelques lignes en plus s'afficheront, correspondant au téléchargement de l'image busybox.)

containers/First_Containers.md

71 / 801

C'était notre premier conteneur!

  • Nous avons utilisé l'une des images les plus petites et simples: busybox.

  • busybox est typiquement utilisée dans les systèmes embarqués (téléphones, routeurs, etc.)

  • Nous avons lancé un seul processus pour afficher hello world.

containers/First_Containers.md

72 / 801

Un conteneur plus utile

Lançons un conteneur un peu plus excitant:

$ docker run -it ubuntu
root@04c0bb0a6c07:/#
  • C'est un conteneur tout neuf.

  • Il exécute un système ubuntu basique et sans fioritures.

  • -it est le raccourci pour -i -t.

    • -i dit à Docker de nous connecter à l'entrée du conteneur.

    • -t dit à Docker que nous voulons un pseudo-terminal.

containers/First_Containers.md

73 / 801

Faire quelque chose dans notre conteneur

Essayez de lancer figlet dans notre conteneur.

root@04c0bb0a6c07:/# figlet hello
bash: figlet: command not found

D'accord, donc nous allons devoir l'installer.

containers/First_Containers.md

74 / 801

Installer un paquet dans notre conteneur

Nous voulons figlet, alors installons-le:

root@04c0bb0a6c07:/# apt-get update
...
Fetched 1514 kB in 14s (103 kB/s)
Reading package lists... Done
root@04c0bb0a6c07:/# apt-get install figlet
Reading package lists... Done
...

Une minute plus tard, figlet est installé!

containers/First_Containers.md

75 / 801

Essayons de lancer notre programme fraichement installé

Le programme figlet prend un message en paramètre.

root@04c0bb0a6c07:/# figlet hello
_ _ _
| |__ ___| | | ___
| '_ \ / _ \ | |/ _ \
| | | | __/ | | (_) |
|_| |_|\___|_|_|\___/

Magnifique! 😍

containers/First_Containers.md

76 / 801

Compter les paquets dans le conteneur

Vérifions maintenant combien de paquets y sont installés.

root@04c0bb0a6c07:/# dpkg -l | wc -l
190
  • dpkg -l liste les paquets installés dans notre conteneur

  • wc -l va les compter

Combien de paquets avons-nous sur notre hôte?

containers/First_Containers.md

77 / 801

Compter les paquets sur l'hôte?

Quittez le conteneur en vous déconnectant du shell, comme d'habitude.

(i.e. avec ^D ou exit)

root@04c0bb0a6c07:/# exit

Maintenant, essayons de:

  • lancer dpkg -l | wc -l. Combien de paquets sont installés?

  • lancer figlet. Est-ce que ça marche?

containers/First_Containers.md

78 / 801

Hôte et conteneurs sont deux systèmes indépendants

  • Nous avons lancé un conteneur ubuntu sur un hôte Linux/Windows/macOS.

  • Ils possèdent des paquets indépendants et différents.

  • Installer quelque chose sur l'hôte ne l'expose pas dans le conteneur.

  • Et vice-versa.

  • Même si l'hôte et le conteneur ont tous deux la même distribution Linux.

  • Nous pouvons lancer n'importe quel conteneur sur n'importe quel hôte.

    (Une exception: les conteneurs Windows ne peuvent tourner sur les machines Linux; par encore en tout cas.)

containers/First_Containers.md

79 / 801

Où est notre conteneur?

  • Notre conteneur est maintenant en état stopped.

  • Il existe encore sur le disque, mais toutes ses ressources ont été libérées.

  • Nous verrons plus tard comment récupérer ce conteneur.

containers/First_Containers.md

80 / 801

Démarrer un autre conteneur

Et si nous démarrions un nouveau conteneur, pour y lancer à nouveau figlet?

$ docker run -it ubuntu
root@b13c164401fb:/# figlet
bash: figlet: command not found
  • Nous avons lancé un tout nouveau conteneur.

  • Dans l'image de base Ubuntu utilisée ci-dessus, figlet est absent.

containers/First_Containers.md

81 / 801

Où est mon conteneur?

  • Pouvons nous réutiliser ce conteneur que nous avons pris le soin de personnaliser?

    On pourrait, mais ce n'est pas le mode de production par défaut avec Docker.

  • Quel est le processus général, alos?

    Toujours démarrer avec un conteneur tout nouveau.
    Si on a besoin d'installer quoique ce soit dans notre conteneur, générer une nouvelle image.

  • Ça a l'air compliqué!

    Nous allons voir que c'est en fait assez simple!

  • Et tout ça pour quoi?

    Tout ça pour appuyer sur l'automatisation et la répétabilité. Voyons voir pourquoi ...

containers/First_Containers.md

82 / 801

Animaux de compagnie et bétail

  • Dans la métaphore "pets vs cattle", il existe deux genres de serveurs.

  • Les animaux de compagnie (Pets):

    • ont un petit nom et une configuration unique

    • quand ils défaillent, on fait tout ce qu'on peut pour les soigner.

  • Le bétail (Cattle):

    • ont des noms génériques (par ex. contenant des numéros) et une configuration générique

    • leur configuration est générée par une couche de gestion de configuration, et des templates ...

    • quand une panne advient, on peut les remplacer immédiatement par un nouveau serveur

  • Quelle est la relation avec Docker et les conteneurs?

containers/First_Containers.md

83 / 801

Environnement de développement locaux

  • Avec l'usage de VMs locales (comme par ex. Virtualbox ou VMware), notre flux de travail ressemble à ce qui suit:

    • créer une VM à partir d'un gabarit de base (Ubuntu, CentOS...)

    • installer les paquets, configurer l'environnement

    • travail sur le projet en tant que tel

    • finalement, éteindre la VM

    • la prochaine fois qu'on aborde ce projet, redémarrer la VM dans l'état où on l'a laissée

    • si on a besoin de retoucher l'environnement, on le fait en direct.

  • Au fil du temps, la configuration de la VM évolue et diverge.

  • Il nous manque une procédure propre, fiable et déterministe de provisionner cet environnement.

containers/First_Containers.md

84 / 801

Développement local avec Docker

  • Avec Docker, la démarche est la suivante:

    • créer une image de conteneur représentant notre environnement de dév.

    • lancer un conteneur basé sur cette image

    • travailler sur notre projet proprement dit

    • finalement, stopper le conteneur

    • la prochaine fois qu'on aborde le projet, démarrer un nouveau contneeur

    • en cas de changement de l'environnement, on créé une nouvelle image.

  • Nous avons une définition claire de notre environnement, qu'on peut partager de manière fiable avec les autres.

  • Nous verrons dans les chapitres suivants comment préparer une image personnalisée avec figlet.

containers/First_Containers.md

85 / 801

Image separating from the next chapter

86 / 801

Conteneurs en tâche de fond

(automatically generated title slide)

87 / 801

Conteneurs en tâche de fond

Background containers

containers/Background_Containers.md

88 / 801

Objectifs

Nos premiers conteneurs étaient interactifs.

Nous allons maintenant voir comment:

  • Lancer un conteneur non-interactif
  • Lancer un conteneur en tâche de fond.
  • Lister les conteneurs en cours d'exécution.
  • Vérifier les logs d'un conteneur.
  • Arrêter un conteneur.
  • Lister les conteneurs à l'arrêt.

containers/Background_Containers.md

89 / 801

Un conteneur non-interactif

Nous allons lancer un petit conteneur spécial.

Ce conteneur ne fait qu'afficher l'heure à chaque seconde.

$ docker run jpetazzo/clock
Fri Feb 20 00:28:53 UTC 2015
Fri Feb 20 00:28:54 UTC 2015
Fri Feb 20 00:28:55 UTC 2015
...
  • Ce conteneur va tourner indéfiniment.
  • Pour l'arrêter, appuyer sur ^C.
  • Docker a automatiquement téléchargé l'image jpetazzo/clock.
  • Cette image est une image utilisateur, créée par jpetazzo.
  • Nous en apprendrons plus sur les images utilisateur (et autres types d'images) plus tard.

containers/Background_Containers.md

90 / 801

Lancer un conteneur en tâche de fond

Les conteneurs peuvent être démarrés en tâche de fond, avec l'option -d (mode daemon)

$ docker run -d jpetazzo/clock
47d677dcfba4277c6cc68fcaa51f932b544cab1a187c853b7d0caf4e8debe5ad
  • Nous ne voyons pas l'affichage du conteneur.
  • Mais pas de souci: Docker collecte tout affichage et l'écrit dans un log!
  • Docker nous retourne un identifiant (ID) du conteneur.

containers/Background_Containers.md

91 / 801

Lister les conteneurs en cours d'exécution

Comment vérifier que notre conteneur est encore lancé?

Avec docker ps, tout comme la commande ps d'UNIX, qui liste les processus qui tournent.

$ docker ps
CONTAINER ID IMAGE ... CREATED STATUS ...
47d677dcfba4 jpetazzo/clock ... 2 minutes ago Up 2 minutes ...

Docker nous indique:

  • l'ID (tronqué) de notre conteneur;
  • l'image utilisée pour démarrer le conteneur;
  • que notre conteneur est lancé (Up) depuis quelques minutes;
  • d'autres informations (COMMAND, PORTS, NAME) que nous verrons plus tard.

containers/Background_Containers.md

92 / 801

Lancer plus de conteneurs

Démarrons deux autres conteneurs.

$ docker run -d jpetazzo/clock
57ad9bdfc06bb4407c47220cf59ce21585dce9a1298d7a67488359aeaea8ae2a
$ docker run -d jpetazzo/clock
068cc994ffd0190bbe025ba74e4c0771a5d8f14734af772ddee8dc1aaf20567d

Vérifiez que docker ps mentionne correctement tous les 3 conteneurs.

containers/Background_Containers.md

93 / 801

Afficher uniquement le dernier conteneur démarré

Quand de nombreux conteneurs tournent déjà, il peut être utile de limiter l'affichage au dernier conteneur démarré.

C'est à ça que sert l'option -l ("Last"):

$ docker ps -l
CONTAINER ID IMAGE ... CREATED STATUS ...
068cc994ffd0 jpetazzo/clock ... 2 minutes ago Up 2 minutes ...

containers/Background_Containers.md

94 / 801

Voir uniquement les IDs des conteneurs

Plusieurs commandes Docker sont basées sur des IDs de conteneurs: docker stop, docker rm, etc.

Si nous voulons lister uniquement les IDs de nos conteneurs (sans les autres colonnes ni en-tête), nous pouvons utiliser l'option -q ("Quiet", "Quick"):

$ docker ps -q
068cc994ffd0
57ad9bdfc06b
47d677dcfba4

containers/Background_Containers.md

95 / 801

Combinaison d'options

Nous pouvons combiner -l et -q pour uniquement voir l'ID du dernier conteneur démarré:

$ docker ps -lq
068cc994ffd0

A première vue, cela parait vraiment utile dans le cadre de scripts.

Toutefois, si nous voulons démarrer un conteneur et récupérer son ID de manière sécurisée, il est plus conseillé d'utiliser docker run -d, ce que nous aborderons dans un instant.

(Using docker ps -lq is prone to race conditions: what happens if someone else, or another program or script, starts another container just before we run docker ps -lq?)

containers/Background_Containers.md

96 / 801

Voir les logs d'un conteneur

On vous a dit que Docker enregistrait l'affichage d'un conteneur.

C'est le moment d'en parler.

$ docker logs 068
Fri Feb 20 00:39:52 UTC 2015
Fri Feb 20 00:39:53 UTC 2015
...
  • Nous avons spécifié un préfixe de l'ID d'un conteneur.
  • On peut, bien sûr, utiliser l'ID complet.
  • La commande logs va afficher les logs complets du conteneur.
    (Parfois, c'est bien trop. Voyons comment gérer ça.)

containers/Background_Containers.md

97 / 801

Afficher uniquement la fin des logs

Pour éviter de se faire spammer avec des dizaines de pages d'infos, on peut utiliser l'option --tail:

$ docker logs --tail 3 068
Fri Feb 20 00:55:35 UTC 2015
Fri Feb 20 00:55:36 UTC 2015
Fri Feb 20 00:55:37 UTC 2015
  • Le paramètre est le nombre de lignes que nous voulons afficher.

containers/Background_Containers.md

98 / 801

Suivre les logs en temps réel

Tout comme la commande UNIX standard tail -f, on peut suivre les logs de notre conteneur:

$ docker logs --tail 1 --follow 068
Fri Feb 20 00:57:12 UTC 2015
Fri Feb 20 00:57:13 UTC 2015
^C
  • Cela affichera la dernière ligne du fichier log
  • Puis, l'affichage continuera de se mettre à jour en temps réel.
  • Pour sortir, appuyer sur ^C.

containers/Background_Containers.md

99 / 801

Arrêter notre conteneur

Il y a deux façons de stopper notre conteneur détaché;

  • Le tuer via la commande docker kill.
  • Le stopper via la commande docker stop.

La première arrête le conteneur immédiatement, en utilisant le signal KILL.

La seconde est plus douce. Elle envoie un signal TERM, et après 10 secondes, si le conteneur n'est pas arrêté, il envoie KILL.

Rappel: le signal KILL ne peut être intercepté, et terminera le conteneur de force.

containers/Background_Containers.md

100 / 801

Arrêter nos conteneurs

Essayons d'arrêter un de ces conteneurs:

$ docker stop 47d6
47d6

Cela va prendre 10 secondes:

  • Docker envoie le signal TERM;
  • le conteneur ne réagit pas à ce signal (c'est un simple script Shell sans gestion de signal spécifique);
  • 10 secondes plus tard, puisque le conteneur est toujours actif, Docker envoie le signal KILL;
  • ceci neutralise le conteneur.

containers/Background_Containers.md

101 / 801

Supprimer le reste des conteneurs

Soyons moins patient avec les deux autres conteneurs:

$ docker kill 068 57ad
068
57ad

Les commandes stop et kill acceptent plusieurs IDs de conteneurs.

Ces conteneurs seront supprimés immédiatement (sans le délai de 10 secondes).

Vérifions que nos conteneurs ne s'affichent plus:

$ docker ps

containers/Background_Containers.md

102 / 801

Lister les conteneurs arrêtés

Nous pouvons aussi afficher les conteneurs stoppés, avec l'option -a (--all).

$ docker ps -a
CONTAINER ID IMAGE ... CREATED STATUS
068cc994ffd0 jpetazzo/clock ... 21 min. ago Exited (137) 3 min. ago
57ad9bdfc06b jpetazzo/clock ... 21 min. ago Exited (137) 3 min. ago
47d677dcfba4 jpetazzo/clock ... 23 min. ago Exited (137) 3 min. ago
5c1dfd4d81f1 jpetazzo/clock ... 40 min. ago Exited (0) 40 min. ago
b13c164401fb ubuntu ... 55 min. ago Exited (130) 53 min. ago

containers/Background_Containers.md

103 / 801

Image separating from the next chapter

104 / 801

Comprendre les images Docker

(automatically generated title slide)

105 / 801

Comprendre les images Docker

image

containers/Initial_Images.md

106 / 801

Objectifs

Dans cette section, nous expliquerons:

  • Ce qu'est une image;

  • Ce qu'est un layer;

  • Les différents nommages d'image;

  • Comment chercher et télécharger des images;

  • Les tags d'image et quand les utiliser.

containers/Initial_Images.md

107 / 801

Qu'est-ce qu'une image?

  • Image = fichiers + méta-données

  • Ces fichiers forment le système de fichier racine de notre conteneur.

  • Les méta-données indiquent un nombre de choses, comme:

    • l'auteur de l'image
    • la commande à exécuter dans le conteneur au démarrage
    • les variables d'environnement à initialiser
    • etc.
  • Des couches composent les images, appelées layers, empilées les unes au-dessus des autres.

  • Chaque couche peut ajouter, modifier, supprimer des fichiers ou des méta-données.

  • Les layers sont partagés entre images pour optimiser l'usage du disque, les temps de transfert et la consommation mémoire.

containers/Initial_Images.md

108 / 801

Exemple d'une web app Java

Chaque des points suivants se traduira par un layer:

  • couche de base CentOS
  • Paquets et fichier de configuration fournis par le service informatique interne
  • JRE
  • Tomcat
  • Dépendances de notre application
  • Code et sources de notre application
  • Configuration de notre appli

containers/Initial_Images.md

109 / 801

Le layer en lecture/écriture

layers

containers/Initial_Images.md

110 / 801

Différences entre conteneurs et images

  • Une image est un système de fichiers en lecture seule.

  • Un conteneur est un ensemble de processus encapsulé dans une copie en lecture/écriture de ce système de fichiers.

  • Pour optimiser le temps de démarrage du conteneur, on fait du copy-on-write au lieu d'une copie traditionnelle.

  • docker run démarre un conteneur depuis une image donnée.

containers/Initial_Images.md

111 / 801

Plusieurs conteneurs partageant la même image

layers

containers/Initial_Images.md

112 / 801

Comparaison avec la programmation orientée objet

  • Conceptuellement, les images sont proches des classes.

  • Conceptuellement, les layers sont proches de l'héritage.

  • Conceptuellement, les conteneurs sont proches des instances.

containers/Initial_Images.md

113 / 801

Attends un peu...

Si une image est en lecture-seule, comment on la change?

  • On ne la change pas.

  • On lance un nouveau conteneur à partir de cette image.

  • Puis on apporte des modifications à ce conteneur.

  • Quand on a fini, nous les figeons dans un nouveau layer.

  • Une nouvelle image est créée en empilant la nouvelle couche au-dessus de l'ancienne image.

containers/Initial_Images.md

114 / 801

Le problème de l'oeuf et la poule

  • La seule façon de créer une image est de geler un conteneur.

  • La seule façon de créer un conteneur est d'instancier une image.

  • A l'aide!

containers/Initial_Images.md

115 / 801

Créer les premières images

Il existe une image spéciale vide, appelée scratch.

  • Elle permet de générer une image de zéro.

La commande docker import charge un fichier tarball dans Docker.

  • L'image importée devient une image indépendante.
  • Cette nouvelle image a un seul layer.

Note: vous n'aurez sans doute jamais à faire cela vous-même.

containers/Initial_Images.md

116 / 801

Créer d'autres images

docker commit

  • Enregistre tous les changements d'un conteneur dans un nouveau layer.
  • Génère une nouvelle image (en réalité une copie du conteneur).

docker build (utilisé 99% du temps)

  • Exécute une séquence répétable de construction.
  • C'est la méthode recommandée!

Nous expliquerons les deux méthodes dans un moment.

containers/Initial_Images.md

117 / 801

Images et espaces de nommage

Il existe trois espaces de nommage (namespaces):

  • Images officielles:

    par ex. ubuntu, busybox, etc.

  • Images d'utilisateurs (et organisations):

    par ex. jpetazzo/clock

  • Images auto hébergées

    par ex. registry.example.com:5000/mon-image/privee

Examinons chacun d'entre eux.

containers/Initial_Images.md

118 / 801

Espace de nom 'racine'

L'espace de nom racine est pour les images officielles. Elles y sont placées par Docker Inc., mais sont généralement écrites et maintenues par des tierces parties.

Ces images incluent:

  • De petites images "couteau suisse", telles busybox.

  • Des images de distributions Linux servant de base aux builds, comme ubuntu, fedora, etc.

  • Des services et composants prêts à l'emploi, comme redis, postgresql, etc.

  • Plus de 150 à ce jour!

containers/Initial_Images.md

119 / 801

Espace de nommage utilisateur

L'espace de nommage pour utilisateur contient les images dans Docker Hub fournies par les utilisateurs et organisations.

Par exemple:

jpetazzo/clock

L'utilisateur Docker Hub est:

jpetazzo

Le nom de l'image est:

clock

containers/Initial_Images.md

120 / 801

Espace de nommage auto-hébergé

Cet espace de nommage contient les images qui ne sont pas hébergées sur Docker Hub, mais sur des registres de tierce partie.

Ils contiennent le nom de serveur (ou adresse IP), et le port (en option), du serveur de registre.

Par exemple:

localhost:5000/wordpress
  • localhost:5000 est l'hôte et le port du registre
  • wordpress est le nom de cette image

Other examples:

quay.io/coreos/etcd
gcr.io/google-containers/hugo

containers/Initial_Images.md

121 / 801

Comment gérer et stocker les images?

On stocke les images:

  • sur votre hôte Docker.
  • dans un registre Docker.

Vous pouvez utiliser le client Docker pour télécharger (pull) ou téléverser (push) des images.

Pour être plus précis: vous pouvez utiliser le client Docker pour intimer au Docker Engine de push et pull des images vers/depuis un registre.

containers/Initial_Images.md

122 / 801

Afficher les images actuelles

Voyons quelles sont les images disponibles sur notre serveur.

$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
fedora latest ddd5c9c1d0f2 3 days ago 204.7 MB
centos latest d0e7f81ca65c 3 days ago 196.6 MB
ubuntu latest 07c86167cdc4 4 days ago 188 MB
redis latest 4f5f397d4b7c 5 days ago 177.6 MB
postgres latest afe2b5e1859b 5 days ago 264.5 MB
alpine latest 70c557e50ed6 5 days ago 4.798 MB
debian latest f50f9524513f 6 days ago 125.1 MB
busybox latest 3240943c9ea3 2 weeks ago 1.114 MB
training/namer latest 902673acc741 9 months ago 289.3 MB
jpetazzo/clock latest 12068b93616f 12 months ago 2.433 MB

containers/Initial_Images.md

123 / 801

Chercher des images

Nous ne pouvons lister toutes les images sur un registre distant, mais nous pouvons chercher un mot-clé spécifique:

$ docker search marathon
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
mesosphere/marathon A cluster-wide init and co... 105 [OK]
mesoscloud/marathon Marathon 31 [OK]
mesosphere/marathon-lb Script to update haproxy b... 22 [OK]
tobilg/mongodb-marathon A Docker image to start a ... 4 [OK]
  • "Stars" mesure la popularité de l'image.

  • "Official" concerne les images qui sont dans le namespace racine.

  • "Automated" indique que l'image est générée automatiquement par le Docker Hub.
    (Cela signifie que leur recette de construction est toujours disponible.)

containers/Initial_Images.md

124 / 801

Télécharger des images

Il y a deux façons de récupérer des images.

  • Explicite, avec docker pull.

  • Implicite, en lançant docker run et l'image n'est pas disponible en local.

containers/Initial_Images.md

125 / 801

Télécharger une image via pull

$ docker pull debian:jessie
Pulling repository debian
b164861940b8: Download complete
b164861940b8: Pulling image (jessie) from debian
d1881793a057: Download complete
  • Comme vu précédemment, les images sont faites de layers.

  • Docker a téléchargé tous les layers nécessaires.

  • Dans notre exemple, :jessie indique quelle version exacte de Debian nous voulons.

C'est un tag (étiquette) de version.

containers/Initial_Images.md

126 / 801

Images et tags

  • On peut associer des tags aux images.

  • C'est utile pour préciser les versions ou variantes d'une image.

  • docker pull ubuntu va se référer à ubuntu:latest.

  • Le tag latest est par tradition mis à jour fréquemment.

containers/Initial_Images.md

127 / 801

Quand utiliser (et ne pas utiliser) les tags

Pas besoin de spécifier d'étiquette (tag) pour:

  • des tests ou prototypes rapides.
  • expérimenter.
  • récupérer la dernière version.

Utiliser des tags pour:

  • Persister une procédure dans un script;
  • Déployer en production;
  • Garantir que la même version sera utilisée partout;
  • Garantir la répétabilité future.

This is similar to what we would do with pip install, npm install, etc.

containers/Initial_Images.md

128 / 801

Résumé du chapitre

Nous avons appris comment:

  • Comprendre les images et layers;
  • Fonctionne les espaces de nom dans Docker;
  • Chercher et télécharger des images.

containers/Initial_Images.md

129 / 801

Image separating from the next chapter

130 / 801

Construire des images Docker avec un Dockerfile

(automatically generated title slide)

131 / 801

Construire des images Docker avec un Dockerfile

Construction site with containers

containers/Building_Images_With_Dockerfiles.md

132 / 801

Objectifs

Nous allons construire une image de conteneur automatiquement, grâce au Dockerfile.

A la fin de cette leçon, vous saurez comment:

  • Ecrire un Dockerfile.

  • Générer une image (build) via un Dockerfile.

containers/Building_Images_With_Dockerfiles.md

133 / 801

Aperçu d'un Dockerfile

  • Un Dockerfile est une recette de construction pour une image Docker.

  • Il contient une série d'instructions indiquant à Docker comment l'image est construite.

  • La commande docker build génère une image à partir d'un Dockerfile.

containers/Building_Images_With_Dockerfiles.md

134 / 801

Ecrire notre premier Dockerfile

Notre Dockerfile doit être dans un dossier nouveau et vide.

  1. Ajouter un nouveau dossier pour accueillir notre Dockerfile.
$ mkdir myimage
  1. Créer un fichier Dockerfile à l'intérieur de ce nouveau dossier.
$ cd myimage
$ vim Dockerfile

Bien sûr, vous pouvez utiliser n'importe quel éditeur de votre choix.

containers/Building_Images_With_Dockerfiles.md

135 / 801

Entrez ces lignes dans notre Dockerfile...

FROM ubuntu
RUN apt-get update
RUN apt-get install figlet
  • FROM indique notre image de base pour notre build.

  • Chaque ligne RUN sera exécutée par Docker pendant le build.

  • Nos commandes RUN doivent être non-interactive.
    (Aucune entrée ne peut être fournie à Docker pendant le build).

  • Dans bien des cas, nous ajouterons l'option -y à apt-get.

containers/Building_Images_With_Dockerfiles.md

136 / 801

Construisons-la!

Enregistrez notre fichier, et lancez:

$ docker build -t figlet .
  • -t indique le tag à appliquer à l'image.

  • . indique la localisation du build context.

Nous parlerons en détails du build context plus tard.

Pour garder les choses simples: c'est le dossier où se trouve notre Dockerfile.

containers/Building_Images_With_Dockerfiles.md

137 / 801

Que se passe-t-il quand nous générons l'image?

L'affichage de docker build ressemble à ceci:

docker build -t figlet .
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM ubuntu
---> f975c5035748
Step 2/3 : RUN apt-get update
---> Running in e01b294dbffd
(...output of the RUN command...)
Removing intermediate container e01b294dbffd
---> eb8d9b561b37
Step 3/3 : RUN apt-get install figlet
---> Running in c29230d70f9b
(...output of the RUN command...)
Removing intermediate container c29230d70f9b
---> 0dfd7a253f21
Successfully built 0dfd7a253f21
Successfully tagged figlet:latest
  • L'affichage des commandes RUN a été omis.
  • Voyons voir en quoi consiste cet affichage.

containers/Building_Images_With_Dockerfiles.md

138 / 801

Envoi du build context à Docker

Sending build context to Docker daemon 2.048 kB
  • Le build context est le dossier . donné à docker build.

  • Il est envoyé (sous forme d'archive) par le client Docker au daemon Docker.

  • Cela permet d'utiliser un serveur distant pour le build utilisant des fichiers locaux.

  • Soyez attentifs (ou patient) si ce dossier est lourd et votre connexion est lente.

  • You can speed up the process with a .dockerignore file

    • It tells docker to ignore specific files in the directory

    • Only ignore files that you won't need in the build context!

containers/Building_Images_With_Dockerfiles.md

139 / 801

Exécution de chaque étape

Step 2/3 : RUN apt-get update
---> Running in e01b294dbffd
(...output of the RUN command...)
Removing intermediate container e01b294dbffd
---> eb8d9b561b37
  • Un container (e01b294dbffd) est créé à partir de l'image de base.

  • La commande RUN se lance dans ce container.

  • Le conteneur est sauvé dans une nouvelle image (eb8d9b561b37)

  • Le conteneur de build (e01b294dbffd) est supprimé.

  • Le résultat de cette étape sera l'image de base pour la prochaine commande.

containers/Building_Images_With_Dockerfiles.md

140 / 801

Le système de cache

Si vous lancez le même build de nouveau, ce sera instantané. Pourquoi?

  • Après chaque étape de build, Docker prend un snapshot de l'image résultante.

  • Avant chaque nouvelle étape, Docker vérifie si la même séquence a été générée.

  • Docker utilise les chaines de caractères exactes définies dans votre Dockerfile, donc:

    • RUN apt-get install figlet cowsay
      est différent de
      RUN apt-get install cowsay figlet
    • RUN apt-get update n'est pas exécuté, quand les miroirs sont mis à jour.

Vous pouvez forcer un nouveau build avec docker build --no-cache....

containers/Building_Images_With_Dockerfiles.md

141 / 801

Lancer l'image

L'image résultante n'est pas différente de celle définie manuellement.

$ docker run -ti figlet
root@91f3c974c9a1:/# figlet hello
_ _ _
| |__ ___| | | ___
| '_ \ / _ \ | |/ _ \
| | | | __/ | | (_) |
|_| |_|\___|_|_|\___/

Youpi! 🎉

containers/Building_Images_With_Dockerfiles.md

142 / 801

Utiliser l'image et afficher l'historique

La commande history liste toutes les couches composant une image.

Pour chaque couche (layer), on voit la date de création, sa taille et la commande utilisée.

Quand une image est générée via un Dockerfile, chaque layer correspond à une ligne du Dockerfile.

$ docker history figlet
IMAGE CREATED CREATED BY SIZE
f9e8f1642759 About an hour ago /bin/sh -c apt-get install fi 1.627 MB
7257c37726a1 About an hour ago /bin/sh -c apt-get update 21.58 MB
07c86167cdc4 4 days ago /bin/sh -c #(nop) CMD ["/bin 0 B
<missing> 4 days ago /bin/sh -c sed -i 's/^#\s*\( 1.895 kB
<missing> 4 days ago /bin/sh -c echo '#!/bin/sh' 194.5 kB
<missing> 4 days ago /bin/sh -c #(nop) ADD file:b 187.8 MB

containers/Building_Images_With_Dockerfiles.md

143 / 801

Introduction à la syntaxe JSON

La plupart des arguments de Dockerfile peuvent être passés sous deux formes:

  • chaine simple:
    RUN apt-get install figlet

  • liste JSON:
    RUN ["apt-get", "install", "figlet"]

Nous allons changer notre Dockerfile et voir comment cela affecte l'image résultante.

containers/Building_Images_With_Dockerfiles.md

144 / 801

Usage de la syntaxe JSON dans notre Dockerfile

Changeons notre Dockerfile comme suit:

FROM ubuntu
RUN apt-get update
RUN ["apt-get", "install", "figlet"]

Puis relançons un build du nouveau Dockerfile.

$ docker build -t figlet .

containers/Building_Images_With_Dockerfiles.md

145 / 801

Syntaxe JSON vs syntaxe simple

Comparons le nouvel historique:

$ docker history figlet
IMAGE CREATED CREATED BY SIZE
27954bb5faaf 10 seconds ago apt-get install figlet 1.627 MB
7257c37726a1 About an hour ago /bin/sh -c apt-get update 21.58 MB
07c86167cdc4 4 days ago /bin/sh -c #(nop) CMD ["/bin 0 B
<missing> 4 days ago /bin/sh -c sed -i 's/^#\s*\( 1.895 kB
<missing> 4 days ago /bin/sh -c echo '#!/bin/sh' 194.5 kB
<missing> 4 days ago /bin/sh -c #(nop) ADD file:b 187.8 MB
  • La syntaxe JSON spécifie une commande exacte à exécuter.

  • La syntaxe simple spécifie une commande à être encapsulée dans /bin/sh -c "...".

containers/Building_Images_With_Dockerfiles.md

146 / 801

Quand utiliser la syntaxe JSON et la syntaxe simple

  • La syntaxe simple:

    • est plus facile à écrire
    • extrapole les variables d'environnement et d'autres expressions de shell
    • crée un processus supplémentaire (/bin/sh -c ...) pour interpréter la commande
    • exige l'existence de /bin/sh dans le conteneur
  • La syntaxe JSON:

    • est plus longue à écrire (et à lire!)
    • passe tous les arguments sans interprétation
    • n'ajoute pas de processus supplémentaire
    • ne requiert pas l'existence de /bin/sh dans le conteneur

containers/Building_Images_With_Dockerfiles.md

147 / 801

Image separating from the next chapter

148 / 801

CMD et ENTRYPOINT

(automatically generated title slide)

149 / 801

CMD et ENTRYPOINT

Container entry doors

containers/Cmd_And_Entrypoint.md

150 / 801

Objectifs

Dans cette leçon, nous verrons deux instructions importantes du Dockerfile:

CMD et ENTRYPOINT.

Ces instructions nous permettent de déclarer la commande par défaut à lancer dans un conteneur.

containers/Cmd_And_Entrypoint.md

151 / 801

Définir une commande par défaut

Quand quelqu'un lancera notre conteneur, nous voulons le saluer avec un sympathique bonjour, et une fonte spéciale.

Pour ça, nous allons lancer:

figlet -f script hello
  • -f script dit à figlet d'utiliser une fonte spécifique.

  • hello est le message que nous voulons afficher.

containers/Cmd_And_Entrypoint.md

152 / 801

Ajouter CMD à notre Dockerfile

Notre nouveau Dockerfile aura cet aspect:

FROM ubuntu
RUN apt-get update
RUN ["apt-get", "install", "figlet"]
CMD figlet -f script hello
  • CMD définit une commande par défaut à lancer quand aucune n'est précisée.

  • Elle peut apparaître n'importe où dans le fichier.

  • Chaque CMD annulera et remplacera la précédente.

  • Par conséquent, même si vous pouvez utiliser plusieurs fois CMD, c'est inutile au final.

containers/Cmd_And_Entrypoint.md

153 / 801

Générer et tester notre image

Essayons de lancer un build:

$ docker build -t figlet .
...
Successfully built 042dff3b4a8d
Successfully tagged figlet:latest

Et de le lancer:

$ docker run figlet
_ _ _
| | | | | |
| | _ | | | | __
|/ \ |/ |/ |/ / \_
| |_/|__/|__/|__/\__/

containers/Cmd_And_Entrypoint.md

154 / 801

Surcharger CMD

Si nous voulons ouvrir un shell dans notre conteneur (au lieu de lancer figlet), il suffit de spécifier un autre programme à lancer:

$ docker run -it figlet bash
root@7ac86a641116:/#
  • On a indiqué bash

  • Il a remplacé la valeur de CMD

containers/Cmd_And_Entrypoint.md

155 / 801

Utiliser ENTRYPOINT

Nous voulons être capable de spécifier un message différent en ligne de commande, tout en gardant figlet et quelques paramètres par défaut.

Autrement dit, on aimerait taper quelque chose comme:

$ docker run figlet salut
_
| |
, __, | | _|_
/ \_/ | |/ | | |
\/ \_/|_/|__/ \_/|_/|_/

Nous utiliserons pour ça l'instruction ENTRYPOINT du Dockerfile.

containers/Cmd_And_Entrypoint.md

156 / 801

Ajouter ENTRYPOINT à notre Dockerfile

Notre nouveau Dockerfile aura cet aspect:

FROM ubuntu
RUN apt-get update
RUN ["apt-get", "install", "figlet"]
ENTRYPOINT ["figlet", "-f", "script"]
  • ENTRYPOINT définit une commande de base (et ses paramètres) pour le conteneur.

  • Les arguments en ligne de commande sont ajoutés aux paramètres ci-dessus.

  • Tout comme CMD, ENTRYPOINT peut apparaître n'importe où, et remplacera la valeur précédente.

Pourquoi avoir utilisé la syntaxe JSON pour notre ENTRYPOINT?

containers/Cmd_And_Entrypoint.md

157 / 801

Implications des syntaxes JSON vs simple

  • Quand CMD ou ENTRYPOINT utilisent la syntaxe simple, ils sont encapsulés dans sh -c

  • Pour éviter ce comportement, on peut utiliser la syntaxe JSON.

Et qu'est-ce qu'il se passerait avec une syntaxe simple dans ENTRYPOINT?

$ docker run figlet salut

On obtiendrait la commande suivante dans l'image figlet:

sh -c "figlet -f script" salut

containers/Cmd_And_Entrypoint.md

158 / 801

Générer et tester notre image

Lançons un build:

$ docker build -t figlet .
...
Successfully built 36f588918d73
Successfully tagged figlet:latest

Exécutons là:

$ docker run figlet salut
_
| |
, __, | | _|_
/ \_/ | |/ | | |
\/ \_/|_/|__/ \_/|_/|_/

containers/Cmd_And_Entrypoint.md

159 / 801

Usage conjoint de CMD et ENTRYPOINT

Et si nous voulions définir un message par défaut pour notre conteneur?

Alors nous utiliserions ENTRYPOINT et CMD ensemble.

  • ENTRYPOINT va définir la commande de base pour notre conteneur.

  • CMD définira les paramètres par défaut pour cette commande.

Ils doivent tous les deux utiliser la syntaxe JSON.

containers/Cmd_And_Entrypoint.md

160 / 801

CMD et ENTRYPOINT ensemble

Notre nouveau Dockerfile a cette tête:

FROM ubuntu
RUN apt-get update
RUN ["apt-get", "install", "figlet"]
ENTRYPOINT ["figlet", "-f", "script"]
CMD ["hello world"]
  • ENTRYPOINT définit une commande de base (et ses paramètres) pour le conteneur.

  • Si nous ne spécifions aucun argument supplémentaire au lancement du conteneur, la valeur de CMD y est ajoutée.

  • Autrement, nos arguments supplémentaires de ligne de commande remplacent CMD.

containers/Cmd_And_Entrypoint.md

161 / 801

Générer et tester notre image

Lançons un build:

$ docker build -t myfiglet .
...
Successfully built 6e0b6a048a07
Successfully tagged myfiglet:latest

Exécutons-là sans paramètres:

$ docker run myfiglet
_ _ _ _
| | | | | | | | |
| | _ | | | | __ __ ,_ | | __|
|/ \ |/ |/ |/ / \_ | | |_/ \_/ | |/ / |
| |_/|__/|__/|__/\__/ \/ \/ \__/ |_/|__/\_/|_/

containers/Cmd_And_Entrypoint.md

162 / 801

Surcharger les paramètres par défaut de l'image

Maintenant, passons des arguments supplémentaires à l'image.

$ docker run myfiglet hola mundo
_ _
| | | | |
| | __ | | __, _ _ _ _ _ __| __
|/ \ / \_|/ / | / |/ |/ | | | / |/ | / | / \_
| |_/\__/ |__/\_/|_/ | | |_/ \_/|_/ | |_/\_/|_/\__/

Nous avons surchargé CMD tout en gardant ENTRYPOINT.

containers/Cmd_And_Entrypoint.md

163 / 801

Surcharger ENTRYPOINT

Et si nous voulons lancer un shell dans notre conteneur?

On ne peut pas juste taper docker run figlet bash car ça dirait juste à figlet d'afficher le mot "bash".

On utilise donc le paramètre --entrypoint:

$ docker run -it --entrypoint bash myfiglet
root@6027e44e2955:/#

containers/Cmd_And_Entrypoint.md

164 / 801

Image separating from the next chapter

165 / 801

Copier des fichiers pendant le build

(automatically generated title slide)

166 / 801

Copier des fichiers pendant le build

Monks copying books

containers/Copying_Files_During_Build.md

167 / 801

Objectifs

Jusqu'ici, nous avons installé des choses dans nos images de conteneurs en téléchargeant des paquets.

Nous pouvons aussi copier des fichiers depuis le build context vers le conteneur que nous générons.

Rappel: le build context est le dossier qui contient le Dockerfile.

Dans ce chapitre, nous apprendrons une nouvelle instruction du Dockerfile: COPY.

containers/Copying_Files_During_Build.md

168 / 801

Compilons du code C

Nous voulons construire un conteneur qui compile un simple programme "Hello world" écrit en C.

Voici le programme, hello.c:

int main () {
puts("Hello, world!");
return 0;
}

Ouvrons un nouveau dossier, et plaçons ce fichier à l'intérieur.

Nous écrirons ensuite le Dockerfile.

containers/Copying_Files_During_Build.md

169 / 801

Le Dockerfile

Sur Debian et Ubuntu, le paquet build-essential nous donnera un compilateur.

En l'installant, n'oubliez pas de spécifier l'option -y, ou sinon le build échouera (puisque cette phase ne peut pas être intéractive).

Puis nous allons utiliser COPY pour placer le fichier source dans le conteneur.

FROM ubuntu
RUN apt-get update
RUN apt-get install -y build-essential
COPY hello.c /
RUN make hello
CMD /hello

Ecrivez ce Dockerfile.

containers/Copying_Files_During_Build.md

170 / 801

Tester notre programme C

  • Créez les fichiers hello.c et Dockerfile dans le même dossier.

  • Lancer docker build -t hello . dans ce dossier.

  • Lancer docker run hello, vous devriez voir Hello, world!.

Victoire!

containers/Copying_Files_During_Build.md

171 / 801

COPY et le cache de build

  • Lancez le build encore.

  • Maintenant, modifiez hello.c et lancer le build encore.

  • Docker peut mettre en cache les étapes impliquant COPY.

  • Ces étapes ne seront pas exécutées si les fichiers n'ont pas changé.

containers/Copying_Files_During_Build.md

172 / 801

Détails

  • On peut COPY des dossiers complets en récursif.

  • D'anciens Dockerfiles peuvent aussi comporter l'instruction ADD.
    C'est similaire sauf qu'il peut aussi extraire des archives automatiquement.

  • Si nous voulions vraiment compiler le code C dans le conteneur, nous aurions:

    • copié le source dans un dossier différent, via l'instruction WORKDIR.

    • ou mieux encore, utilisé l'image officielle gcc.

containers/Copying_Files_During_Build.md

173 / 801

Image separating from the next chapter

174 / 801

Réduire la taille de l'image

(automatically generated title slide)

175 / 801

Réduire la taille de l'image

  • Dans notre précédent exemple, notre image finale contenait:

    • notre programme hello

    • son code source

    • le compilateur

  • Seul le premier élément est strictement nécessaire.

  • Nous allons voir comment obtenir une image sans les composants superflus.

containers/Multi_Stage_Builds.md

176 / 801

Ne pouvons-nous pas retirer les fichiers superflus avec RUN?

Que se passe-t-il si nous utilisons une des commandes suivantes?

  • RUN rm -rf ...

  • RUN apt-get remove ...

  • RUN make clean ...

177 / 801

Ne pouvons-nous pas retirer les fichiers superflus avec RUN?

Que se passe-t-il si nous utilisons une des commandes suivantes?

  • RUN rm -rf ...

  • RUN apt-get remove ...

  • RUN make clean ...

Cela ajoute une couche qui supprime un tas de fichiers.

Mais les couches précédentes (qui ont ajouté les fichiers) existent toujours.

containers/Multi_Stage_Builds.md

178 / 801

Retirer les fichiers avec un layer en plus

En téléchargeant une image, tous les layers doivent être récupérés.

Instruction Dockerfile Taille du layer Taille de l'image
FROM ubuntu Taille de l'image de base Taille de l'image de base
... ... Somme de cette couche
+ toutes les précédentes
RUN apt-get install somepackage Taille des fichiers ajoutés
(e.g. qqes Mo)
Somme de cette couche
+ toutes les couches précédentes
... ... idem
RUN apt-get remove somepackage Env. zéro
(méta-données seules)
Identique à la précédente

En conséquence, RUN rm ne réduit pas la taille de l'image, ni ne libère d'espace disque.

containers/Multi_Stage_Builds.md

179 / 801

Supprimer les fichiers inutiles

Des techniques variées sont disponibles pour obtenir des images plus petites:

  • dégonflement de layers,

  • ajouter des binaires qui sont générés hors du Dockerfile,

  • aplatir l'image finale,

  • les builds multi-stage, à multiples étapes.

Passons-les en revue rapidement.

containers/Multi_Stage_Builds.md

180 / 801

Dégonflement de layers

Vous verrez souvent des Dockerfiles comme suit:

FROM ubuntu
RUN apt-get update && apt-get install xxx && ... && apt-get remove xxx && ...

Ou la variante plus lisible:

FROM ubuntu
RUN apt-get update \
&& apt-get install xxx \
&& ... \
&& apt-get remove xxx \
&& ...

Cette commande RUN nous retourne une seule couche.

Les fichiers qui y sont ajoutés, puis supprimés dans le même layer, n'augmentent pas sa taille.

containers/Multi_Stage_Builds.md

181 / 801

Dégonflement de layers : pour et contre

Pour:

  • fonctionne sur toutes les versions de Docker

  • n'exige pas d'outils spéciaux

Contre:

  • pas très lisible

  • quelques fichiers inutiles pourraient subsister si le nettoyage n'était pas assez poussé

  • ce layer est coûteux (lent à générer)

containers/Multi_Stage_Builds.md

182 / 801

Compiler les binaires hors du Dockerfile

Cela résulte dans un Dockerfile qui ressemble à ça:

FROM ubuntu
COPY xxx /usr/local/bin

Bien sûr, cela suppose que le fichier xxx existe déjà dans le build context.

Ce fichier doit exister avant de lancer docker build.

Par exemple, il peut:

  • exister dans le dépôt du code,
  • être créé par un autre outil (script, Makefile...),
  • être créé par un autre conteneur puis extrait depuis l'image.

Voir par exemple l'image officielle busybox ou cette image busybox plus ancienne.

containers/Multi_Stage_Builds.md

183 / 801

Compiler les binaires en dehors: pour et contre

Pour:

  • l'image finale peut être très petite

Contre:

  • exige un outil de build supplémentaire

  • nous retombons dans l'enfer des dépendances et le "ça-marche-sur-mon-poste"

Contre, si le binaire est ajouté au dépôt de code:

  • brise la portabilité entre différentes plate-formes

  • augmente largement la taille du dépôt si le binaire est souvent mis à jour

containers/Multi_Stage_Builds.md

184 / 801

Aplatir l'image finale

L'idée est de transformer l'image finale en une image à un seul layer.

Cela peut être réalisé de deux manières (au moins).

  • Activer les fonctions expérimentales et aplatir l'image finale:
    docker image build --squash ...
  • Exporter/importer l'image finale.
    docker build -t temp-image .
    docker run --entrypoint true --name temp-container temp-image
    docker export temp-container | docker import - final-image
    docker rm temp-container
    docker rmi temp-image

containers/Multi_Stage_Builds.md

185 / 801

Aplatir l'image finale: pour et contre

Pour:

  • les images à layer unique sont plus légères et rapides à télécharger

  • les fichiers supprimés ne prennent plus de place ni de ressources réseau.

Contre:

  • nous devons quand même activement supprimer les fichiers inutiles;

  • aplatir une image peut prendre beaucoup de temps (pour les plus grosses images)

  • aplatir est une opération qui annule le cache
    (ne changer ne serait-ce qu'un petit fichier, et toute l'image doit être aplatie de nouveau)

containers/Multi_Stage_Builds.md

186 / 801

Builds multi-stage

Un build multi-stage nous permet d'indiquer plusieurs étapes d'images.

Chaque étape constitue une image séparée, et peut copier les fichiers des images précédentes.

Nous allons voir comment ça marche plus en détail.

containers/Multi_Stage_Builds.md

187 / 801

Image separating from the next chapter

188 / 801

Builds multi-stage

(automatically generated title slide)

189 / 801

Builds multi-stage

  • A tout moment dans notre Dockerfile, nous pouvons ajouter une ligne FROM.

  • Cette ligne démarre une nouvelle étape dans notre build.

  • Chaque étape peut accéder aux fichiers des étapes précédentes avec COPY --from=....

  • Quand un build est étiqueté (avec docker build -t ...), c'est la dernière étape qui récupère le tag.

  • Les étapes précédentes ne sont pas supprimées, elle seront utilisées par le cache, et peuvent être référencées.

containers/Multi_Stage_Builds.md

190 / 801

Builds multi-stage en pratique

  • Chaque étape est numérotée, en débutant à 0

  • Nous pouvons copier un fichier d'une étape précédente en indiquant son numéro, par ex.:

    COPY --from=0 /fichier/depuis/etape/une /chemin/dans/etape/courante
  • Nous pouvons aussi nommer les étapes, et y faire référence:

    FROM golang AS builder
    RUN ...
    FROM alpine
    COPY --from=builder /go/bin/mylittlebinary /usr/local/bin/

containers/Multi_Stage_Builds.md

191 / 801

Builds multi-stage pour notre programme C

Nous allons changer notre Dockerfile pour:

  • donner un surnom à notre première étape: compiler

  • ajouter une seconde étape utilisant la même image de base ubuntu

  • ajouter le binaire hello à la seconde étape

  • vérifier que CMD est dans la seconde étape

Le Dockerfile résultant est dans la prochaine diapo.

containers/Multi_Stage_Builds.md

192 / 801

Dockerfile du build multi-stage

Voici le Dockerfile final:

FROM ubuntu AS compiler
RUN apt-get update
RUN apt-get install -y build-essential
COPY hello.c /
RUN make hello
FROM ubuntu
COPY --from=compiler /hello /hello
CMD /hello

Essayons de le générer, et vérifions que cela fonctionne bien:

docker build -t hellomultistage .
docker run hellomultistage

containers/Multi_Stage_Builds.md

193 / 801

Comparaison des tailles d'image en build simple/multi-stage

Listez nos images avec docker images, et vérifiez la taille de:

  • l'image de base ubuntu,

  • l'image hello à étape unique,

  • l'image hellomultistage à plusieurs étapes.

Nous pouvons arriver à des tailles d'images encore plus petites avec des images de base plus petites.

Toutefois, si nous utilisons une image de base commune (par ex. en prenant comme standard ubuntu), ces images en commun ne seront téléchargées qu'une fois par hôte, les rendant virtuellement "gratuites".

containers/Multi_Stage_Builds.md

194 / 801

Cibles de build

  • On peut aussi étiqueter une étape intermédiaire avec docker build --target STAGE --tag NAME

  • Cela va créer une image (appelée NAME) correspondant à l'étape STAGE

  • Elle peut être utilisée pour accéder facilement à une étape intermédiaire pour inspection.

    (Au lieu de fouiller l'affichage de docker build pour trouver l'ID d'image)

  • Elle peut aussi être utilisée pour générer de multiples images avec un seul Dockerfile

    (Au lieu d'utiliser plusieurs Dockerfiles, qu'il faudrait perpétuellement synchroniser)

containers/Multi_Stage_Builds.md

195 / 801

Image separating from the next chapter

196 / 801

Publier des images sur le Docker Hub

(automatically generated title slide)

197 / 801

Publier des images sur le Docker Hub

Nous avons généré nos premières images.

Nous pouvons maintenant les publier vers le Docker Hub!

Vous n'avez pas à faire les exercices de cette section, parce qu'ils exigent un compte sur le Docker Hub, et nous ne voulons forcer personne à en créer un.

Veuillez noter, toutefois, que la création d'un compte sur le Docker Hub est gratuite (sans carte bancaire), et que l'hébergement d'images publiques est aussi gratuit.

containers/Publishing_To_Docker_Hub.md

198 / 801

Connexion au Docker Hub

  • C'est faisable depuis la ligne de commande Docker:
    docker login

Depuis Docker sur Mac/Windows, ou Docker sur un poste de travail Linux, on peut (et on préfére si possible) s'intégrer avec le trousseau de clés du système pour stocker les accès en sécurité. Toutefois, sur la plupart des serveurs Linux, ce sera stocké par défaut dans ~/.docker/config.

containers/Publishing_To_Docker_Hub.md

199 / 801

Tags d'image et adresses de registre

  • Docker et ses tags d'images sont comme Git et ses tags/branches.

  • Ce sont des pointeurs vers un ID d'image spécifique.

  • Marquer une image ne renomme pas cette image: elle ne fait qu'ajouter une étiquette.

  • En poussant une image vers un registre distant, l'adresse de registre est dans le tag.

    Example: registry.example.net:5000/image

  • Qu'en est-il des images du Docker Hub?

200 / 801

Tags d'image et adresses de registre

  • Docker et ses tags d'images sont comme Git et ses tags/branches.

  • Ce sont des pointeurs vers un ID d'image spécifique.

  • Marquer une image ne renomme pas cette image: elle ne fait qu'ajouter une étiquette.

  • En poussant une image vers un registre distant, l'adresse de registre est dans le tag.

    Example: registry.example.net:5000/image

  • Qu'en est-il des images du Docker Hub?

  • jpetazzo/clock est, en fait, index.docker.io/jpetazzo/clock

  • ubuntu est, en fait, library/ubuntu, i.e. index.docker.io/library/ubuntu

containers/Publishing_To_Docker_Hub.md

201 / 801

Etiqueter une image pour la pousser sur le Hub

  • Ajoutons une étiquette à notre image figlet (ou une autre):

    docker tag figlet jpetazzo/figlet
  • Et poussons-là sur le Hub:

    docker push jpetazzo/figlet
  • C'est tout!

202 / 801

Etiqueter une image pour la pousser sur le Hub

  • Ajoutons une étiquette à notre image figlet (ou une autre):

    docker tag figlet jpetazzo/figlet
  • Et poussons-là sur le Hub:

    docker push jpetazzo/figlet
  • C'est tout!

  • N'importe qui peut maintenant docker run jpetazzo/figlet de partout.

containers/Publishing_To_Docker_Hub.md

203 / 801

Les vertus des builds automatisés

  • Vous pouvez lier un dépôt du Docker Hub avec un dépôt Github ou BitBucket.

  • Chaque push dans Github/Bitbucket va déclencher un build sur Docker Hub.

  • Si l'image est générée avec succès, elle sera disponible sur Docker Hub.

  • Vous pouvez associer tags et branches entre le code source et images de conteneurs.

  • Si vous maintenez des dépôts publics, tout ça est gratuit.

containers/Publishing_To_Docker_Hub.md

204 / 801

Installer un build automatisé

  • Vous avez besoin d'un code source "Dockerisé"!
  • Direction https://github.com/jpetazzo/trainingwheels pour le fork.
  • Allez sur Docker Hub (https://hub.docker.com/) et connectez-vous. Sélectionnez "Repositories" dans la barre de navigation bleue.
  • Connectez votre compte Docker Hub à votre compte Github.
  • Cliquez sur le bouton "Create".
  • Puis allez dans l'onglet "Builds".
  • Cliquez sur l'icône Github et choisissez le compte/dépôt que nous avons juste fork.
  • Dans le bloc "Builds rules" en bas de page, indiquez /www dans la colonne "Build context" (ou le dossier qui contient le Dockerfile).
  • Cliquez "Save and build" pour lancer un build immédiatement (sans attendre le prochain git push).
  • Les prochains builds seront automatiques, grâce aux notifications Github.

containers/Publishing_To_Docker_Hub.md

205 / 801

Image separating from the next chapter

206 / 801

Astuces pour Dockerfiles efficaces

(automatically generated title slide)

207 / 801

Astuces pour Dockerfiles efficaces

Nous allons voir comment:

  • réduire le nombre de layers.

  • Exploiter le cache de build pour accélérer la construction.

  • Injecter les tests unitaires dans le processus de génération.

containers/Dockerfile_Tips.md

208 / 801

Réduire le nombre de layers

  • Chaque ligne du Dockerfile ajoute une nouvelle couche.

  • Ecrivez votre Dockerfile pour exploiter le système de cache de Docker.

  • Combinez les commandes avec && pour chainer les commandes et \ pour empiler les lignes.

Note: il est fréquent d'écrire un Dockerfile ligne par ligne:

RUN apt-get install thisthing
RUN apt-get install andthatthing andthatotherone
RUN apt-get install somemorestuff

Puis le corriger très facilement avant déploiement:

RUN apt-get install thisthing andthatthing andthatotherone somemorestuff

containers/Dockerfile_Tips.md

209 / 801

Eviter de ré-installer les dépendances à chaque build

  • Problème classique de Dockerfile:

    "chaque fois que je change une ligne de code, toutes mes dépendances sont ré-installées!"

  • Solution: COPY-er les listes de dépendances (packages.json, requirements.txt, etc.) à part pour éviter de ré-installer des dépendances inchangées chaque fois.

containers/Dockerfile_Tips.md

210 / 801

Exemple de "mauvais" Dockerfile

Les dépendances sont ré-installées chaque fois, car le système de build ne sait pas si requirements.txt a été mis à jour.

FROM python
WORKDIR /src
COPY . .
RUN pip install -qr requirements.txt
EXPOSE 5000
CMD ["python", "app.py"]

containers/Dockerfile_Tips.md

211 / 801

Correction du Dockerfile

Ajouter les dépendances dans une étape à part permet à Docker un cache plus efficace, car il ne les installera que si requirements.txt change.

FROM python
COPY requirements.txt /tmp/requirements.txt
RUN pip install -qr /tmp/requirements.txt
WORKDIR /src
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]

containers/Dockerfile_Tips.md

212 / 801

Injecter les tests unitaires dans le processus de génération.

FROM <baseimage>
RUN <install dependencies>
COPY <code>
RUN <build code>
RUN <install test dependencies>
COPY <test data sets and fixtures>
RUN <unit tests>
FROM <baseimage>
RUN <install dependencies>
COPY <code>
RUN <build code>
CMD, EXPOSE ...
  • Le build échoue dès qu'une instruction échoue
  • Si RUN <unit tests> échoue, le build ne produira aucune image
  • S'il réussit, le build générera une image propre (sans librairie de test ni données)

containers/Dockerfile_Tips.md

213 / 801

Image separating from the next chapter

214 / 801

Exemples de Dockerfile

(automatically generated title slide)

215 / 801

Exemples de Dockerfile

Il y a quelques astuces, conseils et techniques qu'on peut appliquer dans nos Dockerfiles.

Mais parfois, on se doit de passer par des formes différentes, voire opposées, selon:

  • la complexité du projet,

  • le langage de programmation ou le framework choisi,

  • l'étape du projet (nouveau MVP vs prod super-stable),

  • si nous générons une image finale, ou une base pour d'autres images,

  • etc.

Nous allons montrer quelques exemples de techniques très différentes.

containers/Dockerfile_Tips.md

216 / 801

Quand optimiser une image

Au moment d'écrire des images officielles, c'est une bonne idée de réduire au maximum:

  • le nombre de couches,

  • la taille finale de l'image.

C'est souvent au détriment du temps de génération et du confort pour le mainteneur de l'image; mais quand une image est téléchargée des millions de fois, économiser ne serait-ce qu'une poignée de secondes de délai vaut le coup.

RUN apt-get update && apt-get install -y libpng12-dev libjpeg-dev && rm -rf /var/lib/apt/lists/* \
&& docker-php-ext-configure gd --with-png-dir=/usr --with-jpeg-dir=/usr \
&& docker-php-ext-install gd
...
RUN curl -o wordpress.tar.gz -SL https://wordpress.org/wordpress-${WORDPRESS_UPSTREAM_VERSION}.tar.gz \
&& echo "$WORDPRESS_SHA1 *wordpress.tar.gz" | sha1sum -c - \
&& tar -xzf wordpress.tar.gz -C /usr/src/ \
&& rm wordpress.tar.gz \
&& chown -R www-data:www-data /usr/src/wordpress

(Source: Image officielle Wordpress)

containers/Dockerfile_Tips.md

217 / 801

Quand ne pas optimiser une image

Parfois, il est préférable de prioriser le confort du mainteneur

En particulier, si:

  • l'image change beaucoup,

  • l'image a peu d'utilisateurs (par ex. 1 seul, le mainteneur!),

  • l'image est générée et lancée sur la même machine,

  • l'image est générée et lancée sur des machines sur un réseau très rapide...

Dans ces cas, mieux vaut garder les choses simples!

(Prochaine diapo: un Dockerfile qui peut être utilisé pour un aperçu de site Jekyll / github pages)

containers/Dockerfile_Tips.md

218 / 801
FROM debian:sid
RUN apt-get update -q
RUN apt-get install -yq build-essential make
RUN apt-get install -yq zlib1g-dev
RUN apt-get install -yq ruby ruby-dev
RUN apt-get install -yq python-pygments
RUN apt-get install -yq nodejs
RUN apt-get install -yq cmake
RUN gem install --no-rdoc --no-ri github-pages
COPY . /blog
WORKDIR /blog
VOLUME /blog/_site
EXPOSE 4000
CMD ["jekyll", "serve", "--host", "0.0.0.0", "--incremental"]

containers/Dockerfile_Tips.md

219 / 801

Système de version multi-dimensionnels

Un tag d'image peut indiquer une version de l'image.

Mais parfois, plusieurs composants importants co-existent, et nous devons indiquer les versions de chacun.

C'est possible en passant par des variables d'environnement:

ENV PIP=9.0.3 \
ZC_BUILDOUT=2.11.2 \
SETUPTOOLS=38.7.0 \
PLONE_MAJOR=5.1 \
PLONE_VERSION=5.1.0 \
PLONE_MD5=76dc6cfc1c749d763c32fff3a9870d8d

(Source: Image officielle Plone)

containers/Dockerfile_Tips.md

220 / 801

Entrypoints et démarreurs

Il est très courant de définir un entrypoint spécifique.

Ce point d'entrée est généralement un script, réalisant une série d'opérations telles que:

  • vérifications avant démarrage (si une dépendance obligatoire n'est pas disponible, afficher un message d'erreur sympa au lieu d'un obscur paquet de lignes dans un fichier log);

  • génération ou validation de fichier de configuration;

  • limiter les privilèges (avec par ex. su ou gosu, parfois combiné avec chown);

  • et plus encore.

containers/Dockerfile_Tips.md

221 / 801

Un script d'entrypoint typique

#!/bin/sh
set -e
# first arg is '-f' or '--some-option'
# or first arg is 'something.conf'
if [ "${1#-}" != "$1" ] || [ "${1%.conf}" != "$1" ]; then
set -- redis-server "$@"
fi
# allow the container to be started with '--user'
if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
chown -R redis .
exec su-exec redis "$0" "$@"
fi
exec "$@"

(Source: Image officielle Redis)

containers/Dockerfile_Tips.md

222 / 801

Factoriser les informations

Pour faciliter la maintenance (et éviter les erreurs humaines), évitez de répéter des informations comme:

  • numéros de versions,

  • URLs de ressources distantes (par ex. fichiers tarballs) ...

Pour ce faire, utilisez des variables d'environnement.

ENV NODE_VERSION 10.2.1
...
RUN ...
&& curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION.tar.xz" \
&& curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \
&& gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \
&& grep " node-v$NODE_VERSION.tar.xz\$" SHASUMS256.txt | sha256sum -c - \
&& tar -xf "node-v$NODE_VERSION.tar.xz" \
&& cd "node-v$NODE_VERSION" \
...

(Source: Image officielle Nodejs)

containers/Dockerfile_Tips.md

223 / 801

Surcharge

En théorie, les images de production et développement devraient être les mêmes.

En pratique, nous avons souvent besoin d'activer des comportements spécifiques en développement (par ex. trace de debogage).

Une façon de concilier les deux besoins est d'utiliser Compose pour activer ces comportements.

Jetons un oeil à l'appli de démo trainingwheels comme exemple.

containers/Dockerfile_Tips.md

224 / 801

Image de production

Le Dockerfile génère une image exploitant gunicorn:

FROM python
RUN pip install flask
RUN pip install gunicorn
RUN pip install redis
COPY . /src
WORKDIR /src
CMD gunicorn --bind 0.0.0.0:5000 --workers 10 counter:app
EXPOSE 5000

(Source: Dockerfile trainingwheels)

containers/Dockerfile_Tips.md

225 / 801

Fichier Compose de développement

Ce fichier Compose utilise la même image, mais avec quelques valeurs surchargées en développement:

  • On préfère le serveur Flask de développement (surcharge de CMD);

  • On définit la variable d'environnement DEBUG;

  • On utilise un volume pour fournir un processus de développement local plus rapide.

services:
www:
build: www
ports:
- 8000:5000
user: nobody
environment:
DEBUG: 1
command: python counter.py
volumes:
- ./www:/src

(Source: Fichier Compose trainingwheels)

containers/Dockerfile_Tips.md

226 / 801

Comment choisir quelles bonnes pratiques sont les meilleures?

  • Le but principal des conteneurs est de rendre notre vie meilleure;

  • Dans ce chapitre, nous avons montré bien des façons d'écrire des Dockerfiles;

  • Ces Dockerfiles utilisent parfois des techniques diamétralement opposées;

  • Et pourtant, c'était la "bonne" technique pour cette situation spécifique;

  • C'est bien (et souvent encouragé) de commencer simple et d'évoluer selon le besoin;

  • N'hésitez pas à revoir ce chapitre plus tard (après quelques Dockerfiles) pour inspiration!

containers/Dockerfile_Tips.md

227 / 801

Image separating from the next chapter

228 / 801

Dockerfiles avancés

(automatically generated title slide)

229 / 801

Dockerfiles avancés

construction

containers/Advanced_Dockerfiles.md

230 / 801

Objectifs

Nous avons vu des Dockerfiles simples pour illustrer comment Docker construit des images de conteneurs.

Dans cette section, nous allons voir d'autres commandes propres aux Dockerfiles.

containers/Advanced_Dockerfiles.md

231 / 801

Dockerfile, l'essentiel

  • Les instructions d'un Dockerfile sont exécutées dans l'ordre.

  • Chaque instruction ajoute une nouvelle couche à l'image (layer).

  • Docker gère un cache avec les layers des builds précédents.

  • Quand rien ne change dans les instructions ou les fichiers qui définissent un layer, le builder récupère la version en cache, sans exécuter l'instruction de ce layer.

  • L'instruction FROM DOIT être la première instruction (hormis les commentaires).

  • Les lignes débutant par # sont considérées comme des commentaires.

  • Quelques instructions (comme CMD et ENTRYPOINT) concernent les méta-données. (avec pour conséquence que chaque mention de ces instructions rend les précédentes obsolètes)

containers/Advanced_Dockerfiles.md

232 / 801

Instruction RUN

L'instruction RUN peut être utilisée de deux manières.

Via le shell wrapping, qui exécute la commande spécifiée dans un shell, avec /bin/sh -c, exemple:

RUN apt-get update

Ou via la méthode exec, qui évite l'expansion de chaine du shell, et permet l'exécution pour les images qui n'embarquent pas /bin/sh, i.e. :

RUN [ "apt-get", "update" ]

containers/Advanced_Dockerfiles.md

233 / 801

RUN plus en détail

RUN est utile pour:

  • Exécuter une commande.
  • Enregistrer les changements du système de fichiers.
  • Installer efficacement des bibliothèques, paquets et divers fichiers.

RUN n'est pas fait pour:

  • Enregistrer l'état des processus
  • Démarrer automatiquement un process en tache de fond (daemon).

Si vous voulez démarrer automatiquement un processus quand le container se lance, vous devriez passer par CMD et/ou ENTRYPOINT.

containers/Advanced_Dockerfiles.md

234 / 801

Fusion de layers:

Il est possible d'exécuter plusieurs commandes d'un seul coup:

RUN apt-get update && apt-get install -y wget && apt-get clean

Il est aussi possible de répartir une même commande sur plusieurs lignes:

RUN apt-get update \
&& apt-get install -y wget \
&& apt-get clean

containers/Advanced_Dockerfiles.md

235 / 801

Instruction EXPOSE

L'instruction EXPOSE indique à Docker quels ports doivent être publiés pour cette image.

EXPOSE 8080
EXPOSE 80 443
EXPOSE 53/tcp 53/udp
  • Tous les ports sont privés par défaut;

  • Déclarer un port avec EXPOSE ne suffit pas à le rendre public;

  • Le Dockerfile ne contrôle pas sur quel port un service sera exposé.

containers/Advanced_Dockerfiles.md

236 / 801

Exposer des ports

  • Quand vous lancez docker run -p <port> ..., ce port devient public;

    (Même s'il n'a pas été déclaré via EXPOSE.)

  • Quand vous lancez docker run -P...(sans numéro de port), tous les ports déclarés via EXPOSE deviennent publics.

Un port public est accessible depuis les autres containers et depuis l'extérieur de la machine hôte.

Un port privé n'est pas accessible depuis l'extérieur.

containers/Advanced_Dockerfiles.md

237 / 801

Instruction COPY

L'instruction COPY ajoute des fichiers et du contenu depuis votre machine hôte vers l'image.

COPY . /src

Cela va ajouter le contenu du build context (le dossier passé en argument à la commande docker build) au dossier /src dans l'image.

containers/Advanced_Dockerfiles.md

238 / 801

Isolation du build context

Note: vous pouvez manipuler uniquement les fichiers et dossier contenus dans le build context. Tout chemin absolu est traité comme ayant pour racine le build context, i.e que les 2 lignes suivantes sont équivalentes:

COPY . /src
COPY / /src

Toute tentative d'utiliser .. pour sortir du build context sera détectée et bloquée par Docker, et le build échouera.

Sans cela, un Dockerfile pourrait être valide sur une machine A, mais échouer sur une machine B.

containers/Advanced_Dockerfiles.md

239 / 801

Instruction ADD

ADD fonctionne presque comme COPY, mais avec quelques petits plus.

ADD peut récupérer des fichiers à distance:

ADD http://www.example.com/webapp.jar /opt/

Cette ligne irait télécharger le fichier webapp.jar pour le placer dans le dossier /opt.

ADD va automatiquement décompresser les fichiers zip et tar:

ADD ./assets.zip /var/www/htdocs/assets/

Cette ligne décompresse assets.zip pour copier son contenu dans /var/www/htdocs/assets.

Néanmoins, ADD ne décompressera pas automatiquement les fichiers téléchargés à distance.

containers/Advanced_Dockerfiles.md

240 / 801

ADD, COPY, et le cache de build

  • Avant d'ajouter un nouveau layer, Docker vérifie son cache de build.

  • Pour la plupart des instructions Dockerfile, Docker examine simplement le contenu du Dockerfile pour la vérification du cache.

  • Pour les instructions ADD et COPY, Docker vérifie aussi si les fichiers à ajouter à l'image ont été modifiés.

  • ADD doit toujours télécharger tout fichier distant avant de vérifier s'il a changé.

    (Il ne sait pas utiliser par ex. les en-têtes ETags ou If-Modified-Since)

containers/Advanced_Dockerfiles.md

241 / 801

Instruction VOLUME

L'instruction VOLUME indique à Docker qu'un dossier spécifique devrait être un volume.

VOLUME /var/lib/mysql

Les accès filesystem dans les volumes contournent la couche copy-on-write, offrant une performance native dans les I/O de ses dossiers.

Les volumes peuvent être attachés à plusieurs containers, permettant de "transporter" les données d'un container à un autre, dans le cas par ex. d'une mise à jour de base de données vers une version plus récente.

Il est possible de démarrer un container en mode "lecture seule". Le filesystem du container sera restreint en mode "lecture seule", mais tout volume restera en lecture/écriture si nécessaire.

containers/Advanced_Dockerfiles.md

242 / 801

Instruction WORKDIR

L'instruction WORKDIR change le dossier en cours pour les instructions suivantes.

Cela affecte aussi CMD et ENTRYPOINT, puisque cela modifie le dossier de démarrage quand un container se lance.

WORKDIR /src

Vous pouvez spécifier plusieurs WORKDIR pour changer de dossier au cours des différentes opérations.

containers/Advanced_Dockerfiles.md

243 / 801

Instruction ENV

L'instruction ENV déclare des variables d'environnement qui devraient être affectées dans tout container lancé depuis cette image.

ENV WEBAPP_PORT 8080

Ceci a pour résultat de créer une variable d'environnement dans tout container provenant de cette image.

WEBAPP_PORT=8080

Vous pouvez aussi spécifier des variables d'environnement via docker run.

$ docker run -e WEBAPP_PORT=8000 -e WEBAPP_HOST=www.example.com ...

containers/Advanced_Dockerfiles.md

244 / 801

Instruction USER

L'instruction USER change l'utilisateur ou l'UID à utiliser pour la suite des opérations, mais aussi l'utilisateur au lancement du container.

Comme WORKDIR, elle peut être utilisée plusieurs fois, par ex. pour repasser à root ou un autre utilisateur.

containers/Advanced_Dockerfiles.md

245 / 801

Instruction CMD

L'instruction CMD est la commande par défaut qui se lance quand un container est instancié à partir d'une image.

CMD [ "nginx", "-g", "daemon off;" ]

Ceci signifie que nous n'avons pas besoin de spécifier nginx -g "daemon off;" lors du lancement de notre container.

Au lieu de:

$ docker run <dockerhubUsername>/web_image nginx -g "daemon off;"

Nous pouvons juste écrire:

$ docker run <dockerhubUsername>/web_image

containers/Advanced_Dockerfiles.md

246 / 801

CMD plus en détail

Tout comme RUN, l'instruction CMD existe sous deux formes.

La première lance un shell:

CMD nginx -g "daemon off;"

La seconde s'exécute directement, sans passer par un shell:

CMD [ "nginx", "-g", "daemon off;" ]

containers/Advanced_Dockerfiles.md

247 / 801

Surcharger l'instruction CMD

CMD peut être forcé au lancement d'un container.

$ docker run -it <dockerhubUsername>/web_image bash

Ceci lancera bash au lieu de nginx -g "daemon off;".

containers/Advanced_Dockerfiles.md

248 / 801

Instruction ENTRYPOINT

L'instruction ENTRYPOINT ressemble à l'instruction CMD, sauf que les arguments passés en ligne de commande sont ajoutés au point d'entrée.

Note: vous devez utiliser pour cela la syntaxe "exec" (["..."]).

ENTRYPOINT [ "/bin/ls" ]

Avec ceci, si nous lançons la commande:

$ docker run training/ls -l

Au lieu d'essayer de lancer -l, le container va exécuter /bin/ls -l

containers/Advanced_Dockerfiles.md

249 / 801

Surcharger l'instruction the ENTRYPOINT

Le point d'entrée peut aussi être redéfini.

$ docker run -it training/ls
bin dev home lib64 mnt proc run srv tmp var
boot etc lib media opt root sbin sys usr
$ docker run -it --entrypoint bash training/ls
root@d902fb7b1fc7:/#

containers/Advanced_Dockerfiles.md

250 / 801

Comment CMD et ENTRYPOINT interagissent

Les instructions CMD et ENTRYPOINT fonctionnent mieux quand elles sont définies ensemble.

ENTRYPOINT [ "nginx" ]
CMD [ "-g", "daemon off;" ]

La ligne ENTRYPOINT spécifie la command à lancer et la ligne CMD spécifie ses options. En ligne de commande, nous pourrons donc potentiellement surcharger les options le cas échéant.

$ docker run -d <dockerhubUsername>/web_image -t

Cela surchargera les options CMD avec de nouvelles valeurs.

containers/Advanced_Dockerfiles.md

251 / 801

Instructions Dockerfile avancées

  • ONBUILD vous permet de cacher des commandes qui ne seront executées que quand cette image servira de base à une autre.
  • LABEL ajoute des meta-datas libres à l'image.
  • ARG déclare des variables de build (optionelles ou obligatoires).
  • STOPSIGNAL indique le signal à envoyer lors d'un docker stop (TERM par défault).
  • HEALTHCHECK définit une commande de test vérifiant le statut d'un container.
  • SHELL choisit le programme par défaut pour la forme string de RUN, CMD, etc.

containers/Advanced_Dockerfiles.md

252 / 801

Instruction ONBUILD

L'instruction ONBUILD est un déclencheur. Elle indique les commandes à exécuter quand une image se base sur l'image en cours de build.

Ceci est utile pour construire des images qui seront une base pour d'autres images.

ONBUILD COPY . /src
  • Vous ne pouvez pas chainer des instructions ONBUILD.
  • ONBUILD ne peut être utilisé pour déclencher des instructions FROM

containers/Advanced_Dockerfiles.md

253 / 801

Image separating from the next chapter

254 / 801

Bases du réseau pour conteneur

(automatically generated title slide)

255 / 801

Bases du réseau pour conteneur

A dense graph network

containers/Container_Networking_Basics.md

256 / 801

Objectifs

Nous allons maintenant lancer des services connectés (acceptant des requêtes) dans des conteneurs.

À la fin de cette section, vous serez capable de:

  • Lancer un service connecté dans un conteneur;

  • Manipuler les bases du réseau pour conteneur;

  • Trouver l'adresse IP d'un conteneur.

Nous expliquerons aussi les différents modèles de réseau usités par Docker.

containers/Container_Networking_Basics.md

257 / 801

Un serveur web simple, statique

Lancer l'image nginx du Docker Hub, qui contient un serveur web basique:

$ docker run -d -P nginx
66b1ce719198711292c8f34f84a7b68c3876cf9f67015e752b94e189d35a204e
  • Docker va télécharger l'image depuis le Docker Hub.

  • -d dit à Docker de lancer une image en tâche de fond.

  • -P dit à Docker de rendre ce service disponible depuis d'autres serveurs.
    (-P est la version courte de --publish-all)

Mais, comment on se connecte à notre serveur web maintenant?

containers/Container_Networking_Basics.md

258 / 801

Trouver le port de notre serveur web

Nous allons utiliser docker ps:

$ docker ps
CONTAINER ID IMAGE ... PORTS ...
e40ffb406c9e nginx ... 0.0.0.0:32768->80/tcp ...
  • Le serveur web tourne sur le port 80 à l'intérieur du conteneur.

  • Ce port correspond au port 32768 sur notre hôte Docker.

Nous expliquerons les pourquoi et comment de ce mappage.

Mais d'abord, assurons-nous que tout fonctionne correctement.

containers/Container_Networking_Basics.md

259 / 801

Connexion à notre serveur web (IHM)

Pointer votre navigateur à l'adresse IP de votre hôte Docker, sur le port affiché par docker ps, correspondant au port 80 du conteneur.

Screenshot

containers/Container_Networking_Basics.md

260 / 801

Connexion à notre serveur web (CLI)

Vous pouvez aussi utiliser curl directement depuis le hôte Docker.

Assurez-vous d'utiliser le bon numéro de port s'il est différent de notre exemple ci-dessous:

$ curl localhost:32768
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

containers/Container_Networking_Basics.md

261 / 801

Comment Docker sait quel port associer?

  • Il y a des meta-données dans l'image indiquant "cette image fait tourner quelque chose sur le port 80"

  • On peut examiner ces meta-donnéees avec docker inspect:

$ docker inspect --format '{{.Config.ExposedPorts}}' nginx
map[80/tcp:{}]
  • Cette méta-donnée a pour origine le Dockerfile, via le mot-clé EXPOSE.

  • On peut le constater avec docker history:

$ docker history nginx
IMAGE CREATED CREATED BY
7f70b30f2cc6 11 days ago /bin/sh -c #(nop) CMD ["nginx" "-g" "…
<missing> 11 days ago /bin/sh -c #(nop) STOPSIGNAL [SIGTERM]
<missing> 11 days ago /bin/sh -c #(nop) EXPOSE 80/tcp

containers/Container_Networking_Basics.md

262 / 801

Pourquoi le mappage de ports?

  • Nous n'avons plus d'adresses IPv4.

  • Les conteneurs ne peuvent pas avoir d'adresse IPv4 publiques.

  • Ils possèdent des adresses privées.

  • Les services doivent être exposés port par port.

  • Le mappage de ports est obligatoire pour éviter les conflits.

containers/Container_Networking_Basics.md

263 / 801

Trouver le port du serveur web via un script

Manipuler la sortie de docker ps serait fastidieux.

Il y a une commande pour nous aider:

$ docker port <containerID> 80
32768

containers/Container_Networking_Basics.md

264 / 801

Affectation manuelle des numéros de port

Si vous voulez allouer vous-même les numéros de port, aucun souci:

$ docker run -d -p 80:80 nginx
$ docker run -d -p 8000:80 nginx
$ docker run -d -p 8080:80 -p 8888:80 nginx
  • Trois serveurs web NGINX tournent.
  • Le premier est exposé sur le port 80.
  • Le deuxième est exposé sur le port 8000.
  • Le troisième est exposé sur les ports 8080 et 8888.

Note: la convention est port-du-hôte:port-du-conteneur.

containers/Container_Networking_Basics.md

265 / 801

Intégrer les conteneurs dans votre infrastructure

On peut intégrer les conteneurs au réseau de bien des manières.

  • Démarrer le conteneur, pour laisser Docker lui allouer un port public.
    Puis lire le port affecté et l'injecter dans votre configuration.

  • Choisir un numéro de port à l'avance, au moment de générer votre configuration.
    Puis démarrer votre conteneur en forçant les ports à la main.

  • Utiliser un plugin de réseau, pour brancher vos conteneurs sur des VLANs, tunnels, etc.

  • Activer le Mode Swarm pour un déploiement à travers un cluster.
    Le conteneur sera accessible depuis n'importe quel noeud du cluster.

En utilisant Docker à travers une couche de gestion supplémentaire comme Mesos ou Kubernetes, ils fournissent en général leurs propres mécanismes d'exposition de conteneurs.

containers/Container_Networking_Basics.md

266 / 801

Trouver l'adresse IP du conteneur

Nous pouvons utiliser la commande docker inspect pour trouver l'adresse IP de notre conteneur.

$ docker inspect --format '{{ .NetworkSettings.IPAddress }}' <yourContainerID>
172.17.0.3
  • docker inspect est une commande avancée, qui peut retourner une tonne d'informations à propos des conteneurs.

  • Ici, nous lui fournissons une chaîne pour extraire exactement l'adresse IP du conteneur.

containers/Container_Networking_Basics.md

267 / 801

Interroger notre conteneur

Nous pouvons tester la connectivité du conteneur via l'adresse IP déterminée précédemment. Voyons ceci avec l'outil ping.

$ ping <ipAddress>
64 bytes from <ipAddress>: icmp_req=1 ttl=64 time=0.085 ms
64 bytes from <ipAddress>: icmp_req=2 ttl=64 time=0.085 ms
64 bytes from <ipAddress>: icmp_req=3 ttl=64 time=0.085 ms

containers/Container_Networking_Basics.md

268 / 801

Résumé du chapitre

Nous avons appris comment:

  • Exposer un port sur le réseau;

  • Manipuler les bases du réseau pour conteneur;

  • Trouver une adresse IP de conteneur.

Dans le chapitre suivant, nous verrons comment connecter les conteneurs entre eux, sans publier leurs ports.

containers/Container_Networking_Basics.md

269 / 801

Image separating from the next chapter

270 / 801

Pilote réseau pour conteneur

(automatically generated title slide)

271 / 801

Pilote réseau pour conteneur

Le Docker Engine prend en charge de nombreux pilotes réseau.

Certains pilotes sont inclus à l'installation:

  • bridge (par défaut)

  • none

  • host

  • container

Le pilote est indiqué avec docker run --net ....

Les différents pilotes sont expliqués en détail dans les diapos suivantes.

containers/Network_Drivers.md

272 / 801

La passerelle par défaut (bridge)

  • Par défaut, le conteneur dispose d'une interface eth0 virtuelle.
    (En supplément de lo, sa propre interface de boucle interne).

  • Cette interface est fournie par une paire veth.

  • Elle est connectée au Docker bridge.
    (Appelé docker0 par défaut; configurable avec --bridge.)

  • L'allocation d'adresses IP se fait sur un sous-réseau privé interne.
    (Docker utilise 172.17.0.0/16 par défaut; configurable avec --bip.)

  • Le trafic sortant passe à travers une règle iptables MASQUERADE.

  • Le trafic entrant passe à travers une règle iptables DNAT.

  • Le conteneur peut avoir ses propres routes, règles iptables, etc.

containers/Network_Drivers.md

273 / 801

Le pilote null

  • On démarre le conteneur avec docker run --net none ...

  • Il n'aura que l'interface de bouclage lo. Pas de eth0.

  • Il ne peut ni recevoir ni envoyer de trafic réseau.

  • Utile pour les logiciels isolés/suspects.

containers/Network_Drivers.md

274 / 801

Le pilote hôte

  • On démarre le conteneur avec docker run --net host ...

  • Il voit (et peut accéder) aux interfaces réseau de l'hôte.

  • Il peut ouvrir n'importe quelle interface et port (pour le meilleur et pour le pire).

  • Le trafic réseau se passe des couches NAT, bridge ou veth.

  • Performance = native!

Cas d'usage:

  • Applications sensibles à la performance (VOIP, jeu-vidéo, streaming...)

  • découvertes d'homologue (par ex. mappage de port Erlang, Raft, Serf ...)

containers/Network_Drivers.md

275 / 801

Le pilote conteneur

  • On démarre le conteneur avec docker run --net container:id ...

  • Il recycle la pile réseau d'un autre conteneur.

  • Il partage avec l'autre conteneur les mêmes interfaces, adresses IP, routes, règles iptables, etc.

  • Ces conteneurs peuvent communiquer à travers leur interface lo.
    (i.e. l'un peut s'attacher à 127.0.0.1 et les autres peuvent s'y connecter.)

containers/Network_Drivers.md

276 / 801

Image separating from the next chapter

277 / 801

Le Container Network Model

(automatically generated title slide)

278 / 801

Le Container Network Model

A denser graph network

containers/Container_Network_Model.md

279 / 801

Objectifs

Nous aborderons le CNM (Modèle de Réseau pour Container)

A la fin de la leçon, vous serez capable de:

  • Créer un réseau privé pour un groupe de containers;

  • Utiliser le nommage de container pour connecter les services ensemble;

  • Connecter et déconnecter dynamiquement des containers à des réseaux;

  • Affecter l'adresse IP à un container.

Nous expliquerons aussi le principe des réseaux overlay et des plugins de réseau.

containers/Container_Network_Model.md

280 / 801

Le Container Network Model

Le CNM a été introduit dans Engine 1.9.0 (Novembre 2015).

Le CNM ajoute la notion de network, et une commande principale pour manipuler et inspecter ces réseaux: docker network.

$ docker network ls
NETWORK ID NAME DRIVER
6bde79dfcf70 bridge bridge
8d9c78725538 none null
eb0eeab782f4 host host
4c1ff84d6d3f blog-dev overlay
228a4355d548 blog-prod overlay

containers/Container_Network_Model.md

281 / 801

Qu'est-ce qu'il y a dans un réseau?

  • Dans le concept, un réseau est un switch virtuel;

  • Il peut être local (dans un Engine simple) ou global (transversal à plusieurs hôtes);

  • Un réseau possède un sous-réseau IP associé;

  • Docker va affecter de nouvelles adresses IP aux containers connectés à ce réseau;

  • Des containers peuvent être connectés à plusieurs réseaux;

  • Des containers peuvent se voir affecté des noms et alias par réseau;

  • Les noms et alias sont résolus via un serveur DNS embarqué.

containers/Container_Network_Model.md

282 / 801

Détails d'implémentation de réseau

  • Un réseau est géré par un driver.

  • Les drivers inclus par défaut:

    • bridge (par défaut)
    • none
    • host
    • macvlan
  • Un driver multi-hôte, overlay, est inclus sans installation supplémentaire (pour les clusters Swarm).

  • Des drivers supplémentaires sont disponibles sous forme de plugins (OVS, VLAN, etc)

  • Un réseau peut avoir son propre IPAM (allocation d'IP)

containers/Container_Network_Model.md

283 / 801

Différences avec le CNI

  • CNI = Container Network Interface

  • CNI est utilisé en particulier par Kubernetes

  • Dans CNI, toutes les nodes et containers sont sur un seul et même réseau IP

  • CNI et CNM offrent les mêmes fonctions, mais via des méthodes très différentes

containers/Container_Network_Model.md

284 / 801

Container simple dans un réseau Docker

bridge0

containers/Container_Network_Model.md

285 / 801

Deux containers sur un seul réseau Docker

bridge2

containers/Container_Network_Model.md

286 / 801

Deux containers sur deux réseaux Docker

bridge3

containers/Container_Network_Model.md

287 / 801

Créer un réseau

Essayons de déclarer un nouveau réseau appelé dev.

$ docker network create dev
4c1ff84d6d3f1733d3e233ee039cac276f425a9d5228a4355d54878293a889ba

Le réseau est maintenant visible avec la commande network ls;

$ docker network ls
NETWORK ID NAME DRIVER
6bde79dfcf70 bridge bridge
8d9c78725538 none null
eb0eeab782f4 host host
4c1ff84d6d3f dev bridge

containers/Container_Network_Model.md

288 / 801

Placer des containers sur un réseau

Nous allons créer un container nommé sur ce réseau.

Il sera disponible via son nom, es.

$ docker run -d --name es --net dev elasticsearch:2
8abb80e229ce8926c7223beb69699f5f34d6f1d438bfc5682db893e798046863

containers/Container_Network_Model.md

289 / 801

Communication entre containers

Et maintenant, ajoutons un autre container sur ce réseau.

$ docker run -ti --net dev alpine sh
root@0ecccdfa45ef:/#

Depuis ce nouveau container, nous pouvons résoudre et ping l'autre, en utilisant son nom:

/ # ping es
PING es (172.18.0.2) 56(84) bytes of data.
64 bytes from es.dev (172.18.0.2): icmp_seq=1 ttl=64 time=0.221 ms
64 bytes from es.dev (172.18.0.2): icmp_seq=2 ttl=64 time=0.114 ms
64 bytes from es.dev (172.18.0.2): icmp_seq=3 ttl=64 time=0.114 ms
^C
--- es ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2000ms
rtt min/avg/max/mdev = 0.114/0.149/0.221/0.052 ms
root@0ecccdfa45ef:/#

containers/Container_Network_Model.md

290 / 801

Résoudre des adresses de container

Dans le Docker Engine 1.9, la résolution de nom est implémentée avec /etc/hosts, et mise à jour chaque fois que les containers sont ajoutés/supprimés.

[root@0ecccdfa45ef /]# cat /etc/hosts
172.18.0.3 0ecccdfa45ef
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.18.0.2 es
172.18.0.2 es.dev

Dans le Docker Engine 1.10, ceci a été remplacé par une résolution dynamique.

(Cela résoud les race conditions lors de la mise à jour de /etc/hosts)

containers/Container_Network_Model.md

291 / 801

Image separating from the next chapter

292 / 801

Service discovery avec les containers

(automatically generated title slide)

293 / 801

Service discovery avec les containers

  • Essayons de lancer une application reposant sur deux containers;

  • Le premier container est un serveur web;

  • L'autre est une base de données redis;

  • Nous les placerons tous deux sur le réseau dev créé auparavant;

containers/Container_Network_Model.md

294 / 801

Lancer le serveur web

  • L'application est fournie par l'image jpetazzo/trainingwheels.

  • Nous en savons peu sur elle, donc nous la lançons et on verra ce qui arrivera!

Démarrer le container, en publiant tous ses ports:

$ docker run --net dev -d -P jpetazzo/trainingwheels

Vérifier quel port lui a été alloué:

$ docker ps -l

containers/Container_Network_Model.md

295 / 801

Tester le serveur web

  • Si nous ouvrons l'application à ce stage, nous verrons une page d'erreur:

Trainingwheels error

  • C'est parce que le service Redis n'est pas lancé.

  • Ce container essaie de résoudre le nom redis.

Note: nous n'utilisons pas de FQDN ou une adresse IP ici; juste redis.

containers/Container_Network_Model.md

296 / 801

Démarrer la base de données

  • Nous devons démarrer un container Redis.

  • Ce container doit être sur le même réseau que le serveur web.

  • Il doit porter le nom correct (redis) pour que l'application le trouve.

Démarrer le container;

$ docker run --net dev --name redis -d redis

containers/Container_Network_Model.md

297 / 801

Tester à nouveau le serveur web

  • Si nous ouvrons l'application à présent, nous devrions voir que l'appli fonctionne correctement:

Trainingwheels OK

  • Quand l'appli essaie de résoudre redis, au lieu d'avoir une erreur DNS, on récupère l'adresse IP de notre container Redis.

containers/Container_Network_Model.md

298 / 801

A propos du scope

  • Et si nous voulions lancer plusieurs clones de notre application?

  • Puisque les noms sont uniques, il ne peut y avoir qu'un seul container nommé redis.

  • Toutefois, nous pouvons forcer un nom de réseau de notre container avec --net-alias.

  • --net-alias a une portée par réseau, et indépendant du nom de container d'origine.

containers/Container_Network_Model.md

299 / 801

Utiliser un alias de réseau au lieu d'un nom

Supprimons le container redis:

$ docker rm -f redis

Et ajoutons un nouveau qui ne bloque pas le nom redis:

$ docker run --net dev --net-alias redis -d redis

Vérifier que l'appli fonctionne toujours (mais le compteur est revenu à 1, car on a dégagé l'ancien container Redis).

containers/Container_Network_Model.md

300 / 801

Tout nom est spécifique à un seul réseau

Essayons de ping notre container es depuis un autre container, dans le cas où l'autre container n'est pas sur le réseau dev

$ docker run --rm alpine ping es
ping: bad address 'es'

Un nom est résolu uniquement quand les containers sont sur le même réseau.

Les containers peuvent se contacter les uns les autres seulement quand ils sont sur le même réseau (vous pouvez essayer de ping avec l'adresse IP pour vérifier).

containers/Container_Network_Model.md

301 / 801

Alias de réseau

Nous aimerions avoir un autre réseau, prod avec son propre container es. Mais il ne peut y avoir qu'un seul container nommé es!

Nous utiliserons les alias de réseau.

Un container peut avoir plusieurs alias de réseau.

Les alias de réseau sont locaux à un réseau donné (qui existent juste sur ce réseau).

Plusieurs containers peuvent avoir le même alias de réseau (y compris sur le même réseau). Dans Docker Engine 1.11, la résolution d'un alias de réseau renvoie l'adresse IP de tous les containers disposant de cet alias.

containers/Container_Network_Model.md

302 / 801

Créer des containers sur un autre réseau

Créez un réseau prod.

$ docker network create prod
5a41562fecf2d8f115bedc16865f7336232a04268bdf2bd816aecca01b68d50c

Nous pouvons maintenant créer plusieurs containers avec un alias es sur le nouveau réseau prod.

$ docker run -d --name prod-es-1 --net-alias es --net prod elasticsearch:2
38079d21caf0c5533a391700d9e9e920724e89200083df73211081c8a356d771
$ docker run -d --name prod-es-2 --net-alias es --net prod elasticsearch:2
1820087a9c600f43159688050dcc164c298183e1d2e62d5694fd46b10ac3bc3d

containers/Container_Network_Model.md

303 / 801

Résoudre les alias de réseau

Essayons la résolution DNS, en utilisant l'outil nslookup livré dans l'image alpine.

$ docker run --net prod --rm alpine nslookup es
Name: es
Address 1: 172.23.0.3 prod-es-2.prod
Address 2: 172.23.0.2 prod-es-1.prod

(On peut ignorer les erreurs can't resolve '(null)')

containers/Container_Network_Model.md

304 / 801

Se connecter aux containers avec alias

Chaque instance ElasticSearch a un nom (généré au démarrage). Ce nom est visible quand on lance une simple requête HTTP sur le point d'accès de l'API ElasticSearch.

Essayons de lancer la commande suivante plusieurs fois:

$ docker run --rm --net dev centos curl -s es:9200
{
"name" : "Tarot",
...
}

Puis essayons-la à nouveau plusieurs fois en remplaçant --net dev par --net prod:

$ docker run --rm --net prod centos curl -s es:9200
{
"name" : "The Symbiote",
...
}

containers/Container_Network_Model.md

305 / 801

Bon à savoir...

  • Docker ne peut créer des noms de réseau et alias sur le réseau par défaut bridge.

  • Sachant ceci, pour utiliser ces fonctions, vous devez créer un réseau spécifique d'abord.

  • Les alias de réseau ne sont pas uniques au sein d'un réseau donné.

  • i.e plusieurs containers peuvent porter le même alias sur le même réseau.

  • Dans ce scénario, le serveur DNS Docker retournera plusieurs enregistrements.
    (i.e, vous aurez un "DNS round robin" prêt à l'emploi)

  • Activer le Mode Swarm donne accès au traitement distribué (clustering) et la répartition de charge (load balancing) via IPVS.

  • Créer les réseaux et les alias de réseau est en général automatisé par des outils comme Compose.

containers/Container_Network_Model.md

306 / 801

Quelques mots à propos du DNS round robin

Ne comptez pas exclusivement sur le DNS round robin pour de la répartition de charge.

Plusieurs facteurs peuvent affecter la réolution DNS, et vous pourriez avoir:

  • tout le trafic dirigé vers une seule instance;
  • le trafic réparti inégalement entre quelques instances;
  • comportement différent selon le langage de votre application;
  • comportement différent selon votre distribution de base;
  • comportement différent selon d'autres facteurs (sic).

Aucun problème à utiliser le DNS pour explorer les points d'accès disponibles, mais prenez bien soin de les re-résoudre de temps à autre pour trouver les nouveaux points d'accès.

containers/Container_Network_Model.md

307 / 801

Réseaux spécifiques

Lors de la création de réseaux, plusieurs options peuvent être fournies:

When creating a network, extra options can be provided.

  • --internal désactive tout trafic sortant (le réseau n'aura pas de passerelle par défaut).

  • --gateway indique quelle adresse utiliser pour la passerelle (quand le trafic sortant est autorisé).

  • --subnet (en notation CIDR) indique le sous-réseau à utiliser.

  • --ip-range (en notation CIDR) indique le sous-réseau pour l'allocation.

  • --aux-address permet de spécifier une liste d'adresse réservées (qui ne seront jamais affectées aux containers).

containers/Container_Network_Model.md

308 / 801

Choisir l'adresse IP des containers

  • Il est possible de forcer l'addrese IP du container avec --ip.
  • L'adresse IP doit respecter le sous-réseau utilisé par le container

Voici ci-dessous un exemple complet.

$ docker network create --subnet 10.66.0.0/16 pubnet
42fb16ec412383db6289a3e39c3c0224f395d7f85bcb1859b279e7a564d4e135
$ docker run --net pubnet --ip 10.66.66.66 -d nginx
b2887adeb5578a01fd9c55c435cad56bbbe802350711d2743691f95743680b09

Note: ne forcez pas d'adresse IP explicite de container dans votre code!

Je répète: ne forcez pas d'adresse IP de container dans votre code!

containers/Container_Network_Model.md

309 / 801

Réseaux superposés (overlay)

  • Les caractéristiques vues jusqu'ici fonctionnent uniquement quand les containers sont sur un seul hôte.

  • Si les containers sont répartis sur plusieurs hôtes, nous aurons besoin d'un réseau overlay pour les connecter ensemble.

  • Docker est livré avec un plugin de réseau par défaut, overlay, qui implémente un réseau superposé exploitant le concept de VXLAN, qui s'active via le Mode Swarm.

  • D'autres plugins (Weave, Calico...) peuvent aussi fournir des réseaux superposés.

  • Une fois que vous avez un réseau superposé, toutes les fonctions utilisées dans ce chapitre fonctionnent de la même manière à travers plusieurs hôtes.

containers/Container_Network_Model.md

310 / 801

Réseau multi-hôtes (overlay)

Hors-sujet pour cet atelier d'introduction!

Instructions très rapides:

  • activer le Mode Swarm (docker swarm init puis docker swarm join sur les autres noeuds)
  • docker network create mynet --driver overlay
  • docker service create --network mynet myimage

Pour en savoir plus sur le mode Swarm, jetez un oeil à cette vidéo ou ces diapos.

containers/Container_Network_Model.md

311 / 801

Réseau multi-hôtes (plugins)

Hors-sujet pour cet atelier d'introduction!

Idée générale:

  • installer le plugin (souvent livré dans des containers)

  • lancer le plugin ( si c'est dans un container, il y a souvent besoin de paramètres supplémentaires; n'allez pas docker run à l'aveugle!)

  • certains plugins exigent une configuration ou une activation (en créant un fichier spécial qui dit à Docker "utilise le plugin dont la socket est au chemin suivant)

  • vous pouvez ensuite docker network create --driver pluginname

containers/Container_Network_Model.md

312 / 801

Connexion et déconnexion dynamique

  • Jusqu'ici, nous avons choisi quel réseau utiliser au démarrage du container.

  • Le Docker Engine permet aussi la connexion/déconnexion pendant que le container tourne.

  • Cette fonction est exposée via l'API Docker, et à travers deux commandes:

    • docker network connect <network> <container>

    • docker network disconnect <network> <container>

containers/Container_Network_Model.md

313 / 801

Connexion dynamique à un réseau

  • Nous avons un container nommé es connecté à un réseau nommé dev.

  • Démarrons un simple container alpine sur le réseau par défaut:

    $ docker run -ti alpine sh
    / #
  • Dans ce container, essayons de ping le container es:

    / # ping es
    ping: bad address 'es'

Cela ne fonctionne pas, mais nous allons corriger cela en connectant le container.

containers/Container_Network_Model.md

314 / 801

Trouver l'ID du container et le connecter

  • Extraire l'ID de notre container alpine; voici deux méthodes:

    • jeter un oeil à /etc/hostname dans le container,

    • exécuter sur le hôte docker ps -lq.

  • Lancer la commande suivant sur l'hôte:

    $ docker network connect dev <container_id>

containers/Container_Network_Model.md

315 / 801

Vérifier nos actions

  • Essayez encore ping es depuis le container.

  • Cela devrait fonctionner correctement normalement:

    / # ping es
    PING es (172.20.0.3): 56 data bytes
    64 bytes from 172.20.0.3: seq=0 ttl=64 time=0.376 ms
    64 bytes from 172.20.0.3: seq=1 ttl=64 time=0.130 ms
    ^C
  • Stoppez-le avec Ctrl-C.

containers/Container_Network_Model.md

316 / 801

Examen du réseau dans le container

Nous pouvons lister les interfaces réseau avec ifconfig, ip a, ou ip l:

/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
18: eth0@if19: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
20: eth1@if21: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:14:00:04 brd ff:ff:ff:ff:ff:ff
inet 172.20.0.4/16 brd 172.20.255.255 scope global eth1
valid_lft forever preferred_lft forever
/ #

Chaque connexion réseau est matérialisée via une interface réseau virtuelle.

Comme nous pouvons le voir, nous pouvons être connectés à plusieurs réseaux en même temps.

containers/Container_Network_Model.md

317 / 801

Se déconnecter d'un réseau

  • Essayons ce que donne la commande symétrique pour déconnecter le container:

    $ docker network disconnect dev <container_id>
  • A partir de maintenant, si on cherche à ping es, ce ne sera pas résolu:

    / # ping es
    ping: bad address 'es'
  • Si on essaie de ping l'adresse IP directement, cela ne fonctionne plus non plus:

    / # ping 172.20.0.3
    ... (rien ne se passe jusqu'à ce qu'on tape Ctrl-C)

containers/Container_Network_Model.md

318 / 801

Visibilité des alias de réseau par réseau

  • Chaque réseau possède sa propre liste d'alias réseau.

  • Comme vu précédemment: es est résolu avec différentes adresses selon les réseaux dev et prod.

  • Si nous sommes connectés à plusieurs réseaux, la résolution passe les noms en revue dans chaque réseau (dans Docker Engine 18.03, par ordre de connexion), et arrête dès que le nom a été trouvé.

  • Par conséquent, en étant connecté aux réseaux dev et prod, la résolution de es ne nous donnera pas tous les noms des services es, mais seulement ceux dans dev ou prod.

  • Toutefois, on peut interroger es.dev ou es.prod si on a besoin.

containers/Container_Network_Model.md

319 / 801

En apprendre plus sur nos réseaux et noms

  • Nous pouvons lancer des requêtes DNS inverses sur les adresses IP des containers.

  • Si l'adresse IP appartient à un réseau (autre que le bridge par défaut), le résultat sera:

    nom-du-premier-alias-ou-id-container.nom-reseau
  • Exemple:

$ docker run -ti --net prod --net-alias hello alpine
/ # apk add --no-cache drill
...
OK: 5 MiB in 13 packages
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:AC:15:00:03
inet addr:172.21.0.3 Bcast:172.21.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
...
/ # drill -t ptr 3.0.21.172.in-addr.arpa
...
;; ANSWER SECTION:
3.0.21.172.in-addr.arpa. 600 IN PTR hello.prod.
...

containers/Container_Network_Model.md

320 / 801

Générer une image dans un réseau spécifique

  • On peut construire un Dockerfile avec un réseau spécial via docker build --network NAME.

  • Ça peut servir à garantir qu'un build n'accède pas au réseau.

    (Gardez à l'esprit que la plupart des Dockerfiles vont échouer,
    car ils auront besoin d'installer des paquets à distance et leurs dépendances!)

  • Cela servira à accéder à un dépôt interne privé.

    (Mais essayez si possible d'utiliser un build multi-stage!)

containers/Container_Network_Model.md

321 / 801

Image separating from the next chapter

322 / 801

Processus de développement local avec Docker

(automatically generated title slide)

323 / 801

Processus de développement local avec Docker

Construction site

containers/Local_Development_Workflow.md

324 / 801

Objectifs

A la fin de cette section, vous serez capable de:

  • Partager du code entre conteneur et hôte.

  • Utiliser un processus de développement local simple.

containers/Local_Development_Workflow.md

325 / 801

Développement local dans un conteneur

On veut résoudre les problèmes suivants:

  • "Ça marche sur ma machine"

  • "Pas la même version"

  • "Manque une dépendance"

En utilisant les conteneurs Docker, on arrivera à un environnement de développement homogène.

containers/Local_Development_Workflow.md

326 / 801

Travailler à l'application "namer"

  • Nous avons à travailler sur une application dont le code est sur:

    https://github.com/jpetazzo/namer.

  • De quoi s'agit-il? On ne le sait pas encore!

  • Récupérons le code.

$ git clone https://github.com/jpetazzo/namer

containers/Local_Development_Workflow.md

327 / 801

Examiner le code

$ cd namer
$ ls -1
company_name_generator.rb
config.ru
docker-compose.yml
Dockerfile
Gemfile
328 / 801

Examiner le code

$ cd namer
$ ls -1
company_name_generator.rb
config.ru
docker-compose.yml
Dockerfile
Gemfile

Aha, un Gemfile! C'est du Ruby. Probablement. On s'en doute. A moins que?

containers/Local_Development_Workflow.md

329 / 801

Examiner le Dockerfile

FROM ruby
COPY . /src
WORKDIR /src
RUN bundler install
CMD ["rackup", "--host", "0.0.0.0"]
EXPOSE 9292
  • Cette appli utilise l'image de base ruby.
  • Le code est copié dans /src.
  • Les dépendances sont installées avec bundler.
  • L'application est lancée via rackup.
  • Elle écoute sur le port 9292.

containers/Local_Development_Workflow.md

330 / 801

Générer et lancer l'application "namer"

  • Générons l'application grâce au Dockerfile!
331 / 801

Générer et lancer l'application "namer"

  • Générons l'application grâce au Dockerfile!
$ docker build -t namer .
332 / 801

Générer et lancer l'application "namer"

  • Générons l'application grâce au Dockerfile!
$ docker build -t namer .
  • Et maintenant lancez-là. on doit publier ses ports.
333 / 801

Générer et lancer l'application "namer"

  • Générons l'application grâce au Dockerfile!
$ docker build -t namer .
  • Et maintenant lancez-là. on doit publier ses ports.
$ docker run -dP namer
334 / 801

Générer et lancer l'application "namer"

  • Générons l'application grâce au Dockerfile!
$ docker build -t namer .
  • Et maintenant lancez-là. on doit publier ses ports.
$ docker run -dP namer
  • Vérifiez sur quel port le conteneur écoute.
335 / 801

Générer et lancer l'application "namer"

  • Générons l'application grâce au Dockerfile!
$ docker build -t namer .
  • Et maintenant lancez-là. on doit publier ses ports.
$ docker run -dP namer
  • Vérifiez sur quel port le conteneur écoute.
$ docker ps -l

containers/Local_Development_Workflow.md

336 / 801

Accéder à notre application

  • Pointez le navigateur sur le serveur Docker, et sur le port alloué au conteneur.
337 / 801

Accéder à notre application

  • Pointez le navigateur sur le serveur Docker, et sur le port alloué au conteneur.

  • Cliquez "Recharger" plusieurs fois.

338 / 801

Accéder à notre application

  • Pointez le navigateur sur le serveur Docker, et sur le port alloué au conteneur.

  • Cliquez "Recharger" plusieurs fois.

  • C'est un générateur de nom d'entreprise de première classe, certifié ISO, niveau opérateur de réseau!

    (Avec 50% de plus de baratin que la moyenne de la compétition!)

    (Attends, c'était 50% de plus, ou 50% de moins? Qu'importe!)

    web application 1

containers/Local_Development_Workflow.md

339 / 801

Amender le code

Option 1:

  • Modifier le code en local
  • Re-générer une image
  • Relancer un conteneur

Option 2:

  • S'introduire dans le conteneur (avec docker exec)
  • Installer un éditeur
  • Changer le code depuis l'intérieur du conteneur

Option 3:

  • Utiliser un volume pour monter les fichiers locaux dans le conteneur
  • Opérer les changements en local
  • Constater les changements dans le conteneur

containers/Local_Development_Workflow.md

340 / 801

Notre premier volume

On va indiquer à Docker de monter le dossier en cours sur /src dans le conteneur.

$ docker run -d -v $(pwd):/src -P namer
  • -d: le conteneur doit tourner en mode détaché (en arrière-plan).

  • -v: le dossier du hôte mentionné doit être monté à l'intérieur du conteneur.

  • -P: tous les ports exposés par cette image doivent être publiés.

  • namer est le nom de l'image à exécuter.

  • Nous n'ajoutons pas la commande à lancer car elle est déjà dans le Dockerfile avec CMD.

Note: sur Windows, remplacer $(pwd)par %cd% (ou ${pwd} avec PowerShell).

containers/Local_Development_Workflow.md

341 / 801

Monter les volumes dans des conteneurs

L'option -v monte un dossier depuis votre hôte dans le conteneur Docker.

La structure de l'option est:

[chemin-hote]:[chemin-conteneur]:[rw|ro]
  • Si [chemin-hote] ou [chemin-conteneur] n'existe pas, il sera créé.

  • Vous pouvez contrôler l'option d'écriture du volume avec les options ro et rw.

  • Si vous ne spécifiez ni rw ou ro, ce sera rw par défaut.

Il y aura un chapitre complet sur les volumes!

containers/Local_Development_Workflow.md

342 / 801

Tester le conteneur de développement

  • Trouvez le port utilisé par notre nouveau conteneur.
$ docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
045885b68bc5 namer rackup 3 seconds ago Up ... 0.0.0.0:32770->9292/tcp ...
  • Ouvrez l'application sur votre navigateur web.

containers/Local_Development_Workflow.md

343 / 801

Opérer un changement dans notre application

Notre client n'aime pas du tout la couleur de notre texte. Allons la changer.

$ vi company_name_generator.rb

Et changeons:

color: royalblue;

En:

color: red;

containers/Local_Development_Workflow.md

344 / 801

Tester nos changements

  • Recharger l'application dans notre navigateur
345 / 801

Tester nos changements

  • Recharger l'application dans notre navigateur

  • La couleur doit avoir changé.

    web application 2

containers/Local_Development_Workflow.md

346 / 801

Comprendre les volumes

  • Aucune copie ou synchronisation de fichiers entre hôte et conteneur ne se passe dans un volume.

  • Les volumes sont des bind mounts: un mécanisme du noyau associant un chemin à un autre.

  • Un bind mount est une sorte de lien symbolique, mais à un niveau très différent.

  • Tout changement sur l'hôte ou le conteneur sera visible de l'autre côté.

    (Puisque sous le capot, c'est le même fichier de toute façon.)

containers/Local_Development_Workflow.md

347 / 801

Jetez vos serveurs et brûlez votre code

(C'est le titre d'un billet de blog de 2013 par Chad Fowler, expliquant le concept d'infrastructure immuable.)

348 / 801

Jetez vos serveurs et brûlez votre code

(C'est le titre d'un billet de blog de 2013 par Chad Fowler, expliquant le concept d'infrastructure immuable.)

  • Mettons un grand bazar dans notre conteneur.

    (Supprimer des fichiers ou autre.)

  • Maintenant, comment réparer ça?

349 / 801

Jetez vos serveurs et brûlez votre code

(C'est le titre d'un billet de blog de 2013 par Chad Fowler, expliquant le concept d'infrastructure immuable.)

  • Mettons un grand bazar dans notre conteneur.

    (Supprimer des fichiers ou autre.)

  • Maintenant, comment réparer ça?

  • Notre vieux conteneur (avec la version bleue du code) tourne toujours.

  • Voyons sur quel port c'est exposé:

    docker ps
  • Pointez le navigateur dessus pour confirmer que ça marche toujours bien.

containers/Local_Development_Workflow.md

350 / 801

Infrastructure immuable en deux mots

  • Au lieu de modifier le serveur, nous en déployons un nouveau.

  • Cela peut sembler un défi pour les serveurs classiques, mais c'est trivial avec les conteneurs.

  • En fait, avec Docker, le processus le plus logique est de générer une nouvelle image et de la lancer.

  • Si quoique ce soit cloche avec la nouvelle image, on peut toujours relancer l'ancienne.

  • On peut même garder les deux versions côte-à-côte.

  • Si ce motif vous semble intéressant, vous pouvez regarder du côté des déploiements blue/green ou canary

containers/Local_Development_Workflow.md

351 / 801

Récap' du process de développement

  1. Ecrire un Dockerfile pour générer une image contenant l'environnement de développement.
    (Rails, Django, ... et toutes les dépendances de notre appli)

  2. Démarrer un conteneur de cette image.
    Utiliser l'option -v pour monter notre code source dans le conteneur.

  3. Modifier le code source hors des conteneurs, avec les outils habituels.
    (vim, emacs, textmate...)

  4. Tester l'application.
    (Certains frameworks détectent les changements automatiquement
    D'autres exigent un Ctrl+C / redémarrage après chaque modification..)

  5. Reboucler et répéter les étapes 3 et 4 jusqu'à satisfaction.

  6. Quand c'est fini, faire un "commit+push" des changements de code.

containers/Local_Development_Workflow.md

352 / 801

Débugger à l'intérieur du conteneur

Docker dispose d'une commande appelée docker exec.

Cela permet aux utilisateurs de lancer un nouveau processus dans un conteneur déjà lancé.

Si parfois vous sentez que vous aimeriez entrer via SSH sur un conteneur: vous pouvez utiliser docker exec à la place.

Vous pouvez ainsi récupérer un terminal ou lancer une n'importe quelle autre commande pour automatisation.

containers/Local_Development_Workflow.md

353 / 801

Exemple avec docker exec

$ #Vous pouvez lancer des commandes ruby dans le même conteneur où l'appli tourne!
$ docker exec -it <yourContainerId> bash
root@5ca27cf74c2e:/opt/namer# irb
irb(main):001:0> [0, 1, 2, 3, 4].map {|x| x ** 2}.compact
=> [0, 1, 4, 9, 16]
irb(main):002:0> exit

containers/Local_Development_Workflow.md

354 / 801

Arrêter le conteneur

Maintenant que nous avons fini, arrêtons notre conteneur.

$ docker stop <yourContainerID>

Et supprimons-le.

$ docker rm <yourContainerID>

containers/Local_Development_Workflow.md

355 / 801

Résumé de section

Nous avons appris à:

  • Partager le code entre conteneur et hôte.

  • Régler notre dossier de travail.

  • Utiliser un processus simple de développement.

containers/Local_Development_Workflow.md

356 / 801

Image separating from the next chapter

357 / 801

Travailler avec des volumes

(automatically generated title slide)

358 / 801

Travailler avec des volumes

volume

containers/Working_With_Volumes.md

359 / 801

Objectifs

A la fin de cette section, vous serez capable de:

  • Créer des conteneurs gérant des volumes.

  • Partager des volumes à travers des conteneurs.

  • Partager un dossier du serveur avec un ou plusieurs conteneurs.

containers/Working_With_Volumes.md

360 / 801

Travailler avec des volumes

Les volumes Docker sont utilisés pour accomplir bien des buts, y compris:

  • Contourner le système copy-on-write pour obtenir une performance d'I/O native.

  • Contourner le copy-on-write pour laisser quelques fichiers hors de docker commit.

  • Partager un dossier entre plusieurs conteneurs.

  • Partager un dossier entre le serveur et le conteneur.

  • Partager un seul fichier entre l'hôte et le conteneur.

  • Utiliser un stockage distant et un stockage spécifique avec les "pilotes de volumes".

containers/Working_With_Volumes.md

361 / 801

"Volumes", des dossiers spéciaux d'un conteneur

On peut déclarer des volumes de deux façons différentes.

  • Dans un Dockerfile, avec une instruction VOLUME.
VOLUME /uploads
  • En ligne de commande, avec l'option -v avec docker run.
$ docker run -d -v /uploads myapp

Dans les deux cas, /uploads (à l'intérieur du conteneur) sera un volume.

containers/Working_With_Volumes.md

362 / 801

Les volumes pour contourner le système copy-on-write

Les volumes agissent comme des passerelles vers le système de fichier de l'hôte.

  • La performance d'un volume en termes d'I/O disque est exactement la même que sur l'hôte Docker.

  • Quand on fait un docker commit, le contenu des volumes n'est pas intégré dans l'image résultante.

  • Si une instruction RUN dans un Dockerfile change le contenu d'un volume, ces changements ne seront pas non plus enregistrés.

  • Si un conteneur est démarré avec l'option --read-only, le volume sera toujours modifiable (à moins que le volume lui-même soit en lecture-seule).

containers/Working_With_Volumes.md

363 / 801

Les volumes peuvent être partagés entre conteneurs

Vous pouvez démarrer un conteneur avec exactement les mêmes volumes qu'un autre.

Le nouveau conteneur aura les mêmes volumes, dans les mêmes dossiers.

Ils contiendront exactement la même chose, et resteront synchronisés.

Sous le capot, ce sont en fait les mêmes dossiers sur le serveur.

C'est possible avec l'option --volumes-from dans docker run.

Nous allons en voir un exemple dans les diapos suivantes.

containers/Working_With_Volumes.md

364 / 801

Partager les logs d'un serveur d'application avec un autre conteneur

Démarrons un conteneur Tomcat:

$ docker run --name webapp -d -p 8080:8080 -v /usr/local/tomcat/logs tomcat

Maintenant, démarrons un conteneur alpine avec les mêmes volumes:

$ docker run --volumes-from webapp alpine sh -c "tail -f /usr/local/tomcat/logs/*"

Puis, d'une autre fenêtre, envoyons des requêtes à notre conteneur Tomcat:

$ curl localhost:8080

containers/Working_With_Volumes.md

365 / 801

Les volumes existent indépendemment des conteneurs

Si un conteneur est arrêté ou supprimé, ses volumes existent toujours et sont accessibles.

On peut lister et manipuler les volumes avec les sous-commandes de docker volume:

$ docker volume ls
DRIVER VOLUME NAME
local 5b0b65e4316da67c2d471086640e6005ca2264f3...
local pgdata-prod
local pgdata-dev
local 13b59c9936d78d109d094693446e174e5480d973...

Certains des noms de volumes sont explicites (pgdata-prod, pgdata-dev).

D'autres (les IDs hexa) sont générés automatiquement par Docker.

containers/Working_With_Volumes.md

366 / 801

Nommer les volumes

  • On peut créer des volumes sans conteneur, et les utiliser ensuite dans plusieurs conteneurs.

Ajoutons quelques volumes directement.

$ docker volume create webapps
webapps
$ docker volume create logs
logs

Nos volumes ne sont attachés à aucun dossier en particulier.

containers/Working_With_Volumes.md

367 / 801

Utiliser nos volumes nommés

  • On active les volumes avec l'option -v.

  • Quand le chemin côté hôte ne contient pas de /, il est traité comme un nom de volume.

Démarrons un serveur web avec les deux précédents volumes.

$ docker run -d -p 1234:8080 \
-v logs:/usr/local/tomcat/logs \
-v webapps:/usr/local/tomcat/webapps \
tomcat

Vérifions que cela s'exécute normalement:

$ curl localhost:1234
... (Tomcat nous raconte combien il est content de tourner) ...

containers/Working_With_Volumes.md

368 / 801

Utiliser un volume d'un autre conteneur

  • Nous allons modifier le contenu d'un volume depuis un autre conteneur.

  • Dans cet exemple, nous allons lancer un éditeur de texte dans un autre conteneur.

    (Mais ça pourrait être un serveur FTP, un serveur WebDAV, un dépôt Git...)

Démarrons un autre conteneur attaché au volume webapps.

$ docker run -v webapps:/webapps -w /webapps -ti alpine vi ROOT/index.jsp

Il nous reste à vandaliser la page, enregistrer, et sortir.

Exécutons encore un curl localhost:1234 pour voir nos changements.

containers/Working_With_Volumes.md

369 / 801

Usage des "bind-mounts" personnalisés

Dans certains cas, vous voudrez monter un dossier depuis l'hôte vers le conteneur:

  • Pour gérer le stockage et les snapshots vous-même;

    (Avec LVM, ou un SAN, ou ZFS, ou toute autre chose!)

  • ou vous avez un autre disque aux meilleures performances (SSD) ou à résilience supérieure (EBS) et vous voulez y placer d'importantes données.

  • ou vous voulez partager un dossier source entre votre hôte (où se trouve le source) et le conteneur (où se passe la compilation et l'exécution).

Un moment, on a déjà vu ce cas d'usage dans notre exemple de processus de développement! Pas mal.

$ docker run -d -v /chemin/depuis/notre/hote:/chemin/dans/le/conteneur image ...

containers/Working_With_Volumes.md

370 / 801

Migrer des données avec --volumes-from

L'option --volumes-from indique à Docker de reprendre tous les volumes d'un conteneur existant.

  • Scenario: migrer de Redis 2.8 à Redis 3.0.

  • Nous avons un conteneur (myredis) qui fait tourner Redis 2.8.

  • Arrêtez le conteneur myredis.

  • Démarrez un nouveau conteneur, avec l'image Redis 3.0, et l'option --volumes-from.

  • Le nouveau conteneur va hériter des données de l'ancien.

  • Les futurs conteneurs pourront aussi utiliser --volumes-from.

  • Ne marche pas entre serveurs, donc impossible en clusters (Swarm, Kubernetes).

containers/Working_With_Volumes.md

371 / 801

Migration de données en pratique

Créons un conteneur Redis.

$ docker run -d --name redis28 redis:2.8

Puis connectons-nous au conteneur Redis pour ajouter des données.

$ docker run -ti --link redis28:redis busybox telnet redis 6379

Envoyons les commandes suivantes:

SET counter 42
INFO server
SAVE
QUIT

containers/Working_With_Volumes.md

372 / 801

Mettre à jour Redis

Arrêtez le conteneur Redis.

$ docker stop redis28

Démarrer le nouveau conteneur Redis.

$ docker run -d --name redis30 --volumes-from redis28 redis:3.0

containers/Working_With_Volumes.md

373 / 801

Tester le nouveau Redis

Connectez-vous au conteneur Redis pour voir les données.

docker run -ti --link redis30:redis busybox telnet redis 6379

Lancez les commandes suivantes:

GET counter
INFO server
QUIT

containers/Working_With_Volumes.md

374 / 801

Cycle de vie des volumes

  • Au moment de supprimer le conteneur, ses volumes sont conservés.

  • On peut les lister avec docker volume ls.

  • On peut y accéder en créant un conteneur avec docker run -v.

  • On peut les supprimer avec docker volume rm ou docker system prune.

Au final, vous êtes responsable de logger, surveiller, et sauvegarder vos volumes.

containers/Working_With_Volumes.md

375 / 801

Vérifier les volumes définis par une image

Vous vous demandez si une image a des volumes? Il suffit d'appeler docker inspect:

$ # docker inspect training/datavol
[{
"config": {
. . .
"Volumes": {
"/var/webapp": {}
},
. . .
}]

containers/Working_With_Volumes.md

376 / 801

Vérifier les volumes utilisés par un conteneur

Pour voir quels dossiers sont en fait des volumes, et où est-ce qu'ils pointent, passons par docker inspect (encore):

$ docker inspect <yourContainerID>
[{
"ID": "<yourContainerID>",
. . .
"Volumes": {
"/var/webapp": "/var/lib/docker/vfs/dir/f4280c5b6207ed531efd4cc673ff620cef2a7980f747dbbcca001db61de04468"
},
"VolumesRW": {
"/var/webapp": true
},
}]
  • On peut voir que le volume est présent sur le système de fichier de l'hôte Docker.

containers/Working_With_Volumes.md

377 / 801

Partager un seul fichier

La même option -v peut servir à partage un seul fichier (au lieu de tout un dossier).

Un des exemples les plus intéressants est de partager la socket de contrôle de Docker.

$ docker run -it -v /var/run/docker.sock:/var/run/docker.sock docker sh

Depuis ce conteneur, on peut lancer des commandes docker pour communiquer avec le Docker Engine qui tourne sur ce serveur. Essayez docker ps!

Puisque ce conteneur a accès à la socket Docker, il a un accès root au hôte.

containers/Working_With_Volumes.md

378 / 801

Plugins de volume

Vous pouvez installer des plugins pour gérer les volumes adossés à différents systèmes de stockage ou ayant des fonctions spéciales. Par exemple:

  • REX-Ray - créer et gérer des volumes adossés à un système de stockage professionnel (SAN ou NAS), ou des solutions cloud (par ex. EBS, EFS).

  • Portworx - fournit un stockage par bloc distribué pour conteneurs.

  • Gluster - stockage open source, défini par code, qui peut grimper jusqu'à plusieurs petaoctets. Il fournit une interface pour du stockage d'objet, bloc ou fichier.

  • et bien d'autres sur le Docker Store!

containers/Working_With_Volumes.md

379 / 801

Volumes vs. Mounts

  • Depuis Docker 17.06, une nouvelle option est disponible: --mount.

  • Elle offre une syntaxe plus riche pour manipuler les données de conteneurs.

  • Elle introduit une différence explicite entre:

    • les volumes (identifiés par un nom unique, gérés par un plugin de stockage),

    • les bind mounts (identifiés par un chemin du hôte, sans gestion intermédiaire).

  • L'option précédente -v / --volume reste toujours utilisable.

containers/Working_With_Volumes.md

380 / 801

Syntaxe de --mount

Attacher un dossier de l'hôte à un chemin du conteneur:

$ docker run \
--mount type=bind,source=/path/on/host,target=/path/in/container alpine

Monter un volume dans un chemin du conteneur:

$ docker run \
--mount source=myvolume,target=/path/in/container alpine

Monter un tmpfs (pour stockage de fichiers temporaires en mémoire):

$ docker run \
--mount type=tmpfs,destination=/path/in/container,tmpfs-size=1000000 alpine

containers/Working_With_Volumes.md

381 / 801

Résumé de section

Nous avons appris comment:

  • Créer et gérer les images.

  • Partager des volumes entre conteneurs.

  • Partager un dossier de l'hôte avec un ou plusieurs conteneurs.

containers/Working_With_Volumes.md

382 / 801

Image separating from the next chapter

383 / 801

Compose pour les développeurs

(automatically generated title slide)

384 / 801

Compose pour les développeurs

Utiliser des Dockerfiles est super pour générer des images de conteneurs.

Et si nous voulions travailler avec une suite complexe composée de plusieurs conteneurs?

Au final, on voudra disposer de scripts spécifiques et automatisés pour construire, lancer et connecter nos conteneurs entre eux.

Il y a une meilleure méthode: utiliser Docker Compose.

Dans ce chapitre, nous utiliserons Compose pour démarrer un environnement de développement.

containers/Compose_For_Dev_Stacks.md

385 / 801

Qu'est-ce que Docker Compose?

Docker Compose (à l'origine appelé fig) est un outil externe.

Contrairement au Docker Engine, il est écrit en Python. C'est aussi un logiciel libre.

L'idée générale de Compose est de permettre un processus de démarrage très facile et puissant:

  1. Récupérez votre code.

  2. Lancez docker-compose up.

  3. Votre appli est lancée et prête à l'emploi!

containers/Compose_For_Dev_Stacks.md

386 / 801

Aperçu de Compose

Voici comment on travaille avec Compose:

  • Vous décrivez un ensemble (ou stack) de conteneurs dans un fichier YAML appelé docker-compose.yml.

  • Vous lancez docker-compose up.

  • Compose télécharge automatiquement les images, génère les conteneurs et les démarre.

  • Compose configure les liens, volumes et autres options de Docker pour vous.

  • Compose peut lancer les conteneurs en arrière-plan, ou en avant-plan.

  • Quand on lance nos conteneurs en avant-plan, leur sortie est agrégée à l'affichage.

Avant de s'y plonger, voyons un petit exemple de Compose en action.

containers/Compose_For_Dev_Stacks.md

387 / 801

Vérifier si Compose est installé

Si vous utilisez les machines virtuelles de formation officielle, Compose a été pré-installé.

Si vous utilisez Docker pour Mac/Windows ou Docker Toolbox, Compose y est inclus.

Si vous êtes sur Linux (desktop ou serveur), vous devrez install Compose depuis la page de release ou avec pip install docker-compose.

Vous pouvez vérifier votre installation en tapant:

$ docker-compose --version

containers/Compose_For_Dev_Stacks.md

389 / 801

Lancer notre première stack avec Compose

Première étape: cloner le code source de l'appli que nous allons manipuler.

$ cd
$ git clone https://github.com/jpetazzo/trainingwheels
...
$ cd trainingwheels

Seconde étape: démarrer votre appli.

$ docker-compose up

Observez Compose pendant qu'il génère et lance votre appli avec les paramètres corrects, y compris la mise en réseau des conteneurs entre eux.

containers/Compose_For_Dev_Stacks.md

390 / 801

Lancer notre première stack avec Compose

Vérifiez que notre appli répond sur: http://<yourHostIP>:8000.

composeapp

containers/Compose_For_Dev_Stacks.md

391 / 801

Arrêter l'appli

Quand vous tapez ^C, Compose tente d'arrêter en douceur tous les conteneurs.

Après 10 secondes (ou après plusieurs ^C), ils seront tous stoppés de force.

containers/Compose_For_Dev_Stacks.md

392 / 801

Le fichier docker-compose.yml

Voici le fichier utilisé dans la démo:

version: "2"
services:
www:
build: www
ports:
- 8000:5000
user: nobody
environment:
DEBUG: 1
command: python counter.py
volumes:
- ./www:/src
redis:
image: redis

containers/Compose_For_Dev_Stacks.md

393 / 801

Structure du fichier Compose

Un fichier Compose possède plusieurs sections:

  • version est obligatoire. (On devrait utiliser "2" ou plus. La version 1 est obsolète.)

  • services est obligatoire. Un service est une ou plusieurs copies de la même image sous forme de conteneurs.

  • networks est optionnel et indique à quels réseaux devraient se connecter nos conteneurs.
    (Par défaut, les conteneurs seront liés à un réseau privé, unique par fichier compose.)

  • volumes est optionnel et peut définir les volumes utilisés et/ou partagés par les conteneurs.

containers/Compose_For_Dev_Stacks.md

394 / 801

Versions des fichiers Compose

  • La version 1 est obsolète et ne devrait pas être utilisée.

    (Si vous voyez un fichier Compose sans version ni services, c'est une version 1.)

  • La version 2 ajoute le support des réseaux et volumes.

  • La version 3 ajoute le support des options de déploiements (montée en charge, mises à jour progressives, etc.).

La documentation Docker a un excellent niveau d'information sur le format du fichier Compose, à propos de toutes les différentes versions.

containers/Compose_For_Dev_Stacks.md

395 / 801

Conteneurs dans docker-compose.yml

Chaque service dans le fichier YAML doit mentionner soit build, ou image.

  • build indique un chemin contenant un Dockerfile

  • image indique un nom d'image (local, ou sur un registre).

  • Si les deux sont spécifiés, une image sera générée depuis le dossier build et nommée selon image.

Les autres paramètres sont optionnels.

Ils encodent tous les paramètres typiques de la commande docker run.

Ils comportent parfois des améliorations mineures.

containers/Compose_For_Dev_Stacks.md

396 / 801

Paramètres de conteneur

  • command indique quoi lancer (comme la commande CMD du Dockerfile).

  • ports se traduit par une (ou plusieurs) options -p de correspondance des ports.
    Vous pouvez spécifier des ports locaux (par ex. x:y pour exposer le port public x).

  • volumes se traduit par une (ou plusieurs) options -v.
    Vous pouvez utiliser des chemins relatifs ici.

Pour la liste complète, voir: https://docs.docker.com/compose/compose-file/

containers/Compose_For_Dev_Stacks.md

397 / 801

Commandes Compose

Nous avons déjà vu docker-compose up, mais en voici une autre, docker-compose build.

Cela va lancer docker build pour tous les conteneurs mentionnant un chemin build.

On peut aussi l'invoquer automatiquement en lançant l'application:

docker-compose up --build

Une autre option commune est de démarrer les conteneurs en arrière-plan:

docker-compose up -d

containers/Compose_For_Dev_Stacks.md

398 / 801

Vérifier le statut des conteneurs

Cela peut se révéler fastidieux de vérifier le statut de vos conteneurs avec docker ps, surtout quand plusieurs applis tournent en même temps.

Compose nous facilite la tâche; avec docker-compose ps, il n'affichera que le statut des conteneurs de la stack en cours:

$ docker-compose ps
Name Command State Ports
----------------------------------------------------------------------------
trainingwheels_redis_1 /entrypoint.sh red Up 6379/tcp
trainingwheels_www_1 python counter.py Up 0.0.0.0:8000->5000/tcp

containers/Compose_For_Dev_Stacks.md

399 / 801

Nettoyage (1)

Si vous avez démarré votre application en arrière-plan avec Compose, et que vous allez l'arrêter vite fait, vous pouvez passer par la commande kill:

$ docker-compose kill

De même, docker-compose rm vous permet de supprimer les conteneurs (après confirmation):

$ docker-compose rm
Going to remove trainingwheels_redis_1, trainingwheels_www_1
Are you sure? [yN] y
Removing trainingwheels_redis_1...
Removing trainingwheels_www_1...

containers/Compose_For_Dev_Stacks.md

400 / 801

Nettoyage (2)

Par ailleurs, docker-compose down va arrêter et supprimer les conteneurs.

Cette commande va aussi supprimer d'autres ressources, comme les réseaux spécialement créés pour cette application.

$ docker-compose down
Stopping trainingwheels_www_1 ... done
Stopping trainingwheels_redis_1 ... done
Removing trainingwheels_www_1 ... done
Removing trainingwheels_redis_1 ... done

Enfin, docker-compose down -v va tout supprimer, y compris les volumes.

containers/Compose_For_Dev_Stacks.md

401 / 801

Manipulation spéciale de volumes

Compose est malin. Si votre conteneur utilise des volumes, quand vous re-démarrez votre appli, Compose va créer un nouveau conteneur, mais fera attention à reprendre les volumes utilisés à l'origine.

Cela rend plus simple la mise à jour d'un service et ses données, où Compose va télécharger les images et rédémarrer la stack.

containers/Compose_For_Dev_Stacks.md

402 / 801

Nommer un projet avec Compose

  • Quand vous lancez une commande Compose, Compose déduit un "nom de projet" pour votre appli.

  • Par défaut, le "nom de projet" est le nom de votre dossier en cours.

  • Par exemple, si vous êtes dans /home/zelda/src/ocarina, le nom du projet est ocarina.

  • Toutes les ressources initiées par Compose sont marquées avec ce nom de projet.

  • Le nom du projet apparaît comme préfixe des noms pour toutes les ressources.

    Par ex., dans l'exemple précédent, le service www va créer un conteneur ocarina_www_1.

  • Le nom du projet peut être surchargé avec docker-compose -p.

containers/Compose_For_Dev_Stacks.md

403 / 801

Lancer deux copies de la même appli

Si vous voulez exécuter deux exemplaires de la même appli simultanément, tout ce que vous avez à faire est de vous assurer que chaque exemplaire a un nom de projet différent.

Vous pouvez:

  • soit copier votre code dans un nouveau dossier avec un nom différent

  • soit démarrer chaque copie avec docker-compose -p nomdeprojet up

Chaque copie s'exécutera dans un réseau différent, totalement isolé des autres.

C'est idéal pour débogger des régressions, comparer entre 2 versions, etc.

containers/Compose_For_Dev_Stacks.md

404 / 801

Image separating from the next chapter

405 / 801

Configuration d'applications

(automatically generated title slide)

406 / 801

Configuration d'applications

Il y a de nombreuses façons de passer une configuration aux applications conteneurisées.

Il n'y a pas de "bonne manière", cela dépend de plusieurs facteurs, tels que:

  • la taille de la configuration;

  • les paramètres obligatoires et optionnels;

  • la visibilité de la configuration (par conteneur, par app, par client, par site, etc.);

  • la fréquence de changement de configuration.

containers/Application_Configuration.md

407 / 801

Paramètres en ligne de commande

docker run jpetazzo/hamba 80 www1:80 www2:80
  • La configuration est fournie via des paramètres en ligne de commande.

  • Dans l'exemple ci-dessus, ENTRYPOINT pointe sur un script qui va:

    • parser les paramètres,

    • générer un fichier de configuration,

    • démarrer le service réel.

containers/Application_Configuration.md

408 / 801

Pour et contre des paramètres en ligne de commande

  • Convient pour les paramètres obligatoires (sans lesquels le service ne peut pas démarrer);

  • Utile pour les services "boîte à outils" qui se lancent à de nombreuses reprises.

    (Parce qu'il n'y a pas d'autres étapes: juste à le lancer!)

  • Pas terrible pour les configurations dynamiques ou plus conséquentes.

    (Toujours possible à réaliser, mais plus encombrant)

containers/Application_Configuration.md

409 / 801

Variables d'environnement

docker run -e ELASTICSEARCH_URL=http://es42:9201/ kibana
  • Configuration fournie à travers des variables d'environnement.

  • La variable d'environnement peut être utilisée directement par le programme,
    ou par un script générant un fichier de configuration.

containers/Application_Configuration.md

410 / 801

Pour et contre des variables d'environnement

  • Pertinent pour des paramètres optionnels (puisque l'image peut fournir des valeurs par défault)

  • Aussi pratique pour des services se lançant de nombreuses fois.

    (C'est aussi simple que les paramètres en ligne de commande.)

  • Super pour des services avec beaucoup de paramètres, mais vous voulez juste en changer quelques-uns.

    (Et garder les valeurs par défaut pour tout le reste.)

  • Capacité à examiner les paramètres disponibles et leurs valeurs par défaut.

  • Pas terrible pour les configurations dynamiques.

containers/Application_Configuration.md

411 / 801

Configuration incluse

FROM prometheus
COPY prometheus.conf /etc
  • La configuration est ajoutée à l'image.

  • L'image peut avoir une configuration par défaut; la nouvelle config peut:

    • remplacer la configuration par défaut;
    • étendre celle-ci (si le code sait lire plusieurs fichiers de configuration)

containers/Application_Configuration.md

412 / 801

Pour et contre des configurations intégrées

  • Permet une personnalisation avancée, et des fichiers de configuration complexes;

  • Exige d'écrire un fichier de configuration (bien sûr!)

  • Exige de générer une image pour démarrer le service

  • Exige de générer une image pour reconfigurer le service

  • Exige de générer une image pour mettre à jour le service

  • Toute image pré-configurée peut-être stockée dans une Registry.

    (ce qui est super, mais nécessite une Registry)

containers/Application_Configuration.md

413 / 801

Configuration par volume

docker run -v appconfig:/etc/appconfig myapp
  • La configuration est stockée dans un volume;

  • Le volume est attaché au container;

  • L'image peut avoir une configuration par défaut.

    (Mais cela demande un réglage non trivial, via plus de documentation.)

containers/Application_Configuration.md

414 / 801

Pour et contre des configurations par volume

  • Permet une personnalisation avancée, et des fichiers de configuration complexes;

  • Exige de déclarer un nouveau volume pour chaque différente configuration;

  • Les services avec des configurations identiques peuvent ré-utiliser le même volume;

  • Ne force pas à générer/regénérer une image lors des mises à jour ou reconfiguration;

  • Une configuration peut être générée ou modifiée via un container tiers.

containers/Application_Configuration.md

415 / 801

Configuration dynamique par volume

  • C'est une technique puissante pour des configurations dynamiques et complexes;

  • La configuration est stockée dans un volume;

  • La configuration est générée/mise à jour depuis un container spécial;

  • L'application du container détecte quand la configuration a changé;

    (et recharge automatiquement la configuration quand nécessaire.)

  • La configuration peut être partagée entre service si besoin.

containers/Application_Configuration.md

416 / 801

Exemple de configuration dynamique par volume

Dans un premier terminal, démarrer un load balancer avec une configuration initiale:

$ docker run --name loadbalancer jpetazzo/hamba \
80 goo.gl:80

Dans un autre terminal, reconfigurer ce load balancer:

$ docker run --rm --volumes-from loadbalancer jpetazzo/hamba reconfigure \
80 google.com:80

La configuration pourrait aussi mise à jour via une API REST.

(L'API REST tournant elle-même depuis un autre container.)

containers/Application_Configuration.md

417 / 801

Image separating from the next chapter

418 / 801

Stockage des secrets

(automatically generated title slide)

419 / 801

Stockage des secrets

Idéalement, vous ne devriez pas transmettre de secrets (mot de passe, tokens, etc.) via:

  • la ligne de commande ou des variables d'environnement (quiconque avec un accès à l'API Docker peut les récupérer)

  • des images, surtout celles stockées dans une Registry.

La gestion des secrets est mieux supportée avec un orchestrateur (type Swarm ou K8S).

Les orchestrateurs autorisent la transmission de secrets en "sens-unique".

Gérer les secrets de manière sécurisée sans orchestrateur peut se révéler "tiré par les cheveux".

Par ex.:

  • lire le contenu du secret via stdin quand le service démarre;

  • passer le secret via un endpoint d'une API.

containers/Application_Configuration.md

420 / 801

Image separating from the next chapter

421 / 801

Gestion des logs

(automatically generated title slide)

422 / 801

Gestion des logs

Dans ce chapitre, nous expliquerons les différentes manières d'envoyer des logs depuis les conteneurs.

Nous verrons ensuite une méthode particulière en action, avec ELK et les pilotes de logs de Docker.

containers/Logging.md

423 / 801

On peut envoyer des logs de bien des manières

  • La méthode la plus simple est d'écrire sur les sorties standard et d'erreur.

  • Les applications peuvent écrire leur logs dans des fichiers locaux.

    (Ces fichiers sont soumis à une compression et une mise en rotation.)

  • Il est aussi très commun (sur système UNIX) d'utiliser syslog.

    (Les logs sont collectés par syslogd ou un équivalent, tel journald)

  • Pour d'importantes applis aux nombreux composants, il est commun de passer par un service de logs.

    (Le code utilise une bibliothèque pour envoyer des messages au service)

Toutes ces méthodes sont possibles avec les conteneurs.

containers/Logging.md

424 / 801

Écrire sur stdout/stderr

  • Les sorties standard et erreur de conteneurs sont gérées par le moteur de conteneurs.

  • Cela signifie que chaque ligne écrite par le conteneur est reçue par le moteur.

  • Ce moteur peut alors se "débrouiller" avec ces lignes de logs.

  • Avec Docker, la configuration par défaut est d'écrire ces logs dans des fichiers locaux.

  • Les fichiers peuvent être consultés avec docker logs (et les requêtes équivalentes de l'API).

  • Ce comportement peut être personnalisé, comme on le verra plus tard.

containers/Logging.md

425 / 801

Écrire dans des fichiers locaux

  • Si on écrit dans des fichiers, il est possible d'y accéder mais c'est assez laborieux.

    (On doit passer par docker exec ou docker cp.)

  • En outre, si le conteneur s'arrête, on ne peut plus lancer docker exec.

  • Pire, si le conteneur est effacé, les logs disparaîtront.

  • Alors que faire pour les programmes qui peuvent uniquement écrire en local?

426 / 801

Écrire dans des fichiers locaux

  • Si on écrit dans des fichiers, il est possible d'y accéder mais c'est assez laborieux.

    (On doit passer par docker exec ou docker cp.)

  • En outre, si le conteneur s'arrête, on ne peut plus lancer docker exec.

  • Pire, si le conteneur est effacé, les logs disparaîtront.

  • Alors que faire pour les programmes qui peuvent uniquement écrire en local?

  • Il y a plusieurs solutions.

containers/Logging.md

427 / 801

Utiliser un volume ou un point de montage

  • Au lieu de stocker les logs dans un dossier normal, on les place sur un volume.

  • Le volume est accessible par d'autres conteneurs.

  • On lance un exécutable tel que filebeat dans un autre conteneur accédant au même volume.

    (filebeat lit en continu les fichiers de logs locaux, comme tail -f, et les envoie dans un système central tel que ElasticSearch.)

  • On peut aussi passer par un point de montage, par ex. -v /var/log/containers/www:/var/log/tomcat.

  • Le conteneur va écrire les fichiers de logs dans un dossier exposé sur l'hôte.

  • Les fichiers logs vont apparaître sur l'hôte et être accessibles directement sur l'hôte.

containers/Logging.md

428 / 801

Utiliser les services de journalisation

  • On peut utiliser des frameworks (comme log4j) ou le paquet Python logging).

  • Ces frameworks exigent de coder et/ou configurer notre application.

  • Ces mécanismes sont valables aussi bien dans le conteneur qu'en dehors.

  • Parfois, on peut exploiter le réseau de conteneurs pour simplifier de telles configurations.

  • Par exemple, notre code peut envoyer des messages de logs à un serveur appelé log.

  • Le nom log sera résolu différemment selon qu'on est en développement, production, etc.

containers/Logging.md

429 / 801

Utiliser syslog

  • Et si notre code (ou le programme qu'on fait tourner dans le conteneur) utilise syslog?

  • Une possibilité serait de lancer un daemon syslog dans le conteneur.

  • Et ce daemon peut être configuré pour écrire dans des fichiers locaux ou transmettre les logs à travers le réseau.

  • Sous le capot, les clients syslog se connectent à une socket locale UNIX, /dev/log.

  • On devra donc exposer une socket syslog au conteneur (via un volume ou un point de montage).

  • Et terminer en créant un lien symbolique depuis /dev/log vers la socket syslog.

  • Voilà!

containers/Logging.md

430 / 801

Utiliser les pilotes de journalisation

  • Si on écrit sur stdout et stderr, le moteur de conteneur reçoit les messages de log.

  • Le Docker Engine dispose d'un système de log modulaire avec de nombreux plugins, dont:

    • json-file (par défaut)
    • syslog
    • journald
    • gelf
    • fluentd
    • splunk
    • etc.
  • Chaque plugin peut traiter et transmettre les logs à un autre processus ou système.

containers/Logging.md

431 / 801

Avertissement à propos de json-file

  • Par défaut, la taille du fichier de log est illimitée.

  • Cela signifie qu'un conteneur très bavard videra certainement tout l'espace disque.

    (ou un conteneur moins bavard aussi, mais dans un laps de temps très long.)

  • La rotation de logs peut-être activée avec l'option max-size.

  • D'anciens fichiers de logs peuvent être supprimés avec l'option max-file.

  • Toutes ces options relatives à la journalisation peuvent être réglées par conteneur, ou globalement.

Exemple:

$ docker run --log-opt max-size=10m --log-opt max-file=3 elasticsearch

containers/Logging.md

432 / 801

Démo: envoyer les logs à ELK

  • Nous allons déployer la suite ELK.

  • Elle acceptera les logs via une socket GELF.

  • Nous allons lancer quelques conteneurs avec le pilote de log gelf.

  • Nous verrons alors nos logs dans Kibana, l'interface web fournie par ELK.

Avant-propos important: ce n'est pas une installation "officielle" ou "recommandée"; juste un exemple. Nous avons choisi ELK pour cette démo par sa popularité et les demandes qu'il suscite; mais vous serez aussi gagnant avec Fluent ou d'autres solutions de journalisation!

containers/Logging.md

433 / 801

Qu'est-ce qu'il y a dans la solution ELK?

  • ELK, c'est trois composants:

    • ElasticSearch, pour stocker et indexer les messages de log;

    • Logstash, qui reçoit les messages de diverses sources, les traite, et les transmet à diverses destinations;

    • Kibana, pour afficher/chercher les messages dans une jolie interface.

  • Le seul composant que nous allons configurer est Logstash.

  • Nous accepterons des messages de log au format GELF.

  • Les messages seront stockés dans ElasticSearch,
    et affichés dans la sortie standard de Logstash pour débogage.

containers/Logging.md

434 / 801

Lancer ELK

  • Nous allons utiliser un fichier Compose décrivant la solution ELK.

  • Le fichier Compose est dans le dépôt container.training sur Github

$ git clone https://github.com/jpetazzo/container.training
$ cd container.training
$ cd elk
$ docker-compose up
  • Jetons un oeil au fichier Compose pendant qu'il se déploie.

containers/Logging.md

435 / 801

Notre déploiement ELK basique

  • Nous allons utiliser des images du Docker Hub: elasticsearch, logstash, kibana.

  • Pas besoin de changer la configuration d'ElasticSearch.

  • Mais nous devons donner à Kibana l'adresse d'ElasticSearch:

    • elle est indiquée dans la variable d'environnement ELASTICSEARCH_URL

    • par défaut, c'est localhost:9200, on va la changer en elasticsearch:9200.

    • On a besoin de configurer Logstash:

    • on lui passe un fichier de configuration entier via la ligne de commande,

    • c'est une bidouille pour éviter de générer une image juste pour la config.

containers/Logging.md

436 / 801

Envoyer des logs à ELK

  • La solution ELK accepte des messages via une socket GELF.

  • La socket GELF écoute sur le port UDP 12201.

  • Pour envoyer un message, on a besoin de changer le pilote de journalisation utilisé par Docker.

  • Cela peut être réalisé en global (en reconfigurant le moteur) ou par conteneur.

  • Essayons de rédéfinir le pilote de journalisation pour un seul conteneur:

$ docker run --log-driver=gelf --log-opt=gelf-address=udp://localhost:12201 \
alpine echo hello world

containers/Logging.md

437 / 801

Afficher les logs dans ELK

  • Se connecter à l'interface Kibana.

  • Il est exposé sur le port 5601.

  • Ouvrir http://X.X.X.X:5601.

containers/Logging.md

438 / 801

"Configurer" Kibana

  • Kibana devrait vous proposer de "Configure an index pattern":
    dans la liste "Time-field name", choisir "@timestamp" et cliquez le bouton "Create".

  • Puis:

    • cliquer "Discover" (en haut à gauche),
    • cliquer "Last 15 minutes" (en haut à droite),
    • cliquer "Last 1 hour" (dans la liste au milieu),
    • cliquer "Auto-refresh" (coin supérieur droit),
    • cliquer "5 seconds" (en haut à gauche de la liste).
  • Vous pouvez voir une série de barres vertes (avec une nouvelle barre toutes les minutes)

  • Notre message "Hello world" devrait y apparaître.

containers/Logging.md

439 / 801

Postface importante

Ce n'est pas une installation de niveau "production".

Il s'agit d'un exemple à but éducatif. Puisque nous avons un seul serveur, nous avons installé une seule instance ElasticSearch et une seule instance Logstash.

Dans une installation de "production", vous avez besoin d'un cluster ElasticSearch (pour la haute disponibilité et la capacité totale de stockage). Vous avez aussi besoin de plusieurs instances de Logstash.

Et si vous voulez résister aux pics de logs, vous aurez besoin d'une sorte de file d'attente de messages: Redis si c'est léger, Kafka si vous voulez garantir aucune perte. Bonne chance.

Pour en savoir plus sur le pilote GELF, jetez un oeil sur ce billet de blog.

containers/Logging.md

440 / 801

Image separating from the next chapter

441 / 801

Limiter les ressources

(automatically generated title slide)

442 / 801

Limiter les ressources

  • Jusqu'ici, nous avons utilisé les conteneurs comme des unités de déploiements assez pratiques.

  • Que se passe-t-il quand un conteneur essaie d'utiliser plus de ressources que disponible?

    (RAM, CPU, disque, entrées/sortie réseau...)

  • Que se passe-t-il quand plusieurs conteneurs entrent en concurrence pour la même ressource?

  • Pouvons-nous limiter les ressources allouées à un conteneur?

    (Un indice: oui!)

containers/Resource_Limits.md

443 / 801

Processus de conteneur, processus normaux

  • Les conteneurs sont plus proches de "processus spéciaux" que de "VMs légères".

  • Un processus lancé dans un conteneur est, en fait, un processus s'exécutant sur l'hôte.

  • Jetons un oeil à l'affichage de ps sur un hôte hébergeant 3 conteneurs:

    0 2662 0.2 0.3 /usr/bin/dockerd -H fd://
    0 2766 0.1 0.1 \_ docker-containerd --config /var/run/docker/containe
    0 23479 0.0 0.0 \_ docker-containerd-shim -namespace moby -workdir
    0 23497 0.0 0.0 | \_ nginx: master process nginx -g daemon off;
    101 23543 0.0 0.0 | \_ nginx: worker process
    0 23565 0.0 0.0 \_ docker-containerd-shim -namespace moby -workdir
    102 23584 9.4 11.3 | \_ /docker-java-home/jre/bin/java -Xms2g -Xmx2
    0 23707 0.0 0.0 \_ docker-containerd-shim -namespace moby -workdir
    0 23725 0.0 0.0 \_ /bin/sh
  • Les processus surlignés sont des processus conteneurisés.
    (Cet hôte fait tourner nginx, elasticsearch et alpine.)

containers/Resource_Limits.md

444 / 801

Par défaut: rien ne change

  • Que se passe-t-il quand un processus utilise trop de mémoire sur un système Linux?
445 / 801

Par défaut: rien ne change

  • Que se passe-t-il quand un processus utilise trop de mémoire sur un système Linux?

  • Réponse simplifiée:

    • du swap est consommé;

    • au cas où le swap ne suffise pas, au final, le out-of-memory killer est invoqué;

    • le OOM killer exploite des heuristiques pour terminer les processus;

    • parfois, il tue un processus sans lien.

446 / 801

Par défaut: rien ne change

  • Que se passe-t-il quand un processus utilise trop de mémoire sur un système Linux?

  • Réponse simplifiée:

    • du swap est consommé;

    • au cas où le swap ne suffise pas, au final, le out-of-memory killer est invoqué;

    • le OOM killer exploite des heuristiques pour terminer les processus;

    • parfois, il tue un processus sans lien.

  • Que se passe-t-il quand un conteneur utilise trop de mémoire?

  • La même chose!

    (i.e. un processus finira par être supprimé, peut-être même dans un autre conteneur.)

containers/Resource_Limits.md

447 / 801

Limiter les ressources de conteneur

  • Le noyau Linux offre de riches mécanismes pour limiter les ressources de conteneur.

  • Pour l'usage mémoire, ce mécanisme fait partie du sous-système des cgroups.

  • Ce sous-système permet de limiter la mémoire d'un processus ou d'un groupe entier.

  • Un moteur de conteneur exploite ces mécanismes pour limiter la mémoire d'un conteneur.

  • Le OOM killer expose un nouveau comportement:

    • il se lance quand un conteneur dépasse la limite de mémoire autorisée;

    • dans ce cas, il supprime seulement les processus de ce conteneur.

containers/Resource_Limits.md

448 / 801

Limiter la mémoire en pratique

  • Le Docker Engine offre bien des options pour limiter l'usage mémoire.

  • Les deux plus utiles sont --memory et --memory-swap.

  • --memory limite la quantité de RAM physique utilisée par un conteneur.

  • --memory-swap limite la quantité de mémoire totale (RAM+swap) disponible par conteneur.

  • La limite de mémoire peut être exprimée en octets, ou avec un suffixe d'unité.

    (par ex.: --memory 100m = 100 méga-octets.)

  • Nous examinerons ici deux stratégies: limiter l'usage de la RAM, ou les deux (RAM+swap).

containers/Resource_Limits.md

449 / 801

Limiter l'usage de la RAM

Exemple:

docker run -ti --memory 100m python

Si le conteneur tente d'allouer plus de 100Mo de RAM, et que le swap est disponible:

  • le conteneur ne sera pas tué,

  • la mémoire au dessus de 100Mo passera dans le swap,

  • la plupart des cas, l'appli dans le conteneur sera ralentie (beaucoup).

Si nous saturons le swap, le OOM killer global s'activera quand même.

containers/Resource_Limits.md

450 / 801

Limiter à la fois RAM et swap

Exemple:

docker run -ti --memory 100m --memory-swap 100m python

Si un conteneur essaie d'utiliser plus de 100Mo de mémoire, il sera tué.

D'un autre côté, l'application ne sera jamais ralentie à cause du swap.

containers/Resource_Limits.md

451 / 801

Quand adopter quelle stratégie?

  • Les services à données persistentes (telles les bases de données) vont perdre leur données ou les corrompre si elles sont tuées.

  • On préfère les autoriser à utiliser l'espace swap, mais en surveiller leur usage.

  • Les services immuables peuvent normalement être tués avec un impact faible.

  • On pourra limiter leur usage mémoire+swap, mais surveiller s'ils sont tués.

  • Au final, cela revient à la question "ai-je besoin de swap, et combien?"

containers/Resource_Limits.md

452 / 801

Limiter l'usage CPU

  • Il n'y a pas moins de trois moyens de limiter l'usage CPU:

    • régler la priorité relative avec --cpu-shares,

    • placer une limite en pourcentage de CPU avec --cpus,

    • épingler un conteneur à des CPUs spécifiques avec --cpuset-cpus.

  • Ils peuvent être utilisés séparément ou ensemble.

containers/Resource_Limits.md

453 / 801

Régler une priorité relative

  • Chaque conteneur a une priorité relative utilisée par l'ordonnanceur Linux.

  • Par défaut, cette priorité est de 1024.

  • Tant que l'usage du CPU n'est pas maximum, elle n'a pas d'effet.

  • Dès que le CPU atteint sa limite, chaque conteneur reçoit des cycles CPU en proportion de sa priorité relative.

  • Autrement dit: un conteneur avec --cpu-shares 2048 en recevra deux fois plus que celui par défaut.

containers/Resource_Limits.md

454 / 801

Limiter en pourcentage de CPU

  • Ce réglage s'assure qu'un conteneur n'utilise pas plus d'un certain pourcentage de CPU.

  • La limite est exprimée en CPUs; par conséquent:

    --cpus 0.1 signifie 10% d'un CPU, --cpus 1.0 signifie 100% d'un CPU entier, --cpus 10.0 signifie 10 CPUs entiers.

containers/Resource_Limits.md

455 / 801

Épingler des conteneurs aux CPUs

  • Sur des machines multi-coeurs, il est possible de restreindre l'exécution à un ensemble de CPUs.

  • Exemples:

    --cpuset-cpus 0 force le conteneur à tourner sur le CPU 0;

    --cpuset-cpus 3,5,7 limite le conteneur aux CPUs 3, 5, 7;

    --cpuset-cpus 0-3,8-11 épingle le conteneur aux CPUs 0, 1, 2, 3, 8, 9, 10, 11.

  • Cela ne réservera pas les CPUs correspondants!

    (Ils peuvent toujours être sollicités par d'autres conteneurs, ou des processus non-conteneurisés)

containers/Resource_Limits.md

456 / 801

Limiter l'usage du disque

  • La plupart des pilotes de stockage ne supportent pas la limite de disque par conteneur.

    (À l'exception de devicemapper, mais cette limite n'est pas simple à régler.)

  • Cela signifie donc qu'un seul conteneur peut vider l'espace disponible pour tous.

  • En pratique, toutefois, ce n'est pas un souci, car:

    • les données (pour services persistents) devraient occuper des volumes,

    • les assets (par ex. images, contenu généré, etc.) devraient résider dans des banques de données ou des volumes,

    • les logs sont écrits en sortie standard et collectés par le moteur à conteneur.

  • L'usage du disque par les conteneurs peut être audité avec docker ps -s et docker diff.

containers/Resource_Limits.md

457 / 801

Toutes nouvelles versions!

  • Engine 18.09
  • Compose 1.23
  • Machine 0.16
  • Vérifier toutes les versions installées
    docker version
    docker-compose -v
    docker-machine -v

swarm/versions.md

458 / 801

Un moment, pardon, mais 18.09 ?!

459 / 801

Un moment, pardon, mais 18.09 ?!

  • Docker 1.13 = Docker 17.03 (année.mois, comme Ubuntu)

  • Chaque mois sort une version "edge" (avec les dernières nouveautés)

  • Chaque trimestre sort une version "stable"

  • Docker CE maintient ses versions pendant au moins 4 mois

  • Docker EE maintient ses versions pendant au moins 12 mois

  • Pour plus de détails, consultez le billet de blog d'annonce de Docker EE

swarm/versions.md

460 / 801

Docker CE vs Docker EE

  • Docker EE:

    • $$$
    • certifié sur une sélection de distributions, de clouds et plugins
    • fonctions de gestion avancées (contrôle daccès fin, scans de sécurité, etc.)
  • Docker CE:

    • gratuit
    • disponible via Docker for Desktop (éditions Mac etW Windows), et sur toutes les distributions Linux majeures.
    • parfait pour développeurs individuels et petites organisations.

swarm/versions.md

461 / 801

Pourquoi?

  • Plus lisible pour les entreprises

    (i.e. les gentilles personnes qui sont assez sympas pour payer grassement nos services)

  • Pas d'impact sur la communauté

    (en dehors du suffixe CE/EE et du changement de versioning)

  • Les deux lignes exploitent les mêmes composants open source

    (containerd, libcontainer, swarmkit...)

  • Calendrier de mise à jour plus prévisible (voir diapo suivante)

swarm/versions.md

462 / 801

Qu'est-ce qui a été ajouté quand?

2015 1.9 Réseaux superposés (multi-hôte), plugins réseau/IPAM
2016 1.10 DNS dynamique embarqué
2016 1.11 Répartition de charge DNS simple (round robin)
2016 1.12 Mode Swarm, maillage de routage, réseaux chiffrés, healthchecks
2017 1.13 Stacks, réseaux attachables, aplatissement d'image et compression
2017 1.13 Swarm mode pour Windows Server 2016
2017 17.03 Secrets, Raft chiffré
2017 17.04 Retour arrière (rollback), préférences de placement ( non coercitives)
2017 17.06 Configs swarm, events avancés, build multi-étapes, logs de services
2017 17.06 Réseaux superposés Swarm, secrets pour Windows Server 2016
2017 17.09 chown pour ADD/COPY, start_period, signal de stop, overlay2 par défaut
2017 17.12 containerd, isolation Hyper-V, maillage de routage pour Windows
2018 18.03 Modèles pour secrets/configs, stacks à multiple yamls, LCOW
2018 18.03 Support stack natif pour K8S, docker trust, tmpfs, CLI pour manifest

swarm/versions.md

464 / 801

Image separating from the next chapter

465 / 801

Notre application de démo

(automatically generated title slide)

466 / 801

Notre application de démo

  • Nous allons cloner le dépôt Github sur notre node1

  • Le dépôt contient aussi les scripts et outils à utiliser à travers la formation.

  • Cloner le dépôt sur node1:
    git clone https://github.com/jpetazzo/container.training

(Vous pouvez aussi forker le dépôt sur Github et cloner votre version si vous préférez.)

shared/sampleapp.md

467 / 801

Télécharger et lancer l'application

Démarrons-la avant de s'y plonger, puisque le téléchargement peut prendre un peu de temps...

  • Aller dans le dossier dockercoins du dépôt cloné:

    cd ~/container.training/dockercoins
  • Utiliser Compose pour générer et lancer tous les conteneurs:

    docker-compose up

Compose indique à Docker de construire toutes les images de conteneurs (en téléchargeant les images de base correspondantes), puis de démarrer tous les conteneurs et d'afficher les logs agrégés.

shared/sampleapp.md

468 / 801

Qu'est-ce que cette application?

469 / 801

Qu'est-ce que cette application?

  • C'est un miner de DockerCoin! 💰🐳📦🚢
470 / 801

Qu'est-ce que cette application?

  • C'est un miner de DockerCoin! 💰🐳📦🚢

  • Non, on ne paiera pas le café avec des DockerCoins

471 / 801

Qu'est-ce que cette application?

  • C'est un miner de DockerCoin! 💰🐳📦🚢

  • Non, on ne paiera pas le café avec des DockerCoins

  • Comment DockerCoins fonctionne

    • générer quelques octets aléatoires

    • calculer une somme de hachage

    • incrémenter un compteur (pour suivre la vitesse)

    • répéter en boucle!

472 / 801

Qu'est-ce que cette application?

  • C'est un miner de DockerCoin! 💰🐳📦🚢

  • Non, on ne paiera pas le café avec des DockerCoins

  • Comment DockerCoins fonctionne

    • générer quelques octets aléatoires

    • calculer une somme de hachage

    • incrémenter un compteur (pour suivre la vitesse)

    • répéter en boucle!

  • DockerCoins n'est pas une crypto-monnaie

    (les seuls points communs étant "aléatoire", "hachage", et "coins" dans le nom)

shared/sampleapp.md

473 / 801

DockerCoins à l'âge des microservices

  • DockerCoins est composée de 5 services:

    • rng = un service web générant des octets au hasard

    • hasher = un service web calculant un hachage basé sur les données POST-ées

    • worker = un processus en arrière-plan utilisant rng et hasher

    • webui = une interface web pour le suivi du travail

    • redis = base de données (garde un décompte, mis à jour par worker)

  • Ces 5 services sont visibles dans le fichier Compose de l'application, docker-compose.yml

shared/sampleapp.md

474 / 801

Comment fonctionne DockerCoins

  • worker invoque le service web rng pour générer quelques octets aléatoires

  • worker invoque le service web hasher pour générer un hachage de ces octets

  • worker reboucle de manière infinie sur ces 2 tâches

  • chaque seconde, worker écrit dans redis pour indiquer combien de boucles ont été réalisées

  • webui interroge redis, pour calculer et exposer la "vitesse de hachage" dans notre navigateur

(Voir le diagramme en diapo suivante!)

shared/sampleapp.md

475 / 801

Service discovery au pays des conteneurs

  • Comment chaque service trouve l'adresse des autres?
477 / 801

Service discovery au pays des conteneurs

  • Comment chaque service trouve l'adresse des autres?

  • On ne code pas en dur des adresses IP dans le code.

  • On ne code pas en dur des FQDN dans le code, non plus.

  • On se connecte simplement avec un nom de service, et la magie du conteneur fait le reste

    (Par magie du conteneur, nous entendons "l'astucieux DNS embarqué dynamique")

shared/sampleapp.md

478 / 801

Exemple dans worker/worker.py

redis = Redis("redis")
def get_random_bytes():
r = requests.get("http://rng/32")
return r.content
def hash_bytes(data):
r = requests.post("http://hasher/",
data=data,
headers={"Content-Type": "application/octet-stream"})

(Code source complet disponible ici)

shared/sampleapp.md

479 / 801

Liens, nommage et découverte de service

  • Les conteneurs peuvent avoir des alias de réseau (résolus par DNS)

  • Compose dans sa version 2+ rend chaque conteneur disponible via son nom de service

  • Compose en version 1 rendait obligatoire la section "links"

  • Les alias de réseau sont automatiquement préfixé par un espace de nommage

    • vous pouvez avoir plusieurs applications déclarées via un service appelé database

    • les conteneurs dans l'appli bleue vont atteindre database via l'IP de la base de données bleue

    • les conteneurs dans l'appli verte vont atteindre database via l'IP de la base de données verte

shared/sampleapp.md

480 / 801

Montrez-moi le code!

  • Vous pouvez ouvrir le dépôt Github avec tous les contenus de cet atelier:
    https://github.com/jpetazzo/container.training

  • Cette application est dans le sous-dossier dockercoins

  • Le fichier Compose (docker-compose.yml) liste les 5 services

  • redis utilise une image officielle issue du Docker Hub

  • hasher, rng, worker, webui sont générés depuis un Dockerfile

  • Chaque Dockerfile de service et son code source est stocké dans son propre dossier

    (hasher est dans le dossier hasher, rng est dans le dossier rng, etc.)

shared/sampleapp.md

481 / 801

Version du format de fichier Compose

Uniquement pertinent si vous avez utilisé Compose avant 2016...

  • Compose 1.6 a introduit le support d'un nouveau format de fichier Compose (alias "v2")

  • Les services ne sont plus au plus haut niveau, mais dans une section services.

  • Il doit y avoir une clé version tout en haut du fichier, avec la valeur "2" (la chaîne de caractères, pas le chiffre)

  • Les conteneurs sont placés dans un réseau dédié, rendant les links inutiles

  • Il existe d'autres différences mineures, mais la mise à jour est facile et assez directe.

shared/sampleapp.md

482 / 801

Notre application à l'oeuvre

  • A votre gauche, la bande "arc-en-ciel" montrant les noms de conteneurs

  • A votre droite, nous voyons la sortie standard de nos conteneurs

  • On peut voir le service worker exécutant des requêtes vers rng et hasher

  • Pour rng et hasher, on peut lire leur logs d'accès HTTP

shared/sampleapp.md

483 / 801

Se connecter à l'interface web

  • "Les logs, c'est excitant et drôle" (Citation de personne, jamais, vraiment)

  • Le conteneur webui expose un écran de contrôle web; allons-y voir.

  • Avec un navigateur, se connecter à node1 sur le port 8000

  • Rappel: les alias nodeX ne sont valides que sur les noeuds eux-mêmes.

  • Dans votre navigateur, vous aurez besoin de taper l'adresse IP de votre noeud.

Un diagramme devrait s'afficher, et après quelques secondes, une courbe en bleu va apparaître.

shared/sampleapp.md

484 / 801

Pourquoi le rythme semble irrégulier?

  • On dirait peu ou prou que la vitesse est de 4 hachages/seconde.

  • Ou plus précisément: 4 hachages/secondes avec des trous reguliers à zéro

  • Pourquoi?

485 / 801

Pourquoi le rythme semble irrégulier?

  • On dirait peu ou prou que la vitesse est de 4 hachages/seconde.

  • Ou plus précisément: 4 hachages/secondes avec des trous reguliers à zéro

  • Pourquoi?

  • L'appli a en réalité une vitesse constante et régulière de 3.33 hachages/seconde.
    (ce qui correspond à 1 hachage toutes les 0.3 secondes, pour certaines raisons)

  • Oui, et donc?

shared/sampleapp.md

486 / 801

La raison qui fait que ce graphe n'est pas super

  • Le worker ne met pas à jour le compteur après chaque boucle, mais au maximum une fois par seconde.

  • La vitesse est calculée par le navigateur, qui vérifie le compte à peu près une fois par seconde.

  • Entre 2 mise à jours consécutives, le compteur augmentera soit de 4, ou de 0 (zéro).

  • La vitesse perçue sera donc 4 - 4 - 0 - 4 - 4 - 0, etc.

  • Que peut-on conclure de tout cela?

487 / 801

La raison qui fait que ce graphe n'est pas super

  • Le worker ne met pas à jour le compteur après chaque boucle, mais au maximum une fois par seconde.

  • La vitesse est calculée par le navigateur, qui vérifie le compte à peu près une fois par seconde.

  • Entre 2 mise à jours consécutives, le compteur augmentera soit de 4, ou de 0 (zéro).

  • La vitesse perçue sera donc 4 - 4 - 0 - 4 - 4 - 0, etc.

  • Que peut-on conclure de tout cela?

  • "Je suis carrément incapable d'écrire du bon code frontend" 😀 — Jérôme

shared/sampleapp.md

488 / 801

Arrêter notre application

  • Si nous stoppons Compose (avec ^C), il demandera poliment au Docker Engine d'arrêter l'appli

  • Le Docker Engine va envoyer un signal TERM aux conteneurs

  • Si les conteneurs ne quittent pas assez vite, l'Engine envoie le signal KILL

  • Arrêter l'application en tapant ^C
489 / 801

Arrêter notre application

  • Si nous stoppons Compose (avec ^C), il demandera poliment au Docker Engine d'arrêter l'appli

  • Le Docker Engine va envoyer un signal TERM aux conteneurs

  • Si les conteneurs ne quittent pas assez vite, l'Engine envoie le signal KILL

  • Arrêter l'application en tapant ^C

Certains conteneurs quittent immédiatement, d'autres prennent plus de temps. Les conteneurs qui ne gèrent pas le SIGTERM finissent pas être tués après 10 secs. Si nous sommes vraiment impatients, on peut taper ^C une seconde fois!

shared/sampleapp.md

490 / 801

Relancer en arrière-plan

  • Bien des options et des commandes dans Compose sont inspirées par celle de docker
  • Démarrer l'appli en arrière-plan avec l'option -d:

    docker-compose up -d
  • Vérifier que notre appli est lancée avec la commande ps:

    docker-compose ps

docker-compose ps montre aussi les ports exposés par l'application.

shared/composescale.md

491 / 801

Afficher les logs

  • La commande docker-compose logs marche comme docker logs
  • Afficher tous les logs depuis la naissance du conteneur, et sortir juste après:

    docker-compose logs
  • Suivre le flux de logs du conteneur, en commençant par les 10 dernières lignes de chaque conteneur:

    docker-compose logs --tail 10 --follow

Astuce: taper ^S et ^Q pour suspendre/reprendre l'affichage des logs.

shared/composescale.md

492 / 801

Montée en charge de l'application

  • Notre but est de faire monter ce graphique de performance (sans changer une ligne de code!)
493 / 801

Montée en charge de l'application

  • Notre but est de faire monter ce graphique de performance (sans changer une ligne de code!)

  • Avant d'essayer de faire monter en charge l'application, voyons si plus de ressources sont nécessaires

    (CPU, RAM ...)

  • Pour ça, nous allons lancer de bons vieux outils UNIX sur notre noeud Docker.

shared/composescale.md

494 / 801

Examiner l'usage de ressources

  • Jetons un oeil au CPU, à la mémoire et aux E/S
  • lancer top pour voir l'usage CPU et mémoire (on devrait voir des cycles de repos)
  • lancer vmstat 1 pour voir l'usage des entrées/sorties (si/so/bi/bo)
    (les 4 nombres devraient être quasiment à zéro, excepté bo pour le logging)

Nous avons des ressources disponibles.

  • Pourquoi?
  • Comment les exploiter?

shared/composescale.md

495 / 801

Escalader les workers sur un seul noeud

  • Docker Compose supporte la mise à l'échelle
  • Escaladons worker et voyons ce qu'il se passe!
  • Démarrer un conteneur worker supplémentaire:

    docker-compose up -d --scale worker=2
  • Examiner le graphique de performance (on devrait voir un doublement)

  • Examiner les logs agrégés de nos conteneurs (worker_2 devrait y apparaître)

  • Examiner l'impact sur la charge de CPU avec par ex. top (il devrait être négligable)

shared/composescale.md

496 / 801

(Ac)cumuler les workers

  • Super, ajoutons encore plus de workers alors, et le tour est joué!
  • Démarrer huit conteneurs de worker de plus:

    docker-compose up -d --scale worker=10
  • Examiner le graphique de performance: est-ce que l'amélioration est x10?

  • Examiner les logs agrégés de nos conteneurs

  • Examiner l'impact sur la charge CPU et la mémoire

shared/composescale.md

497 / 801

Image separating from the next chapter

498 / 801

Identifier les goulots d'étranglement

(automatically generated title slide)

499 / 801

Identifier les goulots d'étranglement

  • Vous devriez constater un facteur de vitesse x3 (pas x10)

  • Ajouter des workers ne s'est pas traduit pas un gain linéaire.

  • Quelque chose d'autre nous ralentit donc.

500 / 801

Identifier les goulots d'étranglement

  • Vous devriez constater un facteur de vitesse x3 (pas x10)

  • Ajouter des workers ne s'est pas traduit pas un gain linéaire.

  • Quelque chose d'autre nous ralentit donc.

  • ... Mais quoi?

501 / 801

Identifier les goulots d'étranglement

  • Vous devriez constater un facteur de vitesse x3 (pas x10)

  • Ajouter des workers ne s'est pas traduit pas un gain linéaire.

  • Quelque chose d'autre nous ralentit donc.

  • ... Mais quoi?

  • Le code ne dispose d'aucun appareillage de mesure.

  • Sortons donc notre analyseur de performance HTTP dernier cri!
    (i.e les bons vieux outils comme ab, httping, etc.)

shared/composescale.md

502 / 801

Accéder aux services internes

  • rng et hasher sont exposés sur les ports 8001 et 8002

  • C'est déclaré ainsi dans le fichier Compose:

    ...
    rng:
    build: rng
    ports:
    - "8001:80"
    hasher:
    build: hasher
    ports:
    - "8002:80"
    ...

shared/composescale.md

503 / 801

Mesurer la latence sous contrainte

Nous utiliserons pour cela httping.

  • Vérifier la latence de rng:

    httping -c 3 localhost:8001
  • Vérifier la latence de hasher:

    httping -c 3 localhost:8002

rng révèle une latence bien plus grande que hasher.

shared/composescale.md

504 / 801

Nettoyage

  • Avant de continuer, supprimons tous ces conteneurs.
  • Dire à Compose de tout enlever:
    docker-compose down

shared/composedown.md

505 / 801

Image separating from the next chapter

506 / 801

SwarmKit

(automatically generated title slide)

507 / 801

SwarmKit

  • SwarmKit est un projet open source sous forme de boîte à outils pour monter des systèmes multi-noeud.

  • C'est une bibliothèque réutilisable, comme libcontainer, libnetwork, vpnkit ...

  • C'est un des composants qui font la plomberie de l'éco-système Docker

508 / 801

SwarmKit

  • SwarmKit est un projet open source sous forme de boîte à outils pour monter des systèmes multi-noeud.

  • C'est une bibliothèque réutilisable, comme libcontainer, libnetwork, vpnkit ...

  • C'est un des composants qui font la plomberie de l'éco-système Docker

🐳 Saviez-vous que кит veut dire "baleine" en russe?

swarm/swarmkit.md

509 / 801

Fonctionnalités de SwarmKit

  • Base de données distribuée, hautement disponible basée sur Raft
    (évite la dépendance à une base externe, plus simple à déployer, meilleure performance)

  • Reconfiguration dynamique de Raft sans interruption des opérations sur le cluster.

  • Services gérés avec une API déclarative
    (qui applique l'état souhaité grâce à la boucle de réconciliation)

  • Intégré avec les réseaux superposés et la répartition de charge

  • Accent important sur la sécurité:

    • génération automatique des clés et signatures TLS; rotation automatique des certificats
    • chiffrement complet du plan de données; rotation automatique des clés
    • architecture du privilège moindre (faille d'un noeud ≠ faille du cluster)
    • chiffrement sur disque avec phrase de passe optionnelle

swarm/swarmkit.md

510 / 801

Où est la base de données clé-valeur

  • Bien des systèmes d'orchestration utilisent une base clé-valeur exploitée par un algorithme de consensus
    (k8s->etcd->Raft, mesos->zookeeper->ZAB, etc.)

  • SwarmKit implémente l'algorithme Raft directement (Nomad est similaire en ce point, merci à @cbednarski, @diptanu entre autres de l'avoir rappelé!)

  • Analogie offert par @aluzzardi:

    C'est comme les B-trees et les SGBD. Ce sont différentes couches, souvent associées. Mais on n'a pas besoin de lancer un serveur SQL complet, si on a juste besoin d'indexer quelques données.

  • Par conséquent, l'orchestrateur a directement accès à la donnée
    (l'original de la donnée est stocké dans la mémoire de l'orchestrateur)

  • Plus simple, facile à déployer et administrer; et aussi plus rapide

swarm/swarmkit.md

511 / 801

Concepts de SwarmKit (1/2)

  • Un cluster est composé d'au moins une node (plus de préférence)

  • Une node peut prendre le rôle de manager ou worker

  • Un manager prend une part active dans le consensus Raft, et conserve le log du Raft

  • On peut dialoguer avec un manager en utilisant l'API SwarmKit

  • Un manager est élu en tant que leader; les autres managers ne font que lui transmettre les demandes

  • Les workers prennent leurs ordres des managers

  • Tous (workers et managers) peuvent faire tourner des conteneurs

swarm/swarmkit.md

512 / 801

Illustration

Sur la prochaine diapo:

  • baleines = noeuds (workers et managers)

  • singes = managers

  • singe violet = leader

  • singes gris = suiveurs

  • triangle en pointillés = protocole Raft

swarm/swarmkit.md

513 / 801

Concepts de SwarmKit (2/2)

  • Les managers exposent l'API SwarmKit

  • Via cette API, on peut demander à lancer un service

  • Un service est spécifié par son état souhaité: quelle image, combien d'instances, etc.

  • Le leader utilise différent sous-systèmes pour décomposer les services en tasks:
    orchestrateur, ordonnanceur, allocateur, répartiteur

  • Une task correspond à un conteneur spécifique, assigné à une node spécifique

  • Les Nodes savent quelles tasks devraient tourner, et feront lancer et stopper leurs conteneurs en accord (via l'API du Docker Engine)

Vous pouvez vous référer à la NOMENCLATURE du dépôt SwarmKit pour plus de détails. swarm/swarmkit.md

515 / 801

Image separating from the next chapter

516 / 801

Déclaratif vs Impératif

(automatically generated title slide)

517 / 801

Déclaratif vs Impératif

  • Notre orchestrateur de conteneurs insiste fortement sur sa nature déclarative

  • Déclaratif:

    Je voudrais une tasse de thé

  • Impératif:

    Faire bouillir de l'eau. Verser dans la théière. Ajouter les feuilles de thé. Infuser un moment. Servir dans une tasse.

518 / 801

Déclaratif vs Impératif

  • Notre orchestrateur de conteneurs insiste fortement sur sa nature déclarative

  • Déclaratif:

    Je voudrais une tasse de thé

  • Impératif:

    Faire bouillir de l'eau. Verser dans la théière. Ajouter les feuilles de thé. Infuser un moment. Servir dans une tasse.

  • Le mode déclaratif semble plus simple au début...

519 / 801

Déclaratif vs Impératif

  • Notre orchestrateur de conteneurs insiste fortement sur sa nature déclarative

  • Déclaratif:

    Je voudrais une tasse de thé

  • Impératif:

    Faire bouillir de l'eau. Verser dans la théière. Ajouter les feuilles de thé. Infuser un moment. Servir dans une tasse.

  • Le mode déclaratif semble plus simple au début...

  • ... tant qu'on sait comment préparer du thé

shared/declarative.md

520 / 801

Déclaratif vs Impératif

  • Ce que le mode déclaratif devrait vraiment être:

    Je voudrais une tasse de thé, obtenue en versant une infusion¹ de feuilles de thé dans une tasse.

521 / 801

Déclaratif vs Impératif

  • Ce que le mode déclaratif devrait vraiment être:

    Je voudrais une tasse de thé, obtenue en versant une infusion¹ de feuilles de thé dans une tasse.

    ¹Une infusion est obtenue en laissant l'objet infuser quelques minutes dans l'eau chaude².

522 / 801

Déclaratif vs Impératif

  • Ce que le mode déclaratif devrait vraiment être:

    Je voudrais une tasse de thé, obtenue en versant une infusion¹ de feuilles de thé dans une tasse.

    ¹Une infusion est obtenue en laissant l'objet infuser quelques minutes dans l'eau chaude².

    ²Liquide chaud obtenu en le versant dans un contenant³ approprié et le placer sur la gazinière.

523 / 801

Déclaratif vs Impératif

  • Ce que le mode déclaratif devrait vraiment être:

    Je voudrais une tasse de thé, obtenue en versant une infusion¹ de feuilles de thé dans une tasse.

    ¹Une infusion est obtenue en laissant l'objet infuser quelques minutes dans l'eau chaude².

    ²Liquide chaud obtenu en le versant dans un contenant³ approprié et le placer sur la gazinière.

    ³Ah, finalement, des conteneurs! Quelque chose qu'on maitrise. Mettons-nous au boulot, n'est-ce pas?

524 / 801

Déclaratif vs Impératif

  • Ce que le mode déclaratif devrait vraiment être:

    Je voudrais une tasse de thé, obtenue en versant une infusion¹ de feuilles de thé dans une tasse.

    ¹Une infusion est obtenue en laissant l'objet infuser quelques minutes dans l'eau chaude².

    ²Liquide chaud obtenu en le versant dans un contenant³ approprié et le placer sur la gazinière.

    ³Ah, finalement, des conteneurs! Quelque chose qu'on maitrise. Mettons-nous au boulot, n'est-ce pas?

Saviez-vous qu'il existait une norme ISO spécifiant comment infuser le thé?

shared/declarative.md

525 / 801

Déclaratif vs Impératif

  • Système impératifs:

    • plus simple

    • si une tache est interrompue, on doit la redémarrer de zéro

  • Système déclaratifs:

    • si une tache est interrompue (ou si on arrive en plein milieu de la fête), on peut déduire ce qu'il manque, et on complète juste par ce qui est nécessaire.

    • on doit être en mesure d'observer le système

    • ... et de calculer un "diff" entre ce qui tourne en ce moment et ce que nous souhaitons

shared/declarative.md

526 / 801

Image separating from the next chapter

527 / 801

Mode Swarm

(automatically generated title slide)

528 / 801

Mode Swarm

  • Depuis la version 1.12, le Docker Engine embarque SwarmKit

  • Toutes les fonctions SwarmKit sont mise en "sommeil" jusqu'à activer le "Mode Swarm"

  • Exemples de commandes Swarm Mode:

    • docker swarm (active le mode Swarm; rejoint un Swarm; ajuste les paramètres du cluster)

    • docker node (affiche les nodes; désigne les managers; gère les nodes)

    • docker service (crée et gère les services)

  • L'API Docker expose les mêmes concepts

  • L'API SwarmKit est aussi exposée (sur une socket séparée)

swarm/swarmmode.md

529 / 801

Le mode Swarm doit être activé expressément

  • Par défaut, tout ce code nouveau est inactif

  • Le mode Swarm doit être activé, "déverrouillant" ainsi les fonctions SwarmKit
    (services, réseaux superposés prêts à l'emploi, etc.)

  • Essayer une commande spéciale Swarm:
    docker node ls
530 / 801

Le mode Swarm doit être activé expressément

  • Par défaut, tout ce code nouveau est inactif

  • Le mode Swarm doit être activé, "déverrouillant" ainsi les fonctions SwarmKit
    (services, réseaux superposés prêts à l'emploi, etc.)

  • Essayer une commande spéciale Swarm:
    docker node ls

Vous aurez un message d'erreur:

Error response from daemon: This node is not a swarm manager. [...]

swarm/swarmmode.md

531 / 801

Image separating from the next chapter

532 / 801

Créer notre premier Swarm

(automatically generated title slide)

533 / 801

Créer notre premier Swarm

  • Le cluster est initialisé avec docker swarm init

  • Cette commande devrait être lancée depuis la première node d'amorçage.

  • NE PAS exécuter docker swarm init sur d'autres nodes!

    Vous auriez plusieurs cluster disjoints.

  • Créer notre cluster depuis node1:
    docker swarm init
534 / 801

Utiliser une interface séparée pour le circuit de données

  • Vous pouvez indiquer différentes interfaces (ou adresses IP) pour le contrôle et la donnée.

  • On précisera le circuit du plan de contrôle avec --advertise-addr et --listen-addr

    (Cela sera utile pour la communication manager/worker dans SwarmKit, l'élection du leader, etc.)

  • On précisera le circuit du plan de données avec --data-path-addr

    (Cela sera utilisé pour le trafic entre conteneurs)

  • Les deux options acceptent soit une adresse IP, ou un nom d'interface

    (En indiquant un nom d'interface, Docker choisira sa première adresse IP)

swarm/creatingswarm.md

539 / 801

Génération de jeton

  • Dans la réponse à docker swarm init, nous avons un message confirmant que notre noeud est maintenant le (seul) manager:

    Swarm initialized: current node (8jud...) is now a manager.
  • Docker a généré deux jetons de sécurité (comme une phrase de passe, ou un mot de passe) pour notre cluster

  • La ligne de commande nous montre la commande à lancer sur les autres nodes pour les ajouter au cluster sous forme d'un jeton de sécurité:

    To add a worker to this swarm, run the following command:
    docker swarm join \
    --token SWMTKN-1-59fl4ak4nqjmao1ofttrc4eprhrola2l87... \
    172.31.4.182:2377

swarm/creatingswarm.md

540 / 801

Vérifier que le mode Swarm est activé

  • Lancer la commande classique docker info:
    docker info

L'affichage devrait comporter:

Swarm: active
NodeID: 8jud7o8dax3zxbags3f8yox4b
Is Manager: true
ClusterID: 2vcw2oa9rjps3a24m91xhvv0c
...

swarm/creatingswarm.md

541 / 801

Notre première commande en mode Swarm

  • Essayons exactement la même commande que précédemment
  • Lister les noeuds (enfin, le seul) de notre cluster:
    docker node ls

L'affichage devrait ressembler à ce qui suit:

ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
8jud...ox4b * node1 Ready Active Leader

swarm/creatingswarm.md

542 / 801

Ajouter des noeuds au Swarm

  • Un cluster avec une seule node n'est pas marrant

  • Ajoutons node2!

  • On a besoin du token qu'on a vu plus tôt

543 / 801

Ajouter des noeuds au Swarm

  • Un cluster avec une seule node n'est pas marrant

  • Ajoutons node2!

  • On a besoin du token qu'on a vu plus tôt

  • Vous l'avez noté quelque part, pas vrai?

544 / 801

Ajouter des noeuds au Swarm

  • Un cluster avec une seule node n'est pas marrant

  • Ajoutons node2!

  • On a besoin du token qu'on a vu plus tôt

  • Vous l'avez noté quelque part, pas vrai?

  • Pas de panique, on peut le retrouver facilement 😏

swarm/creatingswarm.md

545 / 801

Ajouter des noeuds au Swarm

  • Afficher le token à nouveau:

    docker swarm join-token worker
  • Se connecter à node2:

    ssh node2
  • Copier-coller la commande docker swarm join ...
    (celle qui a été affichée juste avant)

swarm/creatingswarm.md

546 / 801

Vérifier que la node a été vraiment ajoutée

  • Restez sur node2 pour l'instant!
  • On peut encore lancer docker info pour vérifier que la node participe au Swarm:
    docker info | grep ^Swarm
  • Toutefois, les commandes Swarm ne passeront pas; comme, par ex.:
    docker node ls
  • C'est parce que le noeud nouvellement ajouté est un worker
  • Seuls les managers peuvent répondre à des commandes spécial Swarm.

swarm/creatingswarm.md

547 / 801

Afficher notre cluster

  • Retournons sur node1 et voyons quelle tête a notre cluster
  • Basculer vers node1 (avec exit, Ctrl-D ...)
  • Afficher le cluster depuis node1, qui est un manager:
    docker node ls

L'affichage devrait être similaire à ce qui suit:

ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
8jud...ox4b * node1 Ready Active Leader
ehb0...4fvx node2 Ready Active

swarm/creatingswarm.md

548 / 801

Sous le capot: docker swarm init

Quand on lance docker swarm init:

  • une paire de clés est créée pour le CA racine de notre Swarm

  • une paire de clés est créée sur la première node

  • un certificat est émis pour cette node

  • les tokens d'entrée sont créés

swarm/creatingswarm.md

549 / 801

Sous le capot: tokens d'entrée

Il existe un jeton pour entrer en tant que worker, et un autre pour entrer en tant que manager.

Les tokens d'entrée ont deux parties:

  • une clé secrète (empêchant les nodes non autorisées d'entrer)

  • une empreinte digitale du certificat racine du CA (empêchant les attaques MITM)

Si un token est compromis, on peut en changer instantanément avec:

docker swarm join-token --rotate <worker|manager>

swarm/creatingswarm.md

550 / 801

Sous le capot: docker swarm join

Quand une node rejoint le Swarm:

  • on lui délivre sa propre paire de clés, signée par le CA racine

  • si cette node est un manager:

    • elle rejoint le consensus Raft
    • elle se connecte au leader en cours
    • elle accepte des connexions de la part des workers
  • si cette node est un worker:

    • elle se connecte à un des managers (leader ou follower)

swarm/creatingswarm.md

551 / 801

Sous le capot: communication de cluster

  • Le plan de contrôle est chiffré avec AES-GCM; une rotation de clés intervient toutes les 12 heures

  • L'identification est implémentée via un TLS mutuel; la rotation de certificats se fait tous les 90 jours

    (docker swarm update permet de changer ce délai, ou d'utiliser un CA externe)

  • Le plan de données (communication entre conteneurs) n'est pas chiffré par défaut

    (mais on peut l'activer au niveau de chaque réseau, avec IPSEC, exploitant un cryptage matériel si disponible)

swarm/creatingswarm.md

552 / 801

Sous le capot: je veux en savoir plus!

Revisitez les concepts de SwarmKit:

  • Docker 1.12 Swarm Mode Deep Dive Part 1: Topology (video)

  • Docker 1.12 Swarm Mode Deep Dive Part 2: Orchestration (video)

Quelques présentations du Docker Distributed Systems Summit à Berlin:

  • Heart of the SwarmKit: Topology Management (slides)

  • Heart of the SwarmKit: Store, Topology & Object Model (slides) (video)

Et les présentations Black Belt à DockerCon

DC17US: Everything You Thought You Already Knew About Orchestration (video)

DC17EU: Container Orchestration from Theory to Practice (video)

swarm/creatingswarm.md

553 / 801

Ajouter plus de nodes manager

  • Jusqu'ici, on a juste un manager (node1)

  • Si on le perd, on perd le quorum, et c'est très grave!

  • Les conteneurs sur les autres nodes n'auront pas de problème...

  • Si le manager est parti pour de bon, on va devoir réparer à la main!

  • Personne ne veut faire ça... donc passons en haute disponibilité

swarm/morenodes.md

554 / 801

Monter un cluster complet

  • Récupérons le token, et lançons la commande sur la dernière node avec SSH
  • Obtenir le jeton manager:

    TOKEN=$(docker swarm join-token -q manager)
  • Ajouter la node qui reste:

    ssh node3 docker swarm join --token $TOKEN node1:2377

C'était facile.

swarm/morenodes.md

555 / 801

Contrôler le Swarm depuis d'autres nodes

  • Essayer la commande suivante sur les différentes nodes:
    docker node ls

Sur les noeuds manager:
vous verrez la liste des nodes, avec un *, indiquant la node qui répond.

Sur les nodes non-manager:
un message d'erreur s'affichera vous disant que cette node n'est pas un manager.

Comme vu plus haut, on ne peut contrôler le Swarm que depuis une node manager.

swarm/morenodes.md

556 / 801

Changer le rôle d'un noeud à la volée

  • On peut changer le rôle d'une node dynamiquement:

    docker node promote nodeX → passer nodeX en manager
    docker node demote nodeX → passer nodeX en worker

  • Afficher la liste courante des noeuds:

    docker node ls
  • Promouvoir tout worker en manager

    docker node promote <node_name_or_id>

swarm/morenodes.md

557 / 801

Combien de managers sont conseillés?

  • 2N+1 noeuds peuvent (et vont) résister à N pannes
    (vous pouvez mettre un nombre pairs de managers mais ça n'a aucun intérêt)
558 / 801

Combien de managers sont conseillés?

  • 2N+1 noeuds peuvent (et vont) résister à N pannes
    (vous pouvez mettre un nombre pairs de managers mais ça n'a aucun intérêt)

  • 1 manager = pas de tolérance de panne

  • 3 managers = tolérance d'une panne

  • 5 managers = tolérance de 2 pannes (ou 1 panne pendant 1 maintenance)

  • 7 managers ou plus = là vous forcez peut-être un peu sur l'archi

voir Docker's admin guide à propos des pannes de nodes et la redondance en centre de données

swarm/morenodes.md

559 / 801

Pourquoi ne pas passer toutes les nodes en manager?

  • Avec Raft, les écritures doivent atteindre (et être confirmées par) tous les noeuds.

  • Par conséquent, c'est plus dur d'atteindre un consensus dans des groupes plus grands.

  • Un seul manager est Leader (en écriture), donc "plus de managers ≠ plus de capacité"

  • Les managers devraient être < 10ms de latence entre eux

  • Ces facteurs de conceptions nous amènent à de meilleurs designs

swarm/morenodes.md

560 / 801

Que ferait McGyver à notre place?

  • Garder les managers dans une région (ou multi-zone/centre de données/rack)

  • Groupe de 3 à 5 noeuds: tous en managers. Au-delà de 5, séparer les managers et workers

  • Groupe de 10-100 noeuds: prendre 5 noeuds "stables" pour être les managers

  • Groupe de plus de 100 noeuds: surveiller le CPU et la RAM des managers

    • 16Go de mémoire ou plus, 4 CPUs au moins, disque SSD pour les E/S du Raft
    • autrement, répartir vos noeuds en plusieurs clusters plus petits

Astuce de pro du cloud: utiliser des groupes auto-scalings distincts pour les managers et workers

Voir le document chez Docker Inc. "Running Docker at scale"

swarm/morenodes.md
561 / 801

Quelle est la limite maximum?

  • On n'en sait rien!

  • Tests internes chez Docker Inc.: 1000 à 10000 noeuds sans problème

    • déployés sur une région de cloud

    • une des principales leçons était "vous allez avoir besoin d'un plus gros manager"

  • Tests menés par la communauté: 4700 noeuds hétérogènes à travers le net

    • ça marche et c'est tout, à condition d'avoir les ressources nécessaires

    • plus de nodes demandent plus de CPU et réseau pour le manager; plus de conteneurs demande plus de RAM

    • l'orchestration à très large échelle (70 000 conteneurs) est lente et difficile (mais ça s'arrange!)

swarm/morenodes.md

562 / 801

Méthodes de déploiements dans la vie réelle

563 / 801

Méthodes de déploiements dans la vie réelle

Lancer les commandes à la main via SSH

564 / 801

Méthodes de déploiements dans la vie réelle

Lancer les commandes à la main via SSH

(lol je blague)

565 / 801

Méthodes de déploiements dans la vie réelle

Lancer les commandes à la main via SSH

(lol je blague)

swarm/morenodes.md

566 / 801

Image separating from the next chapter

567 / 801

Lancer notre premier service Swarm

(automatically generated title slide)

568 / 801

Lancer notre premier service Swarm

  • Comment lancer des services? Version courte:

    docker rundocker service create

  • Créer un service basé sur un conteneur Alpine qui ping les serveurs de Google:

    docker service create --name pingpong alpine ping 8.8.8.8
  • Vérifier le résultat:

    docker service ps pingpong

swarm/firstservice.md

569 / 801

Consulter les logs de service

(Nouveau dans Docker Engine 17.05)

  • Tout comme docker logs affiche la sortie d'un conteneur local spécifique...

  • ... docker service logs montre les logs de tous les conteneurs d'un certain service

  • Vérifier la sortie de notre commande ping:
    docker service logs pingpong

Les options --follow et --tail sont disponibles, ainsi que quelques autres.

Note: par défaut, quand un conteneur est détruit (par ex. en baisse de charge), ses logs sont perdus.

swarm/firstservice.md

570 / 801

Chercher où tourne notre conteneur

  • La commande docker service ps va nous dire où est placé notre conteneur
  • Trouver quel NODE fait tourner notre conteneur:

    docker service ps pingpong
  • Sur Play-With-Docker, cliquer sur l'onglet de cette node, ou passer par DOCKER_HOST

  • Autrement, se connecter avec ssh sur cette ou lancer $(eval docker-machine env node...)

swarm/firstservice.md

571 / 801

Afficher les logs du conteneur

  • Constater que le conteneur tourne bien, et récupérer son ID:

    docker ps
  • Afficher ses logs:

    docker logs containerID
  • Retourner sur node1 après coup

swarm/firstservice.md

572 / 801

Dimensionner notre service

  • Les services peuvent monter en charge avec une pincée de docker service update
  • Escalader le service pour assurer 2 clones par node:

    docker service update pingpong --replicas 6
  • Vérifier que nous avons bien 2 conteneurs sur la node en cours:

    docker ps

swarm/firstservice.md

573 / 801

Suivre le progrès du déploiement avec --detach

(Nouveauté du Docker Engine 17.10)

  • La ligne de commande surveille les commandes qui créent/modifient/suppriment les services.

  • En pratique, --detach=false est la valeur par défaut

    • opération synchrone
    • le ligne de commande affiche la progression de notre requête
    • elle sort uniquement si l'opération est terminée
    • Ctrl-C permet de récupérer la main à tout moment
  • --detach=true

    • opération asynchrone
    • la ligne de commande ne fait qu'envoyer notre requête
    • elle sort dès que la requête a été écrite dans Raft

swarm/firstservice.md

574 / 801

--detach ou ne pas --detach, là est la question

  • --detach=false

    • super en apprentissage, pour voir ce qui se passe
    • pas mal aussi lors de déploiements complexes à orchestrer
      (quand on veut attendre qu'un service se lance, avant de démarrer le suivant)
  • --detach=true

    • super pour des opérations indépendantes qui peuvent être parallélisées.

    • super pour des scripts non-interactifs (où personne ne regarde de toute façon)

--detach=true ne va pas plus vite. C'est juste qu'il n'attend pas la fin d'exécution.

swarm/firstservice.md

575 / 801

Évolutions de --detach

  • Docker Engine 17.10 et plus: par défaut en --detach=false

  • A partir de Docker Engine 17.05 à 17.09: par défaut en --detach=true

  • Avant Docker 17.05: --detach n'existait pas.

    (Vous pouvez le remplacer par ex. avec watch docker service ps <serviceID>)

swarm/firstservice.md

576 / 801

--detach en action

  • Escalader le service pour garantir 3 conteneurs par node:

    docker service update pingpong --replicas 9 --detach=false
  • Et monter ensuite à 4 replicas par node:

    docker service update pingpong --replicas 12 --detach=true

swarm/firstservice.md

577 / 801

Exposer un service

  • Exposer un service est possible, avec deux propriétés spéciales:

    • le port public est disponible sur chaque node du Swarm,

    • les requêtes provenant du port public sont réparties entre toutes les instances.

  • Techniquement, on utilise l'option -p/--publish; pour faire vite:

    docker run -p → docker service create -p

  • Si vous indiquer un seul numéro de port, il sera mappé sur un port démarrant à 30000
    (vs. 32768 pour un mappage de conteneur unique)

  • On peut indiquer deux numéros de port pour configurer le numéro de port public
    (tout comme avec docker run -p)

swarm/firstservice.md

578 / 801

Exposer ElasticSearch sur son port par défaut

  • Créer un service ElasticSearch (et lui donner un nom tant qu'on y est):
    docker service create --name search --publish 9200:9200 --replicas 5 \
    elasticsearch:2

Note: ne pas oublier le tag :2!

La dernière version de l'image ElasticSearch ne peut démarrer sans une configuration obligatoire.

swarm/firstservice.md

579 / 801

Cycle de vie des tâches

  • Pendant un déploiement, vous pourrez voir les étapes suivantes:

    • assigned, la task a été assignée à un noeud spécifique

    • preparing, qui se résume à "téléchargement de l'image"

    • starting

    • running

  • Quand une tâche est terminée (stopped, killed, etc.) elle ne peut être redémarrée

    (Une tâche de remplacement sera créée)

swarm/firstservice.md

580 / 801

Tester notre service

  • Nous avons attaché le port 9200 sur les nodes au port 9200 des conteneurs.

  • Essayons de communiquer avec ce port!

  • Lancer la commande suivante:
    curl 127.0.0.1:9200

(Si vous recevez un Connection refused: félicitations, vous êtes vraiment rapide! Essayez encore.)

ElasticSearch renvoie un petit doc. JSON avec des informations de base sur cette instance; y compris un nom de super-héros aléatoire.

swarm/firstservice.md

582 / 801

Tester la répartition de charge

  • Si on répète notre commande curl encore et encore, on lire plusieurs noms.
  • Envoyer 10 requêtes, et voir quelles instances répondent:
    for N in $(seq 1 10); do
    curl -s localhost:9200 | jq .name
    done

Note: si vous n'avez pas jq sur votre instance PWD, il suffit de l'installer:

apk add --no-cache jq

swarm/firstservice.md

583 / 801

Résultats du répartiteur de charge

Le trafic est géré par le maillage de routage de notre cluster

Chaque requête est tour à tour transférée à une des instances.

Note: si vous essayez d'accéder au service depuis un navigateur, vous verrez probablement la même instance encore et encore, c'est parce que votre navigateur (contrairement à curl) essaiera de ré-utiliser la même connexion.

swarm/firstservice.md

584 / 801

Sous le capot du routing mesh

  • La répartition de charge est réalisée avec IPVS

  • IPVS est un répartiteur de charge de haute performance, interne au noyau

  • Il existe depuis quelque temps déjà (introduit dans la version 2.4)

  • Chaque noeud exécute un répartiteur de charge local

    (Ce qui permet aux connections d'être routées directement à leur destination, sans sauts superflus)

swarm/firstservice.md

586 / 801

Gérer le trafic entrant (ingress)

Il y a bien des manières de s'occuper du trafic entrant dans un cluster SWarm.

  • Placer tout (ou partie) des noeuds dans un champ A du DNS (marche bien pour les clients web)

  • Assigner tout ou partie des nodes à un répartiteur de charge externe (ELB, etc.)

  • Utiliser une IP virtuelle et s'assurer qu'elle est assignée à une node "vivante"

  • etc.

swarm/firstservice.md

587 / 801

Gérer le trafic HTTP

  • Le routing mesh TCP n'interprète par les en-tête HTTP

  • Si on veut placer plusieurs services HTTP sur le port 80/443, il nous manque un truc.

  • On peut installer NGINX ou HAProxy sur le port 80/443 pour router les requêtes vers le bon service, mais ils auraient besoin d'écouter le Swarm pour ajuster leur config.

589 / 801

Gérer le trafic HTTP

  • Le routing mesh TCP n'interprète par les en-tête HTTP

  • Si on veut placer plusieurs services HTTP sur le port 80/443, il nous manque un truc.

  • On peut installer NGINX ou HAProxy sur le port 80/443 pour router les requêtes vers le bon service, mais ils auraient besoin d'écouter le Swarm pour ajuster leur config.

  • Docker EE fournit son propre routeur de niveau 7

    • Les labels de service comme com.docker.lb.hosts=<FQDN> sont détectés automatiquement via l'API Docker et mettent à jour leur configuration à la volée.
590 / 801

Gérer le trafic HTTP

  • Le routing mesh TCP n'interprète par les en-tête HTTP

  • Si on veut placer plusieurs services HTTP sur le port 80/443, il nous manque un truc.

  • On peut installer NGINX ou HAProxy sur le port 80/443 pour router les requêtes vers le bon service, mais ils auraient besoin d'écouter le Swarm pour ajuster leur config.

  • Docker EE fournit son propre routeur de niveau 7

    • Les labels de service comme com.docker.lb.hosts=<FQDN> sont détectés automatiquement via l'API Docker et mettent à jour leur configuration à la volée.
  • Deux options open source populaires:

    • Traefik - reconnu, riche de fonctions, requiert de tourner sur les managers (par défaut), nécessite une DB clé-valeur pour la haute disponibilité.

    • Docker Flow Proxy - utilise HAProxy, orienté Swarm, par @vfarcic

swarm/firstservice.md

591 / 801

Vous devriez utiliser les labels

  • "Labelliser": verbe, par ex. attacher des informations arbitraires aux services

  • Exemples:

    • le vhost HTTP d'une web app ou d'un service web

    • planifier la sauvegarde de service à données persistentes

    • propriétaire d'un service (pour la facturation, l'astreinte, etc.)

    • grouper les objets Swarm entre eux (services, volumes, configs, secrets, etc.)

swarm/firstservice.md

592 / 801

Astuce de pro pour gérer le trafic ingress

  • Il est possible d'utiliser un réseau local avec les services Swarm

  • Cela signifie qu'on peut faire quelque chose comme:

    docker service create --network host --mode global traefik ...

    (Ça va lancer le load balancer traefik sur chaque noeud de votre cluster, sur le reseau host)

  • On y gagne une performance native (pas de iptables, ni proxy, ni rien!)

  • Le répartiteur de charge "verra" les adresses IP des clients

  • Mais: le conteneur ne peut être en même temps dans le réseau hostet dans un autre.

    (Vous devrez router le trafic aux conteneurs via des ports exposés ou des sockets UNIX)

swarm/firstservice.md

593 / 801

Utiliser les réseaux locaux (host, macvlan...)

  • Il est possible de connecter les services aux réseaux locaux

  • Passer par le réseau host est plutôt simple

    (Avec les réserves décrites dans la diapo précédente)

  • Pour d'autres pilotes réseaux, c'est un poil plus compliqué

    (l'allocation d'IP peut nécessiter une coordination entre les nodes)

  • Voir par exemple ce guide pour bien démarrer avec macvlan

  • Voir cette PR pour plus d'information sur les pilotes réseaux locaux dans le mode Swarm

swarm/firstservice.md

594 / 801

Visualiser le placement de conteneurs

  • Jouons avec l'API Docker!
  • Lancer cette appli simple-mais-sympa de visualisation:

    cd ~/container.training/stacks
    docker-compose -f visualizer.yml up -d

swarm/firstservice.md

595 / 801

Se connecter à la web app de visualisation

  • Elle fait tourner un serveur web sur le port 8080
  • Faire pointer le navigateur sur le port 8080 de votre node1 (son adresse IP)

    (Sur Play-With-Docker, cliquez sur le bouton (8080))

  • L'appli web met à jour l'affichage à la volée (pas besoin de recharger la page)

  • Elle affiche juste les services Swarm (pas les conteneurs indépendants)

  • Elle indique quand les noeuds sont disponibles ou pas.

  • Il y reste quelques couacs (ce n'est pas du logiciel de Classe-Entreprise, ISO-9001, à résistance thermo-nucléaire)

swarm/firstservice.md

596 / 801

Pourquoi c'est plus important que ça

  • Le visualiseur accède à l'API Docker de l'intérieur du conteneur

  • C'est un motif courant: lancer des outils de managements dans un conteneur

  • Au lieu d'afficher notre cluster, on pourrait traiter les logs, les métriques, la montée en charge automatique...

  • On peut le lancer en tant que service, aussi! On ne le fera pas tout de suite, mais la commande ressemblerait à:

    docker service create \
    --mount source=/var/run/docker.sock,type=bind,target=/var/run/docker.sock \
    --name viz --constraint node.role==manager ...

Crédits: le code de visualization code a été écrit par Francisco Miranda.

Mano Marks l'a adapté au Swarm et le maintient.

swarm/firstservice.md

597 / 801

Nettoyer nos services

  • Avant de poursuivre, on va supprimer ces services

  • docker service rm accepte plusieurs noms ou IDs de services

  • docker service ls accepte l'option -q

  • Un bout de code Shell par jour éloigne la dette technique pour toujours.

  • Supprimer tous les services avec cette commande:
    docker service ls -q | xargs docker service rm

swarm/firstservice.md

598 / 801

Image separating from the next chapter

599 / 801

Notre appli sur Swarm

(automatically generated title slide)

600 / 801

Notre appli sur Swarm

Dans cette partie, nous allons:

  • générer les images pour notre appli,

  • envoyer ces images dans un registre,

  • lancer les services basés sur ces images.

swarm/ourapponswarm.md

601 / 801

Pourquoi le besoin de transférer nos images?

  • Avec docker-compose up, les images sont générées pour nos services

  • Ces images sont présentes uniquement sur la node locale

  • Nous avons besoin de distribuer ces images à travers tout le Swarm

  • Le plus simple pour ceci est d'utiliser un Registry Docker

  • Une fois nos images transférées sur un registre, nous les téléchargeons au moment de créer nos services.

swarm/ourapponswarm.md

602 / 801

Builder, transférer et lancer, pour un seul service

Si nous avions seulement un service (généré à partir d'un Dockerfile dans le dossier en cours), notre processus aurait cette tête:

docker build -t jpetazzo/doublerainbow:v0.1 .
docker push jpetazzo/doublerainbow:v0.1
docker service create jpetazzo/doublerainbow:v0.1

Il nous reste juste à l'adapter à notre application, qui comporte 4 services!

swarm/ourapponswarm.md

603 / 801

Le plan

  • Lancer un build sur notre node locale (node1)

  • Étiquetter les images pour les nommer en localhost:5000/<nom-du-service>

  • Téléverser dans le registre

  • Créer les services grâce à ces images

swarm/ourapponswarm.md

604 / 801

Quel registre devons-nous utiliser?

  • Docker Hub

    • hébergé par Docker Inc.
    • exige un compte (gratuit, sans carte bancaire)
    • images publiques (sauf si vous payez)
    • localisé chez AWS sur EC2 us-east-1
  • Docker Trusted Registry

    • produit commercial auto-hébergé
    • exige un abonnement (avec essai gratuit de 30 jours)
    • images publiques ou privées
    • localisé où vous le souhaitez
  • Docker open source registry

    • stockage d'image basique en mode auto-hébergé
    • n'exige rien du tout, aucun pré-requis
    • n'offre pas grand-chose non plus
    • localisé où vous voulez
  • Tout plein d'autres options dans le cloud ou pas

    • AWS/Azure/Google Container Registry
    • GitLab, Quay, JFrog
    • Portus, Harbor

swarm/ourapponswarm.md

605 / 801

Utiliser Docker Hub

Si on voulait passer par Docker Hub...

  • On devrait se connecter au Docker Hub

    docker login
  • Et dans les diapos suivantes, on pourrait utiliser notre compte Docker Hub

    (par ex., jpetazzo au lieu de l'adresse du registre, i.e 127.0.0.1:5000)

swarm/ourapponswarm.md

606 / 801

Utiliser Docker Trusted Registry

Si on voulait utiliser DTR, on devrait...

  • S'assurer d'avoir un compte Docker Hub

  • Activer un abonnement Docker EE

  • Installer DTR sur nos machines

  • Marquer dtraddress:port/user au lieu de l'adresse du registre

Tout ça est hors de notre périmètre

swarm/ourapponswarm.md

607 / 801

Image separating from the next chapter

608 / 801

Héberger notre propre Registry

(automatically generated title slide)

609 / 801

Héberger notre propre Registry

  • On souhaite lancer un conteneur registry

  • Il va stocker les images et layers dans le système de fichiers local
    (mais on peut y ajouter un fichier de conf pour passer sur S3, Swift, etc.)

  • Docker exige TLS pour communiquer avec le registre

    • excepté pour les registres sur 127.0.0.1 (i.e localhost)

    • ou avec l'option globale --insecure-registry

  • Notre stratégie: rendre public le conteneur du registre sur le port 5000,
    pour qu'il soit disponible via 127.0.0.1:5000 sur chaque node

swarm/hostingregistry.md

610 / 801

Déployer le registre

  • Nous allons créer un service à instance unique, en publiant son port sur le cluster en entier
  • Déclarer le service du registre:

    docker service create --name registry --publish 5000:5000 registry
  • Essayer maintenant la commande suivante: on devrait voir {"repositories":[]}:

    curl 127.0.0.1:5000/v2/_catalog

swarm/hostingregistry.md

611 / 801

Tester notre registre local

  • On peut re-tag une petite image, et la pousser dans le registre
  • Vérifier qu'on a une image busybox, et y rajouter un tag:

    docker pull busybox
    docker tag busybox 127.0.0.1:5000/busybox
  • La livrer sur le registre:

    docker push 127.0.0.1:5000/busybox

swarm/testingregistry.md

612 / 801

Vérifier ce qui est dans le registre local

  • L'API du Registry a des points d'entrée pour vérifier son contenu
  • Vérifier que notre image busybox est bien présente en local:
    curl http://127.0.0.1:5000/v2/_catalog

La commande curl devrait afficher:

{"repositories":["busybox"]}

swarm/testingregistry.md

613 / 801

Intégration avec Compose

  • On a vu comment build à la main, étiquetter et pousser les images dans un registre.

  • Mais ...

614 / 801

Intégration avec Compose

  • On a vu comment build à la main, étiquetter et pousser les images dans un registre.

  • Mais ...

Je suis tellement content que mon déploiement se base sur des scripts Shell de taille astronomique

(par M. Personne, vraiment)

615 / 801

Intégration avec Compose

  • On a vu comment build à la main, étiquetter et pousser les images dans un registre.

  • Mais ...

Je suis tellement content que mon déploiement se base sur des scripts Shell de taille astronomique

(par M. Personne, vraiment)

  • Voyons voir comment simplifier ce processus!

swarm/stacks.md

616 / 801

Image separating from the next chapter

617 / 801

Les Stacks Swarm

(automatically generated title slide)

618 / 801

Les Stacks Swarm

  • Compose est super pour le développement local

  • On peut aussi gérer le cycle de vie des images avec

    (i.e générer les images et les pousser dans un registre)

  • Les fichiers Compose en v2 sont bons dans le développement local

  • Les fichiers Compose en v3 sont orientés vers le déploiement en production!

swarm/stacks.md

619 / 801

Fichier Compose version 3

(Nouveau dans Docker Engine 1.13)

  • Pratiquement identique à la version 2

  • Peut être directement appliqué à un cluster Swarm avec les commandes docker stack ...

  • Introduit une section deploy pour spécifier les options Swarm

  • Les limites de ressources sont placées dans cette section deploy

  • Voir ici pour une liste complète de changements

  • Supplante les Distributed Application Bundles

    (Un format JSON décrivant une application; pouvait à l'origine être généré depuis un fichier Compose)

swarm/stacks.md

620 / 801

Notre première stack

On a besoin d'un registre pour déplacer les images de point en point.

Sans ce fichier stack, on devrait taper les commandes suivantes:

docker service create --publish 5000:5000 registry

Dès lors, nous allons le déployer avec le fichier de stack suivant:

version: "3"
services:
registry:
image: registry
ports:
- "5000:5000"

swarm/stacks.md

621 / 801

Vérifier nos fichiers stack

  • Tous les fichiers stack que nous utiliserons sont stockés dans le dossier stacks
  • Aller dans le dossier stack:

    cd ~/container.training/stacks
  • Parcourir registry.yml

    cat registry.yml

swarm/stacks.md

622 / 801

Déployer notre première stack

  • Toutes les commandes de manipulation de stacks commencent avec docker stack

  • En coulisses, ça se traduit par des commandes docker service

  • Une stack porte un nom principal (qui sert aussi de namespace)

  • Une stack est portée avant tout par un fichier Compose de version 3 comme dit plus haut

  • Déployer notre registre local:
    docker stack deploy --compose-file registry.yml registry

swarm/stacks.md

623 / 801

Inspecter les stacks

  • docker stack ps affiche l'état détaillé de tous les services d'une stack
  • Vérifier que notre registre tourne correctement:

    docker stack ps registry
  • Confirmer que nous avons le même affichage avec la commande:

    docker service ps registry_registry

swarm/stacks.md

624 / 801

Particularités d'un déploiement de stack

Notre registre n'est pas exactement identique à celui déployé par docker service create!

  • Chaque stack possède son propre réseau superposé (overlay)

  • Les services de la stack sont connectés à ce réseau
    (sauf si spécifié autrement dans le fichier Compose)

  • Les services récupèrent des alias de réseau correspondant à leur nom dans le fichier Compose
    (tout comme quand Compose lance une appli en version 2)

  • Les services sont nommés explicitement <nom_de_stack>_<nom_de_service>

  • Les services et tâches récupèrent aussi un label interne indiquant à quelle stack ils appartiennent

swarm/stacks.md

625 / 801

Tester notre registre local

  • Accéder au port 5000 de n'importe quelle node nous redirige vers le registre

  • Par conséquent, on peut indiquer localhost:5000 ou 127.0.0.1:5000 comme notre registre

  • Envoyer la requête API qui suit au registre:
    curl 127.0.0.1:5000/v2/_catalog

Ça devrait renvoyer:

{"repositories":[]}

Si ça ne marche pas, ré-essayer encore; le conteneur est peut-être en cours de démarrage.

swarm/stacks.md

626 / 801

Pousser une image vers notre registre local

  • On peut re-tag une petite image, et la pousser vers le registre.
  • Charger l'image busybox, et la re-tag:

    docker pull busybox
    docker tag busybox 127.0.0.1:5000/busybox
  • La transférer:

    docker push 127.0.0.1:5000/busybox

swarm/stacks.md

627 / 801

Vérifier ce qui est dans notre registre local

  • L'API Registre a des points d'entrée pour sélectionner son contenu.
  • S'assurer que notre image busybox est maintenant dans notre registre local:
    curl http://127.0.0.1:5000/v2/_catalog

La commande curl devrait maintenant afficher:

"repositories":["busybox"]}

swarm/stacks.md

628 / 801

Générer et pousser les services d'une stack

  • Avec le fichier Compose version 2 et plus, vous pouvez spécifier à la fois build et image

  • Quand les deux clés sont utilisées:

    • Compose fait "comme si de rien n'était" (activer le build)

    • mais l'image résultante sera nommée selon la valeur de la clé image
      (au lieu de <nom-du-projet>_<nom-du-service>:latest)

    • avec l'avantage qu'on peut la pousser dans un registry avec docker-compose push

  • Exemple:

    webfront:
    build: www
    image: myregistry.company.net:5000/webfront

swarm/stacks.md

629 / 801

Utiliser Compose pour générer et pousser les images

  • Essayer:
    docker-compose -f dockercoins.yml build
    docker-compose -f dockercoins.yml push

Voyons voir à quoi ressemble le fichier dockercoins.yml pendant que les images sont construites et poussées.

swarm/stacks.md

630 / 801
version: "3"
services:
rng:
build: dockercoins/rng
image: ${REGISTRY-127.0.0.1:5000}/rng:${TAG-latest}
deploy:
mode: global
...
redis:
image: redis
...
worker:
build: dockercoins/worker
image: ${REGISTRY-127.0.0.1:5000}/worker:${TAG-latest}
...
deploy:
replicas: 10

swarm/stacks.md

631 / 801

Déployer l'application

  • Maintenant que les images sont sur le registre, on peut déployer notre stack applicative.
  • Créer la stack applicative:
    docker stack deploy --compose-file dockercoins.yml dockercoins

On peut maintenant se connecter à n'importe quel noeud sur le port 8000, et revoir le graphe familier du hachage.

swarm/stacks.md

632 / 801

Maintenir plusieurs environnements

Plusieurs méthodes existent pour gérer les variations entre environnements.

  • Compose charge docker-compose.yml et (s'il existe) docker-compose.override.yml

  • Compose va charger plusieurs fichiers en accumulant l'option -f ou la variable d'environnement COMPOSE_FILE

  • Les fichiers Compose peuvent étendre d'autres fichier Compose, pour y inclure des services:

    web:
    extends:
    file: common-services.yml
    service: webapp

Voir cette page de documentation pour plus de détails sur ces techniques.

swarm/stacks.md

633 / 801

Bon à savoir ...

  • Un fichier Compose en version 3 comporte une section deploy

  • Les versions plus récentes (3.1, ...) ajoutent plus de fonctions (secrets, configs, etc.)

  • Mettre à jour la stack consiste à relancer docker stack deploy

  • On peut changer le service à coups de docker service update ...

  • ... Mais tout changement sera annulé après chaque docker stack deploy

    (C'est le comportement attendu, si on y réfléchit bien!)

  • extends ne marche pas avec docker stack deploy

    (Mais vous pouvez passer par docker-compose config pour "aplatir" votre conf)

swarm/stacks.md

634 / 801

Résumé

  • On a vu comment installer un Swarm

  • On l'a utilisé pour héberger notre propre Registry

  • On a généré nos images de conteneurs

  • On a utilisé la Registry pour héberger ces images

  • On a déployé et escaladé notre application

  • On a vu comment exploiter Compose pour simplifier les déploiements

  • Super boulot à toute l'équipe!

swarm/stacks.md

635 / 801

Image separating from the next chapter

636 / 801

CI/CD avec Docker et l'orchestration

(automatically generated title slide)

637 / 801

CI/CD avec Docker et l'orchestration

Une note rapide à propos de l'intégration rapide et du déploiement

  • Vous n'allez pas monter dans cet atelier vos propres automatisations CI/CD

  • On va tricher un peu en générant les images sur les serveurs hôtes et non sur l'outil "CI".

  • Docker et l'orchestration fonctionne avec tous les outils de CI et de déploiement.

swarm/cicd.md

638 / 801

Processus générique CI/CD

  • En premier, c'est à la CI de build les images, puis de lancer les tests à l'intérieur, avant de les pousser vers la Registry

  • En cas de scan de sécurité, faites-le sur les images générées, après les tests mais avant de les pousser.

  • En option, déployer en continu depuis votre CI, si les phases de build/test/push passent

  • L'outil de CD accèderait ensuite aux noeuds via SSH, ou exploiterait la ligne de commande Docker pour discuter avec le moteur de conteneur distant.

  • Si disponible, on passerait par l'API TCP du Docker Engine (où l'API Swarm vit aussi)

  • Docker KBase Development Pipeline Best Practices

  • Docker KBase Continuous Integration with Docker Hub

  • Docker KBase Building a Docker Secure Supply Chain

swarm/cicd.md

639 / 801

CI-CD with Docker

swarm/cicd.md

640 / 801

Image separating from the next chapter

641 / 801

Mettre à jour les services

(automatically generated title slide)

642 / 801

Mettre à jour les services

  • On doit mettre à jour l'interface web.

  • Pour ce faire, le processus est comme suit:

    • patcher le code

    • générer une nouvelle image (build)

    • stocker cette image à distance (ship)

    • lancer la nouvelle version (run)

swarm/updatingservices.md

643 / 801

Modifier un seul service avec service update

  • Pour mettre à jour un seul service, on pourrait procéder comme suit:

    export REGISTRY=127.0.0.1:5000
    export TAG=v0.2
    IMAGE=$REGISTRY/dockercoins_webui:$TAG
    docker build -t $IMAGE webui/
    docker push $IMAGE
    docker service update dockercoins_webui --image $IMAGE
  • Assurez-vous de mettre le bon tag sur l'image: modifier le TAG à chaque itération

    (Quand vous allez vérifier quelles images tournent, on a intérêt à disposer de tags uniques et explicites)

swarm/updatingservices.md

644 / 801

Modifier nos services avec stack deploy

  • Avec l'intégration de Compose, tout ce que nous avons à faire est:
    export TAG=v0.2
    docker-compose -f composefile.yml build
    docker-compose -f composefile.yml push
    docker stack deploy -c composefile.yml nameofstack
645 / 801

Modifier nos services avec stack deploy

  • Avec l'intégration de Compose, tout ce que nous avons à faire est:
    export TAG=v0.2
    docker-compose -f composefile.yml build
    docker-compose -f composefile.yml push
    docker stack deploy -c composefile.yml nameofstack
  • C'est exactement ce que nous avons utilisé plus tôt pour déployer l'appli

  • Pas besoin d'apprendre de nouvelles commandes!

  • Docker va calculer la différence pour chaque service et ne mettre à jour que ce qui a changé.

swarm/updatingservices.md

646 / 801

Patcher le code

  • Essayons d'agrandir les chiffres sur l'axe Y!
  • Mettre à jour la taille du texte sur notre webui
    sed -i "s/15px/50px/" dockercoins/webui/files/index.html

swarm/updatingservices.md

647 / 801

Générer, livrer et lancer nos changements

  • Quatre étapes:

    1. Définir (et exporter!) la variable d'envionnement TAG
    2. docker-compose build
    3. docker-compose push
    4. docker stack deploy
  • Générer, livrer et lancer:
    export TAG=v0.2
    docker-compose -f dockercoins.yml build
    docker-compose -f dockercoins.yml push
    docker stack deploy -c dockercoins.yml dockercoins
  • Pour info: puisque nous changeons le tag sur toutes les images dans cette démo v0.2, le deploy va relancer tous les services.

swarm/updatingservices.md

648 / 801

Tester nos changements

  • Attendez au moins 10 secondes (pour laisser arriver la nouvelle version)

  • Puis rechargez l'interface web

  • Ou pianoter frénétiquement sur F5 (Cmd-R sur Mac)

  • ... le texte de la légende sur la gauche finira par grossir!

swarm/updatingservices.md

649 / 801

Image separating from the next chapter

650 / 801

Mises à jour progressive

(automatically generated title slide)

651 / 801

Mises à jour progressive

  • Essayons de forcer une mise à jour de hasher pour examiner le processus.
  • Escalader d'abord hasher à 7 replicas:

    docker service scale dockercoins_hasher=7
  • Forcer une mise à jour progressive (qui remplace les conteneurs) vers une image différente:

    docker service update --image 127.0.0.1:5000/hasher:v0.1 dockercoins_hasher
  • Vous pouvez lancer docker events sur un autre terminal de node1 pour voir les actions du Swarm.

  • Vous pouvez forcer --force pour remplacer les conteneurs sans changer la configuration.

swarm/rollingupdates.md

652 / 801

Changer la politique de mise à jour

  • On peut jouer sur plein d'options sur les profils de mise à jour.

    • Changer le parallélisme à 2, et le taux d'échec maximum à 25%:
      docker service update --update-parallelism 2 \
      --update-max-failure-ratio .25 dockercoins_hasher
  • Aucun conteneur n'a été remplacé, c'est ce qu'on appelle une mise à jour "sans op".

  • Les patch de méta-données pures n'exigent aucune opération de l'orchestrateur

swarm/rollingupdates.md

653 / 801

Changer les règles depuis le fichier Compose

  • Cette même politique peut aussi apparaître dans le fichier Compose.

  • On le fait en ajoutant une clé update_config sous la clé deploy:

    deploy:
    replicas: 10
    update_config:
    parallelism: 2
    delay: 10s

swarm/rollingupdates.md

654 / 801

Retour arrière

  • A n'importe quel moment (par ex. avant la fin de la mise à jour), on peut tout annuler:

    • en modifiant le fichier Compose et relancer une mise à jour

    • en utilisant l'option --rollback de service update

    • en utilisant docker service rollback

  • Essayer d'annuler la mise à jour du service webui
    docker service rollback dockercoins_webui

Que se passe-t-il avec le graphique de l'interface web?

swarm/rollingupdates.md

655 / 801

Les subtilités du rollback

  • Le retour arrière annule la dernière définition du service

    • voir PreviousSpec dans docker service inspect <nom_du_service>
  • Si nous nous représentons les mises à jour comme une pile:

    • ça ne va pas "dépiler" la dernière mise à jour

    • cela va "empiler" une copie de la dernière mise à jour tout en haut de la pile

    • ergo, opérer deux retours arrière successifs ne change rien.

  • La "définition de service" inclut la cadence de déploiement.

  • Chaque commande docker service update = une nouvelle définition de service

swarm/rollingupdates.md

656 / 801

Chronologie d'une mise à jour

  • SwarmKit va mettre à jour N instances à la fois
    (suivant la valeur de l'option update-parallelism)

  • De nouvelles tâches sont créées, et leur état souhaité est réglé à Ready
    (cela va télécharger l'image si nécessaire, s'assurer de la disponibilité des ressources, créer le conteneur ... sans encore le démarrer)

  • Si une nouvelle tâche échoue à atteindre le statut Ready, retour à l'étape précédente
    (SwarmKit va persister à essayer, jusqu'à dépannage du problème, ou si l'état souhaité est mis à jour)

  • Quand de nouvelles tâches sont Ready, les anciennes sont passées en Shutdown

  • Quand d'anciennes tâches sont Shutdown, le Swarm en démarre de nouvelles

  • Puis il attend le délai indiqué dans update-delay, et poursuit avec le prochain groupe d'instances.

swarm/rollingupdates.md

657 / 801

Image separating from the next chapter

658 / 801

Healthcheck et rollback automatique

(automatically generated title slide)

659 / 801

Healthcheck et rollback automatique

(Nouveau depuis Docker Engine 1.12)

  • Des commandes exécutées à intervalles réguliers dans un conteneur.

  • Doit retourner 0 ou 1 pour indiquer "Tout va bien" ou "Quelque chose me bloque"

  • Doit s'exécuter rapidement (timeout == erreurs)

  • Exemple:

    curl -f http://localhost/_ping || false
    • l'option -f s'assure que curl retourne un statut non-nul pour 404 et autres erreurs
    • || false garantit que tout code de sortie non nul se traduira par 1
    • curl doit être installé dans le conteneur à vérifier

swarm/healthchecks.md

660 / 801

Définir ses health checks

  • Dans un Dockerfile, avec l'instruction HEALTHCHECK

    HEALTHCHECK --interval=1s --timeout=3s CMD curl -f http://localhost/ || false
  • Depuis la ligne de commande, en lançant conteneurs ou services

    docker run --health-cmd "curl -f http://localhost/ || false" ...
    docker service create --health-cmd "curl -f http://localhost/ || false" ...
  • Depuis les fichiers Compose, avec une section healthcheck par service

    www:
    image: hellowebapp
    healthcheck:
    test: "curl -f https://localhost/ || false"
    timeout: 3s

swarm/healthchecks.md

661 / 801

Utiliser les health checks

  • Avec docker run, tout contrôle de santé est purement informatif

    • docker ps affiche le dernier "bilan" de santé

    • docker inspect détaille certaines infos (comme la commande utilisée pour le contrôle)

  • Avec docker service:

    • les tâches en mauvaise santé sont supprimées (i.e le service est redémarré)

    • les déploiements en échec peuvent être annulés automatiquement
      (en spécifiant au moins l'option --update-failure-action rollback)

swarm/healthchecks.md

662 / 801

Activer les contrôles de santé et les rollback auto

Voici un exemple complet utilisant la ligne de commande:

docker service update \
--update-delay 5s \
--update-failure-action rollback \
--update-max-failure-ratio .25 \
--update-monitor 5s \
--update-parallelism 1 \
--rollback-delay 5s \
--rollback-failure-action pause \
--rollback-max-failure-ratio .5 \
--rollback-monitor 5s \
--rollback-parallelism 0 \
--health-cmd "curl -f http://localhost/ || exit 1" \
--health-interval 2s \
--health-retries 1 \
--image votre-image:nouvelle-version votre_service

swarm/healthchecks.md

663 / 801

Implémenter le rollback auto en pratique

On se basera pour la démo sur le fichier suivant (stacks/dockercoins+healthcheck.yml):

...
hasher:
build: dockercoins/hasher
image: ${REGISTRY-127.0.0.1:5000}/hasher:${TAG-latest}
healthcheck:
test: curl -f http://localhost/ || exit 1
deploy:
replicas: 7
update_config:
delay: 5s
failure_action: rollback
max_failure_ratio: .5
monitor: 5s
parallelism: 1
...

swarm/healthchecks.md

664 / 801

Activer l'auto-rollback dans dockercoins

On a d'abord besoin d'indiquer un healthcheck pour nos services.

  • Entrer dans le dossier stacks:

    cd ~/container.training/stacks
  • Déployer la stack mise à jour avec les healthchecks intégrés

    docker stack deploy --compose-file dockercoins+healthcheck.yml dockercoins

swarm/healthchecks.md

665 / 801

Visualiser un rollback automatisé

  • Voici un bon exemple de l'importance des healthchecks

  • Dans cette nouvelle version, un bug va empêcher l'appli d'écouter sur le bon port

  • Le conteneur va bien se lancer, sauf qu'aucune connexion sur le port 80 n'est possible

  • Changer le port HTTP à écouter:

    sed -i "s/80/81/" dockercoins/hasher/hasher.rb
  • Générer, livrer, et exécuter la nouvelle image:

    export TAG=v0.3
    docker-compose -f dockercoins+healthcheck.yml build
    docker-compose -f dockercoins+healthcheck.yml push
    docker service update --image=127.0.0.1:5000/hasher:$TAG dockercoins_hasher

swarm/healthchecks.md

666 / 801

Options de la CLI pour health checks et rollbacks

--health-cmd string Command to run to check health
--health-interval duration Time between running the check (ms|s|m|h)
--health-retries int Consecutive failures needed to report unhealthy
--health-start-period duration Start period for the container to initialize before counting retries towards unstable (ms|s|m|h)
--health-timeout duration Maximum time to allow one check to run (ms|s|m|h)
--no-healthcheck Disable any container-specified HEALTHCHECK
--restart-condition string Restart when condition is met ("none"|"on-failure"|"any")
--restart-delay duration Delay between restart attempts (ns|us|ms|s|m|h)
--restart-max-attempts uint Maximum number of restarts before giving up
--restart-window duration Window used to evaluate the restart policy (ns|us|ms|s|m|h)
--rollback Rollback to previous specification
--rollback-delay duration Delay between task rollbacks (ns|us|ms|s|m|h)
--rollback-failure-action string Action on rollback failure ("pause"|"continue")
--rollback-max-failure-ratio float Failure rate to tolerate during a rollback
--rollback-monitor duration Duration after each task rollback to monitor for failure (ns|us|ms|s|m|h)
--rollback-order string Rollback order ("start-first"|"stop-first")
--rollback-parallelism uint Maximum number of tasks rolled back simultaneously (0 to roll back all at once)
--update-delay duration Delay between updates (ns|us|ms|s|m|h)
--update-failure-action string Action on update failure ("pause"|"continue"|"rollback")
--update-max-failure-ratio float Failure rate to tolerate during an update
--update-monitor duration Duration after each task update to monitor for failure (ns|us|ms|s|m|h)
--update-order string Update order ("start-first"|"stop-first")
--update-parallelism uint Maximum number of tasks updated simultaneously (0 to update all at once)

swarm/healthchecks.md

667 / 801

Image separating from the next chapter

668 / 801

Gestion des secrets et chiffrement au repos

(automatically generated title slide)

669 / 801

Gestion des secrets et chiffrement au repos

(Nouveau dans Docker Engine 1.13)

  • Gestion des secrets = lier les secrets et les services quand il le faut, et en toute sécurité

  • Chiffrement au repos = protéger contre le vol de données et l'espionnage

  • Rappelez-vous:

    • le plan de contrôle est authentifié via un TLS mutuel, dont les certificats sont renouvelés tous les 90 jours

    • le plan de contrôle est chiffré en AES-GCM, et ses clés sont renouvelées toutes les 12 heures.

    • le plan de données n'est pas chiffré par défaut (pour raison de performance),
      mais nous avons vu plus haut comment l'activer avec une seule option

swarm/security.md

670 / 801

Gestion des secrets

  • Docker a son propre "coffre-fort à secrets" (pour stockage chiffré de clé-valeur)

  • Vous pouvez y déposer autant de secrets que vous souhaitez

  • On peut ensuite associer ces secrets aux services

  • Les secrets sont exposés comme des fichiers texte simples,
    mais ils sont conservés en mémoire (via tmpfs)

  • Les secrets sont immuables (depuis Docker Engine 1.13)

  • Un secret peut peser jusqu'à 500Ko

swarm/secrets.md

671 / 801

Déclarer de nouveaux secrets

  • On doit choisir un nom pour ce secret; et associer le contenu lui-même

Si le secret est dans un fichier, on peut simplement pointer vers le chemin complet du fichier.

(Le chemin spécial - indique que la source est l'entrée standard stdin)

swarm/secrets.md

672 / 801

Mieux déclarer ses secrets

  • Choisir des mots de passe de paresseux conduit toujours à des intrusions
  • Déclarer un meilleur mot de passe, et l'assigner à un autre secret:
    base64 /dev/urandom | head -c16 | docker secret create rienadeclarer -

Note: dans ce cas, on n'a même aucune idée du mot de passe. Mais Swarm, le sait, lui.

swarm/secrets.md

673 / 801

Usage des secrets

  • Les secrets doivent être affectés de façon explicite aux services
  • Déclarer un nouveau service de test avec les 2 secrets:
    docker service create \
    --secret piratemoi --secret rienadeclarer \
    --name dummyservice \
    --constraint node.hostname==$HOSTNAME \
    alpine sleep 1000000000

On force le conteneur à être sur la node locale pour plus de convenance.
(On va lancer un docker exec dans la foulée!)

swarm/secrets.md

674 / 801

Accéder à nos secrets

  • Les secrets sont disponibles dans /run/secrets (qui est en réalité un système de fichiers sur-mémoire)
  • Trouver l'ID du conteneur pour le service de test:

    CID=$(docker ps -q --filter label=com.docker.swarm.service.name=dummyservice)
  • Entrer dans le conteneur:

    docker exec -ti $CID sh
  • Vérifier les fichiers dans /run/secrets

swarm/secrets.md

675 / 801

Renouveler les secrets

  • On ne peut mettre à jour un secret

    (On dirait un inconvénient au premier abord; mais cela permet des rollbacks propres si un changement de secret se passe mal)

  • Vous pouvez ajouter un secret à un service avec docker service update --secret-add

    (Cela va redéployer le service; le secret ne sera pas ajouté à la volée)

  • Vous pouvez retirer un secret avec docker service update --secret-rm

  • Les secrets peuvent être associés à des noms différents en utilisant un micro-format:

    docker service create --secret source=secretname,target=filename

swarm/secrets.md

676 / 801

Changer notre mot de passe non sécurisé

  • On doit remplacer notre secret piratemoi avec une meilleure version.
  • Retirer le secret piratemoi trop faible:

    docker service update dummyservice --secret-rm piratemoi
  • Ajouter notre meilleur secret à sa place:

    docker service update dummyservice \
    --secret-add source=rienadeclarer,target=piratemoi

Attendez que le service soit complètement mis à jour, avec par ex. watch docker service ps dummyservice.
(Avec Docker Engine 17.10 et plus, la CLI attendra pour vous!)

swarm/secrets.md

677 / 801

Vérifier que notre mot de passe est maintenant plus fort!

  • On va invoquer le pouvoir du docker exec!
  • Récupérer l'ID de notre nouveau conteneur:

    CID=$(docker ps -q --filter label=com.docker.swarm.service.name=dummyservice)
  • Vérifier le contenu des fichiers secrets:

    docker exec $CID grep -r . /run/secrets

swarm/secrets.md

678 / 801

Les secrets en pratique

  • A consommer sans modération, jusqu'à stocker des fichiers de config complets

  • Pour renouveler un secret foo, renommez-le plutôt foo.N, et l'attacher à foo

    (N peut être un compteur, un timestamp ...)

    docker service create --secret source=foo.N,target=foo ...
  • On peut mettre à jour (ajouter+supprimer) en une seule commande:

    docker service update ... --secret-rm foo.M --secret-add source=foo.N,target=foo
  • Pour plus de détails et d'exemples, voir la documentation

swarm/secrets.md

679 / 801

Image separating from the next chapter

680 / 801

Modèle du moindre privilège

(automatically generated title slide)

681 / 801

Modèle du moindre privilège

  • Toute la donnée importante est stockée dans le "journal Raft"

  • Les noeuds des managers y ont accès en lecture/écriture

  • les noeuds type workers n'ont aucun accès à cette donnée

  • Les workers ne font que recevoir le strict nécessaire pour savoir:

    • quels services exécuter
    • quelle configuration réseau installer pour ces services
    • quels secrets fournir à ces services
  • Faire tomber un noeud worker ne donne pas accès au cluster en entier swarm/leastprivilege.md

682 / 801

Que puis-je faire si j'arrive à contrôler un worker?

  • Je peux m'introduire dans les conteneurs lancés sur ce noeud

  • Je peux accéder à la configuration et aux secrets utilisés par ces conteneurs

  • Je peux inspecter le trafic réseau entre ces conteneurs

  • Je ne peux pas inspecter ou interrompre le trafic réseau des autres conteneurs

    (la config réseau est fournie par les managers; le spoofing d'ARP est impossible)

  • Je ne peux pas déduire la topologie du cluster et sa taille

  • Je peux uniquement collecter les adresses IP des managers.

swarm/leastprivilege.md

683 / 801

Directives pour l'isolation de processus

  • Définir des niveaux de sécurité

  • Définir des zones de sécurité

  • Placer les managers dans la plus haute zone de sécurité

  • S'assurer que les applicatifs d'un certain niveau de sécurité ne tournent que sur une certaine zone

  • Forcer ce comportement peut se faire via un plugin d'autorisation

swarm/leastprivilege.md

684 / 801

Aller plus loin dans la sécurité de conteneur

DC17US: Securing Containers, One Patch At A Time (video)

DC17EU: Container-relevant Upstream Kernel Developments (video)

DC17EU: What Have Syscalls Done for you Lately? (video)

swarm/leastprivilege.md

685 / 801

Un rappel sur la visibilité

  • A l'installation, l'accès à l'API Docker est "tout ou rien"

  • Quand quelqu'un accès à l'API Docker, il peut faire n'importe quoi

  • Si vos développeurs utilisent l'API Docker pour déployer sur le cluster de dev...

    ... et que le cluster de dev est le même que le cluster de prod ...

    ... ça revient à donner aux devs l'accès aux données de production, mots de passe, etc.

  • C'est assez simple d'éviter ça.

swarm/apiscope.md

686 / 801

Contrôle d'accès à l'API plus fin

Quelques solutions, par ordre croissant de flexibilité:

  • Installer plusieurs clusters avec différent périmètre de sécurité

    (et différents identifiants d'accès pour chacun)

687 / 801

Contrôle d'accès à l'API plus fin

Quelques solutions, par ordre croissant de flexibilité:

  • Installer plusieurs clusters avec différent périmètre de sécurité

    (et différents identifiants d'accès pour chacun)

  • Ajouter une couche supplémentaire d'abstraction (scripts sudo, hooks, ou un vrai PAAS)

688 / 801

Contrôle d'accès à l'API plus fin

Quelques solutions, par ordre croissant de flexibilité:

  • Installer plusieurs clusters avec différent périmètre de sécurité

    (et différents identifiants d'accès pour chacun)

  • Ajouter une couche supplémentaire d'abstraction (scripts sudo, hooks, ou un vrai PAAS)

  • Activer les plugins d'autorisation

689 / 801

Image separating from the next chapter

690 / 801

Logs centralisés

(automatically generated title slide)

691 / 801

Logs centralisés

  • On veut pouvoir envoyer tous nos logs de conteneur à un service central

  • Si ce service pouvait offrir une jolie interface web, ce serait bien.

692 / 801

Logs centralisés

  • On veut pouvoir envoyer tous nos logs de conteneur à un service central

  • Si ce service pouvait offrir une jolie interface web, ce serait bien.

  • Nous allons déployer la suite ELK.

  • Elle acceptera les logs via une socket GELF.

  • Nous allons configurer nos services avec le pilote de log gelf.

swarm/logging.md

693 / 801

Image separating from the next chapter

694 / 801

Installer ELK pour stocker les logs de conteneur

(automatically generated title slide)

695 / 801

Installer ELK pour stocker les logs de conteneur

Avant-propos important: ce n'est pas une installation "officielle" ou "recommandée"; juste un exemple. Nous avons choisi ELK pour cette démo par sa popularité et les demandes qu'il suscite; mais vous serez aussi gagnant avec Fluent ou d'autres solutions de journalisation!

Ce qu'on va faire:

  • Lancer une suite ELK via des services

  • Admirer le chic de l'interface Kibana

  • Envoyer quelques logs à la main avec des conteneurs temporaires

  • Configurer nos conteneurs pour envoyer leurs logs à Logstash

swarm/logging.md

696 / 801

Qu'est-ce qu'il y a dans la solution ELK?

  • ELK, c'est trois composants:

    • ElasticSearch, pour stocker et indexer les messages de log;

    • Logstash, qui reçoit les messages de diverses sources, les traite, et les transmets à diverses destinations;

    • Kibana, pour afficher/chercher les messages dans une jolie interface.

  • Le seul composant que nous allons configurer est Logstash.

  • Nous accepterons des messages de log au format GELF.

  • Les messages seront stockés dans ElasticSearch,
    et affichés dans la sortie standard de Logstash pour débogage.

swarm/logging.md

697 / 801

Installer ELK

  • On aura besoin de trois conteneurs: ElasticSearch, Logstash, Kibana

  • On les placera dans un réseau commun, logging

  • Déclarer le réseau:

    docker network create --driver overlay logging
  • Déclarer le service ElasticSearch:

    docker service create --network logging --name elasticsearch elasticsearch:2.4

swarm/logging.md

698 / 801

Installer Kibana

  • Kibana expose une interface web

  • Son port par défaut (5601) doit être publié.

  • Il a besoin d'une touche de config: l'adresse du service ES

  • On ne voudrait pas des logs Kibana dans l'interface (cela ajouterait de la pollution)
    on va donc dir à Logstash de les ignorer

  • Déclarer le service Kibana:
    docker service create --network logging --name kibana --publish 5601:5601 \
    -e ELASTICSEARCH_URL=http://elasticsearch:9200 kibana:4.6

swarm/logging.md

699 / 801

Installer Logstash

  • Logstash exige une config pour recevoir les messages GELF et les envoyer dans ES.

  • On pourrait générer notre propre image avec la bonne configuration.

  • On peut aussi passer la configuration en ligne de commande

  • Déclarer le service Logstash:
    docker service create --network logging --name logstash -p 12201:12201/udp \
    logstash:2.4 -e "$(cat ~/container.training/elk/logstash.conf)"

swarm/logging.md

700 / 801

Vérifier Logstash

  • Avant de continuer, assurons-nous que Logstash est bien démarré
  • Trouver la node qui exécute le conteneur Logstash:

    docker service ps logstash
  • Se connecter à cette node

swarm/logging.md

701 / 801

Voir les logs de Logstash

  • Afficher les logs du service Logstash:

    docker service logs logstash --follow

Vous devriez voir le message indiquant le "pouls" du service:

{ "message" => "ok",
"host" => "1a4cfb063d13",
"@version" => "1",
"@timestamp" => "2016-06-19T00:45:45.273Z"
}

swarm/logging.md

702 / 801

Déployer notre cluster ELK

  • Nous allons utiliser le fichier stack
  • Générer, livrer et lancer notre suite ELK:
    docker-compose -f elk.yml build
    docker-compose -f elk.yml push
    docker stack deploy -c elk.yml elk

Note: les étapes de build et push ne sont pas strictement nécessaires, c'est juste une bonne habitude!

Jetons un oeil au fichier Compose.

swarm/logging.md

703 / 801

Vérifier que notre suite ELK tourne correctement

  • Affichons les logs de Logstash

    (Qui gardera les gardiens? version log)

  • Faire défiler les logs de Logstash:
    docker service logs --follow --tail 1 elk_logstash

Vous devriez voir passer les messages de "pouls":

{ "message" => "ok",
"host" => "1a4cfb063d13",
"@version" => "1",
"@timestamp" => "2016-06-19T00:45:45.273Z"
}

swarm/logging.md

704 / 801

Tester le receveur GELF

  • Dans une nouvelle fenêtre, nous allons générer un message de log.

  • Nous utiliserons une conteneur éphémère, et le pilote de log GELF de Docker.

  • Envoyer un message de test:
    docker run --log-driver gelf --log-opt gelf-address=udp://127.0.0.1:12201 \
    --rm alpine echo hello

Ce message de test devrait s'afficher dans les logs du conteneur Logstash.

swarm/logging.md

705 / 801

Envoyer des logs depuis un service

  • Jusqu'ici, nos logs partaient d'un conteneur "classique"; allons faire la même chose au niveau d'un service.

  • C'est notre jour de chance: les options --log-driver et --log-opt sont exactement les mêmes!

  • Envoyer un message de test:

    docker service create \
    --log-driver gelf --log-opt gelf-address=udp://127.0.0.1:12201 \
    alpine echo hello

Ce message de test devrait s'afficher pareil dans les logs du conteneur Logstash.

706 / 801

Envoyer des logs depuis un service

  • Jusqu'ici, nos logs partaient d'un conteneur "classique"; allons faire la même chose au niveau d'un service.

  • C'est notre jour de chance: les options --log-driver et --log-opt sont exactement les mêmes!

  • Envoyer un message de test:

    docker service create \
    --log-driver gelf --log-opt gelf-address=udp://127.0.0.1:12201 \
    alpine echo hello

Ce message de test devrait s'afficher pareil dans les logs du conteneur Logstash.

En réalité, plusieurs messages vont remonter, et continuerons d'arriver de temps en temps

swarm/logging.md

707 / 801

Conditions de redémarrage

  • Par défaut, si un conteneur sort (ou est tué par docker kill, ou s'il manque de mémoire...) le Swarm va le redémarrer (potentiellement sur une autre machine)

  • Ce comportement peut être modifié en utilisant l'option de condition de redémarrage

  • Changer la condition de redémarrage pour empêcher Swarm de relancer à l'infini notre conteneur:
    docker service update xxx --restart-condition none

Les conditions de redémarrage sont none, any, and on-error.

D'autres options existent comme --restart-delay, --restart-max-attempts, et --restart-window.

swarm/logging.md

708 / 801

Se connecter à Kibana

  • L'interface web Kibana est exposée sur le port 5601 du cluster
  • Se connecter au port 5601 du cluster

    • si vous utilisez "Play-With-Docker", cliquez sur le badge (5601) au dessus du terminal

    • sinon, ouvrez dans le navigateur l'url : http://(adresse-IP-noeud):5601

swarm/logging.md

709 / 801

"Configurer" Kibana

  • Kibana devrait vous proposer de "Configure an index pattern":
    dans la liste "Time-field name", choisir "@timestamp" et cliquez le bouton "Create".

  • Puis:

    • cliquer "Discover" (en haut à gauche),
    • cliquer "Last 15 minutes" (en haut à droite),
    • cliquer "Last 1 hour" (dans la liste au milieu),
    • cliquer "Auto-refresh" (coin supérieur droit),
    • cliquer "5 seconds" (en haut à gauche de la liste).
  • Vous pouvez voir une série de barres vertes (avec une nouvelle barre toutes les minutes)

swarm/logging.md

710 / 801

Rediriger nos services vers GELF

  • Nous allons dire à notre Swarm d'ajouter le log GELF à tous nos services

  • C'est réalisé avec la commande docker service update

  • Les options de log sont les mêmes qu'avant

  • Activer le log GELF pour le service rng:
    docker service update dockercoins_rng \
    --log-driver gelf --log-opt gelf-address=udp://127.0.0.1:12201

Après env. 15 secondes, vous devriez voir les messages de log dans Kibana.

swarm/logging.md

711 / 801

Afficher nos logs de conteneur

  • Retourner à Kibana

  • Les logs de conteneur devrait s'afficher!

  • On peut personnaliser l'interface web pour la rendre plus claire.

  • Dans la colonne de gauche, bouger la souris sur les colonnes suivantes, et cliquer sur le bouton "Add" qui apparait:
    • host
    • container_name
    • message

swarm/logging.md

712 / 801

Ne pas mettre à jour des services stateful

  • Que se serait-il passé si nous avions modifié le service Redis?

  • Quand un service change, SwarmKit remplace un conteneur existant par un autre.

  • C'est très bien pour des services stateless.

  • Mais si vous changez un service à données persistentes (stateful), ses données vont être perdues dans l'opération.

  • Mais si on met à jour notre service Redis, tous nos DockerCoins vont être perdus.

swarm/logging.md

713 / 801

Postface importante

Ce n'est pas une installation de niveau "production".

Il s'agit d'un exemple à but éducatif. Puisque nous avons un seul serveur, nous avons installé une seule instance ElasticSearch et une seule instance Logstash.

Dans une installation de "production", vous avez besoin d'un cluster ElasticSearch (pour la haute disponibilité et la capacité totale de stockage). Vous avez aussi besoin de plusieurs isntances de Logstash.

Et si vous voulez résister aux pics de logs, vous aurez besoin d'une sorte de file d'attente de messages: Redis si c'est léger, Kafka si vous voulez garantir aucune perte. Bonne chance.

<<<<<<< HEAD Pour en savoir plus sur le pilote GELF, jetez un oeil sur

[ce billet de blog](

If you want to learn more about the GELF driver, have a look at [this blog post](

master https://jpetazzo.github.io/2017/01/20/docker-logging-gelf/).

swarm/logging.md

714 / 801

Image separating from the next chapter

715 / 801

Collecter les métriques

(automatically generated title slide)

716 / 801

Collecter les métriques

  • On veut rassembler des métriques dans sur un seul service

  • On veut collecter les métriques de noeuds et de conteneurs

  • On veut aussi une jolie interface pour les consulter (des graphes)

swarm/metrics.md

717 / 801

Métriques de nodes

  • CPU, RAM, usage disque pour toute la node

  • Nombre total de processus en cours d'exécution, et leur état

  • activité I/O (entrées/sorties sur disque et réseau), par opération ou par volume

  • indicateurs physiques et matériels (si disponible): température, vitesse du ventilateur...

  • ... et bien plus!

swarm/metrics.md

718 / 801

Métriques de conteneurs

  • Similaires aux métriques de nodes, sans être identiques

  • Répartition de la RAM différente:

    • mémoire active vs inactive
    • une partie de la mémoire est partagée entre conteneurs, et comptabilisée à part
  • l'activité I/O est aussi plus difficile à suivre

    • les écritures async peuvent causer une "comptabilité" différée
    • quelques pages-ins sont aussi partagées entre conteneurs

Pour plus de détails sur les métriques de conteneurs, voir:

https://jpetazzo.github.io/2013/10/08/docker-containers-metrics/

swarm/metrics.md

719 / 801

Métriques applicatives

  • Métriques arbitraires liées à notre applicatif et au métier

  • Performance système: latence des requêtes, taux d'erreur ...

  • Information de volume: nombres de lignes dans la base de données, taille de la file d'attente...

  • Données métier: inventaire, articles vendus, chiffre d'affaire ...

swarm/metrics.md

720 / 801

Outils

Nous allons monter deux collecteurs de métriques différents:

  • Le premier basé sur Intel Snap,

  • Le second sur Prometheus.

swarm/metrics.md

721 / 801

Premier collecteur de métriques

Nous allons utiliser trois projets open source en Go pour notre premier collecteur de métriques:

  • Intel Snap

    Collecte, traite, et publie les métriques

  • InfluxDB

    Stocke les métriques

  • Grafana

    Présente les métriques visuellement

swarm/metrics.md

722 / 801

Snap

  • github.com/intelsdi-x/snap

  • Peut collecter, traiter, et exposer les données de métriques

  • Ne stocke aucune métrique

  • Fonctionne en mode daemon, controllé par une ligne de commande (snapctl)

  • Délègue la collecte, le traitement et la publication à des plugins

  • Ne peut rien faire à l'installation; obligation de configurer!

  • Documentation: https://github.com/intelsdi-x/snap/blob/master/docs/

swarm/metrics.md

723 / 801

InfluxDB

  • Snap ne stocke aucune donnée de métrique

  • InfluxDB est spécifiquement conçu pour les données basées sur le temps

    • CRud vs CRUD (on modifie rarement ou jamais ces données)

    • motifs de lecture/écriture orthogonaux

    • la clé est dans l'optimisation du format de stockage (pour l'usage et la performance du disque)

  • Snap dispose d'un plugin permettant la publication vers InfluxDB

swarm/metrics.md

724 / 801

Grafana

  • Snap ne peut pas afficher de graphes

  • InfluxDB ne peut pas non plus

  • Grafana va s'en occuper

  • Grafana peut lire ses données depuis InfluxDB et l'afficher dans des graphes

swarm/metrics.md

725 / 801

Récupérer et installer Snap

  • Nous installerons Snap directement sur chaque noeud

  • Les versions publiées sous tarballs sont disponibles depuis Github

  • Nous l'utiliserons comme service global
    (disponible sur chaque noeud, y compris les futurs arrivants)

  • Ce service va télécharger et décompresser Snap dans /opt et /usr/local

  • /opt et /usr/local sont des points de montage depuis l'hôte

  • Ce service va concrètement installer Snap sur tous les hôtes

swarm/metrics.md

726 / 801

Le service Snap d'installation

  • Ceci va installer Snap sur tous les noeuds
docker service create --restart-condition=none --mode global \
--mount type=bind,source=/usr/local/bin,target=/usr/local/bin \
--mount type=bind,source=/opt,target=/opt centos sh -c '
SNAPVER=v0.16.1-beta
RELEASEURL=https://github.com/intelsdi-x/snap/releases/download/$SNAPVER
curl -sSL $RELEASEURL/snap-$SNAPVER-linux-amd64.tar.gz |
tar -C /opt -zxf-
curl -sSL $RELEASEURL/snap-plugins-$SNAPVER-linux-amd64.tar.gz |
tar -C /opt -zxf-
ln -s snap-$SNAPVER /opt/snap
for BIN in snapd snapctl; do ln -s /opt/snap/bin/$BIN /usr/local/bin/$BIN; done
' # Si vous copier-coller ce block, n'oubliez pas l'apostrophe finale ☺

swarm/metrics.md

727 / 801

Premier contact avec snapd

  • Le coeur de Snap est snapd, le daemon Snap

  • L'application est composée d'une API REST, un module de contrôle et un module d'ordonnancement

  • Démarrer snapd sans vérification de plugin et en mode debug:
    snapd -t 0 -l 1

swarm/metrics.md

728 / 801

Using snapctl to interact with snapd

Utiliser snapctl pour intéragir avec snapd

  • Chargeons des plugins de collection et de publication
  • Ouvrir un nouveau terminal

  • Charger le plugin de collection psutil:

    snapctl plugin load /opt/snap/plugin/snap-plugin-collector-psutil
  • Charger le plugin de publication de fichier:

    snapctl plugin load /opt/snap/plugin/snap-plugin-publisher-mock-file

swarm/metrics.md

729 / 801

Vérifier ce qu'on a fait

  • Bon à savoir: la CLI Docker utilise ls, celle de Snap préfère list
  • Voir vos plugins chargés:

    snapctl plugin list
  • Voir les métriques qu'on peut collecter:

    snapctl metric list

swarm/metrics.md

730 / 801

Réellement collecter des métriques: intro aux tasks

  • Pour démarrer les phases de collecte/traitement/publication des données de métriques, on doit déclarer une nouvelle task

  • Une tâche indique:

    • quoi collecter (quelles métriques)
    • quand collecter (à quelle fréquence)
    • comment les traiter (par ex. sous forme brute, ou après calcul de moyenne)
    • les publier
  • Les tâches peuvent être définies via des manifestes écrits en JSON ou YAML

  • Quelques plugins, tels que le collecteur Docker, autorisent les jokers (*) dans les "chemins" de métriques
    (voir snap/docker-influxdb.json)

  • Plus de ressources: https://github.com/intelsdi-x/snap/blob/master/docs/TASKS.md

swarm/metrics.md

731 / 801

Notre premier manifeste de tâche

version: 1
schedule:
type: "simple" # collect on a set interval
interval: "1s" # of every 1s
max-failures: 10
workflow:
collect: # first collect
metrics: # metrics to collect
/intel/psutil/load/load1: {}
config: # there is no configuration
publish: # after collecting, publish
-
plugin_name: "file" # use the file publisher
config:
file: "/tmp/snap-psutil-file.log" # write to this file

swarm/metrics.md

732 / 801

Créer notre première tâche

  • Le manifest de tâche montré dans la diapo précédente est stocké dans snap/psutil-file.yml.
  • Déclarer une nouvelle tâche basée sur le manifeste:

    cd ~/container.training/snap
    snapctl task create -t psutil-file.yml

L'affichage devrait ressembler à:

Using task manifest to create task
Task created
ID: 240435e8-a250-4782-80d0-6fff541facba
Name: Task-240435e8-a250-4782-80d0-6fff541facba
State: Running

swarm/metrics.md

733 / 801

Vérifier les tâches existantes

  • Cela va confirmer que notre tâche tourne correctement, et nous rappeler son ID de tâche.

    snapctl task list

L'affichage devrait ressembler à ce qui suit:

ID NAME STATE HIT MISS FAIL CREATED
24043...acba Task-24043...acba Running 4 0 0 2:34PM 8-13-2016

swarm/metrics.md

734 / 801

Voir notre tâche à l'oeuvre

  • La tâche utilise un éditeur très simple, mock-file

  • Cet éditeur ne fait qu'écrire des lignes dans un fichier (une ligne par point de donnée)

  • Vérifier que les données circulent vraiment:
    tail -f /tmp/snap-psutil-file.log

Pour sortir, taper ^C

swarm/metrics.md

735 / 801

Diagnostiquer les tâches

  • Quand une tâche n'écrit pas directement dans un fichier local, passez par snapctl task watch

  • snapctl task watch va faire défiler les métriques collectées vers STDOUT

snapctl task watch <ID>

Pour sortir, taper ^C

swarm/metrics.md

736 / 801

Arrêter snap

  • Notre déploiement Snap garde quelques défauts:

    • snapd a été démarré à la main

    • il est lancé sur une seule node

    • la configuration est purement locale

737 / 801

Arrêter snap

  • Notre déploiement Snap garde quelques défauts:

    • snapd a été démarré à la main

    • il est lancé sur une seule node

    • la configuration est purement locale

  • On veut corriger tout ça!
738 / 801

Arrêter snap

  • Notre déploiement Snap garde quelques défauts:

    • snapd a été démarré à la main

    • il est lancé sur une seule node

    • la configuration est purement locale

  • On veut corriger tout ça!
  • Mais d'abord, retournons au terminal où tourne snapd, et tapons ^C

  • Toutes les tâches seront stoppées; tous les plugins déchargés; Snap va sortir

swarm/metrics.md

739 / 801

Snap en mode Tribe

  • Tribe (tribu en français), est le mécanisme de cluster chez Snap

  • Quand le mode tribu est activé, les noeuds peuvent rejoindre des agreements

  • Quand un noeud au sein d'un agreement fait quelque chose (par ex. charger un plugin ou lancer une tâche), les autres noeuds dans le même agreement font de même.

  • Nous allons l'exploiter pour charger le collecteur Docker et l'éditeur InfluxDB sur toutes les nodes, puis lancer une tâche pour les activer.

  • Sans le mode Tribe, nous aurions du charger les plugins et lancer les tâches à la main sur chaque noeud.

  • Pour en savoir plus: https://github.com/intelsdi-x/snap/blob/master/docs/TRIBE.md

swarm/metrics.md

740 / 801

Lancer Snap lui-même sur chaque node

  • Snap tourne en avant-plan, vous devez donc utiliser & ou le démarrer dans un tmux
  • Lancer la commande suivante sur chaque noeud:
    snapd -t 0 -l 1 --tribe --tribe-seed node1:6000

Si vous n'utilisez pas Play-With-Docker, il y a une autre manière de lancer Snap!

swarm/metrics.md

741 / 801

Démarrer un daemon par SSH

Grosse bidouille en vue!

  • Nous allons créer un service global

  • Ce service global va installer un client SSH

  • Avec ce client SSH, le service va se connecter sur sa node locale
    (i.e "s'échapper" du conteneur, grâce à la clé SSH fournie)

  • Une fois connecté à la node, le service démarre snapd avec le mode Tribe

swarm/metrics.md

742 / 801

Lancer Snap lui-même sur chaque noeud

  • Je pourrais aller en prison en vous montrant ça, mais c'est parti ...
  • Démarrer Snap sur toute la longueur:
    docker service create --name snapd --mode global \
    --mount type=bind,source=$HOME/.ssh/id_rsa,target=/sshkey \
    alpine sh -c "
    apk add --no-cache openssh-client &&
    ssh -o StrictHostKeyChecking=no -i /sshkey root@172.17.0.1 \
    /usr/local/bin/snapd -t 0 -l 1 --tribe --tribe-seed node1:6000
    " # Si vous copier-coller ce bloc, n'oubliez pas l'apostrophe finale :-)

Rappel : ceci ne fonctionne pas si vous êtes sur Play-With-Docker (à cause de SSH).

swarm/metrics.md

743 / 801

Afficher les membres de notre tribu

  • Si tout se passe bien, Snap est maintenant lancé en mode tribu
  • Afficher les membres de notre Tribe:
    snapctl member list

Vous devriez voir les 5 noeuds et leurs noms d'hôtes.

swarm/metrics.md

744 / 801

Déclarer un nouvel agreement

  • Un agreement est un pacte entre membres d'un cluster Snap qui garantit le même comportement.

  • Nous pouvons désormais déclarer un agreement pour nos plugins et tâches.

  • Créer un agreement; s'assurer de bien utiliser le même nom tout au long:
    snapctl agreement create docker-influxdb

La sortie d'écran devrait ressembler à ceci:

Name Number of Members plugins tasks
docker-influxdb 0 0 0

swarm/metrics.md

745 / 801

Ordonner à tous les noeuds de rejoindre cet agreeement

  • Pas besoin d'un autre service global superflu!

  • On peut ajouter des noeuds depuis n'importe quel noeud du cluster

  • Ajouter toutes les nodes au nouvel agreement
    snapctl member list | tail -n +2 |
    xargs -n1 snapctl agreement join docker-influxdb

Le dernier bout d'affichage devrait ressembler à ceci:

Name Number of Members plugins tasks
docker-influxdb 5 0 0

swarm/metrics.md

746 / 801

Démarrer un conteneur sur chaque noeud

  • Le plugin Docker exige au moins un conteneur pour être démarré

  • Normalement, à ce niveau de la procédure, vous devriez disposer d'au moins un conteneur sur chaque node

  • Mais, juste au cas où quelque chose aurait divergé, déclarons un service global de démo.

  • Déclarer un conteneur alpine à travers le cluster:
    docker service create --name ping --mode global alpine ping 8.8.8.8

swarm/metrics.md

747 / 801

Faire tourner InfluxDB

  • Nous allons créer un service pour InfluxDB

  • Nous utiliserons pour cela l'image officielle

  • InfluxDB expose plusieurs ports:

    • 8086 (HTTP API; nous en avons besoin)

    • 8083 (l'interface admin; il nous la faut)

    • 8088 (communication de cluster; superflu ici)

    • d'autres ports pour d'autres protocoles (graphite, collectd, etc.)

  • On se suffira des deux premiers ports pour la suite.

swarm/metrics.md

748 / 801

Initialiser le service InfluxDB

  • Lancer un service InfluxDB, tout en ouvrant les ports 8083 et 80806:
    docker service create --name influxdb \
    --publish 8083:8083 \
    --publish 8086:8086 \
    influxdb:0.13

Note: Cela va autoriser n'importe quel noeud à publier des métriques sur localhost:80806, et par la même, ouvrir l'interface admin depuis n'importe quel noeud sur le port 8083.

Assurez-vous bien d'utiliser la version 0.13 d'InfluxDB; quelques petits trucs ont changé en version 1.0 (comme le nom de la politique de rétention par défaut, qui est maintenant "autogen"), ce qui casserait notre démo.

swarm/metrics.md

749 / 801

Configurer InfluxDB

  • On devrait y créer notre base de données "snap"
  • Ouvrir le port 8083 sur navigateur

  • Entrer la requête suivante dans le champ de saisie:

    CREATE DATABASE "snap"
  • En haut à droite, sélectionner "Database: snap"

Note: le langage de requête InfluxDB ressemble à SQL, mais il n'en est rien.

swarm/metrics.md

750 / 801

Régler la politique de rétention

  • En passant à la version 1.0, InfluxDB a changé le nom de la politique par défaut.

  • A l'origine baptisée "default", elle s'appelle désormais "autogen"

  • Au grand dam de Snap qui ne connait que "default", nous occasionnant des erreurs potentielles.

  • Déclarer une politique de rétention "default", en lançant la requête suivante:
    CREATE RETENTION POLICY "default" ON "snap" DURATION 1w REPLICATION 1

swarm/metrics.md

751 / 801

Lancer le collecteur Docker et l'éditeur InfluxDB

  • Nous allons charger les plugins depuis la node locale

  • Puisque notre node locale est un membre d'agreement, toutes les autres nodes de ce même agreement vont agir en miroir.

  • Charger le collecteur Docker:

    snapctl plugin load /opt/snap/plugin/snap-plugin-collector-docker
  • Charger l'éditeur InfluxDB:

    snapctl plugin load /opt/snap/plugin/snap-plugin-publisher-influxdb

swarm/metrics.md

752 / 801

Démarrer une simple tâche de collecte

  • Comme tout à l'heure, nous allons déclarer une nouvelle tâche sur la node locale

  • Ladite tâche va être répliquée sur les nodes membres du même agreement

  • Charge le fichier du manifeste de tâche, pour collecter une ou deux métriques
    sur tous les conteneurs, et les envoyer à InfluxDB:
    cd ~/container.training/snap
    snapctl task create -t docker-influxdb.json

Note: la description de tâche envoie les métriques au point d'entrée de l'API InfluxDB, écoutant sur 127.0.0.1:8086. Puisque le conteneur InfluxDB est publié sur le port 8086, 127.0.0.1:8086 va toujours router le trafic vers le conteneur InfluxDB.

swarm/metrics.md

753 / 801

Si quelque chose dérape...

Note: si une tâche tombe en panne (par ex. en essayant de publier des données vers une base de métrique inaccessible), la tâche va se mettre à l'arrêt.

Vous devrez la redémarrer à la main en lançant:

snapctl task enable <ID>
snapctl task start <ID>

C'est une procédure à lancer sur chaque noeud. L'alternative serait de supprimer+re-déclarer la tâche (commandes à l'effet global sur tout le cluster)

swarm/metrics.md

754 / 801

Voir si les métriques remontent dans InfluxDB

  • Vérifions les données existantes avec ces requêtes manuelles dans l'admin InfluxDB
  • Lister les "measurements":

    SHOW MEASUREMENTS

    (Vous devriez voir deux entrées génériques correspondant aux deux métriques collectées.)

  • Afficher les données séries-temps pour une des métriques:

    SELECT * FROM "intel/docker/stats/cgroups/cpu_stats/cpu_usage/total_usage"

    (Vous devriez voir une liste de points de données avec time, docker_id, source, et value.)

swarm/metrics.md

755 / 801

Déployer Grafana

  • Vous pouvez utiliser une image quasi-officielle, grafana/grafana

  • Vous pouvez rendre publique l'interface web de Grafana sur son port par défaut (3000)

  • Créer un service Grafana:
    docker service create --name grafana --publish 3000:3000 grafana/grafana:3.1.1

swarm/metrics.md

756 / 801

Configurer Grafana

  • Ouvrir le port 3000 avec le navigateur

  • Se connecter en "admin" en identifiant/mot de passe

  • Cliquer sur le logo Grafana (la spirale orange dans le coin en haut à gauche)

  • Cliquer sur les "Data sources"

  • Cliquer sur "Add data source" (le bouton vert à droite)

swarm/metrics.md

757 / 801

Ajouter InfluxDB comme source dans Grafana

Remplir le formulaire exactmeent comme suit:

  • Name = "snap"
  • Type = "InfluxDB"

Dans les paramètres HTTP, renseigner comme suit:

Dans les détails pour InfluxDB, écrire comme suit:

  • Database = "snap"
  • Laisser l'utilisateur et le mot de passe vierges

Pour finir, cliquer sur "add", vous devriez voir un message vert affirmant "Success - Data source is working". Si vous voyez un encart orange (parfois sans message), cela veut dire que quelque chose s'est mal passé. Vérifier bien à nouveau.

swarm/metrics.md

758 / 801

Déclarer un tableau de bord dans Grafana

  • Cliquer sur le logo Grafana encore (la spirale orange dans le coin en haut à gauche)

  • Passer sur "Dashboards"

  • Cliquer sur "+ New"

  • Cliquer sur le petit rectangle vert qui apparait en haut à gauche

  • Passer sur "Add panel"

  • Cliquer sur "Graph"

A ce moment précis, vous devriez voir un graphe d'exemple s'afficher.

swarm/metrics.md

760 / 801

Configurer un graphe dans Grafana

  • Panel data source: choisir "snap"
  • Cliquer sur les requêtes de métriques SELECT pour les agrandir
  • Cliquer sur "select measurement" et choisir "CPU usage"
  • Cliquer sur le "+" juste à côté de "WHERE"
  • Choisir "docker_id"
  • Choisir l'ID d'un conteneur de votre choix (par ex. celui qui fait tourner InfluxDB)
  • Cliquer sur le "+" à droite right de la ligne "SELECT"
  • Ajouter "derivative"
  • Dans l'option "derivative", choisir "1s"
  • Dans le coin en haut à droite, cliquer sur la montre, et choisir "last 5 minutes"

Félicitations, vous avez sous les yeux l'usage CPU d'un seul conteneur!

swarm/metrics.md

761 / 801

Avant de poursuivre ...

  • Laissez cet onglet ouvert!

  • Nous allons installer un autre système de métrique

  • ... Puis comparer les 2 graphes côte-à-côte

swarm/metrics.md

763 / 801

Prometheus vs. Snap

  • Prometheus est un autre système de collecte de métriques

  • Snap pousse les métriques, là où Prometheus les aspire

swarm/metrics.md

764 / 801

Composants de Prometheus

  • Le serveur Prometheus aspire, stocke et affiche les métriques

  • Sa configuration définit une liste de points exportateurs
    (cette liste peut être dynamique, via par ex. Consul, DNS, etcd ...)

  • Les exportateurs exposent des métriques via HTTP dans un simple format ligne à ligne

    (Un format optimisé usant de protobuf existe aussi)

swarm/metrics.md

765 / 801

Tout est dans les /metrics

swarm/metrics.md

766 / 801

Collecter les métriques avec Prometheus sur Swarm

  • Nous allons lancer deux services globaux (i.e. planifiés sur toutes les nodes):

    • Un exportateur de noeud Prometheus pour lire les métriques de node

    • Le cAdvisor de Google pour lire les métriques de conteneurs.

  • C'est un serveur Prometheus qui va interroger ces exportateurs.

  • Ce serveur Prometheus sera configuré pour la découverte de services par DNS

  • Nous utiliserons tasks.<nom_du_service> pour cette découverte de services.

  • Tous ces services seront placés dans un réseau privé interne.

swarm/metrics.md

767 / 801

Ajouter un réseau overlay pour Prometheus

  • C'est l'étape la plus facile ☺
  • Déclarer un réseau superposé:
    docker network create --driver overlay prom

swarm/metrics.md

768 / 801

Lancer l'exportateur pour node

  • L'exportateur de node devrait tourner directement sur les hôtes
  • Toutefois, il peut tourner dans un conteneur, si correctement configuré
    (il devra quand même avoir accès aux système de fichier hôte, particulièrement à /proc et /sys)
  • Démarrer l'exportateur de noeud:
    docker service create --name node --mode global --network prom \
    --mount type=bind,source=/proc,target=/host/proc \
    --mount type=bind,source=/sys,target=/host/sys \
    --mount type=bind,source=/,target=/rootfs \
    prom/node-exporter \
    --path.procfs /host/proc \
    --path.sysfs /host/proc \
    --collector.filesystem.ignored-mount-points "^/(sys|proc|dev|host|etc)($|/)"

swarm/metrics.md

769 / 801

Installer cAdvisor

  • Dans la même veine, cAdvisor devrait tourner directement sur nos hôtes.

  • Mais on peut le lancer dans des conteneurs configurés correctement.

  • Démarrer le collecteur cAdvisor:
    docker service create --name cadvisor --network prom --mode global \
    --mount type=bind,source=/,target=/rootfs \
    --mount type=bind,source=/var/run,target=/var/run \
    --mount type=bind,source=/sys,target=/sys \
    --mount type=bind,source=/var/lib/docker,target=/var/lib/docker \
    google/cadvisor:latest

swarm/metrics.md

770 / 801

Configuration de serveur Prometheus

Voici notre fichier de configuration pour Prometheus:

global:
scrape_interval: 10s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'node'
dns_sd_configs:
- names: ['tasks.node']
type: 'A'
port: 9100
- job_name: 'cadvisor'
dns_sd_configs:
- names: ['tasks.cadvisor']
type: 'A'
port: 8080

swarm/metrics.md

771 / 801

Transmettre la configuration à Prometheus

  • Le plus simple serait de générer une image spécifique, incluant cette config.

  • On va utiliser un Dockerfile très simple:

    FROM prom/prometheus:v1.4.1
    COPY prometheus.yml /etc/prometheus/prometheus.yml

    (Le fichier de configuraiton et le Dockerfile sont tous deux dans le dossier prom)

  • On va lancer un build, puis pousser cette image dans notre Registry locale

  • On terminera en créant un service invoquant cette image

Note: il est aussi possible d'utiliser un objet config pour injecter ce fichier de configuration sans avoir à créer une image spéciale.

swarm/metrics.md

772 / 801

Générer notre image Prometheus sur-mesure

  • Nous allons utiliser le registre local démarré précédemment sur 127.0.0.1:5000
  • Générer l'image grâce au Dockerfile fourni:

    docker build -t 127.0.0.1:5000/prometheus ~/container.training/prom
  • Pousser l'image sur notre registre local:

    docker push 127.0.0.1:5000/prometheus

swarm/metrics.md

773 / 801

Lancer notre image Prometheus sur-mesure

  • C'est le seul service qu'on devra rendre public

    (Si on veut pouvoir accéder à Prometheus de l'extérieur!)

  • Démarrer notre serveur Prometheus:
    docker service create --network prom --name prom \
    --publish 9090:9090 127.0.0.1:5000/prometheus

swarm/metrics.md

774 / 801

Déployer Prometheus sur notre cluster

  • Nous allons (encore une fois) utiliser une définition de stack
  • S'assurer que nous sommes dans le dossier stacks:

    cd ~/container.training/stacks
  • Générer, envoyer et lancer la stack Prometheus:

    docker-compose -f prometheus.yml build
    docker-compose -f prometheus.yml push
    docker stack deploy -c prometheus.yml prometheus

swarm/metrics.md

775 / 801

Vérifier notre serveur Prometheus

  • D'abord, assurons-nous que Prometheus aspire correctement toutes les métriques
  • Ouvrir le port 9090 avec le navigateur

  • Cliquer sur "status", puis "targets"

Vous devriez voir sept points d'entrées (3 cadvisor, 3 node, 1 prometheus)

Leur statut devrait être "UP".

swarm/metrics.md

776 / 801

Injecter un fichier de configuration

(Nouveau dans Docker Engine 17.06)

  • Nous générons une image sur-mesure juste pour injecter un fichier de configuration

  • Au lieu de cela, nous pourrions rester sur l'image Prometheus officielle + une config

  • Une config est un blob (habituellement, un fichier de conf) qui:

    • est créé et géré à travers l'API Docker (dont la ligne de commande)

    • est stocké dans le log Raft (synonyme de sécurité)

    • peut être associé à un service
      (cette opération consistant à injecter le blob sous forme de fichier classique dans les conteneurs du service)

swarm/metrics.md

777 / 801

Différences entre configs et secrets

Les deux se ressemblent vraiment, à ceci près que:

  • configs:

    • peut être injecté à n'importe quel endroit du système de fichiers

    • peut être affiché et extrait à l'aide de l'API Docker ou la CLI

  • secrets

    • peut uniquement être injecté dans /run/secrets

    • n'est jamais stocké en clair sur le disque

    • ne pourra jamais être affiché ou extrait avec l'API Docker ou la CLI

swarm/metrics.md

778 / 801

Déployer Prometheus avec un config

Le fichier Compose qui suit (prometheus+config.yml) accomplit la même tâche, mais en utilisant un config au lieu de cuisiner une nouvelle image "farcie" de configuration.

version: "3.3"
services:
prometheus:
image: prom/prometheus:v1.4.1
ports:
- "9090:9090"
configs:
- source: prometheus
target: /etc/prometheus/prometheus.yml
...
configs:
prometheus:
file: ../prom/prometheus.yml

swarm/metrics.md

779 / 801

Spécifier un config dans un fichier Compose

  • Dans chaque service, une section configs optionnelle peut lister autant de configuration que nécessaire.

  • Chaque config peut préciser:

    • un champ target optionnel (chemin où injecter la config; par défaut: à la racine du conteneur)

    • les permissions et/ou propriété (par défaut, le fichier appartient à l'UID 0, i.e. root)

  • Ces configs pointent vers la section principale de configs

  • Cette section principale peut déclarer une ou plusieurs configs telles que:

    • external, à savoir qu'elle est supposée pré-exister avant de déployer la stack

    • le référencement d'un fichier, dont le contenu est utilisé pour initialiser la config

swarm/metrics.md

780 / 801

Re-déployer Prometheus avec une config

  • Nous allons mettre à jour la stack existante grâce à prometheus+config.yml
  • Re-déployer la stack prometheus:

    docker stack deploy -c prometheus+config.yml prometheus
  • Vérifier que Prometheus fonctionne encore comme attendu:

    (En se connectant à n'importe quel noeud du cluster, sur le port 9090)

swarm/metrics.md

781 / 801

Accéder à l'objet de config depuis la CLI

  • Les objets de config peuvent être consultés depuis la CLI Docker (ou l'API)
  • Lister les objets de config existant:

    docker config ls
  • Afficher les détails sur notre objet de config:

    docker config inspect prometheus_prometheus

Note: le contenu du blob de configuration est affiché en encodate BASE64
(En effet, cela peut ne pas être du texte; par exemple une image ou n'importe quel binaire!)

swarm/metrics.md

782 / 801

Extraire un blob de config

  • Récupérons cette configuration Prometheus!
  • Extraire le contenu en BASE64 avec jq:

    docker config inspect prometheus_prometheus | jq -r .[0].Spec.Data
  • Le décoder avec base64 -d:

    docker config inspect prometheus_prometheus | jq -r .[0].Spec.Data | base64 -d

swarm/metrics.md

783 / 801

Afficher les métriques directement depuis Prometheus

  • C'est facile ... si vous êtes familier avec PromQL
  • Cliquer sur "Graph", et dans "expression", coller ce qui suit:

    sum by (container_label_com_docker_swarm_node_id) (
    irate(
    container_cpu_usage_seconds_total{
    container_label_com_docker_swarm_service_name="dockercoins_worker"
    }[1m]
    )
    )
  • Cliquer sur le bouton bleu "Execute" et sur l'onglet "Graph" juste en dessous.

swarm/metrics.md

784 / 801

Construire le requête de zéro

  • Nous allons monter la même requête de zéro

  • Le but n'est pas de remplacer un vrai cours détaillé sur PromQL

  • C'est juste suffisant pour que vous (et moi) faisions semblant de comprendre la requête précédente et pour impressioner vos collègues au bureau (ou pas)

    (ou, pour construire d'autres requêtes si nécessaire, ou les adapter si cAdvisor, Prometheus, ou n'importe quoi demande des changements, et exige de changer la requête!)

swarm/metrics.md

785 / 801

Voir les métriques brutes pour tout conteneur

  • Cliquer sur l'onglet "Graph" au dessus

    On arrive dans un tableau de bord vierge

  • Cliquer sur la liste "Insert metric at cursor", et choisir container_cpu_usage_seconds_total

    Ça va placer le nom de la métrique dans le champ de requête

  • Cliquer sur "Execute"

    La table des mesures du dessous va se remplir

  • Cliquer sur "Graph" (à côté de "Console")

    La table des mesures est remplacée par une série de graphes (après quelques secondes)

swarm/metrics.md

786 / 801

Choisir les métriques pour un service spécifique

  • Passer sur les lignes du graphe

    (Essayer de repérer ceux qui ont des labels comme container_label_com_docker_...)

  • Changer la requête, en ajoutant une condition entre accolades:

    container_cpu_usage_seconds_total{container_label_com_docker_swarm_service_name="dockercoins_worker"}

  • Cliquer sur "Execute"

    On devrait voir maintenant une ligne par CPU par conteneur

  • Si vous voulez limiter à un conteneur précis, ajouter une expression régulière: id=~"/docker/c4bf.*"

  • Vous pouvez aussi cumuler les conditions, en les séparant par virgule.

swarm/metrics.md

787 / 801

Transformer les compteurs en taux

  • Ce qu'on voit, c'est le montant total de CPU utilisé (en secondes)

  • On voudrait afficher un taux (temps de CPU utilisé / temps réel)

  • Pour avoir une moyenne mobile sur 1 minute, encapsulez l'expression en cours dans:

    rate ( ... { ... } [1m] )

    Cela devrait convertir notre compteur CPU qui grimpe en courbe gracieuse

  • Pour afficher plutôt un taux instantané, choisir irate au lieu de rate

    (La fenêtre de temps sert ensuite à filtrer la quantité de données dans le passé à récupérer, dans le cas où des points sont manquants à cause de collecte défaillante; voir ici pour plus de détails!)

    On devrait voir des pics, qui étaient restés cachés, à cause du lissage sur le temps

swarm/metrics.md

788 / 801

Agréger des séries de données multiples

  • On a une courbe par CPU par conteneur; on voudrait les cumuler

  • Encapsulez toute l'expression dans:

    sum ( ... )

    On peut voir maintenant une seule courbe

swarm/metrics.md

789 / 801

Eclatement de dimensions

  • Avec plusieurs conteneurs, on peut juste éclater la dimension "CPU":

    sum without (cpu) ( ... )

    On affichera la même courbe, en préservant les autres labels

  • Fécilitations, vous venez d'écrire votre première expression PromQL de zéro!

    (Merci à Johannes Ziemke et Julius Volz pour leur aide avec Prometheus!)

swarm/metrics.md

790 / 801

Comparer les données de Snap et Prometheus

  • Si vous n'avez pas monté Snap, InfluxDB et Grafana, sautez cette section

  • Si vous avez fermé l'onglet Grafana, il faudra peut-être ré-installer un nouveau tableau de bord

    (sauf si vous l'avez enregistré avant de quitter)

  • Pour tout récupérer, il suffit de suivre les instructions du chapitre précédent

swarm/metrics.md

791 / 801

Ajouter Prometheus comme source de données dans Grafana

  • Dans un nouvel onglet, ouvrir Grafana (port 3000)

  • Cliquer sur le logo Grafana (la spirale Orange dans le coin en haut à gauche)

  • Cliquer sur "Data sources"

  • Cliquer sur le bouton vert "Add data source"

On voit le même formulaire qu'on a rempli la dernière fois pour InfluxDB.

swarm/metrics.md

792 / 801

Connecter Prometheus à Grafana

  • Entrer "prom" dans le champ "name"

  • Choisir "Prometheus" comme le type de source

  • Entrer http://(addresse.IP.de.votre.node):9090 dans le champ Url

  • Choisir "direct" dans la méthode d'accès

  • Cliquer sur "Save and Test"

Encore une fois, on devrait voir une boîte verte disant "Data source is working".

Autrement, réviser chaque étape de la procédure!

swarm/metrics.md

793 / 801

Ajouter les données de Prometheus au tableau de bord

  • Retourner à l'onglet de notre premier tableau de bord Grafana

  • Cliquer sur le bouton bleu "Add row" dans le coin en bas à droite

  • Cliquer sur l'onglet vert à gauche; choisir "Add panel" et "Graph"

On atterrit alors sur l'éditeur de graphe vu précédemment.

swarm/metrics.md

794 / 801

Interroger Prometheus depuis Grafana

L'éditeur est un peu moins sympa que celui pour InfluxDB.

  • Choisir "prom" comme source de données du panneau

  • Coller la requête dans le champ "requête":

    sum without (cpu, id) ( irate (
    container_cpu_usage_seconds_total{
    container_label_com_docker_swarm_service_name="influxdb"}[1m] ) )
  • Cliquer hors du champ de requête pour confirmer

  • Fermer l'éditeur de ligne en cliquant "X" dans le coin en haut à droite.

swarm/metrics.md

795 / 801

Interpréter les résultats

  • Les deux courbes devraient se ressembler

  • Astuce de pro: alignez les légendes de temps!

  • Cliquer sur l'horloge dans le coin haut-droit

  • Choisir "last 30 minutes"

  • Cliquer sur "Zoom out"

  • Maintenant taper sur la touche "flèche droite" (rester appuyé pour faire monter le CPU!)

Ajuster les unités est un exercice laissé au lecteur.

swarm/metrics.md

796 / 801

Pour aller plus loin avec les métriques de conteneur

DC17US: Monitoring, the Prometheus Way (video)

DC17EU: Prometheus 2.0 Storage Engine (video)

swarm/metrics.md

797 / 801

C'est tout pour aujourd'hui!
Des questions?

end

shared/thankyou.md

798 / 801

Image separating from the next chapter

799 / 801

Liens et ressources

(automatically generated title slide)

800 / 801

Présentations

  • Bonjour, je suis:

    • 👨🏾‍🎓 djalal
  • Cet atelier se déroulera de 9h à 17h.

  • La pause déjeuner se fera entre 12h et 13h30.

    (avec 2 pauses café à 10h30 et 15h!)

  • N'hésitez pas à m'interrompre pour vos questions, à n'importe quel moment.

  • Surtout quand vous verrez des photos de conteneurs en plein écran!

logistics.md

2 / 801
Paused

Help

Keyboard shortcuts

, , Pg Up, k Go to previous slide
, , Pg Dn, Space, j Go to next slide
Home Go to first slide
End Go to last slide
Number + Return Go to specific slide
b / m / f Toggle blackout / mirrored / fullscreen mode
c Clone slideshow
p Toggle presenter mode
t Restart the presentation timer
?, h Toggle this help
Esc Back to slideshow