RUN RUN COPY RUN FROM RUN COPY RUN CMD, EXPOSE ... ``` * Le _build_ échoue dès qu'une instruction échoue * Si `RUN ` é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) .debug[[containers/Dockerfile_Tips.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Dockerfile_Tips.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/two-containers-on-a-truck.jpg)] --- name: toc-exemples-de-dockerfile class: title Exemples de Dockerfile .nav[ [Section préc.](#toc-astuces-pour-dockerfiles-efficaces) | [Retour à la table des matières](#toc-chapter-3) | [Section suivante](#toc-dockerfiles-avancs) ] .debug[(automatically generated title slide)] --- # 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. .debug[[containers/Dockerfile_Tips.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Dockerfile_Tips.md)] --- ## 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. .small[ ```dockerfile 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](https://github.com/docker-library/wordpress/blob/618490d4bdff6c5774b84b717979bfe3d6ba8ad1/apache/Dockerfile)) .debug[[containers/Dockerfile_Tips.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Dockerfile_Tips.md)] --- ## 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*) .debug[[containers/Dockerfile_Tips.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Dockerfile_Tips.md)] --- ```dockerfile 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"] ``` .debug[[containers/Dockerfile_Tips.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Dockerfile_Tips.md)] --- ## 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: ```dockerfile 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](https://github.com/plone/plone.docker/blob/master/5.1/5.1.0/alpine/Dockerfile)) .debug[[containers/Dockerfile_Tips.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Dockerfile_Tips.md)] --- ## _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. .debug[[containers/Dockerfile_Tips.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Dockerfile_Tips.md)] --- ## Un script d'_entrypoint_ typique ```dockerfile #!/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](https://github.com/docker-library/redis/blob/d24f2be82673ccef6957210cc985e392ebdc65e4/4.0/alpine/docker-entrypoint.sh)) .debug[[containers/Dockerfile_Tips.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Dockerfile_Tips.md)] --- ## 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. .small[ ```dockerfile 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](https://github.com/nodejs/docker-node/blob/master/10/alpine/Dockerfile)) .debug[[containers/Dockerfile_Tips.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Dockerfile_Tips.md)] --- ## 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](https://github.com/jpetazzo/trainingwheels) comme exemple. .debug[[containers/Dockerfile_Tips.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Dockerfile_Tips.md)] --- ## Image de production Le Dockerfile génère une image exploitant gunicorn: ```dockerfile 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](https://github.com/jpetazzo/trainingwheels/blob/master/www/Dockerfile)) .debug[[containers/Dockerfile_Tips.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Dockerfile_Tips.md)] --- ## 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. .small[ ```yaml services: www: build: www ports: - 8000:5000 user: nobody environment: DEBUG: 1 command: python counter.py volumes: - ./www:/src ``` ] (Source: [Fichier Compose trainingwheels](https://github.com/jpetazzo/trainingwheels/blob/master/docker-compose.yml)) .debug[[containers/Dockerfile_Tips.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Dockerfile_Tips.md)] --- ## 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! .debug[[containers/Dockerfile_Tips.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Dockerfile_Tips.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/wall-of-containers.jpeg)] --- name: toc-dockerfiles-avancs class: title Dockerfiles avancés .nav[ [Section préc.](#toc-exemples-de-dockerfile) | [Retour à la table des matières](#toc-chapter-3) | [Section suivante](#toc-bases-du-rseau-pour-conteneur) ] .debug[(automatically generated title slide)] --- class: title # Dockerfiles avancés ![construction](images/title-advanced-dockerfiles.jpg) .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## 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. .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## `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) .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## 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: ```dockerfile 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. : ```dockerfile RUN [ "apt-get", "update" ] ``` .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## `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`. .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## Fusion de _layers_: Il est possible d'exécuter plusieurs commandes d'un seul coup: ```dockerfile 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: ```dockerfile RUN apt-get update \ && apt-get install -y wget \ && apt-get clean ``` .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## Instruction `EXPOSE` L'instruction `EXPOSE` indique à Docker quels ports doivent être publiés pour cette image. ```dockerfile 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é. .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## Exposer des ports * Quand vous lancez `docker run -p ...`, 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. .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## Instruction `COPY` L'instruction `COPY` ajoute des fichiers et du contenu depuis votre machine hôte vers l'image. ```dockerfile 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. .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## 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: ```dockerfile 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. .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## Instruction `ADD` `ADD` fonctionne presque comme `COPY`, mais avec quelques petits plus. `ADD` peut récupérer des fichiers à distance: ```dockerfile 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: ```dockerfile 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. .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## `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) .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## Instruction `VOLUME` L'instruction `VOLUME` indique à Docker qu'un dossier spécifique devrait être un *volume*. ```dockerfile 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. .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## 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. ```dockerfile WORKDIR /src ``` Vous pouvez spécifier plusieurs `WORKDIR` pour changer de dossier au cours des différentes opérations. .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## Instruction `ENV` L'instruction `ENV` déclare des variables d'environnement qui devraient être affectées dans tout _container_ lancé depuis cette image. ```dockerfile ENV WEBAPP_PORT 8080 ``` Ceci a pour résultat de créer une variable d'environnement dans tout container provenant de cette image. ```bash WEBAPP_PORT=8080 ``` Vous pouvez aussi spécifier des variables d'environnement via `docker run.` ```bash $ docker run -e WEBAPP_PORT=8000 -e WEBAPP_HOST=www.example.com ... ``` .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## 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. .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## Instruction `CMD` L'instruction `CMD` est la commande par défaut qui se lance quand un container est instancié à partir d'une image. ```dockerfile 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: ```bash $ docker run /web_image nginx -g "daemon off;" ``` Nous pouvons juste écrire: ```bash $ docker run /web_image ``` .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## `CMD` plus en détail Tout comme `RUN`, l'instruction `CMD` existe sous deux formes. La première lance un *shell*: ```dockerfile CMD nginx -g "daemon off;" ``` La seconde s'exécute directement, sans passer par un *shell*: ```dockerfile CMD [ "nginx", "-g", "daemon off;" ] ``` .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- class: extra-details ## Surcharger l'instruction `CMD` `CMD` peut être forcé au lancement d'un container. ```bash $ docker run -it /web_image bash ``` Ceci lancera `bash` au lieu de `nginx -g "daemon off;"`. .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## 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" (`["..."]`). ```dockerfile ENTRYPOINT [ "/bin/ls" ] ``` Avec ceci, si nous lançons la commande: ```bash $ docker run training/ls -l ``` Au lieu d'essayer de lancer `-l`, le container va exécuter `/bin/ls -l` .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- class: extra-details ## Surcharger l'instruction the `ENTRYPOINT` Le point d'entrée peut aussi être redéfini. ```bash $ 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:/# ``` .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## Comment `CMD` et `ENTRYPOINT` interagissent Les instructions `CMD` et `ENTRYPOINT` fonctionnent mieux quand elles sont définies ensemble. ```dockerfile 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. ```bash $ docker run -d /web_image -t ``` Cela surchargera les options `CMD` avec de nouvelles valeurs. .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## 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. .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- class: extra-details ## 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. ```dockerfile ONBUILD COPY . /src ``` * Vous ne pouvez pas chainer des instructions `ONBUILD`. * `ONBUILD` ne peut être utilisé pour déclencher des instructions `FROM` .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/Container-Ship-Freighter-Navigation-Elbe-Romance-1782991.jpg)] --- name: toc-bases-du-rseau-pour-conteneur class: title Bases du réseau pour conteneur .nav[ [Section préc.](#toc-dockerfiles-avancs) | [Retour à la table des matières](#toc-chapter-4) | [Section suivante](#toc-pilote-rseau-pour-conteneur) ] .debug[(automatically generated title slide)] --- class: title # Bases du réseau pour conteneur ![A dense graph network](images/title-container-networking-basics.jpg) .debug[[containers/Container_Networking_Basics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Networking_Basics.md)] --- ## 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. .debug[[containers/Container_Networking_Basics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Networking_Basics.md)] --- ## Un serveur web simple, statique Lancer l'image `nginx` du Docker Hub, qui contient un serveur web basique: ```bash $ 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? .debug[[containers/Container_Networking_Basics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Networking_Basics.md)] --- ## Trouver le port de notre serveur web Nous allons utiliser `docker ps`: ```bash $ 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. .debug[[containers/Container_Networking_Basics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Networking_Basics.md)] --- ## 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](images/welcome-to-nginx.png) .debug[[containers/Container_Networking_Basics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Networking_Basics.md)] --- ## 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: ```bash $ curl localhost:32768 Welcome to nginx! ... ``` .debug[[containers/Container_Networking_Basics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Networking_Basics.md)] --- ## 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`: ```bash $ 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`: ```bash $ docker history nginx IMAGE CREATED CREATED BY 7f70b30f2cc6 11 days ago /bin/sh -c #(nop) CMD ["nginx" "-g" "… 11 days ago /bin/sh -c #(nop) STOPSIGNAL [SIGTERM] 11 days ago /bin/sh -c #(nop) EXPOSE 80/tcp ``` .debug[[containers/Container_Networking_Basics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Networking_Basics.md)] --- ## 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. .debug[[containers/Container_Networking_Basics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Networking_Basics.md)] --- ## 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: ```bash $ docker port 80 32768 ``` .debug[[containers/Container_Networking_Basics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Networking_Basics.md)] --- ## Affectation manuelle des numéros de port Si vous voulez allouer vous-même les numéros de port, aucun souci: ```bash $ 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`. .debug[[containers/Container_Networking_Basics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Networking_Basics.md)] --- ## 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. .debug[[containers/Container_Networking_Basics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Networking_Basics.md)] --- ## Trouver l'adresse IP du conteneur Nous pouvons utiliser la commande `docker inspect` pour trouver l'adresse IP de notre conteneur. ```bash $ docker inspect --format '{{ .NetworkSettings.IPAddress }}' 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. .debug[[containers/Container_Networking_Basics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Networking_Basics.md)] --- ## 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`. ```bash $ ping 64 bytes from : icmp_req=1 ttl=64 time=0.085 ms 64 bytes from : icmp_req=2 ttl=64 time=0.085 ms 64 bytes from : icmp_req=3 ttl=64 time=0.085 ms ``` .debug[[containers/Container_Networking_Basics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Networking_Basics.md)] --- ## 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. .debug[[containers/Container_Networking_Basics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Networking_Basics.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/ShippingContainerSFBay.jpg)] --- name: toc-pilote-rseau-pour-conteneur class: title Pilote réseau pour conteneur .nav[ [Section préc.](#toc-bases-du-rseau-pour-conteneur) | [Retour à la table des matières](#toc-chapter-4) | [Section suivante](#toc-le-container-network-model) ] .debug[(automatically generated title slide)] --- # 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. .debug[[containers/Network_Drivers.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Network_Drivers.md)] --- ## 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. .debug[[containers/Network_Drivers.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Network_Drivers.md)] --- ## 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. .debug[[containers/Network_Drivers.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Network_Drivers.md)] --- ## 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 ...) .debug[[containers/Network_Drivers.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Network_Drivers.md)] --- ## 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.) .debug[[containers/Network_Drivers.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Network_Drivers.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/aerial-view-of-containers.jpg)] --- name: toc-le-container-network-model class: title Le _Container Network Model_ .nav[ [Section préc.](#toc-pilote-rseau-pour-conteneur) | [Retour à la table des matières](#toc-chapter-4) | [Section suivante](#toc-service-discovery-avec-les-containers) ] .debug[(automatically generated title slide)] --- class: title # Le _Container Network Model_ ![A denser graph network](images/title-the-container-network-model.jpg) .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## 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. .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## 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`. ```bash $ docker network ls NETWORK ID NAME DRIVER 6bde79dfcf70 bridge bridge 8d9c78725538 none null eb0eeab782f4 host host 4c1ff84d6d3f blog-dev overlay 228a4355d548 blog-prod overlay ``` .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## 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é. .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## 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) .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## 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 .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: pic ## _Container_ simple dans un réseau Docker ![bridge0](images/bridge1.png) .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: pic ## Deux _containers_ sur un seul réseau Docker ![bridge2](images/bridge2.png) .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: pic ## Deux _containers_ sur deux réseaux Docker ![bridge3](images/bridge3.png) .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## Créer un réseau Essayons de déclarer un nouveau réseau appelé `dev`. ```bash $ docker network create dev 4c1ff84d6d3f1733d3e233ee039cac276f425a9d5228a4355d54878293a889ba ``` Le réseau est maintenant visible avec la commande `network ls`; ```bash $ docker network ls NETWORK ID NAME DRIVER 6bde79dfcf70 bridge bridge 8d9c78725538 none null eb0eeab782f4 host host 4c1ff84d6d3f dev bridge ``` .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## 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`. ```bash $ docker run -d --name es --net dev elasticsearch:2 8abb80e229ce8926c7223beb69699f5f34d6f1d438bfc5682db893e798046863 ``` .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## Communication entre _containers_ Et maintenant, ajoutons un autre _container_ sur ce réseau. .small[ ```bash $ 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: .small[ ```bash / # 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:/# ``` ] .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## 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. .small[ ```bash [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`) .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/blue-containers.jpg)] --- name: toc-service-discovery-avec-les-containers class: title _Service discovery_ avec les _containers_ .nav[ [Section préc.](#toc-le-container-network-model) | [Retour à la table des matières](#toc-chapter-4) | [Section suivante](#toc-processus-de-dveloppement-local-avec-docker) ] .debug[(automatically generated title slide)] --- # _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; .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## 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: ```bash $ docker run --net dev -d -P jpetazzo/trainingwheels ``` Vérifier quel port lui a été alloué: ```bash $ docker ps -l ``` .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## Tester le serveur web * Si nous ouvrons l'application à ce stage, nous verrons une page d'erreur: ![Trainingwheels error](images/trainingwheels-error.png) * 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`. .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## 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_; ```bash $ docker run --net dev --name redis -d redis ``` .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## Tester à nouveau le serveur web * Si nous ouvrons l'application à présent, nous devrions voir que l'appli fonctionne correctement: ![Trainingwheels OK](images/trainingwheels-ok.png) * 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. .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## 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. .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## Utiliser un alias de réseau au lieu d'un nom Supprimons le _container_ `redis`: ```bash $ docker rm -f redis ``` Et ajoutons un nouveau qui ne bloque pas le nom `redis`: ```bash $ 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). .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## 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` ```bash $ 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). .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## 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. .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## Créer des _containers_ sur un autre réseau Créez un réseau `prod`. ```bash $ docker network create prod 5a41562fecf2d8f115bedc16865f7336232a04268bdf2bd816aecca01b68d50c ``` Nous pouvons maintenant créer plusieurs _containers_ avec un alias `es` sur le nouveau réseau `prod`. ```bash $ 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 ``` .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## Résoudre les alias de réseau Essayons la résolution DNS, en utilisant l'outil `nslookup` livré dans l'image `alpine`. ```bash $ 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)'`) .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## 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: .small[ ```bash $ 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`: .small[ ```bash $ docker run --rm --net prod centos curl -s es:9200 { "name" : "The Symbiote", ... } ``` ] .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## 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. .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## 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. .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## 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_). .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## 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. ```bash $ 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!* .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## 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*. .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## 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](https://www.youtube.com/watch?v=EuzoEaE6Cqs) ou [ces diapos](https://container.training/swarm-selfpaced.yml.html). .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## 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` .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## 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 ` * `docker network disconnect ` .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## 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: ```bash $ docker run -ti alpine sh / # ``` * Dans ce _container_, essayons de _ping_ le _container_ `es`: ```bash / # ping es ping: bad address 'es' ``` Cela ne fonctionne pas, mais nous allons corriger cela en connectant le _container_. .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## 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: ```bash $ docker network connect dev `` ``` .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## Vérifier nos actions * Essayez encore `ping es` depuis le _container_. * Cela devrait fonctionner correctement normalement: ```bash / # 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. .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## Examen du réseau dans le _container_ Nous pouvons lister les interfaces réseau avec `ifconfig`, `ip a`, ou `ip l`: .small[ ```bash / # ip a 1: lo: 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: 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: 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. .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## Se déconnecter d'un réseau * Essayons ce que donne la commande symétrique pour déconnecter le _container_: ```bash $ docker network disconnect dev ``` * A partir de maintenant, si on cherche à _ping_ `es`, ce ne sera pas résolu: ```bash / # ping es ping: bad address 'es' ``` * Si on essaie de _ping_ l'adresse IP directement, cela ne fonctionne plus non plus: ```bash / # ping 172.20.0.3 ... (rien ne se passe jusqu'à ce qu'on tape Ctrl-C) ``` .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## 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. .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## 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: .small[ ```bash $ 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`. ... ``` ] .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## 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!) .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/chinook-helicopter-container.jpg)] --- name: toc-processus-de-dveloppement-local-avec-docker class: title Processus de développement local avec Docker .nav[ [Section préc.](#toc-service-discovery-avec-les-containers) | [Retour à la table des matières](#toc-chapter-5) | [Section suivante](#toc-travailler-avec-des-volumes) ] .debug[(automatically generated title slide)] --- class: title # Processus de développement local avec Docker ![Construction site](images/title-local-development-workflow-with-docker.jpg) .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## 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. .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## 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. .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## 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. ```bash $ git clone https://github.com/jpetazzo/namer ``` .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## Examiner le code ```bash $ 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? .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## Examiner le `Dockerfile` ```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. .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## Générer et lancer l'application "namer" * Générons l'application grâce au `Dockerfile`! -- ```bash $ docker build -t namer . ``` -- * Et maintenant lancez-là. *on doit publier ses ports.* -- ```bash $ docker run -dP namer ``` -- * Vérifiez sur quel port le conteneur écoute. -- ```bash $ docker ps -l ``` .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## 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](images/webapp-in-blue.png) .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## 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 .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## Notre premier volume On va indiquer à Docker de monter le dossier en cours sur `/src` dans le conteneur. ```bash $ 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). .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## 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: ```bash [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! .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## Tester le conteneur de développement * Trouvez le port utilisé par notre nouveau conteneur. ```bash $ 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. .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## Opérer un changement dans notre application Notre client n'aime pas du tout la couleur de notre texte. Allons la changer. ```bash $ vi company_name_generator.rb ``` Et changeons: ```css color: royalblue; ``` En: ```css color: red; ``` .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## Tester nos changements * Recharger l'application dans notre navigateur -- * La couleur doit avoir changé. ![web application 2](images/webapp-in-red.png) .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## 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.) .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## Jetez vos serveurs et brûlez votre code *(C'est le titre d'un [billet de blog de 2013](http://chadfowler.com/2013/06/23/immutable-deployments.html) 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é: ```bash docker ps ``` * Pointez le navigateur dessus pour confirmer que ça marche toujours bien. .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## 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* .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## 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. .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- class: extra-details ## 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. .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- class: extra-details ## Exemple avec `docker exec` ```bash $ #Vous pouvez lancer des commandes ruby dans le même conteneur où l'appli tourne! $ docker exec -it 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 ``` .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- class: extra-details ## Arrêter le conteneur Maintenant que nous avons fini, arrêtons notre conteneur. ```bash $ docker stop ``` Et supprimons-le. ```bash $ docker rm ``` .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## 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. .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/container-cranes.jpg)] --- name: toc-travailler-avec-des-volumes class: title Travailler avec des volumes .nav[ [Section préc.](#toc-processus-de-dveloppement-local-avec-docker) | [Retour à la table des matières](#toc-chapter-5) | [Section suivante](#toc-compose-pour-les-dveloppeurs) ] .debug[(automatically generated title slide)] --- class: title # Travailler avec des volumes ![volume](images/title-working-with-volumes.jpg) .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- ## 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. .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- ## 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". .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- ## "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`. ```dockerfile VOLUME /uploads ``` * En ligne de commande, avec l'option `-v` avec `docker run`. ```bash $ docker run -d -v /uploads myapp ``` Dans les deux cas, `/uploads` (à l'intérieur du conteneur) sera un volume. .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- class: extra-details ## 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). .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- class: extra-details ## 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. .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- class: extra-details ## Partager les logs d'un serveur d'application avec un autre conteneur Démarrons un conteneur Tomcat: ```bash $ 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: ```bash $ 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: ```bash $ curl localhost:8080 ``` .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- ## 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`: ```bash $ 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. .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- ## Nommer les volumes * On peut créer des volumes sans conteneur, et les utiliser ensuite dans plusieurs conteneurs. Ajoutons quelques volumes directement. ```bash $ docker volume create webapps webapps ``` ```bash $ docker volume create logs logs ``` Nos volumes ne sont attachés à aucun dossier en particulier. .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- ## 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. ```bash $ 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: ```bash $ curl localhost:1234 ... (Tomcat nous raconte combien il est content de tourner) ... ``` .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- ## 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`. ```bash $ 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. .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- ## 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. ```bash $ docker run -d -v /chemin/depuis/notre/hote:/chemin/dans/le/conteneur image ... ``` .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- class: extra-details ## 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). .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- class: extra-details ## Migration de données en pratique Créons un conteneur Redis. ```bash $ docker run -d --name redis28 redis:2.8 ``` Puis connectons-nous au conteneur Redis pour ajouter des données. ```bash $ docker run -ti --link redis28:redis busybox telnet redis 6379 ``` Envoyons les commandes suivantes: ```bash SET counter 42 INFO server SAVE QUIT ``` .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- class: extra-details ## Mettre à jour Redis Arrêtez le conteneur Redis. ```bash $ docker stop redis28 ``` Démarrer le nouveau conteneur Redis. ```bash $ docker run -d --name redis30 --volumes-from redis28 redis:3.0 ``` .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- class: extra-details ## Tester le nouveau Redis Connectez-vous au conteneur Redis pour voir les données. ```bash docker run -ti --link redis30:redis busybox telnet redis 6379 ``` Lancez les commandes suivantes: ```bash GET counter INFO server QUIT ``` .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- ## 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. .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- class: extra-details ## Vérifier les volumes définis par une image Vous vous demandez si une image a des volumes? Il suffit d'appeler `docker inspect`: ```bash $ # docker inspect training/datavol [{ "config": { . . . "Volumes": { "/var/webapp": {} }, . . . }] ``` .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- class: extra-details ## 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): ```bash $ docker inspect [{ "ID": "", . . . "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. .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- ## 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. ```bash $ 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`! .warning[Puisque ce conteneur a accès à la socket Docker, il a un accès root au hôte.] .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- ## 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](https://rexray.io/) - 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](https://portworx.com/) - fournit un stockage par bloc distribué pour conteneurs. * [Gluster](https://www.gluster.org/) - 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](https://store.docker.com/search?category=volume&q=&type=plugin)! .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- ## 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. .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- ## Syntaxe de `--mount` Attacher un dossier de l'hôte à un chemin du conteneur: ```bash $ docker run \ --mount type=bind,source=/path/on/host,target=/path/in/container alpine ``` Monter un volume dans un chemin du conteneur: ```bash $ docker run \ --mount source=myvolume,target=/path/in/container alpine ``` Monter un _tmpfs_ (pour stockage de fichiers temporaires en mémoire): ```bash $ docker run \ --mount type=tmpfs,destination=/path/in/container,tmpfs-size=1000000 alpine ``` .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- ## 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. .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/container-housing.jpg)] --- name: toc-compose-pour-les-dveloppeurs class: title Compose pour les développeurs .nav[ [Section préc.](#toc-travailler-avec-des-volumes) | [Retour à la table des matières](#toc-chapter-5) | [Section suivante](#toc-configuration-dapplications) ] .debug[(automatically generated title slide)] --- # 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. .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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! .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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. .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- class: pic ![composeup](images/composeup.gif) .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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](https://github.com/docker/compose/releases) ou avec `pip install docker-compose`. Vous pouvez vérifier votre installation en tapant: ```bash $ docker-compose --version ``` .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## Lancer notre première _stack_ avec Compose Première étape: cloner le code source de l'appli que nous allons manipuler. ```bash $ cd $ git clone https://github.com/jpetazzo/trainingwheels ... $ cd trainingwheels ``` Seconde étape: démarrer votre appli. ```bash $ 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. .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## Lancer notre première _stack_ avec Compose Vérifiez que notre appli répond sur: `http://:8000`. ![composeapp](images/composeapp.png) .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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. .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## Le fichier `docker-compose.yml` Voici le fichier utilisé dans la démo: .small[ ```yaml version: "2" services: www: build: www ports: - 8000:5000 user: nobody environment: DEBUG: 1 command: python counter.py volumes: - ./www:/src redis: image: redis ``` ] .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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. .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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](https://docs.docker.com/compose/compose-file/) a un excellent niveau d'information sur le format du fichier Compose, à propos de toutes les différentes versions. .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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. .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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/ .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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: ```bash docker-compose up --build ``` Une autre option commune est de démarrer les conteneurs en arrière-plan: ```bash docker-compose up -d ``` .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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: ```bash $ 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 ``` .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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`: ```bash $ docker-compose kill ``` De même, `docker-compose rm` vous permet de supprimer les conteneurs (après confirmation): ```bash $ 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... ``` .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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. ```bash $ 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. .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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_. .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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`. .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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. .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/containers-by-the-water.jpg)] --- name: toc-configuration-dapplications class: title Configuration d'applications .nav[ [Section préc.](#toc-compose-pour-les-dveloppeurs) | [Retour à la table des matières](#toc-chapter-6) | [Section suivante](#toc-stockage-des-secrets) ] .debug[(automatically generated title slide)] --- # 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. .debug[[containers/Application_Configuration.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Application_Configuration.md)] --- ## Paramètres en ligne de commande ```bash 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. .debug[[containers/Application_Configuration.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Application_Configuration.md)] --- ## 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) .debug[[containers/Application_Configuration.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Application_Configuration.md)] --- ## Variables d'environnement ```bash 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. .debug[[containers/Application_Configuration.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Application_Configuration.md)] --- ## 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. .debug[[containers/Application_Configuration.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Application_Configuration.md)] --- ## Configuration incluse ```dockerfile 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) .debug[[containers/Application_Configuration.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Application_Configuration.md)] --- ## 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) .debug[[containers/Application_Configuration.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Application_Configuration.md)] --- ## Configuration par volume ```bash 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.) .debug[[containers/Application_Configuration.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Application_Configuration.md)] --- ## 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. .debug[[containers/Application_Configuration.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Application_Configuration.md)] --- ## 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. .debug[[containers/Application_Configuration.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Application_Configuration.md)] --- ## Exemple de configuration dynamique par volume Dans un premier terminal, démarrer un _load balancer_ avec une configuration initiale: ```bash $ docker run --name loadbalancer jpetazzo/hamba \ 80 goo.gl:80 ``` Dans un autre terminal, reconfigurer ce _load balancer_: ```bash $ 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_.) .debug[[containers/Application_Configuration.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Application_Configuration.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/distillery-containers.jpg)] --- name: toc-stockage-des-secrets class: title Stockage des secrets .nav[ [Section préc.](#toc-configuration-dapplications) | [Retour à la table des matières](#toc-chapter-6) | [Section suivante](#toc-gestion-des-logs) ] .debug[(automatically generated title slide)] --- # Stockage des secrets .warning[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. .debug[[containers/Application_Configuration.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Application_Configuration.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/lots-of-containers.jpg)] --- name: toc-gestion-des-logs class: title Gestion des _logs_ .nav[ [Section préc.](#toc-stockage-des-secrets) | [Retour à la table des matières](#toc-chapter-6) | [Section suivante](#toc-limiter-les-ressources) ] .debug[(automatically generated title slide)] --- # 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. .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## 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.* .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## É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. .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## É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. .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## 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. .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## 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. .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## 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à! .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## 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. .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## 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: ```bash $ docker run --log-opt max-size=10m --log-opt max-file=3 elasticsearch ``` .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## 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!* .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## 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. .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## 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 ```bash $ 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. .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## 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. .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## 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: ```bash $ docker run --log-driver=gelf --log-opt=gelf-address=udp://localhost:12201 \ alpine echo hello world ``` .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## Afficher les _logs_ dans ELK - Se connecter à l'interface Kibana. - Il est exposé sur le port 5601. - Ouvrir http://X.X.X.X:5601. .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## "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. .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## 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]( https://jpetazzo.github.io/2017/01/20/docker-logging-gelf/). .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/plastic-containers.JPG)] --- name: toc-limiter-les-ressources class: title Limiter les ressources .nav[ [Section préc.](#toc-gestion-des-logs) | [Retour à la table des matières](#toc-chapter-6) | [Section suivante](#toc-notre-application-de-dmo) ] .debug[(automatically generated title slide)] --- # 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!) .debug[[containers/Resource_Limits.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Resource_Limits.md)] --- ## 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.) .debug[[containers/Resource_Limits.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Resource_Limits.md)] --- ## 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.) .debug[[containers/Resource_Limits.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Resource_Limits.md)] --- ## 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. .debug[[containers/Resource_Limits.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Resource_Limits.md)] --- ## 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_). .debug[[containers/Resource_Limits.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Resource_Limits.md)] --- ## Limiter l'usage de la RAM Exemple: ```bash 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. .debug[[containers/Resource_Limits.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Resource_Limits.md)] --- ## Limiter à la fois RAM et _swap_ Exemple: ```bash 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_. .debug[[containers/Resource_Limits.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Resource_Limits.md)] --- ## 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?" .debug[[containers/Resource_Limits.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Resource_Limits.md)] --- ## 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. .debug[[containers/Resource_Limits.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Resource_Limits.md)] --- ## 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. .debug[[containers/Resource_Limits.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Resource_Limits.md)] --- ## 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. .debug[[containers/Resource_Limits.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Resource_Limits.md)] --- ## É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) .debug[[containers/Resource_Limits.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Resource_Limits.md)] --- ## 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`. .debug[[containers/Resource_Limits.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Resource_Limits.md)] --- ## Toutes nouvelles versions! - Engine 18.09 - Compose 1.23 - Machine 0.16 .exercise[ - Vérifier toutes les versions installées ```bash docker version docker-compose -v docker-machine -v ``` ] .debug[[swarm/versions.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/versions.md)] --- ## 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](https://blog.docker.com/2017/03/docker-enterprise-edition/) .debug[[swarm/versions.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/versions.md)] --- class: extra-details ## 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. .debug[[swarm/versions.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/versions.md)] --- class: extra-details ## 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) .debug[[swarm/versions.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/versions.md)] --- class: pic ![Docker CE/EE release cycle](images/docker-ce-ee-lifecycle.png) .debug[[swarm/versions.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/versions.md)] --- ## 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_ .debug[[swarm/versions.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/versions.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/train-of-containers-1.jpg)] --- name: toc-notre-application-de-dmo class: title Notre application de démo .nav[ [Section préc.](#toc-limiter-les-ressources) | [Retour à la table des matières](#toc-chapter-6) | [Section suivante](#toc-identifier-les-goulots-dtranglement) ] .debug[(automatically generated title slide)] --- # 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. .exercise[ - Cloner le dépôt sur `node1`: ```bash 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.) .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- ## 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... .exercise[ - Aller dans le dossier `dockercoins` du dépôt cloné: ```bash cd ~/container.training/dockercoins ``` - Utiliser Compose pour générer et lancer tous les conteneurs: ```bash 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. .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- ## Qu'est-ce que cette application? -- - C'est un miner de DockerCoin! .emoji[💰🐳📦🚢] -- - 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) .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- ## 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]( https://github.com/jpetazzo/container.training/blob/master/dockercoins/docker-compose.yml) .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- ## 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!)* .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- class: pic ![Diagramme montrant les 5 conteneurs de notre application](images/dockercoins-diagram.svg) .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- ## _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") .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- ## Exemple dans `worker/worker.py` ```python 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]( https://github.com/jpetazzo/container.training/blob/8279a3bce9398f7c1a53bdd95187c53eda4e6435/dockercoins/worker/worker.py#L17 )) .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- class: extra-details ## 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 .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- ## 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]( https://github.com/jpetazzo/container.training/tree/master/dockercoins) - Le fichier Compose ([docker-compose.yml]( https://github.com/jpetazzo/container.training/blob/master/dockercoins/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](https://github.com/jpetazzo/container.training/blob/master/dockercoins/hasher/), `rng` est dans le dossier [rng](https://github.com/jpetazzo/container.training/blob/master/dockercoins/rng/), etc.) .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- class: extra-details ## 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. .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- ## 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 .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- ## 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. .exercise[ - 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. .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- class: self-paced, extra-details ## Si le graphique ne se charge pas Si tout ce que vous voyez est une erreur `Page not found`, cela peut être à cause de votre Docker Engine qui tourne sur une machine différente. Cela peut être le cas si: - vous utilisez Docker Toolbox - vous utilisez une VM (locale ou distante) créée avec Docker Machine - vous contrôlez un Docker Engine distant Quand vous lancez DockerCoins en mode développement, les fichiers statiques de l'interface web sont appliqués au conteneur via un volume. Hélas, les volumes ne fonctionnent que sur un environnement local, ou quand vous passez par Docker for Desktop. Comment corriger cela? Arrêtez l'appli avec `^C`, modifiez `dockercoins.yml`, commentez la section `volumes`, et relancez le tout. .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- class: extra-details ## 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? -- class: extra-details - 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? .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- class: extra-details ## 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? -- class: extra-details - "Je suis carrément incapable d'écrire du bon code frontend" 😀 — Jérôme .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- ## 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` .exercise[ - 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! .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- ## Relancer en arrière-plan - Bien des options et des commandes dans Compose sont inspirées par celle de `docker` .exercise[ - Démarrer l'appli en arrière-plan avec l'option `-d`: ```bash docker-compose up -d ``` - Vérifier que notre appli est lancée avec la commande `ps`: ```bash docker-compose ps ``` ] `docker-compose ps` montre aussi les ports exposés par l'application. .debug[[shared/composescale.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/composescale.md)] --- class: extra-details ## Afficher les logs - La commande `docker-compose logs` marche comme `docker logs` .exercise[ - Afficher tous les logs depuis la naissance du conteneur, et sortir juste après: ```bash docker-compose logs ``` - Suivre le flux de logs du conteneur, en commençant par les 10 dernières lignes de chaque conteneur: ```bash docker-compose logs --tail 10 --follow ``` ] Astuce: taper `^S` et `^Q` pour suspendre/reprendre l'affichage des logs. .debug[[shared/composescale.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/composescale.md)] --- ## 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. .debug[[shared/composescale.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/composescale.md)] --- ## Examiner l'usage de ressources - Jetons un oeil au CPU, à la mémoire et aux E/S .exercise[ - 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? .debug[[shared/composescale.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/composescale.md)] --- ## Escalader les workers sur un seul noeud - Docker Compose supporte la mise à l'échelle - Escaladons `worker` et voyons ce qu'il se passe! .exercise[ - Démarrer un conteneur `worker` supplémentaire: ```bash 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) ] .debug[[shared/composescale.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/composescale.md)] --- ## (Ac)cumuler les workers - Super, ajoutons encore plus de workers alors, et le tour est joué! .exercise[ - Démarrer huit conteneurs de `worker` de plus: ```bash 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 ] .debug[[shared/composescale.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/composescale.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/train-of-containers-2.jpg)] --- name: toc-identifier-les-goulots-dtranglement class: title Identifier les goulots d'étranglement .nav[ [Section préc.](#toc-notre-application-de-dmo) | [Retour à la table des matières](#toc-chapter-6) | [Section suivante](#toc-swarmkit) ] .debug[(automatically generated title slide)] --- # 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.) .debug[[shared/composescale.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/composescale.md)] --- ## 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: ```yaml ... rng: build: rng ports: - "8001:80" hasher: build: hasher ports: - "8002:80" ... ``` .debug[[shared/composescale.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/composescale.md)] --- ## Mesurer la latence sous contrainte Nous utiliserons pour cela `httping`. .exercise[ - Vérifier la latence de `rng`: ```bash httping -c 3 localhost:8001 ``` - Vérifier la latence de `hasher`: ```bash httping -c 3 localhost:8002 ``` ] `rng` révèle une latence bien plus grande que `hasher`. .debug[[shared/composescale.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/composescale.md)] --- ## Nettoyage - Avant de continuer, supprimons tous ces conteneurs. .exercise[ - Dire à Compose de tout enlever: ```bash docker-compose down ``` ] .debug[[shared/composedown.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/composedown.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/two-containers-on-a-truck.jpg)] --- name: toc-swarmkit class: title SwarmKit .nav[ [Section préc.](#toc-identifier-les-goulots-dtranglement) | [Retour à la table des matières](#toc-chapter-6) | [Section suivante](#toc-dclaratif-vs-impratif) ] .debug[(automatically generated title slide)] --- # SwarmKit - [SwarmKit](https://github.com/docker/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 -- .footnote[.emoji[🐳] Saviez-vous que кит veut dire "baleine" en russe?] .debug[[swarm/swarmkit.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/swarmkit.md)] --- ## Fonctionnalités de SwarmKit - Base de données distribuée, hautement disponible basée sur [Raft]( https://en.wikipedia.org/wiki/Raft_%28computer_science%29) (é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 .debug[[swarm/swarmkit.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/swarmkit.md)] --- class: extra-details ## 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](https://twitter.com/@cbednarski), [@diptanu](https://twitter.com/diptanu) entre autres de l'avoir rappelé!) - Analogie offert par [@aluzzardi](https://twitter.com/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 .debug[[swarm/swarmkit.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/swarmkit.md)] --- ## 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 .debug[[swarm/swarmkit.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/swarmkit.md)] --- ## Illustration Sur la prochaine diapo: - baleines = noeuds (workers et managers) - singes = managers - singe violet = leader - singes gris = suiveurs - triangle en pointillés = protocole Raft .debug[[swarm/swarmkit.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/swarmkit.md)] --- class: pic ![Illustration](images/swarm-mode.svg) .debug[[swarm/swarmkit.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/swarmkit.md)] --- ## 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](https://github.com/docker/swarmkit/blob/master/design/nomenclature.md) du dépôt SwarmKit pour plus de détails. .debug[[swarm/swarmkit.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/swarmkit.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/wall-of-containers.jpeg)] --- name: toc-dclaratif-vs-impratif class: title Déclaratif vs Impératif .nav[ [Section préc.](#toc-swarmkit) | [Retour à la table des matières](#toc-chapter-6) | [Section suivante](#toc-mode-swarm) ] .debug[(automatically generated title slide)] --- # 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é .debug[[shared/declarative.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/declarative.md)] --- ## 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?* -- .footnote[Saviez-vous qu'il existait une [norme ISO](https://fr.wikipedia.org/wiki/ISO_3103) spécifiant comment infuser le thé?] .debug[[shared/declarative.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/declarative.md)] --- ## 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* .debug[[shared/declarative.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/declarative.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/Container-Ship-Freighter-Navigation-Elbe-Romance-1782991.jpg)] --- name: toc-mode-swarm class: title Mode Swarm .nav[ [Section préc.](#toc-dclaratif-vs-impratif) | [Retour à la table des matières](#toc-chapter-6) | [Section suivante](#toc-crer-notre-premier-swarm) ] .debug[(automatically generated title slide)] --- # 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) .debug[[swarm/swarmmode.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/swarmmode.md)] --- ## 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.) .exercise[ - Essayer une commande spéciale Swarm: ```bash docker node ls ``` ] -- Vous aurez un message d'erreur: ``` Error response from daemon: This node is not a swarm manager. [...] ``` .debug[[swarm/swarmmode.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/swarmmode.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/ShippingContainerSFBay.jpg)] --- name: toc-crer-notre-premier-swarm class: title Créer notre premier Swarm .nav[ [Section préc.](#toc-mode-swarm) | [Retour à la table des matières](#toc-chapter-6) | [Section suivante](#toc-lancer-notre-premier-service-swarm) ] .debug[(automatically generated title slide)] --- # 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. - .warning[NE PAS exécuter `docker swarm init` sur d'autres _nodes_!] Vous auriez plusieurs cluster disjoints. .exercise[ - Créer notre cluster depuis node1: ```bash docker swarm init ``` ] -- class: advertise-addr Si Docker vous dit `could not choose an IP address to advertise`, regardez la prochaine diapo! .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- class: advertise-addr ## Adresse IP à annoncer - En lançant le mode Swarm, chaque noeud *annonce* son adresse aux autres. (i.e. il leur dit *"vous pouvez me contacter sur 10.1.2.3:2377"*) - Si le noeud a une seule adresse IP, c'est activé automatiquement (Les adresses de l'interface _loopback_ et Docker bridge sont ignorées) - Si le noeud à plusieurs adresses IP, vous **devez** spécifier laquelle utiliser (Docker refusera d'en choisir une au hasard) - On peut indiquer une adresse IP ou un nom d'interface (Dans ce dernier cas, Docker va lire l'adresse IP de l'interface et l'utiliser) - On peut aussi spécifier un numéro de port (autrement, le port par défaut 2377 sera utilisé) .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- class: advertise-addr ## Utiliser un numéro de port non standard - Changer le port *annoncé* ne change pas le port *d'écoute* - Si on passe uniquement `--advertise-addr eth0:7777`, Swarm va quand même écouter sur 2377 - Vous devrez problablement aussi passer l'option `--listen-addr eth0:7777` - C'est utile dans le cas où il faut s'adapter à des scénarios où les ports *doivent* être différents (mapping de ports, répartiteurs de charge...) Exemple pour lancer Swarm sur un port différent: ```bash docker swarm init --advertise-addr eth0:7777 --listen-addr eth0:7777 ``` .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- class: advertise-addr ## Quelle adresse IP devrait-on annoncer? - Si vos noeuds ont une seule adresse IP, il est plus sûr de laisser l'auto-détection agir. .small[(Sauf si vos instances ont des adresses ip publiques et privées différentes, par ex. sur EC2, et que vous montez un Swarm impliquant des noeurs à l'intérieur et à l'extérieur du réseau privé: alors vous devriez annoncer l'adresse publique.)] - Si vos noeuds ont plusieurs adresses IP, choisissez une adresse qui est visible *par tous les autres noeuds* du Swarm. - Si vous êtes sur [play-with-docker](http://play-with-docker.com/), indiquez l'adresse IP affichée à coté du nom de la _node_. .small[(C'est l'adresse de votre noeud sur votre réseau privé interne superposé. L'autre adresse que vous pourriez voir est l'adresse de votre noeud sur le réseau `docker_gwbridge`, qui est utilisée pour le trafic sortant.)] Exemples: ```bash docker swarm init --advertise-addr 172.24.0.2 docker swarm init --advertise-addr eth0 ``` .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- class: extra-details ## 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) .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- ## 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 ``` .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- class: extra-details ## Vérifier que le mode Swarm est activé .exercise[ - Lancer la commande classique `docker info`: ```bash docker info ``` ] L'affichage devrait comporter: ``` Swarm: active NodeID: 8jud7o8dax3zxbags3f8yox4b Is Manager: true ClusterID: 2vcw2oa9rjps3a24m91xhvv0c ... ``` .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- ## Notre première commande en mode Swarm - Essayons exactement la même commande que précédemment .exercise[ - Lister les noeuds (enfin, le seul) de notre cluster: ```bash docker node ls ``` ] L'affichage devrait ressembler à ce qui suit: ``` ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS 8jud...ox4b * node1 Ready Active Leader ``` .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- ## 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 .emoji[😏] .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- ## Ajouter des noeuds au Swarm .exercise[ - Afficher le _token_ à nouveau: ```bash docker swarm join-token worker ``` - Se connecter à `node2`: ```bash ssh node2 ``` - Copier-coller la commande `docker swarm join ...` (celle qui a été affichée juste avant) ] .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- class: extra-details ## Vérifier que la node a été vraiment ajoutée - Restez sur `node2` pour l'instant! .exercise[ - On peut encore lancer `docker info` pour vérifier que la node participe au Swarm: ```bash docker info | grep ^Swarm ``` ] - Toutefois, les commandes Swarm ne passeront pas; comme, par ex.: ```bash 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. .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- ## Afficher notre cluster - Retournons sur `node1` et voyons quelle tête a notre cluster .exercise[ - Basculer vers `node1` (avec `exit`, `Ctrl-D` ...) - Afficher le cluster depuis `node1`, qui est un *manager*: ```bash 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 ``` .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- class: under-the-hood ## 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 .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- class: under-the-hood ## 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 ``` .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- class: under-the-hood ## 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_) .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- class: under-the-hood ## 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) .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- class: under-the-hood ## Sous le capot: je veux en savoir plus! Revisitez les concepts de SwarmKit: - Docker 1.12 Swarm Mode Deep Dive Part 1: Topology ([video](https://www.youtube.com/watch?v=dooPhkXT9yI)) - Docker 1.12 Swarm Mode Deep Dive Part 2: Orchestration ([video](https://www.youtube.com/watch?v=_F6PSP-qhdA)) Quelques présentations du Docker Distributed Systems Summit à Berlin: - Heart of the SwarmKit: Topology Management ([slides](https://speakerdeck.com/aluzzardi/heart-of-the-swarmkit-topology-management)) - Heart of the SwarmKit: Store, Topology & Object Model ([slides](https://www.slideshare.net/Docker/heart-of-the-swarmkit-store-topology-object-model)) ([video](https://www.youtube.com/watch?v=EmePhjGnCXY)) Et les présentations _Black Belt_ à DockerCon .blackbelt[DC17US: Everything You Thought You Already Knew About Orchestration ([video](https://www.youtube.com/watch?v=Qsv-q8WbIZY&list=PLkA60AVN3hh-biQ6SCtBJ-WVTyBmmYho8&index=6))] .blackbelt[DC17EU: Container Orchestration from Theory to Practice ([video](https://dockercon.docker.com/watch/5fhwnQxW8on1TKxPwwXZ5r))] .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- ## 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é .debug[[swarm/morenodes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/morenodes.md)] --- class: self-paced ## Ajouter plus de managers Avec Play-With-Docker: ```bash TOKEN=$(docker swarm join-token -q manager) for N in $(seq 3 5); do export DOCKER_HOST=tcp://node$N:2375 docker swarm join --token $TOKEN node1:2377 done unset DOCKER_HOST ``` .debug[[swarm/morenodes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/morenodes.md)] --- class: in-person ## Monter un cluster complet - Récupérons le *token*, et lançons la commande sur la dernière node avec SSH .exercise[ - Obtenir le jeton *manager*: ```bash TOKEN=$(docker swarm join-token -q manager) ``` - Ajouter la node qui reste: ```bash ssh node3 docker swarm join --token $TOKEN node1:2377 ``` ] [C'était facile.](https://www.youtube.com/watch?v=3YmMNpbFjp0) .debug[[swarm/morenodes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/morenodes.md)] --- ## Contrôler le Swarm depuis d'autres nodes .exercise[ - Essayer la commande suivante sur les différentes nodes: ```bash 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*. .debug[[swarm/morenodes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/morenodes.md)] --- class: self-paced ## L'icône de statut des nodes sur Play-With-Docker - Si vous passez via Play-With-Docker, vous verrez des icones de statut par node. - Les icônes de statut sont affichées à gauche du nom de chaque node. - Sans icône = pas de mode Swarm détecté - Icône bleue remplie = *manager* Swarm - Icône blanche à bord bleu = *worker* Swarm ![Icône Play-With-Docker icons](images/pwd-icons.png) .debug[[swarm/morenodes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/morenodes.md)] --- ## 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* .exercise[ - Afficher la liste courante des noeuds: ``` docker node ls ``` - Promouvoir tout *worker* en *manager* ``` docker node promote ``` ] .debug[[swarm/morenodes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/morenodes.md)] --- ## 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 .footnote[ voir [Docker's admin guide](https://docs.docker.com/engine/swarm/admin_guide/#add-manager-nodes-for-fault-tolerance) à propos des pannes de nodes et la redondance en centre de données ] .debug[[swarm/morenodes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/morenodes.md)] --- ## 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 .debug[[swarm/morenodes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/morenodes.md)] --- ## 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 .footnote[ 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](http://success.docker.com/article/running-docker-ee-at-scale)" ] .debug[[swarm/morenodes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/morenodes.md)] --- ## 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](https://sematext.com/blog/2016/11/14/docker-swarm-lessons-from-swarm3k/) - ç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](https://github.com/moby/moby/pull/37372)!) .debug[[swarm/morenodes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/morenodes.md)] --- ## Méthodes de déploiements dans la vie réelle -- Lancer les commandes à la main via SSH -- (lol je blague) -- - Exploiter votre outil de gestion de configuration préféré - [Docker for AWS](https://docs.docker.com/docker-for-aws/#quickstart) - [Docker for Azure](https://docs.docker.com/docker-for-azure/) .debug[[swarm/morenodes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/morenodes.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/aerial-view-of-containers.jpg)] --- name: toc-lancer-notre-premier-service-swarm class: title Lancer notre premier service Swarm .nav[ [Section préc.](#toc-crer-notre-premier-swarm) | [Retour à la table des matières](#toc-chapter-7) | [Section suivante](#toc-notre-appli-sur-swarm) ] .debug[(automatically generated title slide)] --- # Lancer notre premier service Swarm - Comment lancer des services? Version courte: `docker run` → `docker service create` .exercise[ - Créer un service basé sur un conteneur Alpine qui _ping_ les serveurs de Google: ```bash docker service create --name pingpong alpine ping 8.8.8.8 ``` - Vérifier le résultat: ```bash docker service ps pingpong ``` ] .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## 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 .exercise[ - Vérifier la sortie de notre commande ping: ```bash 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. .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- class: extra-details ## Chercher où tourne notre conteneur - La commande `docker service ps` va nous dire où est placé notre conteneur .exercise[ - Trouver quel `NODE` fait tourner notre conteneur: ```bash 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...)` ] .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- class: extra-details ## Afficher les logs du conteneur .exercise[ - Constater que le conteneur tourne bien, et récupérer son ID: ```bash docker ps ``` - Afficher ses logs: ```bash docker logs containerID ``` - Retourner sur `node1` après coup ] .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## Dimensionner notre service - Les services peuvent monter en charge avec une pincée de `docker service update` .exercise[ - Escalader le service pour assurer 2 clones par node: ```bash docker service update pingpong --replicas 6 ``` - Vérifier que nous avons bien 2 conteneurs sur la node en cours: ```bash docker ps ``` ] .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## 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 .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## `--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) .warning[`--detach=true` ne va *pas plus vite*. C'est juste qu'il *n'attend pas* la fin d'exécution.] .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- class: extra-details ## É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 `) .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## `--detach` en action .exercise[ - Escalader le service pour garantir 3 conteneurs par node: ```bash docker service update pingpong --replicas 9 --detach=false ``` - Et monter ensuite à 4 replicas par node: ```bash docker service update pingpong --replicas 12 --detach=true ``` ] .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## 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`) .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## Exposer ElasticSearch sur son port par défaut .exercise[ - Créer un service ElasticSearch (et lui donner un nom tant qu'on y est): ```bash 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. .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## 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) .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- class: extra-details, pic ![diagramme affichant les évenements durant docker service create, par @aluzzardi](images/docker-service-create.svg) .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## Tester notre service - Nous avons attaché le port 9200 sur les _nodes_ au port 9200 des conteneurs. - Essayons de communiquer avec ce port! .exercise[ - Lancer la commande suivante: ```bash 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. .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## Tester la répartition de charge - Si on répète notre commande `curl` encore et encore, on lire plusieurs noms. .exercise[ - Envoyer 10 requêtes, et voir quelles instances répondent: ```bash 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 ``` .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## Résultats du répartiteur de charge Le trafic est géré par le [maillage de routage]( https://docs.docker.com/engine/swarm/ingress/) 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. .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- class: pic ![routing mesh](images/ingress-routing-mesh.png) .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## 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) .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## 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. .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- class: pic ![LB externe](images/ingress-lb.png) .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## 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](https://docs.docker.com/ee/ucp/interlock/) - Les labels de service comme `com.docker.lb.hosts=` sont détectés automatiquement via l'API Docker et mettent à jour leur configuration à la volée. -- - Deux options open source populaires: - [Traefik](https://traefik.io/) - 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](http://proxy.dockerflow.com/) - utilise HAProxy, orienté Swarm, par [@vfarcic](https://twitter.com/vfarcic) .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- class: btw-labels ## 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.) .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## 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: ```bash 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 `host`et dans un autre. (Vous devrez router le trafic aux conteneurs via des ports exposés ou des sockets UNIX) .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- class: extra-details ## 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]( https://docs.docker.com/engine/userguide/networking/get-started-macvlan/ ) pour bien démarrer avec `macvlan` - Voir [cette PR](https://github.com/moby/moby/pull/32981) pour plus d'information sur les pilotes réseaux locaux dans le mode Swarm .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## Visualiser le placement de conteneurs - Jouons avec l'API Docker! .exercise[ - Lancer cette appli simple-mais-sympa de visualisation: ```bash cd ~/container.training/stacks docker-compose -f visualizer.yml up -d ``` ] .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## Se connecter à la web app de visualisation - Elle fait tourner un serveur web sur le port 8080 .exercise[ - 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) .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## 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 à: ```bash docker service create \ --mount source=/var/run/docker.sock,type=bind,target=/var/run/docker.sock \ --name viz --constraint node.role==manager ... ``` .footnote[ Crédits: le code de visualization code a été écrit par [Francisco Miranda](https://github.com/maroshii). [Mano Marks](https://twitter.com/manomarks) l'a adapté au Swarm et le maintient. ] .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## 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. .exercise[ - Supprimer tous les services avec cette commande: ```bash docker service ls -q | xargs docker service rm ``` ] .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/blue-containers.jpg)] --- name: toc-notre-appli-sur-swarm class: title Notre appli sur Swarm .nav[ [Section préc.](#toc-lancer-notre-premier-service-swarm) | [Retour à la table des matières](#toc-chapter-7) | [Section suivante](#toc-hberger-notre-propre-registry) ] .debug[(automatically generated title slide)] --- # 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. .debug[[swarm/ourapponswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/ourapponswarm.md)] --- ## 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. .debug[[swarm/ourapponswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/ourapponswarm.md)] --- class: extra-details ## 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! .debug[[swarm/ourapponswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/ourapponswarm.md)] --- ## Le plan - Lancer un _build_ sur notre node locale (`node1`) - Étiquetter les images pour les nommer en `localhost:5000/` - Téléverser dans le registre - Créer les services grâce à ces images .debug[[swarm/ourapponswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/ourapponswarm.md)] --- ## Quel registre devons-nous utiliser? .small[ - **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 ] .debug[[swarm/ourapponswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/ourapponswarm.md)] --- class: extra-details ## Utiliser Docker Hub *Si on voulait passer par Docker Hub...* - On devrait se connecter au Docker Hub ```bash 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`) .debug[[swarm/ourapponswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/ourapponswarm.md)] --- class: extra-details ## Utiliser Docker Trusted Registry *Si on voulait utiliser DTR, on devrait...* - S'assurer d'avoir un compte Docker Hub - [Activer un abonnement Docker EE]( https://hub.docker.com/enterprise/trial/) - 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* .debug[[swarm/ourapponswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/ourapponswarm.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/chinook-helicopter-container.jpg)] --- name: toc-hberger-notre-propre-registry class: title Héberger notre propre _Registry_ .nav[ [Section préc.](#toc-notre-appli-sur-swarm) | [Retour à la table des matières](#toc-chapter-7) | [Section suivante](#toc-les-stacks-swarm) ] .debug[(automatically generated title slide)] --- # 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_ .debug[[swarm/hostingregistry.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/hostingregistry.md)] --- ## Déployer le registre - Nous allons créer un service à instance unique, en publiant son port sur le cluster en entier .exercise[ - Déclarer le service du registre: ```bash docker service create --name registry --publish 5000:5000 registry ``` - Essayer maintenant la commande suivante: on devrait voir `{"repositories":[]}`: ```bash curl 127.0.0.1:5000/v2/_catalog ``` ] .debug[[swarm/hostingregistry.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/hostingregistry.md)] --- ## Tester notre registre local - On peut re-_tag_ une petite image, et la pousser dans le registre .exercise[ - Vérifier qu'on a une image busybox, et y rajouter un _tag_: ```bash docker pull busybox docker tag busybox 127.0.0.1:5000/busybox ``` - La livrer sur le registre: ```bash docker push 127.0.0.1:5000/busybox ``` ] .debug[[swarm/testingregistry.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/testingregistry.md)] --- ## Vérifier ce qui est dans le registre local - L'API du _Registry_ a des points d'entrée pour vérifier son contenu .exercise[ - Vérifier que notre image busybox est bien présente en local: ```bash curl http://127.0.0.1:5000/v2/_catalog ``` ] La commande curl devrait afficher: ```json {"repositories":["busybox"]} ``` .debug[[swarm/testingregistry.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/testingregistry.md)] --- class: btp-manual ## Intégration avec Compose - On a vu comment _build_ à la main, étiquetter et pousser les images dans un registre. - Mais ... -- class: btp-manual *Je suis tellement content que mon déploiement se base sur des scripts Shell de taille astronomique* *(par M. Personne, vraiment)* -- class: btp-manual - Voyons voir comment simplifier ce processus! .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/container-cranes.jpg)] --- name: toc-les-stacks-swarm class: title Les _Stacks_ Swarm .nav[ [Section préc.](#toc-hberger-notre-propre-registry) | [Retour à la table des matières](#toc-chapter-7) | [Section suivante](#toc-cicd-avec-docker-et-lorchestration) ] .debug[(automatically generated title slide)] --- # 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! .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- ## 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](https://github.com/docker/docker.github.io/blob/master/compose/compose-file/compose-versioning.md#upgrading) 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) .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- ## 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: ```bash docker service create --publish 5000:5000 registry ``` Dès lors, nous allons le déployer avec le fichier de _stack_ suivant: ```yaml version: "3" services: registry: image: registry ports: - "5000:5000" ``` .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- ## Vérifier nos fichiers _stack_ - Tous les fichiers _stack_ que nous utiliserons sont stockés dans le dossier `stacks` .exercise[ - Aller dans le dossier `stack`: ```bash cd ~/container.training/stacks ``` - Parcourir `registry.yml` ```bash cat registry.yml ``` ] .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- ## 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 .exercise[ - Déployer notre registre local: ```bash docker stack deploy --compose-file registry.yml registry ``` ] .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- ## Inspecter les _stacks_ - `docker stack ps` affiche l'état détaillé de tous les services d'une _stack_ .exercise[ - Vérifier que notre registre tourne correctement: ```bash docker stack ps registry ``` - Confirmer que nous avons le même affichage avec la commande: ```bash docker service ps registry_registry ``` ] .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- class: btp-manual ## 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 `_` - Les services et tâches récupèrent aussi un label interne indiquant à quelle _stack_ ils appartiennent .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- class: btp-auto ## 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 .exercise[ - Envoyer la requête API qui suit au registre: ```bash curl 127.0.0.1:5000/v2/_catalog ``` ] Ça devrait renvoyer: ```json {"repositories":[]} ``` Si ça ne marche pas, ré-essayer encore; le conteneur est peut-être en cours de démarrage. .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- class: btp-auto ## Pousser une image vers notre registre local - On peut re-_tag_ une petite image, et la pousser vers le registre. .exercise[ - Charger l'image busybox, et la re-_tag_: ```bash docker pull busybox docker tag busybox 127.0.0.1:5000/busybox ``` - La transférer: ```bash docker push 127.0.0.1:5000/busybox ``` ] .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- class: btp-auto ## Vérifier ce qui est dans notre registre local - L'API Registre a des points d'entrée pour sélectionner son contenu. .exercise[ - S'assurer que notre image busybox est maintenant dans notre registre local: ```bash curl http://127.0.0.1:5000/v2/_catalog ``` ] La commande curl devrait maintenant afficher: ```json "repositories":["busybox"]} ``` .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- ## 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 `_:latest`) - avec l'avantage qu'on peut la pousser dans un registry avec `docker-compose push` - Exemple: ```yaml webfront: build: www image: myregistry.company.net:5000/webfront ``` .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- ## Utiliser Compose pour générer et pousser les images .exercise[ - Essayer: ```bash 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. .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- ```yaml 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 ``` .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- ## Déployer l'application - Maintenant que les images sont sur le registre, on peut déployer notre _stack_ applicative. .exercise[ - Créer la _stack_ applicative: ```bash 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. .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- ## 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: ```yaml web: extends: file: common-services.yml service: webapp ``` Voir [cette page de documentation](https://docs.docker.com/compose/extends/) pour plus de détails sur ces techniques. .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- class: extra-details ## 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) .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- ## 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! .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/container-housing.jpg)] --- name: toc-cicd-avec-docker-et-lorchestration class: title CI/CD avec Docker et l'orchestration .nav[ [Section préc.](#toc-les-stacks-swarm) | [Retour à la table des matières](#toc-chapter-7) | [Section suivante](#toc-mettre--jour-les-services) ] .debug[(automatically generated title slide)] --- name: cicd # 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. .debug[[swarm/cicd.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/cicd.md)] --- ## 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](https://success.docker.com/article/dev-pipeline) - Docker KBase [Continuous Integration with Docker Hub](https://success.docker.com/article/continuous-integration-with-docker-hub) - Docker KBase [Building a Docker Secure Supply Chain](https://success.docker.com/article/secure-supply-chain) .debug[[swarm/cicd.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/cicd.md)] --- class: pic ![CI-CD with Docker](images/ci-cd-with-docker.png) .debug[[swarm/cicd.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/cicd.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/containers-by-the-water.jpg)] --- name: toc-mettre--jour-les-services class: title Mettre à jour les services .nav[ [Section préc.](#toc-cicd-avec-docker-et-lorchestration) | [Retour à la table des matières](#toc-chapter-7) | [Section suivante](#toc-mises--jour-progressive) ] .debug[(automatically generated title slide)] --- # 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_) .debug[[swarm/updatingservices.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/updatingservices.md)] --- ## Modifier un seul service avec `service update` - Pour mettre à jour un seul service, on pourrait procéder comme suit: ```bash 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) .debug[[swarm/updatingservices.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/updatingservices.md)] --- ## Modifier nos services avec `stack deploy` - Avec l'intégration de Compose, tout ce que nous avons à faire est: ```bash 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é. .debug[[swarm/updatingservices.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/updatingservices.md)] --- ## _Patcher_ le code - Essayons d'agrandir les chiffres sur l'axe Y! .exercise[ - Mettre à jour la taille du texte sur notre _webui_ ```bash sed -i "s/15px/50px/" dockercoins/webui/files/index.html ``` ] .debug[[swarm/updatingservices.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/updatingservices.md)] --- ## 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` .exercise[ - Générer, livrer et lancer: ```bash 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. .debug[[swarm/updatingservices.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/updatingservices.md)] --- ## 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! .debug[[swarm/updatingservices.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/updatingservices.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/distillery-containers.jpg)] --- name: toc-mises--jour-progressive class: title Mises à jour progressive .nav[ [Section préc.](#toc-mettre--jour-les-services) | [Retour à la table des matières](#toc-chapter-7) | [Section suivante](#toc-healthcheck-et-rollback-automatique) ] .debug[(automatically generated title slide)] --- # Mises à jour progressive - Essayons de forcer une mise à jour de _hasher_ pour examiner le processus. .exercise[ - Escalader d'abord _hasher_ à 7 replicas: ```bash docker service scale dockercoins_hasher=7 ``` - Forcer une mise à jour progressive (qui remplace les conteneurs) vers une image différente: ```bash 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. .debug[[swarm/rollingupdates.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/rollingupdates.md)] --- ## Changer la politique de mise à jour - On peut jouer sur plein d'options sur les profils de mise à jour. .exercise[ - Changer le parallélisme à 2, et le taux d'échec maximum à 25%: ```bash 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 .debug[[swarm/rollingupdates.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/rollingupdates.md)] --- ## 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`: ```yaml deploy: replicas: 10 update_config: parallelism: 2 delay: 10s ``` .debug[[swarm/rollingupdates.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/rollingupdates.md)] --- ## 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` .exercise[ - Essayer d'annuler la mise à jour du service _webui_ ```bash docker service rollback dockercoins_webui ``` ] Que se passe-t-il avec le graphique de l'interface web? .debug[[swarm/rollingupdates.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/rollingupdates.md)] --- ## Les subtilités du _rollback_ - Le retour arrière annule la dernière définition du service - voir `PreviousSpec` dans `docker service inspect ` - 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 .debug[[swarm/rollingupdates.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/rollingupdates.md)] --- class: extra-details ## 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` .small[(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 .small[(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. .debug[[swarm/rollingupdates.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/rollingupdates.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/lots-of-containers.jpg)] --- name: toc-healthcheck-et-rollback-automatique class: title Healthcheck et _rollback_ automatique .nav[ [Section préc.](#toc-mises--jour-progressive) | [Retour à la table des matières](#toc-chapter-7) | [Section suivante](#toc-gestion-des-secrets-et-chiffrement-au-repos) ] .debug[(automatically generated title slide)] --- name: healthchecks # 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: ```bash 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 .debug[[swarm/healthchecks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/healthchecks.md)] --- ## Définir ses _health checks_ - Dans un Dockerfile, avec l'instruction [HEALTHCHECK](https://docs.docker.com/engine/reference/builder/#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](https://docs.docker.com/compose/compose-file/#healthcheck) par service ```yaml www: image: hellowebapp healthcheck: test: "curl -f https://localhost/ || false" timeout: 3s ``` .debug[[swarm/healthchecks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/healthchecks.md)] --- ## 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`) .debug[[swarm/healthchecks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/healthchecks.md)] --- ## Activer les contrôles de santé et les _rollback_ auto Voici un exemple complet utilisant la ligne de commande: .small[ ```bash 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 ``` ] .debug[[swarm/healthchecks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/healthchecks.md)] --- ## Implémenter le _rollback_ auto en pratique On se basera pour la démo sur le fichier suivant (`stacks/dockercoins+healthcheck.yml`): ```yaml ... 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 ... ``` .debug[[swarm/healthchecks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/healthchecks.md)] --- ## Activer l'auto-_rollback_ dans `dockercoins` On a d'abord besoin d'indiquer un _healthcheck_ pour nos services. .exercise[ - Entrer dans le dossier `stacks`: ```bash cd ~/container.training/stacks ``` - Déployer la _stack_ mise à jour avec les _healthchecks_ intégrés ```bash docker stack deploy --compose-file dockercoins+healthcheck.yml dockercoins ``` ] .debug[[swarm/healthchecks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/healthchecks.md)] --- ## 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 .exercise[ - Changer le port HTTP à écouter: ```bash sed -i "s/80/81/" dockercoins/hasher/hasher.rb ``` - Générer, livrer, et exécuter la nouvelle image: ```bash 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 ``` ] .debug[[swarm/healthchecks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/healthchecks.md)] --- ## Options de la CLI pour _health checks_ et _rollbacks_ .small[ ``` --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) ``` ] .debug[[swarm/healthchecks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/healthchecks.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/plastic-containers.JPG)] --- name: toc-gestion-des-secrets-et-chiffrement-au-repos class: title Gestion des secrets et chiffrement au repos .nav[ [Section préc.](#toc-healthcheck-et-rollback-automatique) | [Retour à la table des matières](#toc-chapter-8) | [Section suivante](#toc-modle-du-moindre-privilge) ] .debug[(automatically generated title slide)] --- # 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 .debug[[swarm/security.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/security.md)] --- class: secrets ## 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 .debug[[swarm/secrets.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/secrets.md)] --- class: secrets ## Déclarer de nouveaux _secrets_ - On doit choisir un nom pour ce _secret_; et associer le contenu lui-même .exercise[ - Assigner [un des quatre mots de passe les plus communs](https://www.youtube.com/watch?v=0Jx8Eay5fWQ) à un secret baptisé `piratemoi`: ```bash echo love | docker secret create piratemoi - ``` ] 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_) .debug[[swarm/secrets.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/secrets.md)] --- class: secrets ## Mieux déclarer ses _secrets_ - Choisir des mots de passe de paresseux conduit toujours à des intrusions .exercise[ - Déclarer un meilleur mot de passe, et l'assigner à un autre _secret_: ```bash 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. .debug[[swarm/secrets.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/secrets.md)] --- class: secrets ## Usage des _secrets_ - Les _secrets_ doivent être affectés de façon explicite aux services .exercise[ - Déclarer un nouveau service de test avec les 2 secrets: ```bash 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!) .debug[[swarm/secrets.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/secrets.md)] --- class: secrets ## Accéder à nos secrets - Les secrets sont disponibles dans `/run/secrets` (qui est en réalité un système de fichiers sur-mémoire) .exercise[ - Trouver l'ID du conteneur pour le service de test: ```bash CID=$(docker ps -q --filter label=com.docker.swarm.service.name=dummyservice) ``` - Entrer dans le conteneur: ```bash docker exec -ti $CID sh ``` - Vérifier les fichiers dans `/run/secrets` ] .debug[[swarm/secrets.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/secrets.md)] --- class: secrets ## 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: ```bash docker service create --secret source=secretname,target=filename ``` .debug[[swarm/secrets.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/secrets.md)] --- class: secrets ## Changer notre mot de passe non sécurisé - On doit remplacer notre _secret_ `piratemoi` avec une meilleure version. .exercise[ - Retirer le secret `piratemoi` trop faible: ```bash docker service update dummyservice --secret-rm piratemoi ``` - Ajouter notre meilleur _secret_ à sa place: ```bash 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!) .debug[[swarm/secrets.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/secrets.md)] --- class: secrets ## Vérifier que notre mot de passe est maintenant plus fort! - On va invoquer le pouvoir du `docker exec`! .exercise[ - Récupérer l'ID de notre nouveau conteneur: ```bash CID=$(docker ps -q --filter label=com.docker.swarm.service.name=dummyservice) ``` - Vérifier le contenu des fichiers secrets: ```bash docker exec $CID grep -r . /run/secrets ``` ] .debug[[swarm/secrets.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/secrets.md)] --- class: secrets ## 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 ...) ```bash docker service create --secret source=foo.N,target=foo ... ``` - On peut mettre à jour (ajouter+supprimer) en une seule commande: ```bash 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](https://docs.docker.com/engine/swarm/secrets/) .debug[[swarm/secrets.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/secrets.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/train-of-containers-1.jpg)] --- name: toc-modle-du-moindre-privilge class: title Modèle du moindre privilège .nav[ [Section préc.](#toc-gestion-des-secrets-et-chiffrement-au-repos) | [Retour à la table des matières](#toc-chapter-8) | [Section suivante](#toc-logs-centraliss) ] .debug[(automatically generated title slide)] --- # 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 .debug[[swarm/leastprivilege.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/leastprivilege.md)] --- ## 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. .debug[[swarm/leastprivilege.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/leastprivilege.md)] --- ## 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](https://docs.docker.com/engine/extend/plugins_authorization/) .debug[[swarm/leastprivilege.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/leastprivilege.md)] --- ## Aller plus loin dans la sécurité de conteneur .blackbelt[DC17US: Securing Containers, One Patch At A Time ([video](https://www.youtube.com/watch?v=jZSs1RHwcqo&list=PLkA60AVN3hh-biQ6SCtBJ-WVTyBmmYho8&index=4))] .blackbelt[DC17EU: Container-relevant Upstream Kernel Developments ([video](https://dockercon.docker.com/watch/7JQBpvHJwjdW6FKXvMfCK1))] .blackbelt[DC17EU: What Have Syscalls Done for you Lately? ([video](https://dockercon.docker.com/watch/4ZxNyWuwk9JHSxZxgBBi6J))] .debug[[swarm/leastprivilege.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/leastprivilege.md)] --- ## 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. .debug[[swarm/apiscope.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/apiscope.md)] --- ## 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] - chaque requête vers l'API est filtrée par un ou plusieurs plugins(s) - par défaut, le champ *subject name* du certificat TLS client est utilisé comme identifiant - exemple: [user and permission management] dans [UCP] [plugins d'autorisation]: https://docs.docker.com/engine/extend/plugins_authorization/ [UCP]: https://docs.docker.com/datacenter/ucp/2.1/guides/ [user and permission management]: https://docs.docker.com/datacenter/ucp/2.1/guides/admin/manage-users/ .debug[[swarm/apiscope.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/apiscope.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/train-of-containers-2.jpg)] --- name: toc-logs-centraliss class: title _Logs_ centralisés .nav[ [Section préc.](#toc-modle-du-moindre-privilge) | [Retour à la table des matières](#toc-chapter-8) | [Section suivante](#toc-installer-elk-pour-stocker-les-logs-de-conteneur) ] .debug[(automatically generated title slide)] --- name: logging # _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`. .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/two-containers-on-a-truck.jpg)] --- name: toc-installer-elk-pour-stocker-les-logs-de-conteneur class: title Installer ELK pour stocker les _logs_ de conteneur .nav[ [Section préc.](#toc-logs-centraliss) | [Retour à la table des matières](#toc-chapter-8) | [Section suivante](#toc-collecter-les-mtriques) ] .debug[(automatically generated title slide)] --- # 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 .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- ## 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. .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- class: elk-manual ## Installer ELK - On aura besoin de trois conteneurs: ElasticSearch, Logstash, Kibana - On les placera dans un réseau commun, `logging` .exercise[ - Déclarer le réseau: ```bash docker network create --driver overlay logging ``` - Déclarer le service ElasticSearch: ```bash docker service create --network logging --name elasticsearch elasticsearch:2.4 ``` ] .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- class: elk-manual ## 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 .exercise[ - Déclarer le service Kibana: ```bash docker service create --network logging --name kibana --publish 5601:5601 \ -e ELASTICSEARCH_URL=http://elasticsearch:9200 kibana:4.6 ``` ] .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- class: elk-manual ## 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](https://github.com/jpetazzo/container.training/blob/master/elk/logstash.conf) en ligne de commande .exercise[ - Déclarer le service Logstash: ```bash docker service create --network logging --name logstash -p 12201:12201/udp \ logstash:2.4 -e "$(cat ~/container.training/elk/logstash.conf)" ``` ] .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- class: elk-manual ## Vérifier Logstash - Avant de continuer, assurons-nous que Logstash est bien démarré .exercise[ - Trouver la _node_ qui exécute le conteneur Logstash: ```bash docker service ps logstash ``` - Se connecter à cette _node_ ] .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- class: elk-manual ## Voir les logs de Logstash .exercise[ - Afficher les logs du service Logstash: ```bash docker service logs logstash --follow ``` ] Vous devriez voir le message indiquant le "pouls" du service: .small[ ```json { "message" => "ok", "host" => "1a4cfb063d13", "@version" => "1", "@timestamp" => "2016-06-19T00:45:45.273Z" } ``` ] .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- class: elk-auto ## Déployer notre cluster ELK - Nous allons utiliser le fichier _stack_ .exercise[ - Générer, livrer et lancer notre suite ELK: ```bash 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]( https://github.com/jpetazzo/container.training/blob/master/stacks/elk.yml). .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- class: elk-auto ## Vérifier que notre suite ELK tourne correctement - Affichons les logs de Logstash (_Qui gardera les gardiens?_ version log) .exercise[ - Faire défiler les _logs_ de Logstash: ```bash docker service logs --follow --tail 1 elk_logstash ``` ] Vous devriez voir passer les messages de "pouls": .small[ ```json { "message" => "ok", "host" => "1a4cfb063d13", "@version" => "1", "@timestamp" => "2016-06-19T00:45:45.273Z" } ``` ] .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- ## 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. .exercise[ - Envoyer un message de test: ```bash 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. .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- ## 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! .exercise[ - Envoyer un message de test: ```bash 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* .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- ## 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* .exercise[ - Changer la condition de redémarrage pour empêcher Swarm de relancer à l'infini notre conteneur: ```bash 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`. .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- ## Se connecter à Kibana - L'interface web Kibana est exposée sur le port 5601 du cluster .exercise[ - 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 ] .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- ## "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) .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- ## 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 .exercise[ - Activer le log GELF pour le service `rng`: ```bash 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. .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- ## 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. .exercise[ - Dans la colonne de gauche, bouger la souris sur les colonnes suivantes, et cliquer sur le bouton "Add" qui apparait: - host - container_name - message ] .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- ## .warning[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. .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- ## 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/). .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/wall-of-containers.jpeg)] --- name: toc-collecter-les-mtriques class: title Collecter les métriques .nav[ [Section préc.](#toc-installer-elk-pour-stocker-les-logs-de-conteneur) | [Retour à la table des matières](#toc-chapter-8) | [Section suivante](#toc-liens-et-ressources) ] .debug[(automatically generated title slide)] --- # 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) .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- ## 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! .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- ## 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/ .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- ## 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 ... .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap, prom ## Outils Nous allons monter *deux* collecteurs de métriques différents: - Le premier basé sur Intel Snap, - Le second sur Prometheus. .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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 .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Snap - [github.com/intelsdi-x/snap](https://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/ .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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 .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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 .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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 .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Le service Snap d'installation - Ceci va installer Snap sur tous les noeuds .exercise[ ```bash 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 ☺ ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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 .exercise[ - Démarrer `snapd` sans vérification de plugin et en mode debug: ```bash snapd -t 0 -l 1 ``` ] - Pour aller plus loin: https://github.com/intelsdi-x/snap/blob/master/docs/SNAPD.md https://github.com/intelsdi-x/snap/blob/master/docs/SNAPD_CONFIGURATION.md .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Using `snapctl` to interact with `snapd` ## Utiliser `snapctl` pour intéragir avec `snapd` - Chargeons des plugins de *collection* et de *publication* .exercise[ - Ouvrir un nouveau terminal - Charger le plugin de collection psutil: ```bash snapctl plugin load /opt/snap/plugin/snap-plugin-collector-psutil ``` - Charger le plugin de publication de fichier: ```bash snapctl plugin load /opt/snap/plugin/snap-plugin-publisher-mock-file ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Vérifier ce qu'on a fait - Bon à savoir: la CLI Docker utilise `ls`, celle de Snap préfère `list` .exercise[ - Voir vos plugins chargés: ```bash snapctl plugin list ``` - Voir les métriques qu'on peut collecter: ```bash snapctl metric list ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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) - *où* 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 .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Notre premier manifeste de tâche ```yaml 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 ``` .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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`. .exercise[ - Déclarer une nouvelle tâche basée sur le manifeste: ```bash 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 ``` .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Vérifier les tâches existantes .exercise[ - Cela va confirmer que notre tâche tourne correctement, et nous rappeler son ID de tâche. ```bash 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 ``` .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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) .exercise[ - Vérifier que les données circulent vraiment: ```bash tail -f /tmp/snap-psutil-file.log ``` ] Pour sortir, taper `^C` .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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 .exercise[ ```bash snapctl task watch ``` ] Pour sortir, taper `^C` .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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 -- class: snap - On veut corriger tout ça! -- class: snap - 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 .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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 .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Lancer Snap lui-même sur chaque _node_ - Snap tourne en avant-plan, vous devez donc utiliser `&` ou le démarrer dans un _tmux_ .exercise[ - Lancer la commande suivante *sur chaque noeud*: ```bash 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! .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Démarrer un _daemon_ par SSH .warning[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_ .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Lancer Snap lui-même sur chaque noeud - Je pourrais aller en prison en vous montrant ça, mais c'est parti ... .exercise[ - Démarrer Snap sur toute la longueur: ```bash 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). .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Afficher les membres de notre tribu - Si tout se passe bien, Snap est maintenant lancé en mode tribu .exercise[ - Afficher les membres de notre _Tribe_: ```bash snapctl member list ``` ] Vous devriez voir les 5 noeuds et leurs noms d'hôtes. .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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. .exercise[ - Créer un _agreement_; s'assurer de bien utiliser le même nom tout au long: ```bash snapctl agreement create docker-influxdb ``` ] La sortie d'écran devrait ressembler à ceci: ``` Name Number of Members plugins tasks docker-influxdb 0 0 0 ``` .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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 .exercise[ - Ajouter toutes les _nodes_ au nouvel _agreement_ ```bash 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 ``` .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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. .exercise[ - Déclarer un conteneur alpine à travers le cluster: ```bash docker service create --name ping --mode global alpine ping 8.8.8.8 ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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. .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Initialiser le service InfluxDB .exercise[ - Lancer un service InfluxDB, tout en ouvrant les ports 8083 et 80806: ```bash 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. .warning[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.] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Configurer InfluxDB - On devrait y créer notre base de données "snap" .exercise[ - 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. .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class:snap ## 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. .exercise[ - 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 ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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. .exercise[ - Charger le collecteur Docker: ```bash snapctl plugin load /opt/snap/plugin/snap-plugin-collector-docker ``` - Charger l'éditeur InfluxDB: ```bash snapctl plugin load /opt/snap/plugin/snap-plugin-publisher-influxdb ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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_ .exercise[ - Charge le fichier du manifeste de tâche, pour collecter une ou deux métriques sur tous les conteneurs, et les envoyer à InfluxDB: ```bash 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. .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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: ```bash snapctl task enable snapctl task start ``` 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) .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Voir si les métriques remontent dans InfluxDB - Vérifions les données existantes avec ces requêtes manuelles dans l'admin InfluxDB .exercise[ - 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**.) ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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) .exercise[ - Créer un service Grafana: ```bash docker service create --name grafana --publish 3000:3000 grafana/grafana:3.1.1 ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Configurer Grafana .exercise[ - 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) ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Ajouter InfluxDB comme source dans Grafana .small[ Remplir le formulaire exactmeent comme suit: - Name = "snap" - Type = "InfluxDB" Dans les paramètres HTTP, renseigner comme suit: - Url = "http://(adresse.IP.de.votre.noeud.prefere):8086" - Access = "direct" - Laisser "HTTP Auth" vide 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. ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ![Copie d'écran montrant comment remplir le formulaire](images/grafana-add-source.png) .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Déclarer un tableau de bord dans Grafana .exercise[ - 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. .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Configurer un graphe dans Grafana .exercise[ - 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! .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ![Copie d'écran affichant le résultat final](images/grafana-add-graph.png) .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap, prom ## 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 .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap, prom ## Prometheus vs. Snap - Prometheus est un autre système de collecte de métriques - Snap *pousse* les métriques, là où Prometheus les *aspire* .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom ## 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) .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom ## Tout est dans les `/metrics` - Voici à quoi ressemble un *exportateur de noeud*: http://demo.robustperception.io:9100/metrics - Prometheus lui-même expose aussi ses propres métriques internes: http://demo.robustperception.io:9090/metrics - Un *serveur Prometheus* va *aspirer* les URLs telles que celles-ci (On passera plutôt par protobuf pour éviter le supplément de traitement des formats ligne-à-ligne!) .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-manual ## 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.` pour cette découverte de services. - Tous ces services seront placés dans un réseau privé interne. .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-manual ## Ajouter un réseau _overlay_ pour Prometheus - C'est l'étape la plus facile ☺ .exercise[ - Déclarer un réseau superposé: ```bash docker network create --driver overlay prom ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-manual ## 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) .exercise[ - Démarrer l'exportateur de noeud: ```bash 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)($|/)" ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-manual ## 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. .exercise[ - Démarrer le collecteur cAdvisor: ```bash 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 ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-manual ## Configuration de serveur Prometheus Voici notre fichier de configuration pour Prometheus: .small[ ```yaml 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 ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-manual ## 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: ```dockerfile 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. .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-manual ## 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 .exercise[ - Générer l'image grâce au Dockerfile fourni: ```bash docker build -t 127.0.0.1:5000/prometheus ~/container.training/prom ``` - Pousser l'image sur notre registre local: ```bash docker push 127.0.0.1:5000/prometheus ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-manual ## 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!) .exercise[ - Démarrer notre serveur Prometheus: ```bash docker service create --network prom --name prom \ --publish 9090:9090 127.0.0.1:5000/prometheus ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-auto ## Déployer Prometheus sur notre cluster - Nous allons (encore une fois) utiliser une définition de _stack_ .exercise[ - S'assurer que nous sommes dans le dossier `stacks`: ```bash cd ~/container.training/stacks ``` - Générer, envoyer et lancer la _stack_ Prometheus: ```bash docker-compose -f prometheus.yml build docker-compose -f prometheus.yml push docker stack deploy -c prometheus.yml prometheus ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom ## Vérifier notre serveur Prometheus - D'abord, assurons-nous que Prometheus aspire correctement toutes les métriques .exercise[ - 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". .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-auto, config ## 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) .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-auto, config ## 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 .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-auto, config ## 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. .small[ ```yaml 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 ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-auto, config ## 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 .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-auto, config ## Re-déployer Prometheus avec une config - Nous allons mettre à jour la _stack_ existante grâce à `prometheus+config.yml` .exercise[ - Re-déployer la _stack_ `prometheus`: ```bash 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) ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-auto, config ## Accéder à l'objet de config depuis la CLI - Les objets de config peuvent être consultés depuis la CLI Docker (ou l'API) .exercise[ - Lister les objets de config existant: ```bash docker config ls ``` - Afficher les détails sur notre objet de config: ```bash 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!) .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-auto, config ## Extraire un _blob_ de config - Récupérons cette configuration Prometheus! .exercise[ - Extraire le contenu en BASE64 avec `jq`: ```bash docker config inspect prometheus_prometheus | jq -r .[0].Spec.Data ``` - Le décoder avec `base64 -d`: ```bash docker config inspect prometheus_prometheus | jq -r .[0].Spec.Data | base64 -d ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom ## Afficher les métriques directement depuis Prometheus - C'est facile ... si vous êtes familier avec PromQL .exercise[ - 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. ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom ## 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!) .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom ## 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)* .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom ## 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: .small[`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. .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom ## 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](https://www.robustperception.io/irate-graphs-are-better-graphs/) pour plus de détails!) *On devrait voir des pics, qui étaient restés cachés, à cause du lissage sur le temps* .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom ## 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* .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom ## 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](https://twitter.com/discordianfish) et [Julius Volz](https://twitter.com/juliusvolz) pour leur aide avec Prometheus!) .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom, snap ## 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 .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom, snap ## Ajouter Prometheus comme source de données dans Grafana .exercise[ - 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. .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom, snap ## Connecter Prometheus à Grafana .exercise[ - 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! .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom, snap ## Ajouter les données de Prometheus au tableau de bord .exercise[ - 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. .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom, snap ## Interroger Prometheus depuis Grafana L'éditeur est un peu moins sympa que celui pour InfluxDB. .exercise[ - 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. ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom, snap ## Interpréter les résultats - Les deux courbes *devraient* se ressembler - Astuce de pro: alignez les légendes de temps! .exercise[ - 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.* .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- ## Pour aller plus loin avec les métriques de conteneur - [Prometheus, a Whirlwind Tour](https://speakerdeck.com/copyconstructor/prometheus-a-whirlwind-tour), an original overview of Prometheus - [Docker Swarm & Container Overview](https://grafana.net/dashboards/609), a custom dashboard for Grafana - [Gathering Container Metrics](http://jpetazzo.github.io/2013/10/08/docker-containers-metrics/), a blog post about cgroups - [The Prometheus Time Series Database](https://www.youtube.com/watch?v=HbnGSNEjhUc), a talk explaining why custom data storage is necessary for metrics .blackbelt[DC17US: Monitoring, the Prometheus Way ([video](https://www.youtube.com/watch?v=PDxcEzu62jk&list=PLkA60AVN3hh-biQ6SCtBJ-WVTyBmmYho8&index=5))] .blackbelt[DC17EU: Prometheus 2.0 Storage Engine ([video](https://dockercon.docker.com/watch/NNZ8GXHGomouwSXtXnxb8P))] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: title, self-paced Merci à tous et toutes! .debug[[shared/thankyou.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/thankyou.md)] --- class: title, in-person C'est tout pour aujourd'hui! Des questions? ![end](images/end.jpg) .debug[[shared/thankyou.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/thankyou.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/Container-Ship-Freighter-Navigation-Elbe-Romance-1782991.jpg)] --- name: toc-liens-et-ressources class: title Liens et ressources .nav[ [Section préc.](#toc-collecter-les-mtriques) | [Retour à la table des matières](#toc-chapter-8) | [Section suivante](#toc-) ] .debug[(automatically generated title slide)] --- # Liens et ressources - [Docker Community Slack](https://community.docker.com/registrations/groups/4316) - [Docker Community Forums](https://forums.docker.com/) - [Docker Hub](https://hub.docker.com) - [Docker Blog](https://blog.docker.com/) - [Docker documentation](https://docs.docker.com/) - [Docker on StackOverflow](https://stackoverflow.com/questions/tagged/docker) - [Docker on Twitter](https://twitter.com/docker) - [Play With Docker Hands-On Labs](https://training.play-with-docker.com/) .footnote[Ces diapos (et les futures mises à jour) sont sur → https://container.training/] .debug[[containers/links.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/links.md)]
RUN CMD, EXPOSE ... ``` * Le _build_ échoue dès qu'une instruction échoue * Si `RUN ` é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) .debug[[containers/Dockerfile_Tips.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Dockerfile_Tips.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/two-containers-on-a-truck.jpg)] --- name: toc-exemples-de-dockerfile class: title Exemples de Dockerfile .nav[ [Section préc.](#toc-astuces-pour-dockerfiles-efficaces) | [Retour à la table des matières](#toc-chapter-3) | [Section suivante](#toc-dockerfiles-avancs) ] .debug[(automatically generated title slide)] --- # 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. .debug[[containers/Dockerfile_Tips.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Dockerfile_Tips.md)] --- ## 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. .small[ ```dockerfile 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](https://github.com/docker-library/wordpress/blob/618490d4bdff6c5774b84b717979bfe3d6ba8ad1/apache/Dockerfile)) .debug[[containers/Dockerfile_Tips.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Dockerfile_Tips.md)] --- ## 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*) .debug[[containers/Dockerfile_Tips.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Dockerfile_Tips.md)] --- ```dockerfile 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"] ``` .debug[[containers/Dockerfile_Tips.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Dockerfile_Tips.md)] --- ## 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: ```dockerfile 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](https://github.com/plone/plone.docker/blob/master/5.1/5.1.0/alpine/Dockerfile)) .debug[[containers/Dockerfile_Tips.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Dockerfile_Tips.md)] --- ## _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. .debug[[containers/Dockerfile_Tips.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Dockerfile_Tips.md)] --- ## Un script d'_entrypoint_ typique ```dockerfile #!/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](https://github.com/docker-library/redis/blob/d24f2be82673ccef6957210cc985e392ebdc65e4/4.0/alpine/docker-entrypoint.sh)) .debug[[containers/Dockerfile_Tips.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Dockerfile_Tips.md)] --- ## 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. .small[ ```dockerfile 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](https://github.com/nodejs/docker-node/blob/master/10/alpine/Dockerfile)) .debug[[containers/Dockerfile_Tips.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Dockerfile_Tips.md)] --- ## 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](https://github.com/jpetazzo/trainingwheels) comme exemple. .debug[[containers/Dockerfile_Tips.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Dockerfile_Tips.md)] --- ## Image de production Le Dockerfile génère une image exploitant gunicorn: ```dockerfile 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](https://github.com/jpetazzo/trainingwheels/blob/master/www/Dockerfile)) .debug[[containers/Dockerfile_Tips.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Dockerfile_Tips.md)] --- ## 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. .small[ ```yaml services: www: build: www ports: - 8000:5000 user: nobody environment: DEBUG: 1 command: python counter.py volumes: - ./www:/src ``` ] (Source: [Fichier Compose trainingwheels](https://github.com/jpetazzo/trainingwheels/blob/master/docker-compose.yml)) .debug[[containers/Dockerfile_Tips.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Dockerfile_Tips.md)] --- ## 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! .debug[[containers/Dockerfile_Tips.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Dockerfile_Tips.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/wall-of-containers.jpeg)] --- name: toc-dockerfiles-avancs class: title Dockerfiles avancés .nav[ [Section préc.](#toc-exemples-de-dockerfile) | [Retour à la table des matières](#toc-chapter-3) | [Section suivante](#toc-bases-du-rseau-pour-conteneur) ] .debug[(automatically generated title slide)] --- class: title # Dockerfiles avancés ![construction](images/title-advanced-dockerfiles.jpg) .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## 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. .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## `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) .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## 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: ```dockerfile 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. : ```dockerfile RUN [ "apt-get", "update" ] ``` .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## `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`. .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## Fusion de _layers_: Il est possible d'exécuter plusieurs commandes d'un seul coup: ```dockerfile 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: ```dockerfile RUN apt-get update \ && apt-get install -y wget \ && apt-get clean ``` .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## Instruction `EXPOSE` L'instruction `EXPOSE` indique à Docker quels ports doivent être publiés pour cette image. ```dockerfile 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é. .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## Exposer des ports * Quand vous lancez `docker run -p ...`, 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. .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## Instruction `COPY` L'instruction `COPY` ajoute des fichiers et du contenu depuis votre machine hôte vers l'image. ```dockerfile 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. .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## 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: ```dockerfile 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. .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## Instruction `ADD` `ADD` fonctionne presque comme `COPY`, mais avec quelques petits plus. `ADD` peut récupérer des fichiers à distance: ```dockerfile 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: ```dockerfile 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. .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## `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) .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## Instruction `VOLUME` L'instruction `VOLUME` indique à Docker qu'un dossier spécifique devrait être un *volume*. ```dockerfile 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. .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## 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. ```dockerfile WORKDIR /src ``` Vous pouvez spécifier plusieurs `WORKDIR` pour changer de dossier au cours des différentes opérations. .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## Instruction `ENV` L'instruction `ENV` déclare des variables d'environnement qui devraient être affectées dans tout _container_ lancé depuis cette image. ```dockerfile ENV WEBAPP_PORT 8080 ``` Ceci a pour résultat de créer une variable d'environnement dans tout container provenant de cette image. ```bash WEBAPP_PORT=8080 ``` Vous pouvez aussi spécifier des variables d'environnement via `docker run.` ```bash $ docker run -e WEBAPP_PORT=8000 -e WEBAPP_HOST=www.example.com ... ``` .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## 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. .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## Instruction `CMD` L'instruction `CMD` est la commande par défaut qui se lance quand un container est instancié à partir d'une image. ```dockerfile 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: ```bash $ docker run /web_image nginx -g "daemon off;" ``` Nous pouvons juste écrire: ```bash $ docker run /web_image ``` .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## `CMD` plus en détail Tout comme `RUN`, l'instruction `CMD` existe sous deux formes. La première lance un *shell*: ```dockerfile CMD nginx -g "daemon off;" ``` La seconde s'exécute directement, sans passer par un *shell*: ```dockerfile CMD [ "nginx", "-g", "daemon off;" ] ``` .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- class: extra-details ## Surcharger l'instruction `CMD` `CMD` peut être forcé au lancement d'un container. ```bash $ docker run -it /web_image bash ``` Ceci lancera `bash` au lieu de `nginx -g "daemon off;"`. .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## 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" (`["..."]`). ```dockerfile ENTRYPOINT [ "/bin/ls" ] ``` Avec ceci, si nous lançons la commande: ```bash $ docker run training/ls -l ``` Au lieu d'essayer de lancer `-l`, le container va exécuter `/bin/ls -l` .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- class: extra-details ## Surcharger l'instruction the `ENTRYPOINT` Le point d'entrée peut aussi être redéfini. ```bash $ 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:/# ``` .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## Comment `CMD` et `ENTRYPOINT` interagissent Les instructions `CMD` et `ENTRYPOINT` fonctionnent mieux quand elles sont définies ensemble. ```dockerfile 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. ```bash $ docker run -d /web_image -t ``` Cela surchargera les options `CMD` avec de nouvelles valeurs. .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- ## 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. .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- class: extra-details ## 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. ```dockerfile ONBUILD COPY . /src ``` * Vous ne pouvez pas chainer des instructions `ONBUILD`. * `ONBUILD` ne peut être utilisé pour déclencher des instructions `FROM` .debug[[containers/Advanced_Dockerfiles.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Advanced_Dockerfiles.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/Container-Ship-Freighter-Navigation-Elbe-Romance-1782991.jpg)] --- name: toc-bases-du-rseau-pour-conteneur class: title Bases du réseau pour conteneur .nav[ [Section préc.](#toc-dockerfiles-avancs) | [Retour à la table des matières](#toc-chapter-4) | [Section suivante](#toc-pilote-rseau-pour-conteneur) ] .debug[(automatically generated title slide)] --- class: title # Bases du réseau pour conteneur ![A dense graph network](images/title-container-networking-basics.jpg) .debug[[containers/Container_Networking_Basics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Networking_Basics.md)] --- ## 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. .debug[[containers/Container_Networking_Basics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Networking_Basics.md)] --- ## Un serveur web simple, statique Lancer l'image `nginx` du Docker Hub, qui contient un serveur web basique: ```bash $ 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? .debug[[containers/Container_Networking_Basics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Networking_Basics.md)] --- ## Trouver le port de notre serveur web Nous allons utiliser `docker ps`: ```bash $ 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. .debug[[containers/Container_Networking_Basics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Networking_Basics.md)] --- ## 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](images/welcome-to-nginx.png) .debug[[containers/Container_Networking_Basics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Networking_Basics.md)] --- ## 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: ```bash $ curl localhost:32768 Welcome to nginx! ... ``` .debug[[containers/Container_Networking_Basics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Networking_Basics.md)] --- ## 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`: ```bash $ 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`: ```bash $ docker history nginx IMAGE CREATED CREATED BY 7f70b30f2cc6 11 days ago /bin/sh -c #(nop) CMD ["nginx" "-g" "… 11 days ago /bin/sh -c #(nop) STOPSIGNAL [SIGTERM] 11 days ago /bin/sh -c #(nop) EXPOSE 80/tcp ``` .debug[[containers/Container_Networking_Basics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Networking_Basics.md)] --- ## 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. .debug[[containers/Container_Networking_Basics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Networking_Basics.md)] --- ## 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: ```bash $ docker port 80 32768 ``` .debug[[containers/Container_Networking_Basics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Networking_Basics.md)] --- ## Affectation manuelle des numéros de port Si vous voulez allouer vous-même les numéros de port, aucun souci: ```bash $ 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`. .debug[[containers/Container_Networking_Basics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Networking_Basics.md)] --- ## 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. .debug[[containers/Container_Networking_Basics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Networking_Basics.md)] --- ## Trouver l'adresse IP du conteneur Nous pouvons utiliser la commande `docker inspect` pour trouver l'adresse IP de notre conteneur. ```bash $ docker inspect --format '{{ .NetworkSettings.IPAddress }}' 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. .debug[[containers/Container_Networking_Basics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Networking_Basics.md)] --- ## 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`. ```bash $ ping 64 bytes from : icmp_req=1 ttl=64 time=0.085 ms 64 bytes from : icmp_req=2 ttl=64 time=0.085 ms 64 bytes from : icmp_req=3 ttl=64 time=0.085 ms ``` .debug[[containers/Container_Networking_Basics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Networking_Basics.md)] --- ## 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. .debug[[containers/Container_Networking_Basics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Networking_Basics.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/ShippingContainerSFBay.jpg)] --- name: toc-pilote-rseau-pour-conteneur class: title Pilote réseau pour conteneur .nav[ [Section préc.](#toc-bases-du-rseau-pour-conteneur) | [Retour à la table des matières](#toc-chapter-4) | [Section suivante](#toc-le-container-network-model) ] .debug[(automatically generated title slide)] --- # 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. .debug[[containers/Network_Drivers.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Network_Drivers.md)] --- ## 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. .debug[[containers/Network_Drivers.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Network_Drivers.md)] --- ## 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. .debug[[containers/Network_Drivers.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Network_Drivers.md)] --- ## 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 ...) .debug[[containers/Network_Drivers.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Network_Drivers.md)] --- ## 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.) .debug[[containers/Network_Drivers.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Network_Drivers.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/aerial-view-of-containers.jpg)] --- name: toc-le-container-network-model class: title Le _Container Network Model_ .nav[ [Section préc.](#toc-pilote-rseau-pour-conteneur) | [Retour à la table des matières](#toc-chapter-4) | [Section suivante](#toc-service-discovery-avec-les-containers) ] .debug[(automatically generated title slide)] --- class: title # Le _Container Network Model_ ![A denser graph network](images/title-the-container-network-model.jpg) .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## 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. .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## 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`. ```bash $ docker network ls NETWORK ID NAME DRIVER 6bde79dfcf70 bridge bridge 8d9c78725538 none null eb0eeab782f4 host host 4c1ff84d6d3f blog-dev overlay 228a4355d548 blog-prod overlay ``` .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## 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é. .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## 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) .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## 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 .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: pic ## _Container_ simple dans un réseau Docker ![bridge0](images/bridge1.png) .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: pic ## Deux _containers_ sur un seul réseau Docker ![bridge2](images/bridge2.png) .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: pic ## Deux _containers_ sur deux réseaux Docker ![bridge3](images/bridge3.png) .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## Créer un réseau Essayons de déclarer un nouveau réseau appelé `dev`. ```bash $ docker network create dev 4c1ff84d6d3f1733d3e233ee039cac276f425a9d5228a4355d54878293a889ba ``` Le réseau est maintenant visible avec la commande `network ls`; ```bash $ docker network ls NETWORK ID NAME DRIVER 6bde79dfcf70 bridge bridge 8d9c78725538 none null eb0eeab782f4 host host 4c1ff84d6d3f dev bridge ``` .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## 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`. ```bash $ docker run -d --name es --net dev elasticsearch:2 8abb80e229ce8926c7223beb69699f5f34d6f1d438bfc5682db893e798046863 ``` .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## Communication entre _containers_ Et maintenant, ajoutons un autre _container_ sur ce réseau. .small[ ```bash $ 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: .small[ ```bash / # 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:/# ``` ] .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## 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. .small[ ```bash [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`) .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/blue-containers.jpg)] --- name: toc-service-discovery-avec-les-containers class: title _Service discovery_ avec les _containers_ .nav[ [Section préc.](#toc-le-container-network-model) | [Retour à la table des matières](#toc-chapter-4) | [Section suivante](#toc-processus-de-dveloppement-local-avec-docker) ] .debug[(automatically generated title slide)] --- # _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; .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## 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: ```bash $ docker run --net dev -d -P jpetazzo/trainingwheels ``` Vérifier quel port lui a été alloué: ```bash $ docker ps -l ``` .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## Tester le serveur web * Si nous ouvrons l'application à ce stage, nous verrons une page d'erreur: ![Trainingwheels error](images/trainingwheels-error.png) * 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`. .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## 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_; ```bash $ docker run --net dev --name redis -d redis ``` .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## Tester à nouveau le serveur web * Si nous ouvrons l'application à présent, nous devrions voir que l'appli fonctionne correctement: ![Trainingwheels OK](images/trainingwheels-ok.png) * 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. .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## 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. .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## Utiliser un alias de réseau au lieu d'un nom Supprimons le _container_ `redis`: ```bash $ docker rm -f redis ``` Et ajoutons un nouveau qui ne bloque pas le nom `redis`: ```bash $ 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). .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## 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` ```bash $ 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). .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## 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. .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## Créer des _containers_ sur un autre réseau Créez un réseau `prod`. ```bash $ docker network create prod 5a41562fecf2d8f115bedc16865f7336232a04268bdf2bd816aecca01b68d50c ``` Nous pouvons maintenant créer plusieurs _containers_ avec un alias `es` sur le nouveau réseau `prod`. ```bash $ 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 ``` .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## Résoudre les alias de réseau Essayons la résolution DNS, en utilisant l'outil `nslookup` livré dans l'image `alpine`. ```bash $ 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)'`) .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## 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: .small[ ```bash $ 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`: .small[ ```bash $ docker run --rm --net prod centos curl -s es:9200 { "name" : "The Symbiote", ... } ``` ] .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## 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. .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## 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. .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## 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_). .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## 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. ```bash $ 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!* .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## 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*. .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## 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](https://www.youtube.com/watch?v=EuzoEaE6Cqs) ou [ces diapos](https://container.training/swarm-selfpaced.yml.html). .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## 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` .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## 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 ` * `docker network disconnect ` .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## 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: ```bash $ docker run -ti alpine sh / # ``` * Dans ce _container_, essayons de _ping_ le _container_ `es`: ```bash / # ping es ping: bad address 'es' ``` Cela ne fonctionne pas, mais nous allons corriger cela en connectant le _container_. .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## 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: ```bash $ docker network connect dev `` ``` .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## Vérifier nos actions * Essayez encore `ping es` depuis le _container_. * Cela devrait fonctionner correctement normalement: ```bash / # 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. .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## Examen du réseau dans le _container_ Nous pouvons lister les interfaces réseau avec `ifconfig`, `ip a`, ou `ip l`: .small[ ```bash / # ip a 1: lo: 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: 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: 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. .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- ## Se déconnecter d'un réseau * Essayons ce que donne la commande symétrique pour déconnecter le _container_: ```bash $ docker network disconnect dev ``` * A partir de maintenant, si on cherche à _ping_ `es`, ce ne sera pas résolu: ```bash / # ping es ping: bad address 'es' ``` * Si on essaie de _ping_ l'adresse IP directement, cela ne fonctionne plus non plus: ```bash / # ping 172.20.0.3 ... (rien ne se passe jusqu'à ce qu'on tape Ctrl-C) ``` .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## 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. .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## 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: .small[ ```bash $ 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`. ... ``` ] .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: extra-details ## 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!) .debug[[containers/Container_Network_Model.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Container_Network_Model.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/chinook-helicopter-container.jpg)] --- name: toc-processus-de-dveloppement-local-avec-docker class: title Processus de développement local avec Docker .nav[ [Section préc.](#toc-service-discovery-avec-les-containers) | [Retour à la table des matières](#toc-chapter-5) | [Section suivante](#toc-travailler-avec-des-volumes) ] .debug[(automatically generated title slide)] --- class: title # Processus de développement local avec Docker ![Construction site](images/title-local-development-workflow-with-docker.jpg) .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## 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. .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## 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. .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## 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. ```bash $ git clone https://github.com/jpetazzo/namer ``` .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## Examiner le code ```bash $ 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? .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## Examiner le `Dockerfile` ```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. .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## Générer et lancer l'application "namer" * Générons l'application grâce au `Dockerfile`! -- ```bash $ docker build -t namer . ``` -- * Et maintenant lancez-là. *on doit publier ses ports.* -- ```bash $ docker run -dP namer ``` -- * Vérifiez sur quel port le conteneur écoute. -- ```bash $ docker ps -l ``` .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## 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](images/webapp-in-blue.png) .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## 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 .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## Notre premier volume On va indiquer à Docker de monter le dossier en cours sur `/src` dans le conteneur. ```bash $ 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). .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## 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: ```bash [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! .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## Tester le conteneur de développement * Trouvez le port utilisé par notre nouveau conteneur. ```bash $ 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. .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## Opérer un changement dans notre application Notre client n'aime pas du tout la couleur de notre texte. Allons la changer. ```bash $ vi company_name_generator.rb ``` Et changeons: ```css color: royalblue; ``` En: ```css color: red; ``` .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## Tester nos changements * Recharger l'application dans notre navigateur -- * La couleur doit avoir changé. ![web application 2](images/webapp-in-red.png) .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## 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.) .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## Jetez vos serveurs et brûlez votre code *(C'est le titre d'un [billet de blog de 2013](http://chadfowler.com/2013/06/23/immutable-deployments.html) 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é: ```bash docker ps ``` * Pointez le navigateur dessus pour confirmer que ça marche toujours bien. .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## 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* .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## 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. .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- class: extra-details ## 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. .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- class: extra-details ## Exemple avec `docker exec` ```bash $ #Vous pouvez lancer des commandes ruby dans le même conteneur où l'appli tourne! $ docker exec -it 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 ``` .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- class: extra-details ## Arrêter le conteneur Maintenant que nous avons fini, arrêtons notre conteneur. ```bash $ docker stop ``` Et supprimons-le. ```bash $ docker rm ``` .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- ## 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. .debug[[containers/Local_Development_Workflow.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Local_Development_Workflow.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/container-cranes.jpg)] --- name: toc-travailler-avec-des-volumes class: title Travailler avec des volumes .nav[ [Section préc.](#toc-processus-de-dveloppement-local-avec-docker) | [Retour à la table des matières](#toc-chapter-5) | [Section suivante](#toc-compose-pour-les-dveloppeurs) ] .debug[(automatically generated title slide)] --- class: title # Travailler avec des volumes ![volume](images/title-working-with-volumes.jpg) .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- ## 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. .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- ## 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". .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- ## "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`. ```dockerfile VOLUME /uploads ``` * En ligne de commande, avec l'option `-v` avec `docker run`. ```bash $ docker run -d -v /uploads myapp ``` Dans les deux cas, `/uploads` (à l'intérieur du conteneur) sera un volume. .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- class: extra-details ## 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). .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- class: extra-details ## 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. .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- class: extra-details ## Partager les logs d'un serveur d'application avec un autre conteneur Démarrons un conteneur Tomcat: ```bash $ 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: ```bash $ 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: ```bash $ curl localhost:8080 ``` .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- ## 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`: ```bash $ 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. .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- ## Nommer les volumes * On peut créer des volumes sans conteneur, et les utiliser ensuite dans plusieurs conteneurs. Ajoutons quelques volumes directement. ```bash $ docker volume create webapps webapps ``` ```bash $ docker volume create logs logs ``` Nos volumes ne sont attachés à aucun dossier en particulier. .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- ## 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. ```bash $ 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: ```bash $ curl localhost:1234 ... (Tomcat nous raconte combien il est content de tourner) ... ``` .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- ## 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`. ```bash $ 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. .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- ## 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. ```bash $ docker run -d -v /chemin/depuis/notre/hote:/chemin/dans/le/conteneur image ... ``` .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- class: extra-details ## 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). .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- class: extra-details ## Migration de données en pratique Créons un conteneur Redis. ```bash $ docker run -d --name redis28 redis:2.8 ``` Puis connectons-nous au conteneur Redis pour ajouter des données. ```bash $ docker run -ti --link redis28:redis busybox telnet redis 6379 ``` Envoyons les commandes suivantes: ```bash SET counter 42 INFO server SAVE QUIT ``` .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- class: extra-details ## Mettre à jour Redis Arrêtez le conteneur Redis. ```bash $ docker stop redis28 ``` Démarrer le nouveau conteneur Redis. ```bash $ docker run -d --name redis30 --volumes-from redis28 redis:3.0 ``` .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- class: extra-details ## Tester le nouveau Redis Connectez-vous au conteneur Redis pour voir les données. ```bash docker run -ti --link redis30:redis busybox telnet redis 6379 ``` Lancez les commandes suivantes: ```bash GET counter INFO server QUIT ``` .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- ## 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. .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- class: extra-details ## Vérifier les volumes définis par une image Vous vous demandez si une image a des volumes? Il suffit d'appeler `docker inspect`: ```bash $ # docker inspect training/datavol [{ "config": { . . . "Volumes": { "/var/webapp": {} }, . . . }] ``` .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- class: extra-details ## 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): ```bash $ docker inspect [{ "ID": "", . . . "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. .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- ## 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. ```bash $ 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`! .warning[Puisque ce conteneur a accès à la socket Docker, il a un accès root au hôte.] .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- ## 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](https://rexray.io/) - 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](https://portworx.com/) - fournit un stockage par bloc distribué pour conteneurs. * [Gluster](https://www.gluster.org/) - 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](https://store.docker.com/search?category=volume&q=&type=plugin)! .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- ## 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. .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- ## Syntaxe de `--mount` Attacher un dossier de l'hôte à un chemin du conteneur: ```bash $ docker run \ --mount type=bind,source=/path/on/host,target=/path/in/container alpine ``` Monter un volume dans un chemin du conteneur: ```bash $ docker run \ --mount source=myvolume,target=/path/in/container alpine ``` Monter un _tmpfs_ (pour stockage de fichiers temporaires en mémoire): ```bash $ docker run \ --mount type=tmpfs,destination=/path/in/container,tmpfs-size=1000000 alpine ``` .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- ## 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. .debug[[containers/Working_With_Volumes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Working_With_Volumes.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/container-housing.jpg)] --- name: toc-compose-pour-les-dveloppeurs class: title Compose pour les développeurs .nav[ [Section préc.](#toc-travailler-avec-des-volumes) | [Retour à la table des matières](#toc-chapter-5) | [Section suivante](#toc-configuration-dapplications) ] .debug[(automatically generated title slide)] --- # 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. .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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! .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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. .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- class: pic ![composeup](images/composeup.gif) .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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](https://github.com/docker/compose/releases) ou avec `pip install docker-compose`. Vous pouvez vérifier votre installation en tapant: ```bash $ docker-compose --version ``` .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## Lancer notre première _stack_ avec Compose Première étape: cloner le code source de l'appli que nous allons manipuler. ```bash $ cd $ git clone https://github.com/jpetazzo/trainingwheels ... $ cd trainingwheels ``` Seconde étape: démarrer votre appli. ```bash $ 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. .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## Lancer notre première _stack_ avec Compose Vérifiez que notre appli répond sur: `http://:8000`. ![composeapp](images/composeapp.png) .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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. .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## Le fichier `docker-compose.yml` Voici le fichier utilisé dans la démo: .small[ ```yaml version: "2" services: www: build: www ports: - 8000:5000 user: nobody environment: DEBUG: 1 command: python counter.py volumes: - ./www:/src redis: image: redis ``` ] .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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. .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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](https://docs.docker.com/compose/compose-file/) a un excellent niveau d'information sur le format du fichier Compose, à propos de toutes les différentes versions. .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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. .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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/ .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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: ```bash docker-compose up --build ``` Une autre option commune est de démarrer les conteneurs en arrière-plan: ```bash docker-compose up -d ``` .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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: ```bash $ 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 ``` .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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`: ```bash $ docker-compose kill ``` De même, `docker-compose rm` vous permet de supprimer les conteneurs (après confirmation): ```bash $ 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... ``` .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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. ```bash $ 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. .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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_. .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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`. .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- ## 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. .debug[[containers/Compose_For_Dev_Stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Compose_For_Dev_Stacks.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/containers-by-the-water.jpg)] --- name: toc-configuration-dapplications class: title Configuration d'applications .nav[ [Section préc.](#toc-compose-pour-les-dveloppeurs) | [Retour à la table des matières](#toc-chapter-6) | [Section suivante](#toc-stockage-des-secrets) ] .debug[(automatically generated title slide)] --- # 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. .debug[[containers/Application_Configuration.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Application_Configuration.md)] --- ## Paramètres en ligne de commande ```bash 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. .debug[[containers/Application_Configuration.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Application_Configuration.md)] --- ## 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) .debug[[containers/Application_Configuration.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Application_Configuration.md)] --- ## Variables d'environnement ```bash 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. .debug[[containers/Application_Configuration.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Application_Configuration.md)] --- ## 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. .debug[[containers/Application_Configuration.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Application_Configuration.md)] --- ## Configuration incluse ```dockerfile 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) .debug[[containers/Application_Configuration.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Application_Configuration.md)] --- ## 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) .debug[[containers/Application_Configuration.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Application_Configuration.md)] --- ## Configuration par volume ```bash 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.) .debug[[containers/Application_Configuration.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Application_Configuration.md)] --- ## 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. .debug[[containers/Application_Configuration.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Application_Configuration.md)] --- ## 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. .debug[[containers/Application_Configuration.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Application_Configuration.md)] --- ## Exemple de configuration dynamique par volume Dans un premier terminal, démarrer un _load balancer_ avec une configuration initiale: ```bash $ docker run --name loadbalancer jpetazzo/hamba \ 80 goo.gl:80 ``` Dans un autre terminal, reconfigurer ce _load balancer_: ```bash $ 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_.) .debug[[containers/Application_Configuration.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Application_Configuration.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/distillery-containers.jpg)] --- name: toc-stockage-des-secrets class: title Stockage des secrets .nav[ [Section préc.](#toc-configuration-dapplications) | [Retour à la table des matières](#toc-chapter-6) | [Section suivante](#toc-gestion-des-logs) ] .debug[(automatically generated title slide)] --- # Stockage des secrets .warning[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. .debug[[containers/Application_Configuration.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Application_Configuration.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/lots-of-containers.jpg)] --- name: toc-gestion-des-logs class: title Gestion des _logs_ .nav[ [Section préc.](#toc-stockage-des-secrets) | [Retour à la table des matières](#toc-chapter-6) | [Section suivante](#toc-limiter-les-ressources) ] .debug[(automatically generated title slide)] --- # 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. .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## 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.* .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## É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. .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## É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. .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## 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. .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## 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. .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## 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à! .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## 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. .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## 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: ```bash $ docker run --log-opt max-size=10m --log-opt max-file=3 elasticsearch ``` .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## 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!* .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## 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. .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## 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 ```bash $ 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. .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## 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. .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## 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: ```bash $ docker run --log-driver=gelf --log-opt=gelf-address=udp://localhost:12201 \ alpine echo hello world ``` .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## Afficher les _logs_ dans ELK - Se connecter à l'interface Kibana. - Il est exposé sur le port 5601. - Ouvrir http://X.X.X.X:5601. .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## "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. .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- ## 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]( https://jpetazzo.github.io/2017/01/20/docker-logging-gelf/). .debug[[containers/Logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Logging.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/plastic-containers.JPG)] --- name: toc-limiter-les-ressources class: title Limiter les ressources .nav[ [Section préc.](#toc-gestion-des-logs) | [Retour à la table des matières](#toc-chapter-6) | [Section suivante](#toc-notre-application-de-dmo) ] .debug[(automatically generated title slide)] --- # 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!) .debug[[containers/Resource_Limits.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Resource_Limits.md)] --- ## 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.) .debug[[containers/Resource_Limits.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Resource_Limits.md)] --- ## 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.) .debug[[containers/Resource_Limits.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Resource_Limits.md)] --- ## 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. .debug[[containers/Resource_Limits.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Resource_Limits.md)] --- ## 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_). .debug[[containers/Resource_Limits.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Resource_Limits.md)] --- ## Limiter l'usage de la RAM Exemple: ```bash 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. .debug[[containers/Resource_Limits.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Resource_Limits.md)] --- ## Limiter à la fois RAM et _swap_ Exemple: ```bash 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_. .debug[[containers/Resource_Limits.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Resource_Limits.md)] --- ## 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?" .debug[[containers/Resource_Limits.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Resource_Limits.md)] --- ## 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. .debug[[containers/Resource_Limits.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Resource_Limits.md)] --- ## 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. .debug[[containers/Resource_Limits.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Resource_Limits.md)] --- ## 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. .debug[[containers/Resource_Limits.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Resource_Limits.md)] --- ## É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) .debug[[containers/Resource_Limits.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Resource_Limits.md)] --- ## 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`. .debug[[containers/Resource_Limits.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/Resource_Limits.md)] --- ## Toutes nouvelles versions! - Engine 18.09 - Compose 1.23 - Machine 0.16 .exercise[ - Vérifier toutes les versions installées ```bash docker version docker-compose -v docker-machine -v ``` ] .debug[[swarm/versions.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/versions.md)] --- ## 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](https://blog.docker.com/2017/03/docker-enterprise-edition/) .debug[[swarm/versions.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/versions.md)] --- class: extra-details ## 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. .debug[[swarm/versions.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/versions.md)] --- class: extra-details ## 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) .debug[[swarm/versions.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/versions.md)] --- class: pic ![Docker CE/EE release cycle](images/docker-ce-ee-lifecycle.png) .debug[[swarm/versions.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/versions.md)] --- ## 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_ .debug[[swarm/versions.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/versions.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/train-of-containers-1.jpg)] --- name: toc-notre-application-de-dmo class: title Notre application de démo .nav[ [Section préc.](#toc-limiter-les-ressources) | [Retour à la table des matières](#toc-chapter-6) | [Section suivante](#toc-identifier-les-goulots-dtranglement) ] .debug[(automatically generated title slide)] --- # 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. .exercise[ - Cloner le dépôt sur `node1`: ```bash 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.) .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- ## 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... .exercise[ - Aller dans le dossier `dockercoins` du dépôt cloné: ```bash cd ~/container.training/dockercoins ``` - Utiliser Compose pour générer et lancer tous les conteneurs: ```bash 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. .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- ## Qu'est-ce que cette application? -- - C'est un miner de DockerCoin! .emoji[💰🐳📦🚢] -- - 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) .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- ## 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]( https://github.com/jpetazzo/container.training/blob/master/dockercoins/docker-compose.yml) .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- ## 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!)* .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- class: pic ![Diagramme montrant les 5 conteneurs de notre application](images/dockercoins-diagram.svg) .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- ## _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") .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- ## Exemple dans `worker/worker.py` ```python 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]( https://github.com/jpetazzo/container.training/blob/8279a3bce9398f7c1a53bdd95187c53eda4e6435/dockercoins/worker/worker.py#L17 )) .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- class: extra-details ## 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 .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- ## 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]( https://github.com/jpetazzo/container.training/tree/master/dockercoins) - Le fichier Compose ([docker-compose.yml]( https://github.com/jpetazzo/container.training/blob/master/dockercoins/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](https://github.com/jpetazzo/container.training/blob/master/dockercoins/hasher/), `rng` est dans le dossier [rng](https://github.com/jpetazzo/container.training/blob/master/dockercoins/rng/), etc.) .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- class: extra-details ## 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. .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- ## 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 .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- ## 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. .exercise[ - 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. .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- class: self-paced, extra-details ## Si le graphique ne se charge pas Si tout ce que vous voyez est une erreur `Page not found`, cela peut être à cause de votre Docker Engine qui tourne sur une machine différente. Cela peut être le cas si: - vous utilisez Docker Toolbox - vous utilisez une VM (locale ou distante) créée avec Docker Machine - vous contrôlez un Docker Engine distant Quand vous lancez DockerCoins en mode développement, les fichiers statiques de l'interface web sont appliqués au conteneur via un volume. Hélas, les volumes ne fonctionnent que sur un environnement local, ou quand vous passez par Docker for Desktop. Comment corriger cela? Arrêtez l'appli avec `^C`, modifiez `dockercoins.yml`, commentez la section `volumes`, et relancez le tout. .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- class: extra-details ## 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? -- class: extra-details - 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? .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- class: extra-details ## 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? -- class: extra-details - "Je suis carrément incapable d'écrire du bon code frontend" 😀 — Jérôme .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- ## 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` .exercise[ - 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! .debug[[shared/sampleapp.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/sampleapp.md)] --- ## Relancer en arrière-plan - Bien des options et des commandes dans Compose sont inspirées par celle de `docker` .exercise[ - Démarrer l'appli en arrière-plan avec l'option `-d`: ```bash docker-compose up -d ``` - Vérifier que notre appli est lancée avec la commande `ps`: ```bash docker-compose ps ``` ] `docker-compose ps` montre aussi les ports exposés par l'application. .debug[[shared/composescale.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/composescale.md)] --- class: extra-details ## Afficher les logs - La commande `docker-compose logs` marche comme `docker logs` .exercise[ - Afficher tous les logs depuis la naissance du conteneur, et sortir juste après: ```bash docker-compose logs ``` - Suivre le flux de logs du conteneur, en commençant par les 10 dernières lignes de chaque conteneur: ```bash docker-compose logs --tail 10 --follow ``` ] Astuce: taper `^S` et `^Q` pour suspendre/reprendre l'affichage des logs. .debug[[shared/composescale.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/composescale.md)] --- ## 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. .debug[[shared/composescale.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/composescale.md)] --- ## Examiner l'usage de ressources - Jetons un oeil au CPU, à la mémoire et aux E/S .exercise[ - 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? .debug[[shared/composescale.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/composescale.md)] --- ## Escalader les workers sur un seul noeud - Docker Compose supporte la mise à l'échelle - Escaladons `worker` et voyons ce qu'il se passe! .exercise[ - Démarrer un conteneur `worker` supplémentaire: ```bash 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) ] .debug[[shared/composescale.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/composescale.md)] --- ## (Ac)cumuler les workers - Super, ajoutons encore plus de workers alors, et le tour est joué! .exercise[ - Démarrer huit conteneurs de `worker` de plus: ```bash 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 ] .debug[[shared/composescale.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/composescale.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/train-of-containers-2.jpg)] --- name: toc-identifier-les-goulots-dtranglement class: title Identifier les goulots d'étranglement .nav[ [Section préc.](#toc-notre-application-de-dmo) | [Retour à la table des matières](#toc-chapter-6) | [Section suivante](#toc-swarmkit) ] .debug[(automatically generated title slide)] --- # 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.) .debug[[shared/composescale.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/composescale.md)] --- ## 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: ```yaml ... rng: build: rng ports: - "8001:80" hasher: build: hasher ports: - "8002:80" ... ``` .debug[[shared/composescale.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/composescale.md)] --- ## Mesurer la latence sous contrainte Nous utiliserons pour cela `httping`. .exercise[ - Vérifier la latence de `rng`: ```bash httping -c 3 localhost:8001 ``` - Vérifier la latence de `hasher`: ```bash httping -c 3 localhost:8002 ``` ] `rng` révèle une latence bien plus grande que `hasher`. .debug[[shared/composescale.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/composescale.md)] --- ## Nettoyage - Avant de continuer, supprimons tous ces conteneurs. .exercise[ - Dire à Compose de tout enlever: ```bash docker-compose down ``` ] .debug[[shared/composedown.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/composedown.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/two-containers-on-a-truck.jpg)] --- name: toc-swarmkit class: title SwarmKit .nav[ [Section préc.](#toc-identifier-les-goulots-dtranglement) | [Retour à la table des matières](#toc-chapter-6) | [Section suivante](#toc-dclaratif-vs-impratif) ] .debug[(automatically generated title slide)] --- # SwarmKit - [SwarmKit](https://github.com/docker/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 -- .footnote[.emoji[🐳] Saviez-vous que кит veut dire "baleine" en russe?] .debug[[swarm/swarmkit.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/swarmkit.md)] --- ## Fonctionnalités de SwarmKit - Base de données distribuée, hautement disponible basée sur [Raft]( https://en.wikipedia.org/wiki/Raft_%28computer_science%29) (é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 .debug[[swarm/swarmkit.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/swarmkit.md)] --- class: extra-details ## 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](https://twitter.com/@cbednarski), [@diptanu](https://twitter.com/diptanu) entre autres de l'avoir rappelé!) - Analogie offert par [@aluzzardi](https://twitter.com/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 .debug[[swarm/swarmkit.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/swarmkit.md)] --- ## 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 .debug[[swarm/swarmkit.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/swarmkit.md)] --- ## Illustration Sur la prochaine diapo: - baleines = noeuds (workers et managers) - singes = managers - singe violet = leader - singes gris = suiveurs - triangle en pointillés = protocole Raft .debug[[swarm/swarmkit.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/swarmkit.md)] --- class: pic ![Illustration](images/swarm-mode.svg) .debug[[swarm/swarmkit.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/swarmkit.md)] --- ## 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](https://github.com/docker/swarmkit/blob/master/design/nomenclature.md) du dépôt SwarmKit pour plus de détails. .debug[[swarm/swarmkit.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/swarmkit.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/wall-of-containers.jpeg)] --- name: toc-dclaratif-vs-impratif class: title Déclaratif vs Impératif .nav[ [Section préc.](#toc-swarmkit) | [Retour à la table des matières](#toc-chapter-6) | [Section suivante](#toc-mode-swarm) ] .debug[(automatically generated title slide)] --- # 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é .debug[[shared/declarative.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/declarative.md)] --- ## 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?* -- .footnote[Saviez-vous qu'il existait une [norme ISO](https://fr.wikipedia.org/wiki/ISO_3103) spécifiant comment infuser le thé?] .debug[[shared/declarative.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/declarative.md)] --- ## 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* .debug[[shared/declarative.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/declarative.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/Container-Ship-Freighter-Navigation-Elbe-Romance-1782991.jpg)] --- name: toc-mode-swarm class: title Mode Swarm .nav[ [Section préc.](#toc-dclaratif-vs-impratif) | [Retour à la table des matières](#toc-chapter-6) | [Section suivante](#toc-crer-notre-premier-swarm) ] .debug[(automatically generated title slide)] --- # 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) .debug[[swarm/swarmmode.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/swarmmode.md)] --- ## 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.) .exercise[ - Essayer une commande spéciale Swarm: ```bash docker node ls ``` ] -- Vous aurez un message d'erreur: ``` Error response from daemon: This node is not a swarm manager. [...] ``` .debug[[swarm/swarmmode.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/swarmmode.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/ShippingContainerSFBay.jpg)] --- name: toc-crer-notre-premier-swarm class: title Créer notre premier Swarm .nav[ [Section préc.](#toc-mode-swarm) | [Retour à la table des matières](#toc-chapter-6) | [Section suivante](#toc-lancer-notre-premier-service-swarm) ] .debug[(automatically generated title slide)] --- # 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. - .warning[NE PAS exécuter `docker swarm init` sur d'autres _nodes_!] Vous auriez plusieurs cluster disjoints. .exercise[ - Créer notre cluster depuis node1: ```bash docker swarm init ``` ] -- class: advertise-addr Si Docker vous dit `could not choose an IP address to advertise`, regardez la prochaine diapo! .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- class: advertise-addr ## Adresse IP à annoncer - En lançant le mode Swarm, chaque noeud *annonce* son adresse aux autres. (i.e. il leur dit *"vous pouvez me contacter sur 10.1.2.3:2377"*) - Si le noeud a une seule adresse IP, c'est activé automatiquement (Les adresses de l'interface _loopback_ et Docker bridge sont ignorées) - Si le noeud à plusieurs adresses IP, vous **devez** spécifier laquelle utiliser (Docker refusera d'en choisir une au hasard) - On peut indiquer une adresse IP ou un nom d'interface (Dans ce dernier cas, Docker va lire l'adresse IP de l'interface et l'utiliser) - On peut aussi spécifier un numéro de port (autrement, le port par défaut 2377 sera utilisé) .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- class: advertise-addr ## Utiliser un numéro de port non standard - Changer le port *annoncé* ne change pas le port *d'écoute* - Si on passe uniquement `--advertise-addr eth0:7777`, Swarm va quand même écouter sur 2377 - Vous devrez problablement aussi passer l'option `--listen-addr eth0:7777` - C'est utile dans le cas où il faut s'adapter à des scénarios où les ports *doivent* être différents (mapping de ports, répartiteurs de charge...) Exemple pour lancer Swarm sur un port différent: ```bash docker swarm init --advertise-addr eth0:7777 --listen-addr eth0:7777 ``` .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- class: advertise-addr ## Quelle adresse IP devrait-on annoncer? - Si vos noeuds ont une seule adresse IP, il est plus sûr de laisser l'auto-détection agir. .small[(Sauf si vos instances ont des adresses ip publiques et privées différentes, par ex. sur EC2, et que vous montez un Swarm impliquant des noeurs à l'intérieur et à l'extérieur du réseau privé: alors vous devriez annoncer l'adresse publique.)] - Si vos noeuds ont plusieurs adresses IP, choisissez une adresse qui est visible *par tous les autres noeuds* du Swarm. - Si vous êtes sur [play-with-docker](http://play-with-docker.com/), indiquez l'adresse IP affichée à coté du nom de la _node_. .small[(C'est l'adresse de votre noeud sur votre réseau privé interne superposé. L'autre adresse que vous pourriez voir est l'adresse de votre noeud sur le réseau `docker_gwbridge`, qui est utilisée pour le trafic sortant.)] Exemples: ```bash docker swarm init --advertise-addr 172.24.0.2 docker swarm init --advertise-addr eth0 ``` .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- class: extra-details ## 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) .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- ## 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 ``` .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- class: extra-details ## Vérifier que le mode Swarm est activé .exercise[ - Lancer la commande classique `docker info`: ```bash docker info ``` ] L'affichage devrait comporter: ``` Swarm: active NodeID: 8jud7o8dax3zxbags3f8yox4b Is Manager: true ClusterID: 2vcw2oa9rjps3a24m91xhvv0c ... ``` .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- ## Notre première commande en mode Swarm - Essayons exactement la même commande que précédemment .exercise[ - Lister les noeuds (enfin, le seul) de notre cluster: ```bash docker node ls ``` ] L'affichage devrait ressembler à ce qui suit: ``` ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS 8jud...ox4b * node1 Ready Active Leader ``` .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- ## 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 .emoji[😏] .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- ## Ajouter des noeuds au Swarm .exercise[ - Afficher le _token_ à nouveau: ```bash docker swarm join-token worker ``` - Se connecter à `node2`: ```bash ssh node2 ``` - Copier-coller la commande `docker swarm join ...` (celle qui a été affichée juste avant) ] .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- class: extra-details ## Vérifier que la node a été vraiment ajoutée - Restez sur `node2` pour l'instant! .exercise[ - On peut encore lancer `docker info` pour vérifier que la node participe au Swarm: ```bash docker info | grep ^Swarm ``` ] - Toutefois, les commandes Swarm ne passeront pas; comme, par ex.: ```bash 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. .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- ## Afficher notre cluster - Retournons sur `node1` et voyons quelle tête a notre cluster .exercise[ - Basculer vers `node1` (avec `exit`, `Ctrl-D` ...) - Afficher le cluster depuis `node1`, qui est un *manager*: ```bash 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 ``` .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- class: under-the-hood ## 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 .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- class: under-the-hood ## 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 ``` .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- class: under-the-hood ## 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_) .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- class: under-the-hood ## 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) .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- class: under-the-hood ## Sous le capot: je veux en savoir plus! Revisitez les concepts de SwarmKit: - Docker 1.12 Swarm Mode Deep Dive Part 1: Topology ([video](https://www.youtube.com/watch?v=dooPhkXT9yI)) - Docker 1.12 Swarm Mode Deep Dive Part 2: Orchestration ([video](https://www.youtube.com/watch?v=_F6PSP-qhdA)) Quelques présentations du Docker Distributed Systems Summit à Berlin: - Heart of the SwarmKit: Topology Management ([slides](https://speakerdeck.com/aluzzardi/heart-of-the-swarmkit-topology-management)) - Heart of the SwarmKit: Store, Topology & Object Model ([slides](https://www.slideshare.net/Docker/heart-of-the-swarmkit-store-topology-object-model)) ([video](https://www.youtube.com/watch?v=EmePhjGnCXY)) Et les présentations _Black Belt_ à DockerCon .blackbelt[DC17US: Everything You Thought You Already Knew About Orchestration ([video](https://www.youtube.com/watch?v=Qsv-q8WbIZY&list=PLkA60AVN3hh-biQ6SCtBJ-WVTyBmmYho8&index=6))] .blackbelt[DC17EU: Container Orchestration from Theory to Practice ([video](https://dockercon.docker.com/watch/5fhwnQxW8on1TKxPwwXZ5r))] .debug[[swarm/creatingswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/creatingswarm.md)] --- ## 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é .debug[[swarm/morenodes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/morenodes.md)] --- class: self-paced ## Ajouter plus de managers Avec Play-With-Docker: ```bash TOKEN=$(docker swarm join-token -q manager) for N in $(seq 3 5); do export DOCKER_HOST=tcp://node$N:2375 docker swarm join --token $TOKEN node1:2377 done unset DOCKER_HOST ``` .debug[[swarm/morenodes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/morenodes.md)] --- class: in-person ## Monter un cluster complet - Récupérons le *token*, et lançons la commande sur la dernière node avec SSH .exercise[ - Obtenir le jeton *manager*: ```bash TOKEN=$(docker swarm join-token -q manager) ``` - Ajouter la node qui reste: ```bash ssh node3 docker swarm join --token $TOKEN node1:2377 ``` ] [C'était facile.](https://www.youtube.com/watch?v=3YmMNpbFjp0) .debug[[swarm/morenodes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/morenodes.md)] --- ## Contrôler le Swarm depuis d'autres nodes .exercise[ - Essayer la commande suivante sur les différentes nodes: ```bash 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*. .debug[[swarm/morenodes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/morenodes.md)] --- class: self-paced ## L'icône de statut des nodes sur Play-With-Docker - Si vous passez via Play-With-Docker, vous verrez des icones de statut par node. - Les icônes de statut sont affichées à gauche du nom de chaque node. - Sans icône = pas de mode Swarm détecté - Icône bleue remplie = *manager* Swarm - Icône blanche à bord bleu = *worker* Swarm ![Icône Play-With-Docker icons](images/pwd-icons.png) .debug[[swarm/morenodes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/morenodes.md)] --- ## 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* .exercise[ - Afficher la liste courante des noeuds: ``` docker node ls ``` - Promouvoir tout *worker* en *manager* ``` docker node promote ``` ] .debug[[swarm/morenodes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/morenodes.md)] --- ## 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 .footnote[ voir [Docker's admin guide](https://docs.docker.com/engine/swarm/admin_guide/#add-manager-nodes-for-fault-tolerance) à propos des pannes de nodes et la redondance en centre de données ] .debug[[swarm/morenodes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/morenodes.md)] --- ## 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 .debug[[swarm/morenodes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/morenodes.md)] --- ## 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 .footnote[ 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](http://success.docker.com/article/running-docker-ee-at-scale)" ] .debug[[swarm/morenodes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/morenodes.md)] --- ## 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](https://sematext.com/blog/2016/11/14/docker-swarm-lessons-from-swarm3k/) - ç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](https://github.com/moby/moby/pull/37372)!) .debug[[swarm/morenodes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/morenodes.md)] --- ## Méthodes de déploiements dans la vie réelle -- Lancer les commandes à la main via SSH -- (lol je blague) -- - Exploiter votre outil de gestion de configuration préféré - [Docker for AWS](https://docs.docker.com/docker-for-aws/#quickstart) - [Docker for Azure](https://docs.docker.com/docker-for-azure/) .debug[[swarm/morenodes.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/morenodes.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/aerial-view-of-containers.jpg)] --- name: toc-lancer-notre-premier-service-swarm class: title Lancer notre premier service Swarm .nav[ [Section préc.](#toc-crer-notre-premier-swarm) | [Retour à la table des matières](#toc-chapter-7) | [Section suivante](#toc-notre-appli-sur-swarm) ] .debug[(automatically generated title slide)] --- # Lancer notre premier service Swarm - Comment lancer des services? Version courte: `docker run` → `docker service create` .exercise[ - Créer un service basé sur un conteneur Alpine qui _ping_ les serveurs de Google: ```bash docker service create --name pingpong alpine ping 8.8.8.8 ``` - Vérifier le résultat: ```bash docker service ps pingpong ``` ] .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## 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 .exercise[ - Vérifier la sortie de notre commande ping: ```bash 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. .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- class: extra-details ## Chercher où tourne notre conteneur - La commande `docker service ps` va nous dire où est placé notre conteneur .exercise[ - Trouver quel `NODE` fait tourner notre conteneur: ```bash 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...)` ] .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- class: extra-details ## Afficher les logs du conteneur .exercise[ - Constater que le conteneur tourne bien, et récupérer son ID: ```bash docker ps ``` - Afficher ses logs: ```bash docker logs containerID ``` - Retourner sur `node1` après coup ] .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## Dimensionner notre service - Les services peuvent monter en charge avec une pincée de `docker service update` .exercise[ - Escalader le service pour assurer 2 clones par node: ```bash docker service update pingpong --replicas 6 ``` - Vérifier que nous avons bien 2 conteneurs sur la node en cours: ```bash docker ps ``` ] .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## 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 .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## `--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) .warning[`--detach=true` ne va *pas plus vite*. C'est juste qu'il *n'attend pas* la fin d'exécution.] .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- class: extra-details ## É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 `) .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## `--detach` en action .exercise[ - Escalader le service pour garantir 3 conteneurs par node: ```bash docker service update pingpong --replicas 9 --detach=false ``` - Et monter ensuite à 4 replicas par node: ```bash docker service update pingpong --replicas 12 --detach=true ``` ] .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## 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`) .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## Exposer ElasticSearch sur son port par défaut .exercise[ - Créer un service ElasticSearch (et lui donner un nom tant qu'on y est): ```bash 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. .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## 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) .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- class: extra-details, pic ![diagramme affichant les évenements durant docker service create, par @aluzzardi](images/docker-service-create.svg) .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## Tester notre service - Nous avons attaché le port 9200 sur les _nodes_ au port 9200 des conteneurs. - Essayons de communiquer avec ce port! .exercise[ - Lancer la commande suivante: ```bash 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. .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## Tester la répartition de charge - Si on répète notre commande `curl` encore et encore, on lire plusieurs noms. .exercise[ - Envoyer 10 requêtes, et voir quelles instances répondent: ```bash 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 ``` .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## Résultats du répartiteur de charge Le trafic est géré par le [maillage de routage]( https://docs.docker.com/engine/swarm/ingress/) 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. .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- class: pic ![routing mesh](images/ingress-routing-mesh.png) .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## 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) .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## 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. .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- class: pic ![LB externe](images/ingress-lb.png) .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## 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](https://docs.docker.com/ee/ucp/interlock/) - Les labels de service comme `com.docker.lb.hosts=` sont détectés automatiquement via l'API Docker et mettent à jour leur configuration à la volée. -- - Deux options open source populaires: - [Traefik](https://traefik.io/) - 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](http://proxy.dockerflow.com/) - utilise HAProxy, orienté Swarm, par [@vfarcic](https://twitter.com/vfarcic) .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- class: btw-labels ## 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.) .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## 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: ```bash 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 `host`et dans un autre. (Vous devrez router le trafic aux conteneurs via des ports exposés ou des sockets UNIX) .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- class: extra-details ## 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]( https://docs.docker.com/engine/userguide/networking/get-started-macvlan/ ) pour bien démarrer avec `macvlan` - Voir [cette PR](https://github.com/moby/moby/pull/32981) pour plus d'information sur les pilotes réseaux locaux dans le mode Swarm .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## Visualiser le placement de conteneurs - Jouons avec l'API Docker! .exercise[ - Lancer cette appli simple-mais-sympa de visualisation: ```bash cd ~/container.training/stacks docker-compose -f visualizer.yml up -d ``` ] .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## Se connecter à la web app de visualisation - Elle fait tourner un serveur web sur le port 8080 .exercise[ - 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) .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## 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 à: ```bash docker service create \ --mount source=/var/run/docker.sock,type=bind,target=/var/run/docker.sock \ --name viz --constraint node.role==manager ... ``` .footnote[ Crédits: le code de visualization code a été écrit par [Francisco Miranda](https://github.com/maroshii). [Mano Marks](https://twitter.com/manomarks) l'a adapté au Swarm et le maintient. ] .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- ## 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. .exercise[ - Supprimer tous les services avec cette commande: ```bash docker service ls -q | xargs docker service rm ``` ] .debug[[swarm/firstservice.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/firstservice.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/blue-containers.jpg)] --- name: toc-notre-appli-sur-swarm class: title Notre appli sur Swarm .nav[ [Section préc.](#toc-lancer-notre-premier-service-swarm) | [Retour à la table des matières](#toc-chapter-7) | [Section suivante](#toc-hberger-notre-propre-registry) ] .debug[(automatically generated title slide)] --- # 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. .debug[[swarm/ourapponswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/ourapponswarm.md)] --- ## 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. .debug[[swarm/ourapponswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/ourapponswarm.md)] --- class: extra-details ## 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! .debug[[swarm/ourapponswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/ourapponswarm.md)] --- ## Le plan - Lancer un _build_ sur notre node locale (`node1`) - Étiquetter les images pour les nommer en `localhost:5000/` - Téléverser dans le registre - Créer les services grâce à ces images .debug[[swarm/ourapponswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/ourapponswarm.md)] --- ## Quel registre devons-nous utiliser? .small[ - **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 ] .debug[[swarm/ourapponswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/ourapponswarm.md)] --- class: extra-details ## Utiliser Docker Hub *Si on voulait passer par Docker Hub...* - On devrait se connecter au Docker Hub ```bash 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`) .debug[[swarm/ourapponswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/ourapponswarm.md)] --- class: extra-details ## Utiliser Docker Trusted Registry *Si on voulait utiliser DTR, on devrait...* - S'assurer d'avoir un compte Docker Hub - [Activer un abonnement Docker EE]( https://hub.docker.com/enterprise/trial/) - 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* .debug[[swarm/ourapponswarm.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/ourapponswarm.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/chinook-helicopter-container.jpg)] --- name: toc-hberger-notre-propre-registry class: title Héberger notre propre _Registry_ .nav[ [Section préc.](#toc-notre-appli-sur-swarm) | [Retour à la table des matières](#toc-chapter-7) | [Section suivante](#toc-les-stacks-swarm) ] .debug[(automatically generated title slide)] --- # 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_ .debug[[swarm/hostingregistry.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/hostingregistry.md)] --- ## Déployer le registre - Nous allons créer un service à instance unique, en publiant son port sur le cluster en entier .exercise[ - Déclarer le service du registre: ```bash docker service create --name registry --publish 5000:5000 registry ``` - Essayer maintenant la commande suivante: on devrait voir `{"repositories":[]}`: ```bash curl 127.0.0.1:5000/v2/_catalog ``` ] .debug[[swarm/hostingregistry.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/hostingregistry.md)] --- ## Tester notre registre local - On peut re-_tag_ une petite image, et la pousser dans le registre .exercise[ - Vérifier qu'on a une image busybox, et y rajouter un _tag_: ```bash docker pull busybox docker tag busybox 127.0.0.1:5000/busybox ``` - La livrer sur le registre: ```bash docker push 127.0.0.1:5000/busybox ``` ] .debug[[swarm/testingregistry.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/testingregistry.md)] --- ## Vérifier ce qui est dans le registre local - L'API du _Registry_ a des points d'entrée pour vérifier son contenu .exercise[ - Vérifier que notre image busybox est bien présente en local: ```bash curl http://127.0.0.1:5000/v2/_catalog ``` ] La commande curl devrait afficher: ```json {"repositories":["busybox"]} ``` .debug[[swarm/testingregistry.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/testingregistry.md)] --- class: btp-manual ## Intégration avec Compose - On a vu comment _build_ à la main, étiquetter et pousser les images dans un registre. - Mais ... -- class: btp-manual *Je suis tellement content que mon déploiement se base sur des scripts Shell de taille astronomique* *(par M. Personne, vraiment)* -- class: btp-manual - Voyons voir comment simplifier ce processus! .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/container-cranes.jpg)] --- name: toc-les-stacks-swarm class: title Les _Stacks_ Swarm .nav[ [Section préc.](#toc-hberger-notre-propre-registry) | [Retour à la table des matières](#toc-chapter-7) | [Section suivante](#toc-cicd-avec-docker-et-lorchestration) ] .debug[(automatically generated title slide)] --- # 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! .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- ## 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](https://github.com/docker/docker.github.io/blob/master/compose/compose-file/compose-versioning.md#upgrading) 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) .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- ## 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: ```bash docker service create --publish 5000:5000 registry ``` Dès lors, nous allons le déployer avec le fichier de _stack_ suivant: ```yaml version: "3" services: registry: image: registry ports: - "5000:5000" ``` .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- ## Vérifier nos fichiers _stack_ - Tous les fichiers _stack_ que nous utiliserons sont stockés dans le dossier `stacks` .exercise[ - Aller dans le dossier `stack`: ```bash cd ~/container.training/stacks ``` - Parcourir `registry.yml` ```bash cat registry.yml ``` ] .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- ## 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 .exercise[ - Déployer notre registre local: ```bash docker stack deploy --compose-file registry.yml registry ``` ] .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- ## Inspecter les _stacks_ - `docker stack ps` affiche l'état détaillé de tous les services d'une _stack_ .exercise[ - Vérifier que notre registre tourne correctement: ```bash docker stack ps registry ``` - Confirmer que nous avons le même affichage avec la commande: ```bash docker service ps registry_registry ``` ] .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- class: btp-manual ## 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 `_` - Les services et tâches récupèrent aussi un label interne indiquant à quelle _stack_ ils appartiennent .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- class: btp-auto ## 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 .exercise[ - Envoyer la requête API qui suit au registre: ```bash curl 127.0.0.1:5000/v2/_catalog ``` ] Ça devrait renvoyer: ```json {"repositories":[]} ``` Si ça ne marche pas, ré-essayer encore; le conteneur est peut-être en cours de démarrage. .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- class: btp-auto ## Pousser une image vers notre registre local - On peut re-_tag_ une petite image, et la pousser vers le registre. .exercise[ - Charger l'image busybox, et la re-_tag_: ```bash docker pull busybox docker tag busybox 127.0.0.1:5000/busybox ``` - La transférer: ```bash docker push 127.0.0.1:5000/busybox ``` ] .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- class: btp-auto ## Vérifier ce qui est dans notre registre local - L'API Registre a des points d'entrée pour sélectionner son contenu. .exercise[ - S'assurer que notre image busybox est maintenant dans notre registre local: ```bash curl http://127.0.0.1:5000/v2/_catalog ``` ] La commande curl devrait maintenant afficher: ```json "repositories":["busybox"]} ``` .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- ## 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 `_:latest`) - avec l'avantage qu'on peut la pousser dans un registry avec `docker-compose push` - Exemple: ```yaml webfront: build: www image: myregistry.company.net:5000/webfront ``` .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- ## Utiliser Compose pour générer et pousser les images .exercise[ - Essayer: ```bash 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. .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- ```yaml 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 ``` .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- ## Déployer l'application - Maintenant que les images sont sur le registre, on peut déployer notre _stack_ applicative. .exercise[ - Créer la _stack_ applicative: ```bash 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. .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- ## 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: ```yaml web: extends: file: common-services.yml service: webapp ``` Voir [cette page de documentation](https://docs.docker.com/compose/extends/) pour plus de détails sur ces techniques. .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- class: extra-details ## 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) .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- ## 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! .debug[[swarm/stacks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/stacks.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/container-housing.jpg)] --- name: toc-cicd-avec-docker-et-lorchestration class: title CI/CD avec Docker et l'orchestration .nav[ [Section préc.](#toc-les-stacks-swarm) | [Retour à la table des matières](#toc-chapter-7) | [Section suivante](#toc-mettre--jour-les-services) ] .debug[(automatically generated title slide)] --- name: cicd # 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. .debug[[swarm/cicd.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/cicd.md)] --- ## 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](https://success.docker.com/article/dev-pipeline) - Docker KBase [Continuous Integration with Docker Hub](https://success.docker.com/article/continuous-integration-with-docker-hub) - Docker KBase [Building a Docker Secure Supply Chain](https://success.docker.com/article/secure-supply-chain) .debug[[swarm/cicd.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/cicd.md)] --- class: pic ![CI-CD with Docker](images/ci-cd-with-docker.png) .debug[[swarm/cicd.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/cicd.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/containers-by-the-water.jpg)] --- name: toc-mettre--jour-les-services class: title Mettre à jour les services .nav[ [Section préc.](#toc-cicd-avec-docker-et-lorchestration) | [Retour à la table des matières](#toc-chapter-7) | [Section suivante](#toc-mises--jour-progressive) ] .debug[(automatically generated title slide)] --- # 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_) .debug[[swarm/updatingservices.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/updatingservices.md)] --- ## Modifier un seul service avec `service update` - Pour mettre à jour un seul service, on pourrait procéder comme suit: ```bash 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) .debug[[swarm/updatingservices.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/updatingservices.md)] --- ## Modifier nos services avec `stack deploy` - Avec l'intégration de Compose, tout ce que nous avons à faire est: ```bash 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é. .debug[[swarm/updatingservices.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/updatingservices.md)] --- ## _Patcher_ le code - Essayons d'agrandir les chiffres sur l'axe Y! .exercise[ - Mettre à jour la taille du texte sur notre _webui_ ```bash sed -i "s/15px/50px/" dockercoins/webui/files/index.html ``` ] .debug[[swarm/updatingservices.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/updatingservices.md)] --- ## 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` .exercise[ - Générer, livrer et lancer: ```bash 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. .debug[[swarm/updatingservices.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/updatingservices.md)] --- ## 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! .debug[[swarm/updatingservices.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/updatingservices.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/distillery-containers.jpg)] --- name: toc-mises--jour-progressive class: title Mises à jour progressive .nav[ [Section préc.](#toc-mettre--jour-les-services) | [Retour à la table des matières](#toc-chapter-7) | [Section suivante](#toc-healthcheck-et-rollback-automatique) ] .debug[(automatically generated title slide)] --- # Mises à jour progressive - Essayons de forcer une mise à jour de _hasher_ pour examiner le processus. .exercise[ - Escalader d'abord _hasher_ à 7 replicas: ```bash docker service scale dockercoins_hasher=7 ``` - Forcer une mise à jour progressive (qui remplace les conteneurs) vers une image différente: ```bash 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. .debug[[swarm/rollingupdates.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/rollingupdates.md)] --- ## Changer la politique de mise à jour - On peut jouer sur plein d'options sur les profils de mise à jour. .exercise[ - Changer le parallélisme à 2, et le taux d'échec maximum à 25%: ```bash 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 .debug[[swarm/rollingupdates.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/rollingupdates.md)] --- ## 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`: ```yaml deploy: replicas: 10 update_config: parallelism: 2 delay: 10s ``` .debug[[swarm/rollingupdates.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/rollingupdates.md)] --- ## 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` .exercise[ - Essayer d'annuler la mise à jour du service _webui_ ```bash docker service rollback dockercoins_webui ``` ] Que se passe-t-il avec le graphique de l'interface web? .debug[[swarm/rollingupdates.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/rollingupdates.md)] --- ## Les subtilités du _rollback_ - Le retour arrière annule la dernière définition du service - voir `PreviousSpec` dans `docker service inspect ` - 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 .debug[[swarm/rollingupdates.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/rollingupdates.md)] --- class: extra-details ## 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` .small[(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 .small[(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. .debug[[swarm/rollingupdates.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/rollingupdates.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/lots-of-containers.jpg)] --- name: toc-healthcheck-et-rollback-automatique class: title Healthcheck et _rollback_ automatique .nav[ [Section préc.](#toc-mises--jour-progressive) | [Retour à la table des matières](#toc-chapter-7) | [Section suivante](#toc-gestion-des-secrets-et-chiffrement-au-repos) ] .debug[(automatically generated title slide)] --- name: healthchecks # 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: ```bash 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 .debug[[swarm/healthchecks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/healthchecks.md)] --- ## Définir ses _health checks_ - Dans un Dockerfile, avec l'instruction [HEALTHCHECK](https://docs.docker.com/engine/reference/builder/#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](https://docs.docker.com/compose/compose-file/#healthcheck) par service ```yaml www: image: hellowebapp healthcheck: test: "curl -f https://localhost/ || false" timeout: 3s ``` .debug[[swarm/healthchecks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/healthchecks.md)] --- ## 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`) .debug[[swarm/healthchecks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/healthchecks.md)] --- ## Activer les contrôles de santé et les _rollback_ auto Voici un exemple complet utilisant la ligne de commande: .small[ ```bash 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 ``` ] .debug[[swarm/healthchecks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/healthchecks.md)] --- ## Implémenter le _rollback_ auto en pratique On se basera pour la démo sur le fichier suivant (`stacks/dockercoins+healthcheck.yml`): ```yaml ... 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 ... ``` .debug[[swarm/healthchecks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/healthchecks.md)] --- ## Activer l'auto-_rollback_ dans `dockercoins` On a d'abord besoin d'indiquer un _healthcheck_ pour nos services. .exercise[ - Entrer dans le dossier `stacks`: ```bash cd ~/container.training/stacks ``` - Déployer la _stack_ mise à jour avec les _healthchecks_ intégrés ```bash docker stack deploy --compose-file dockercoins+healthcheck.yml dockercoins ``` ] .debug[[swarm/healthchecks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/healthchecks.md)] --- ## 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 .exercise[ - Changer le port HTTP à écouter: ```bash sed -i "s/80/81/" dockercoins/hasher/hasher.rb ``` - Générer, livrer, et exécuter la nouvelle image: ```bash 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 ``` ] .debug[[swarm/healthchecks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/healthchecks.md)] --- ## Options de la CLI pour _health checks_ et _rollbacks_ .small[ ``` --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) ``` ] .debug[[swarm/healthchecks.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/healthchecks.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/plastic-containers.JPG)] --- name: toc-gestion-des-secrets-et-chiffrement-au-repos class: title Gestion des secrets et chiffrement au repos .nav[ [Section préc.](#toc-healthcheck-et-rollback-automatique) | [Retour à la table des matières](#toc-chapter-8) | [Section suivante](#toc-modle-du-moindre-privilge) ] .debug[(automatically generated title slide)] --- # 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 .debug[[swarm/security.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/security.md)] --- class: secrets ## 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 .debug[[swarm/secrets.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/secrets.md)] --- class: secrets ## Déclarer de nouveaux _secrets_ - On doit choisir un nom pour ce _secret_; et associer le contenu lui-même .exercise[ - Assigner [un des quatre mots de passe les plus communs](https://www.youtube.com/watch?v=0Jx8Eay5fWQ) à un secret baptisé `piratemoi`: ```bash echo love | docker secret create piratemoi - ``` ] 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_) .debug[[swarm/secrets.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/secrets.md)] --- class: secrets ## Mieux déclarer ses _secrets_ - Choisir des mots de passe de paresseux conduit toujours à des intrusions .exercise[ - Déclarer un meilleur mot de passe, et l'assigner à un autre _secret_: ```bash 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. .debug[[swarm/secrets.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/secrets.md)] --- class: secrets ## Usage des _secrets_ - Les _secrets_ doivent être affectés de façon explicite aux services .exercise[ - Déclarer un nouveau service de test avec les 2 secrets: ```bash 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!) .debug[[swarm/secrets.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/secrets.md)] --- class: secrets ## Accéder à nos secrets - Les secrets sont disponibles dans `/run/secrets` (qui est en réalité un système de fichiers sur-mémoire) .exercise[ - Trouver l'ID du conteneur pour le service de test: ```bash CID=$(docker ps -q --filter label=com.docker.swarm.service.name=dummyservice) ``` - Entrer dans le conteneur: ```bash docker exec -ti $CID sh ``` - Vérifier les fichiers dans `/run/secrets` ] .debug[[swarm/secrets.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/secrets.md)] --- class: secrets ## 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: ```bash docker service create --secret source=secretname,target=filename ``` .debug[[swarm/secrets.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/secrets.md)] --- class: secrets ## Changer notre mot de passe non sécurisé - On doit remplacer notre _secret_ `piratemoi` avec une meilleure version. .exercise[ - Retirer le secret `piratemoi` trop faible: ```bash docker service update dummyservice --secret-rm piratemoi ``` - Ajouter notre meilleur _secret_ à sa place: ```bash 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!) .debug[[swarm/secrets.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/secrets.md)] --- class: secrets ## Vérifier que notre mot de passe est maintenant plus fort! - On va invoquer le pouvoir du `docker exec`! .exercise[ - Récupérer l'ID de notre nouveau conteneur: ```bash CID=$(docker ps -q --filter label=com.docker.swarm.service.name=dummyservice) ``` - Vérifier le contenu des fichiers secrets: ```bash docker exec $CID grep -r . /run/secrets ``` ] .debug[[swarm/secrets.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/secrets.md)] --- class: secrets ## 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 ...) ```bash docker service create --secret source=foo.N,target=foo ... ``` - On peut mettre à jour (ajouter+supprimer) en une seule commande: ```bash 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](https://docs.docker.com/engine/swarm/secrets/) .debug[[swarm/secrets.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/secrets.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/train-of-containers-1.jpg)] --- name: toc-modle-du-moindre-privilge class: title Modèle du moindre privilège .nav[ [Section préc.](#toc-gestion-des-secrets-et-chiffrement-au-repos) | [Retour à la table des matières](#toc-chapter-8) | [Section suivante](#toc-logs-centraliss) ] .debug[(automatically generated title slide)] --- # 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 .debug[[swarm/leastprivilege.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/leastprivilege.md)] --- ## 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. .debug[[swarm/leastprivilege.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/leastprivilege.md)] --- ## 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](https://docs.docker.com/engine/extend/plugins_authorization/) .debug[[swarm/leastprivilege.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/leastprivilege.md)] --- ## Aller plus loin dans la sécurité de conteneur .blackbelt[DC17US: Securing Containers, One Patch At A Time ([video](https://www.youtube.com/watch?v=jZSs1RHwcqo&list=PLkA60AVN3hh-biQ6SCtBJ-WVTyBmmYho8&index=4))] .blackbelt[DC17EU: Container-relevant Upstream Kernel Developments ([video](https://dockercon.docker.com/watch/7JQBpvHJwjdW6FKXvMfCK1))] .blackbelt[DC17EU: What Have Syscalls Done for you Lately? ([video](https://dockercon.docker.com/watch/4ZxNyWuwk9JHSxZxgBBi6J))] .debug[[swarm/leastprivilege.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/leastprivilege.md)] --- ## 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. .debug[[swarm/apiscope.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/apiscope.md)] --- ## 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] - chaque requête vers l'API est filtrée par un ou plusieurs plugins(s) - par défaut, le champ *subject name* du certificat TLS client est utilisé comme identifiant - exemple: [user and permission management] dans [UCP] [plugins d'autorisation]: https://docs.docker.com/engine/extend/plugins_authorization/ [UCP]: https://docs.docker.com/datacenter/ucp/2.1/guides/ [user and permission management]: https://docs.docker.com/datacenter/ucp/2.1/guides/admin/manage-users/ .debug[[swarm/apiscope.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/apiscope.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/train-of-containers-2.jpg)] --- name: toc-logs-centraliss class: title _Logs_ centralisés .nav[ [Section préc.](#toc-modle-du-moindre-privilge) | [Retour à la table des matières](#toc-chapter-8) | [Section suivante](#toc-installer-elk-pour-stocker-les-logs-de-conteneur) ] .debug[(automatically generated title slide)] --- name: logging # _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`. .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/two-containers-on-a-truck.jpg)] --- name: toc-installer-elk-pour-stocker-les-logs-de-conteneur class: title Installer ELK pour stocker les _logs_ de conteneur .nav[ [Section préc.](#toc-logs-centraliss) | [Retour à la table des matières](#toc-chapter-8) | [Section suivante](#toc-collecter-les-mtriques) ] .debug[(automatically generated title slide)] --- # 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 .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- ## 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. .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- class: elk-manual ## Installer ELK - On aura besoin de trois conteneurs: ElasticSearch, Logstash, Kibana - On les placera dans un réseau commun, `logging` .exercise[ - Déclarer le réseau: ```bash docker network create --driver overlay logging ``` - Déclarer le service ElasticSearch: ```bash docker service create --network logging --name elasticsearch elasticsearch:2.4 ``` ] .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- class: elk-manual ## 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 .exercise[ - Déclarer le service Kibana: ```bash docker service create --network logging --name kibana --publish 5601:5601 \ -e ELASTICSEARCH_URL=http://elasticsearch:9200 kibana:4.6 ``` ] .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- class: elk-manual ## 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](https://github.com/jpetazzo/container.training/blob/master/elk/logstash.conf) en ligne de commande .exercise[ - Déclarer le service Logstash: ```bash docker service create --network logging --name logstash -p 12201:12201/udp \ logstash:2.4 -e "$(cat ~/container.training/elk/logstash.conf)" ``` ] .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- class: elk-manual ## Vérifier Logstash - Avant de continuer, assurons-nous que Logstash est bien démarré .exercise[ - Trouver la _node_ qui exécute le conteneur Logstash: ```bash docker service ps logstash ``` - Se connecter à cette _node_ ] .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- class: elk-manual ## Voir les logs de Logstash .exercise[ - Afficher les logs du service Logstash: ```bash docker service logs logstash --follow ``` ] Vous devriez voir le message indiquant le "pouls" du service: .small[ ```json { "message" => "ok", "host" => "1a4cfb063d13", "@version" => "1", "@timestamp" => "2016-06-19T00:45:45.273Z" } ``` ] .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- class: elk-auto ## Déployer notre cluster ELK - Nous allons utiliser le fichier _stack_ .exercise[ - Générer, livrer et lancer notre suite ELK: ```bash 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]( https://github.com/jpetazzo/container.training/blob/master/stacks/elk.yml). .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- class: elk-auto ## Vérifier que notre suite ELK tourne correctement - Affichons les logs de Logstash (_Qui gardera les gardiens?_ version log) .exercise[ - Faire défiler les _logs_ de Logstash: ```bash docker service logs --follow --tail 1 elk_logstash ``` ] Vous devriez voir passer les messages de "pouls": .small[ ```json { "message" => "ok", "host" => "1a4cfb063d13", "@version" => "1", "@timestamp" => "2016-06-19T00:45:45.273Z" } ``` ] .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- ## 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. .exercise[ - Envoyer un message de test: ```bash 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. .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- ## 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! .exercise[ - Envoyer un message de test: ```bash 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* .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- ## 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* .exercise[ - Changer la condition de redémarrage pour empêcher Swarm de relancer à l'infini notre conteneur: ```bash 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`. .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- ## Se connecter à Kibana - L'interface web Kibana est exposée sur le port 5601 du cluster .exercise[ - 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 ] .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- ## "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) .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- ## 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 .exercise[ - Activer le log GELF pour le service `rng`: ```bash 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. .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- ## 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. .exercise[ - Dans la colonne de gauche, bouger la souris sur les colonnes suivantes, et cliquer sur le bouton "Add" qui apparait: - host - container_name - message ] .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- ## .warning[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. .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- ## 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/). .debug[[swarm/logging.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/logging.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/wall-of-containers.jpeg)] --- name: toc-collecter-les-mtriques class: title Collecter les métriques .nav[ [Section préc.](#toc-installer-elk-pour-stocker-les-logs-de-conteneur) | [Retour à la table des matières](#toc-chapter-8) | [Section suivante](#toc-liens-et-ressources) ] .debug[(automatically generated title slide)] --- # 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) .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- ## 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! .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- ## 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/ .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- ## 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 ... .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap, prom ## Outils Nous allons monter *deux* collecteurs de métriques différents: - Le premier basé sur Intel Snap, - Le second sur Prometheus. .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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 .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Snap - [github.com/intelsdi-x/snap](https://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/ .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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 .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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 .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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 .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Le service Snap d'installation - Ceci va installer Snap sur tous les noeuds .exercise[ ```bash 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 ☺ ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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 .exercise[ - Démarrer `snapd` sans vérification de plugin et en mode debug: ```bash snapd -t 0 -l 1 ``` ] - Pour aller plus loin: https://github.com/intelsdi-x/snap/blob/master/docs/SNAPD.md https://github.com/intelsdi-x/snap/blob/master/docs/SNAPD_CONFIGURATION.md .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Using `snapctl` to interact with `snapd` ## Utiliser `snapctl` pour intéragir avec `snapd` - Chargeons des plugins de *collection* et de *publication* .exercise[ - Ouvrir un nouveau terminal - Charger le plugin de collection psutil: ```bash snapctl plugin load /opt/snap/plugin/snap-plugin-collector-psutil ``` - Charger le plugin de publication de fichier: ```bash snapctl plugin load /opt/snap/plugin/snap-plugin-publisher-mock-file ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Vérifier ce qu'on a fait - Bon à savoir: la CLI Docker utilise `ls`, celle de Snap préfère `list` .exercise[ - Voir vos plugins chargés: ```bash snapctl plugin list ``` - Voir les métriques qu'on peut collecter: ```bash snapctl metric list ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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) - *où* 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 .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Notre premier manifeste de tâche ```yaml 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 ``` .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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`. .exercise[ - Déclarer une nouvelle tâche basée sur le manifeste: ```bash 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 ``` .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Vérifier les tâches existantes .exercise[ - Cela va confirmer que notre tâche tourne correctement, et nous rappeler son ID de tâche. ```bash 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 ``` .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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) .exercise[ - Vérifier que les données circulent vraiment: ```bash tail -f /tmp/snap-psutil-file.log ``` ] Pour sortir, taper `^C` .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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 .exercise[ ```bash snapctl task watch ``` ] Pour sortir, taper `^C` .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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 -- class: snap - On veut corriger tout ça! -- class: snap - 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 .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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 .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Lancer Snap lui-même sur chaque _node_ - Snap tourne en avant-plan, vous devez donc utiliser `&` ou le démarrer dans un _tmux_ .exercise[ - Lancer la commande suivante *sur chaque noeud*: ```bash 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! .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Démarrer un _daemon_ par SSH .warning[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_ .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Lancer Snap lui-même sur chaque noeud - Je pourrais aller en prison en vous montrant ça, mais c'est parti ... .exercise[ - Démarrer Snap sur toute la longueur: ```bash 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). .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Afficher les membres de notre tribu - Si tout se passe bien, Snap est maintenant lancé en mode tribu .exercise[ - Afficher les membres de notre _Tribe_: ```bash snapctl member list ``` ] Vous devriez voir les 5 noeuds et leurs noms d'hôtes. .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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. .exercise[ - Créer un _agreement_; s'assurer de bien utiliser le même nom tout au long: ```bash snapctl agreement create docker-influxdb ``` ] La sortie d'écran devrait ressembler à ceci: ``` Name Number of Members plugins tasks docker-influxdb 0 0 0 ``` .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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 .exercise[ - Ajouter toutes les _nodes_ au nouvel _agreement_ ```bash 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 ``` .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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. .exercise[ - Déclarer un conteneur alpine à travers le cluster: ```bash docker service create --name ping --mode global alpine ping 8.8.8.8 ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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. .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Initialiser le service InfluxDB .exercise[ - Lancer un service InfluxDB, tout en ouvrant les ports 8083 et 80806: ```bash 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. .warning[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.] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Configurer InfluxDB - On devrait y créer notre base de données "snap" .exercise[ - 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. .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class:snap ## 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. .exercise[ - 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 ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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. .exercise[ - Charger le collecteur Docker: ```bash snapctl plugin load /opt/snap/plugin/snap-plugin-collector-docker ``` - Charger l'éditeur InfluxDB: ```bash snapctl plugin load /opt/snap/plugin/snap-plugin-publisher-influxdb ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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_ .exercise[ - Charge le fichier du manifeste de tâche, pour collecter une ou deux métriques sur tous les conteneurs, et les envoyer à InfluxDB: ```bash 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. .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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: ```bash snapctl task enable snapctl task start ``` 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) .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Voir si les métriques remontent dans InfluxDB - Vérifions les données existantes avec ces requêtes manuelles dans l'admin InfluxDB .exercise[ - 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**.) ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## 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) .exercise[ - Créer un service Grafana: ```bash docker service create --name grafana --publish 3000:3000 grafana/grafana:3.1.1 ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Configurer Grafana .exercise[ - 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) ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Ajouter InfluxDB comme source dans Grafana .small[ Remplir le formulaire exactmeent comme suit: - Name = "snap" - Type = "InfluxDB" Dans les paramètres HTTP, renseigner comme suit: - Url = "http://(adresse.IP.de.votre.noeud.prefere):8086" - Access = "direct" - Laisser "HTTP Auth" vide 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. ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ![Copie d'écran montrant comment remplir le formulaire](images/grafana-add-source.png) .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Déclarer un tableau de bord dans Grafana .exercise[ - 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. .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ## Configurer un graphe dans Grafana .exercise[ - 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! .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap ![Copie d'écran affichant le résultat final](images/grafana-add-graph.png) .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap, prom ## 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 .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: snap, prom ## Prometheus vs. Snap - Prometheus est un autre système de collecte de métriques - Snap *pousse* les métriques, là où Prometheus les *aspire* .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom ## 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) .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom ## Tout est dans les `/metrics` - Voici à quoi ressemble un *exportateur de noeud*: http://demo.robustperception.io:9100/metrics - Prometheus lui-même expose aussi ses propres métriques internes: http://demo.robustperception.io:9090/metrics - Un *serveur Prometheus* va *aspirer* les URLs telles que celles-ci (On passera plutôt par protobuf pour éviter le supplément de traitement des formats ligne-à-ligne!) .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-manual ## 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.` pour cette découverte de services. - Tous ces services seront placés dans un réseau privé interne. .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-manual ## Ajouter un réseau _overlay_ pour Prometheus - C'est l'étape la plus facile ☺ .exercise[ - Déclarer un réseau superposé: ```bash docker network create --driver overlay prom ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-manual ## 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) .exercise[ - Démarrer l'exportateur de noeud: ```bash 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)($|/)" ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-manual ## 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. .exercise[ - Démarrer le collecteur cAdvisor: ```bash 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 ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-manual ## Configuration de serveur Prometheus Voici notre fichier de configuration pour Prometheus: .small[ ```yaml 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 ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-manual ## 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: ```dockerfile 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. .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-manual ## 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 .exercise[ - Générer l'image grâce au Dockerfile fourni: ```bash docker build -t 127.0.0.1:5000/prometheus ~/container.training/prom ``` - Pousser l'image sur notre registre local: ```bash docker push 127.0.0.1:5000/prometheus ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-manual ## 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!) .exercise[ - Démarrer notre serveur Prometheus: ```bash docker service create --network prom --name prom \ --publish 9090:9090 127.0.0.1:5000/prometheus ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-auto ## Déployer Prometheus sur notre cluster - Nous allons (encore une fois) utiliser une définition de _stack_ .exercise[ - S'assurer que nous sommes dans le dossier `stacks`: ```bash cd ~/container.training/stacks ``` - Générer, envoyer et lancer la _stack_ Prometheus: ```bash docker-compose -f prometheus.yml build docker-compose -f prometheus.yml push docker stack deploy -c prometheus.yml prometheus ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom ## Vérifier notre serveur Prometheus - D'abord, assurons-nous que Prometheus aspire correctement toutes les métriques .exercise[ - 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". .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-auto, config ## 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) .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-auto, config ## 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 .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-auto, config ## 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. .small[ ```yaml 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 ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-auto, config ## 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 .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-auto, config ## Re-déployer Prometheus avec une config - Nous allons mettre à jour la _stack_ existante grâce à `prometheus+config.yml` .exercise[ - Re-déployer la _stack_ `prometheus`: ```bash 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) ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-auto, config ## Accéder à l'objet de config depuis la CLI - Les objets de config peuvent être consultés depuis la CLI Docker (ou l'API) .exercise[ - Lister les objets de config existant: ```bash docker config ls ``` - Afficher les détails sur notre objet de config: ```bash 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!) .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom-auto, config ## Extraire un _blob_ de config - Récupérons cette configuration Prometheus! .exercise[ - Extraire le contenu en BASE64 avec `jq`: ```bash docker config inspect prometheus_prometheus | jq -r .[0].Spec.Data ``` - Le décoder avec `base64 -d`: ```bash docker config inspect prometheus_prometheus | jq -r .[0].Spec.Data | base64 -d ``` ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom ## Afficher les métriques directement depuis Prometheus - C'est facile ... si vous êtes familier avec PromQL .exercise[ - 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. ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom ## 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!) .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom ## 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)* .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom ## 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: .small[`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. .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom ## 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](https://www.robustperception.io/irate-graphs-are-better-graphs/) pour plus de détails!) *On devrait voir des pics, qui étaient restés cachés, à cause du lissage sur le temps* .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom ## 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* .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom ## 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](https://twitter.com/discordianfish) et [Julius Volz](https://twitter.com/juliusvolz) pour leur aide avec Prometheus!) .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom, snap ## 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 .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom, snap ## Ajouter Prometheus comme source de données dans Grafana .exercise[ - 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. .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom, snap ## Connecter Prometheus à Grafana .exercise[ - 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! .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom, snap ## Ajouter les données de Prometheus au tableau de bord .exercise[ - 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. .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom, snap ## Interroger Prometheus depuis Grafana L'éditeur est un peu moins sympa que celui pour InfluxDB. .exercise[ - 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. ] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: prom, snap ## Interpréter les résultats - Les deux courbes *devraient* se ressembler - Astuce de pro: alignez les légendes de temps! .exercise[ - 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.* .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- ## Pour aller plus loin avec les métriques de conteneur - [Prometheus, a Whirlwind Tour](https://speakerdeck.com/copyconstructor/prometheus-a-whirlwind-tour), an original overview of Prometheus - [Docker Swarm & Container Overview](https://grafana.net/dashboards/609), a custom dashboard for Grafana - [Gathering Container Metrics](http://jpetazzo.github.io/2013/10/08/docker-containers-metrics/), a blog post about cgroups - [The Prometheus Time Series Database](https://www.youtube.com/watch?v=HbnGSNEjhUc), a talk explaining why custom data storage is necessary for metrics .blackbelt[DC17US: Monitoring, the Prometheus Way ([video](https://www.youtube.com/watch?v=PDxcEzu62jk&list=PLkA60AVN3hh-biQ6SCtBJ-WVTyBmmYho8&index=5))] .blackbelt[DC17EU: Prometheus 2.0 Storage Engine ([video](https://dockercon.docker.com/watch/NNZ8GXHGomouwSXtXnxb8P))] .debug[[swarm/metrics.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/swarm/metrics.md)] --- class: title, self-paced Merci à tous et toutes! .debug[[shared/thankyou.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/thankyou.md)] --- class: title, in-person C'est tout pour aujourd'hui! Des questions? ![end](images/end.jpg) .debug[[shared/thankyou.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/shared/thankyou.md)] --- class: pic .interstitial[![Image separating from the next chapter](https://gallant-turing-d0d520.netlify.com/containers/Container-Ship-Freighter-Navigation-Elbe-Romance-1782991.jpg)] --- name: toc-liens-et-ressources class: title Liens et ressources .nav[ [Section préc.](#toc-collecter-les-mtriques) | [Retour à la table des matières](#toc-chapter-8) | [Section suivante](#toc-) ] .debug[(automatically generated title slide)] --- # Liens et ressources - [Docker Community Slack](https://community.docker.com/registrations/groups/4316) - [Docker Community Forums](https://forums.docker.com/) - [Docker Hub](https://hub.docker.com) - [Docker Blog](https://blog.docker.com/) - [Docker documentation](https://docs.docker.com/) - [Docker on StackOverflow](https://stackoverflow.com/questions/tagged/docker) - [Docker on Twitter](https://twitter.com/docker) - [Play With Docker Hands-On Labs](https://training.play-with-docker.com/) .footnote[Ces diapos (et les futures mises à jour) sont sur → https://container.training/] .debug[[containers/links.md](https://github.com/djalal/orchestration-workshop.git/tree/master-french-patch/slides/containers/links.md)]