Docker swarm & Portainer UI
Tags: docker, portainer, orchestration, devops
Published: 2021-10-02 13:43:27

## Sommaire
  * [Introduction](#intro)
  * [Docker swarm](#swarm)
    * [Commandes pour gérer un cluster Swarm](#commands)
  * [Portainer](#portainer)
    * [Introduction à Portainer](#introportainer)
    * [Fonctionnalités utiles](#fonctions)
  * [Conclusion](#conclusion)

# Introduction <a name="intro"></a>

De nos jours, il est courant et fortement recommandé de mettre en place de la "conteneurisation", souvent au travers de la technologie Docker qui a accéléré la tendance depuis 10 ans :

Dans une équipe de développements applicatifs, sur l'**environnement de développement** :

- avoir des images prêtes à l'emploi : par exemple une image du serveur d'applications, que toute l'équipe utilise, sur la même base de modules et versions, annuaire LDAP, base de données, etc
- facilité de mise en place d'images, facilement déployables sur chaque station de travail : une image est légère et son exécution sera rapide, pour tester ou effectuer des POC, cela s'avère plus accessible
- une gestion des dépendances (versions des packages, modules, ...) nettement améliorée et contrôlée

Dans un **environnements de tests ou de production**

Ces **images** pourront être packagées pour une utilisation sur une plateforme de tests et / ou la plateforme de prod, de façon rapide et efficace : déploiements facilités et mise en production efficiente. 

Sur un environnement de **production**, il y aura régulièrement **plusieurs instances** d'applications sur plusieurs VM / serveurs, afin d'absorber la charge des accès utilisateurs, il va donc falloir **déployer** toutes ces images applicatives sous forme de conteneurs légers vers ces différentes instances de machines, et ensuite, s'assurer d'orienter tel flux (https, accès base, etc) vers tels conteneurs : cette façon de faire se nomme de l'**orchestration de conteneurs**, qui va également gérer le **cycle de vie** des conteneurs : scaling, création, déploiement / rechargement pour aller vers du Zero downtime deployment, load-balancing, la communcation entre les containers etc

Docker propose un orchestrateur natif à leur technologie, dans le moteur de conteneurs : [Docker Swarm](https://docs.docker.com/engine/swarm/), où l'on va retrouver nos habitudes :

* avec le [docker CLI](https://docs.docker.com/engine/reference/commandline/cli/)
* le [docker-compose Yaml](https://docs.docker.com/compose/), du déclaratif, qui servira aussi à la gestion des conteneurs dans Swarm (_stack_)

# Docker swarm & Portainer

## Vocabulaire

**Worker** = un noeud du cluster Swarm, une VM ou une machine physique

**Manager** = un noeud spécial du cluster Swarm qui aura pour charge d'orchestrer les services / containers du cluster (flux, déploiements, _load balancing_ et haute disponibilité des services exposés), tel un chef d'orchestre ! Un manager peut aussi jouer le rôle de *worker*

**Stack** = un "fichier" docker-compose dans lequel ait décrit les services avec les règles associées : d'images sources à utiliser, de déploiement, de ressources (volumes, networks, ...voire CPU & co)

**Service** = un élément d'une stack à déployer selon des règles

**Task** = un container déployé sur un / des noeuds

## Registry docker

Dans un écosystème où l'orchestration demande des images pour les transformer en conteneurs ("instances" d'une image), il est nécessaire d'avoir un *registry docker* qui permettra de stocker ces images à déployer :

* le Docker Hub en est un

ou avoir une machine *registry* dédiée à cela :

* via une solution légère de type [registry]( https://docs.docker.com/engine/swarm/stack-deploy/#set-up-a-docker-registry) développé par Docker et disponible sur le [Hub](https://hub.docker.com/_/registry)
* via une solution avec une interface Web de type [harbor](https://goharbor.io/) / [github](https://github.com/goharbor/harbor)

## Docker swarm <a name="swarm"></a>

Docker swarm est **inclus** dans le moteur docker, c'est un **orchestrateur de containers**, qui permettra de gérer sur un ensemble de machines / VM, les containers sur chacune de ces instances (flux, déploiement, cycle de vie, répartitions et disponibilité), et, avouons-le, la mise en place reste accessible et rapide, une fois les concepts appréhendés.

Un _cluster_ Swarm a plusieurs nœuds avec au moins 2 nœuds :

* de type **manager** : aura la mission de diriger les flux entrants vers les nœuds et la gestion de l'orchestration des conteneurs : réplication dans le _cluster_, déploiement / rechargement des services, load-balancing. Il est conseillé d'avoir un [nombre de manager impairs et au moins 3](https://docs.docker.com/engine/swarm/how-swarm-mode-works/nodes/#manager-nodes), selon l'algorithme [Raft](https://raft.github.io/raft.pdf). Un _manager_ peut aussi jouter le rôle de _worker_
* de type **worker**, on pourra avoir plusieurs nœuds de ce type, afin de répartir la charge, un noeud à différents état : **Active**, **Drain** et Pause. Le mode Drain permettra de dire au manager de ne plus déployer vers les noeuds _worker_ les services et donc d'arrêter les flux vers celui-ci. Le **Pause**, que j'utilise rarement, permet de suspendre tous les processus dans les containers contenus sur le noeud, en revanche, les flux continuent à être dirigés vers ce dernier.

Schématiquement, un cluster composé de _managers_ et de _workers_ se représentera ainsi :

![swarm-diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gdpgjttgwyzqfvbob2ua.png)

Source : https://docs.docker.com/engine/swarm/how-swarm-mode-works/nodes/  

Le cluster swarm se gère avec la commande [docker (CLI)](https://docs.docker.com/engine/reference/commandline/docker/), tout comme on le ferait habituellement avec des images / containers /... en local d'une machine, c'est le gros avantage, on ne perd pas ses habitudes :)

### Quelques commande avec le docker CLI <a name="commands"></a>

Les commandes ont le plus souvent une aide, il suffit d'utiliser l'option `--help` pour avoir la liste des arguments possible.

Le docker CLI permet de gérer un cluster swarm, quelques commandes que j'utilise régulièrement, hormis l'initialisation : 

#### [Swarm](https://docs.docker.com/engine/reference/commandline/swarm/)

* initialisation d'un cluster swarm

```bash
$ docker swarm init --advertise-addr IP_MANAGER:2377
```

* informations sur le cluster

```bash
$ docker info
```

* obtenir un TOKEN worker / node **à partir du manager**

```bash
$ docker swarm join-token worker
docker swarm join --token SWMTKN-1-id1-id2 IP_MANAGER:2377
```

* faire rejoindre une machine sur le cluster en tant que _worker_

A partir de la commande donnée précédemment, il suffit de la recopier **à partir d'une machine cluster**

```bash
$ docker swarm join --token SWMTKN-1-id1-id2 IP_MANAGER:2377
```

* worker / noeud : faire quitter un noeud du cluster, **à partir d'un noeud** :

```bash
* docker swarm leave
```

#### [Noeud(s)](https://docs.docker.com/engine/reference/commandline/node/)


* liste des noeuds

```bash
$ docker node ls
```

* liste des containers / tâches des noeuds ou d'un noeud

```bash
$ docker node ps
$ docker node ps <NODE_NAME>
```

* modifier l'état d'un noeud 

```bash
$ docker node <ID_NODE> --availability "drain"
$ docker node <ID_NODE> --availability "active"
```

#### [Stack(s)](https://docs.docker.com/engine/reference/commandline/stack/)

* liste des *stacks* du cluster contenant, pour chacun, des services

```bash
$ docker stack ls 
NAME                SERVICES            ORCHESTRATOR
api-XYZ             7                   Swarm
ws                  1                   Swarm
fluend              3                   Swarm
ABC                 7                   Swarm
loki-grafana        3                   Swarm
portainer           2                   Swarm
prometheus          8                   Swarm

```

* liste des containers / tâches d'une stack

```bash
$  docker stack ps <STACK_NAME>
```

* liste des services d'une *stack* (un service correspond à une section du docker compose)

```bash
$ docker stack services fluend
```

#### [Service(s)](https://docs.docker.com/engine/reference/commandline/service/)

* liste des services d'un noeud

```bash
$ docker service ls
```

* informations d'un service 

```bash
$ docker service inspect --pretty <SERVICE_NAME>
```

* liste des containers d'un service avec répartition sur les noeuds

```bash
$ docker service ps <SERVICE_NAME>
$ # ou enlever le troncage du log de démarrage et la résolution DNS
$ docker service ps <SERVICE_NAME> --no-resolve --no-trunc
```
* rechargement d'un service

```bash
# forcer le redéploiement d'un service
$ docker service update --force <SERVICE_NAME>
``` 

* réplication d'un service

```bash
# réplication à 2 d'un service
$ docker service scale <SERVICE_NAME>=2
```

* logs d'un service

```bash
$ docker service logs <SERVICE_NAME> -f
```

* liste des configurations du cluster

```bash
$ docker config ls 
```

#### [Config(s)](https://docs.docker.com/engine/reference/commandline/config/) 
* lire une config

```bash
$ docker config inspect <CONFIG_NAME> --pretty
```

## Portainer <a name="portainer"></a>

### Introduction <a name="introportainer"></a>

[Portainer](https://portainer.io/) propose une **interface Web** qui vous aidera à gérer votre cluster, à la place de la ligne de commande qui peut s'avérer rébarbative ou tout simplement pour simplifier la connaissance de toutes les options possible du CLI docker pour gérer le cluster ou tout simplement pour avoir une meilleure "vue" du cluster.

Comme vous avez pu le voir sur le docker CLI, il y a beaucoup d'options ou d'arguments à connaitre pour gérer son cluster, même s'il est bien de les savoir au cas où **portainer** venait à ne plus fonctionner.

La version présentée est la version **1.2x** qui ne prend en compte que Docker ou Docker swarm. La version **2.x** a grandement évolué pour gérer en plus un cluster de type Kubernetes, K8s de son petit nom qui est un autre orchestrateur de containers.
 
**Portainer** est livré sous forme d'**images**, soit à utiliser en "local" pour gérer votre écosystème docker, soit à déployer en tant que *stack* dans le cluster (ie : fichier docker-compose YAML) qui contiendra le service **portainer** ("serveur") et **agent** ("client" pour portainer), ce dernier sera déployé sur l'ensemble des _workers_ / noeuds, manager compris.

Avec un fichier compose `docker-compose.yml` contenant 

```yaml
version: '3'

services:
  agent:
    image: portainer/agent:1.3.0
    environment:
      # REQUIRED: Should be equal to the service name prefixed by "tasks." when
      # deployed inside an overlay network
      AGENT_CLUSTER_ADDR: tasks.agent
      # AGENT_PORT: 9001
      LOG_LEVEL: debug
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /etc/localtime:/etc/localtime:ro
      - /etc/timezone:/etc/timezone:ro
    networks:
      - agent_network
    deploy:
      mode: global

  portainer:
    image: portainer/portainer:1.21.0
    command: -H tcp://tasks.agent:9001 --tlsskipverify
    ports:
      - "9000:9000"
    volumes:
      - portainer_data:/data
      - /etc/localtime:/etc/localtime:ro
      - /etc/timezone:/etc/timezone:ro
    networks:
      - agent_network
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints: [node.role == manager]

networks:
  agent_network:
    driver: overlay

volumes:
  portainer_data:

```

Pour rappel : le mode _global_ déploie le service (ici agent) sur tous les noeuds du cluster, pour le serveur portainer, il ne déploiera le service uniquement sur le manager swarm. Le type / _driver_ docker utilisé est [_overlay_](https://docs.docker.com/network/overlay/), qui permet d'avoir un réseau "virtuel" entre les machines du cluster docker swarm.

Il suffit, sur notre cluster prêt (un manager / un worker) de déployer portainer avec un [deploy](https://docs.docker.com/engine/reference/commandline/stack_deploy/), cela va créer la _stack_ portainer en déploiement l'agent et le manager.

```bash
$ docker stack deploy -c docker-compose.yml portainer
```

Les services de la _stack_ portainer ont bien été déployés :

```bash
$ docker stack services portainer
ID                  NAME                  MODE                REPLICAS            IMAGE                        PORTS
65trhtm7ei17        portainer_agent       global              1/1               portainer/agent:latest
l4ucvho6ms2m        portainer_portainer   replicated          1/1                 portainer/portainer:latest   *:9000->9000/tcp
```

Vous pouvez alors accéder à l'interface Portainer (certainement sur le port 9000 ou via un reverse proxy devant le cas échéant) pour initialiser le compte **admin**

 ![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f4qzrob7jix0fpkpg399.png)

### Fonctionnalités utiles <a name="fonctions"></a>

Portainer propose un ensemble de fonctionnalités qui va nous soulager dans la gestion de notre cluster swarm, parmi lesquelles :

![2021-09-26_12h09_29](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6houwx3cyfu9823o2t3h.png)
 
**1 :** **tableau de bord synthétique** des _stacks_, images, volumes, containers, etc, avec la possibilité d'avoir une vue sur des containers actifs sur chacun des noeuds du cluster


![2021-09-29_14h04_35](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dfwarpx4h1mic2h0zc22.png)
 
![2021-09-29_14h05_05](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0its45g2bx8g4nn0se21.png)
 

**2 :** vous accéderez à l'ensemble de vos **_stacks_** afin d'en créer, en supprimer ou voir le détail des services déployés (containers / _tasks_) de la _stack_ : **redéployer** une _stack_ (pour une mise en production par exemple avec de nouvelles images), **modifier** le **docker-compose** associé qui décrit la _stack_ avec, en plus du compose habituel docker, ses règles de déploiements, de loggin, etc

![2021-10-02_13h35_52](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vjahqeptp7hxlmu18u05.png)
 
Par exemple, sur la _stack_ **prometheus**, a **4 services**, décrit dans le compose (cf. Editor) : prometheus_alertmanager, prometheus_caddy, prometheus_cadvisor, prometheus_dockerd-exporter, ...

Les noms des services sont nommés selon le motif : *nom_de_la_stack***_***nom_du_service*

Nous aurons **2 onglets** : 

- un général pour recharger / mettre à jour, entrer en mode console dans un container, avoir des logs ou l'information (_inspect_) d'un container, l'état des containers / _tasks_, le _scaling_ / réplication opérées, et un onglet 
- pour éditer le docker-compose de cette _stack_.

![2021-10-02_15h46_11](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pb6ayehug02mludlv147.png)
 
Sur une _stack_

**1**- onglet **Stack** :

   **3-** : pouvoir **filtrer** les containers / _tasks_ sur un service
   **4-** : permet d'avoir dans l'ordre des icones : les **logs** du container (stdout / stderr des process du container, par ex. les logs nginx), les **infos** (_inspect_ docker) de celui-ci : cela peut être utile pour avoir de l'informations sur d'éventuelles erreurs d'un container qui ne voudrait pas se lancer (en failed / shutdown), des **stats** (mémoire, CPU, flux réseau et les process lancés dans le container), **accéder à la console** du container
   **5-** : permet de recharger les services de la _stack_, en cochant ceux souhaités : soit en recharge simple vers les noeuds (reploiement des services de la _stack_), soit en forçant le _pull_ des images du compose (cf. Editor) pour redéploiement.

![2021-10-03_11h52_51](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wvh3p4ve706hybk93ti9.png)
 
**1**- onglet "Editor" : permet d'écrire un _compose_ (lors d'une création d'une _stack_ ou sa mise à jour), en Yaml, qui reprend la syntaxe du docker-compose avec en plus des sections propres à Swarm (section **[deploy](https://docs.docker.com/compose/compose-file/compose-file-v3/#deploy)** qui donnera les règles de déploiement  /réplication / contraintes, ressources, section **configs* les configurations à monter lorsqu'il y en a besoin)
 
![2021-10-03_12h37_26](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9scz2kikmt8h4jii4fg7.png)

![2021-10-03_12h38_58](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s71bl0zmmp52e806s2q8.png)

![2021-10-03_12h39_10](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ohah70j6njh6kl31av68.png)

Le bouton "**Update the stack**" permet de déployer toute la _stack_ sur le cluster et ses services décrits dans le compose une fois modifié le _compose_    

**3 :** les **services déployés** sur les nœuds du cluster et les containers / _tasks_ associés et leur état, des actions seront possible : rechargement, suppression voire création de service (mais on passera plus volontiers par de la gestion de _stack_ via un compose), indications de la répartition des containers (_replicas_)

![2021-10-02_13h37_18](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y4xlm7856gqb8flng2mi.png)
 

**4 :** liste des **containers** du cluster swarm avec la possibilité de les filtrer selon leur état ("running", "stopped", "created", "healthy") et d'opération une action dessus : en supprimer (très utile pour effectuer un ménage de temps en temps), en relancer, voire en créer ou d'entrer en mode console dans un container

![2021-10-02_13h40_44](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mkkkawpjcee5ltyq4t2a.png)

![2021-10-02_13h42_11](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j6b0orj1p0ow12ptw92c.png)

![2021-10-02_13h42_24](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8e5yfpx7jezmzc7lslle.png)

![2021-10-02_13h42_31](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ssmlckvvo16ba5p3gctq.png)
  

**5 :** liste des **images** du cluster : vous permettra de faire un peu le ménage de temps en temps afin de supprimer les images inutilisées

![2021-09-29_14h10_45](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kgl4f0bpb69lj03z5gll.png)
 
**6 :** **volumes** : les volumes dockers utilisés ou non sur les différents nœuds : cela peut être pratique de repérer les volumes inutilisés à supprimer. On peut aussi en créer mais je passe la plupart par les _stacks_ et un docker-compose. Les volumes sont soient locaux à un noeud (par exemple, le volume portainer de portainer déployé uniquement sur un noeud manager, ou via des partages réseaux de NFS, Longhorn, ... qui permettra un accès de plusieurs noeuds sur ces partages)

![2021-10-02_13h52_50](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d0cyg8ik96wcknhcvqn1.png)

![2021-10-02_13h53_05](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8b760as8blfdgcyasr59.png)

![2021-10-02_13h53_32](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gw5el32eas7c0u2g5x1y.png)
   
**7 :** les **configurations** docker qui seront utilisées dans les services, où l'on peut en créer ou en cloner (à la place d'une modification qui n'est pas possible en état). Sur une instance isolée, nous utilisons les volumes pour monter un répertoire, un fichier dans le container. Dans un environnement distribué, un cluster de machines, ces fichiers doivent être centralisé (sur le _manager_) afin de le distribuer sur chacun des noeuds, ce à quoi servent les configurations sauf à les inclure lors de la création de l'image, mais cela rendra moins souple leur gestion, cela dépend.

![2021-09-29_17h31_54](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bbbuk25mojjt43pkdl5d.png)

Par exemple, la configuration  **fluen-elasticsearch-conf.v2** aura ce type de contenu

```xml
<source>
  @type  forward
  @id    input1
  @label @mainstream
  port  24224
</source>

<filter **>
  @type stdout
</filter>

<label @mainstream>
  <match api-prod>
    @type copy
    <store>
        @type file
        @id   output_docker1
        path         /fluentd/log/api-prod.*.log
        symlink_path /fluentd/log/api-prod.log
        append       true
        time_slice_format %Y%m%d
        time_slice_wait   1m
        time_format       %Y%m%dT%H%M%S%z
        compress gzip
        utc
       format json
    </store>
    </match>
</label>
```

La config `fluent-elasticsearch-conf.v2` pour être utilisée dans une _stack_ comme suit, grâce à la section `configs` :

```yaml
version: "3.6"
configs: # déclaration de la config à prendre
  fluent-elasticsearch-conf.v2:
    external: true
    
services:
  fluentd:
    image: fluend:latest
    volumes:
      - /home/root/data/logs:/fluentd/log
    ports:
      - "24224:24224"
      - "24224:24224/udp"
    networks:
      - net
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints: [node.labels.type == fluent]
    configs: # montage de la config vers la target
      - source: fluent-elasticsearch-conf.v2
        target: /fluentd/etc/fluent.conf
```

**8 :** vue **synthétique des nœuds** du cluster et de leur état, permettra de changer leur **état** ("Active" => "Drain" notamment) ou d'ajouter / modifier des __labels__ sur les nœuds, ces **labels** pourront servir pour les règles de déploiement d'un service , sur ses contraintes (cf. [deploy](https://docs.docker.com/compose/compose-file/compose-file-v3/#deploy) d'un service dans le _compose_)

![2021-09-29_14h27_10](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/erw3ldihbepnd27194oc.png)
 
![2021-09-29_14h30_59](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wc44jsqfzsa38rqi108x.png)

Par exemple, le **label** _nginx_ avec une valeur `true` pourra être une contrainte pour un service nginx pour son déploiement : déploie l'image nginx sur 3 noeuds ayant un **label** _nginx_ avec une valeur à `true`

![2021-10-03_17h00_00](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9l89jq0yuocirlv8c695.png)
 
  

**9 :** gestion des **dépôts d'images docker** : soit DockerHub, soit le _registry_ de votre écosystème : les images des _stacks_ pourront alors être prise du registry paramétré

![2021-09-29_17h29_49](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m1t7inn8nm6s2rkq6411.png)
 
# Conclusion <a name="conclusion"></a>

[Docker swarm](https://docs.docker.com/engine/swarm/) peut être mis en place relativement rapidement, bien entendu, charge à vous de construire vos images et les VM qui hébergeront _manager_ et _workers_. 

Pour sa gestion, [portainer](https://portainer.io/) permet de gérer votre cluster directement en ligne, pour la majeure partie des fonctionnalités de swarm, et...c'est très appréciable.

Katacoda propose d'essayer Portainer directement en ligne, avec les VM provisionnées, un bac à sable pour découvrir ces technologies : https://www.katacoda.com/portainer/scenarios/deploying-to-swarm