<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Joe Linux Pages</title>
    <description>Ceci est mon minuscule site perso. Gérer un CMS m'a demandé trop de travail pour la valeur finale que ça pouvait avoir. Même dokuwiki, qui était bien pratique, a fini par me demander de se mettre à jour. Alors, c'est décidé, plus de site perso dynamique. Uniquement des pages statiques générées par Jekyll. Une fois pour toutes ?
</description>
    <link>http://0.0.0.0:4000/</link>
    <atom:link href="http://0.0.0.0:4000/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Tue, 20 Jul 2021 08:37:31 -0500</pubDate>
    <lastBuildDate>Tue, 20 Jul 2021 08:37:31 -0500</lastBuildDate>
    <generator>Jekyll v4.2.0</generator>
    
      <item>
        <title>Adventures with Docker - S01E05 - The Testing environment</title>
        <description>&lt;h2 id=&quot;rappel-des-épisodes-précédents&quot;&gt;Rappel des épisodes précédents&lt;/h2&gt;

&lt;p&gt;Nous voilà arrivés à une architecture un peu plus claire, bien que l’ensemble
soit encore fragile.&lt;/p&gt;

&lt;p&gt;Certains éléments sont encore en dehors du cluster swarm, et je ne trouve pas
que ce soit une bonne solution.&lt;/p&gt;

&lt;h2 id=&quot;objectifs-de-lépisode-5&quot;&gt;Objectifs de l’épisode 5&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/s01e05testingenv.png&quot; alt=&quot;Architecture cible&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Je vais essayer de mettre en place une architecture de test, sur des serveurs
hébergés sur Online (de petites Dedibox) et faire tourner les services suivants:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Base de données&lt;/li&gt;
  &lt;li&gt;Traefik pour la gestion du trafic entrant&lt;/li&gt;
  &lt;li&gt;Un simple serveur NGinX pour héberger un site static.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Au cours de l’installation, vous verrez de nouveaux composants arriver. Docker,
et de manière générale les environnements de Saas et assimilés, amènent avec
eux tout un écosystème de petits programmes construits avec la philosophie unix
de la modularité: ils font une seule chose, et se raccordent aux autres.&lt;/p&gt;

&lt;h2 id=&quot;le-cluster-swarm&quot;&gt;Le cluster swarm&lt;/h2&gt;

&lt;p&gt;Je vais avoir besoin de deux serveurs, au moins, pour tester le mode
de fonctionnement du Swarm en réel - et c’est là que les choses intéressantes
commencent. Il se trouve que j’ai deux serveurs chez Online, dans deux différents
data centers. Comment vont-il donc se parler de manière sécurisée ?&lt;/p&gt;

&lt;p&gt;La bonne nouvelle, c’est que le Swarm Docker met en place automatiquement des liens
sécurisés (au moins TLS 1.2) entre les managers et les workers.&lt;/p&gt;

&lt;p&gt;Voilà qui va nous éviter de configurer un lien OpenVPN; si jamais j’avais
décidé de mettre MySQL sur l’un des deux serveurs, cela n’aurait pas pu marcher.
Le fait que ce soit dans le swarm va nous permettre d’utiliser un routage
interne ET sécurisé, sans charge de “travail” supplémentaire, ce qui n’est pas
plus mal, car j’ai une relation très compliquée avec ce truc de “travail” 
(dit le gars qui écrit des articles à 7h00 le dimanche matin).&lt;/p&gt;

&lt;h3 id=&quot;re-commençons-du-début&quot;&gt;(Re-)Commençons du début.&lt;/h3&gt;

&lt;p&gt;Le premier serveur portera l’IP publique; il sera également le manager du swarm.
Nous allons donc installer docker dessus, faire un init du swarm, etc.&lt;/p&gt;

&lt;h3 id=&quot;petite-précaution-qui-na-pas-grand-chose-à-voir&quot;&gt;Petite précaution qui n’a pas grand chose à voir&lt;/h3&gt;

&lt;p&gt;Ça fait à peine 10 minutes que le serveur est installé, et voyez par vous-même:&lt;/p&gt;

&lt;pre&gt;
# grep &quot;Failed password for root&quot; auth.log | wc -l
530
# grep &quot;Invalid user&quot; auth.log | wc -l
52
&lt;/pre&gt;

&lt;p&gt;Je pense qu’installer et paramètrer un firewall minimaliste sera tout sauf un luxe.
J’avais dans l’idée de restreindre l’accès SSH à mon adresse IP (fixe) mais
ce genre de choses peut vite mal se terminer.&lt;/p&gt;

&lt;p&gt;Je vais plutôt faire le minimum vital et mettre en place un &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fail2ban&lt;/code&gt; pour éviter
le gros des casse-pieds.&lt;/p&gt;

&lt;pre&gt;
# apt-get install fail2ban ufw
...
# ufw allow 22/tcp
Rules updated
Rules updated (v6)
root@sd-91418:/var/log# ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup
root@sd-91418:/var/log# ufw status
Status: active

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW       Anywhere                  
22/tcp (v6)                ALLOW       Anywhere (v6)   
&lt;/pre&gt;

&lt;p&gt;Je mettrai plus tard les ouvertures de flux entre les membres du swarm.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fail2ban&lt;/code&gt; est configuré correctement. J’ai fait des essais de connexions depuis
un serveur tiers, et je suis bloqué au bout de cinq essais - notez que la durée du ban
est de 10 minutes par défaut. J’ai passé cette durée à 86400 secondes, soit une journée;
de toutes façons, personne à part moi ne devrait venir en SSH sur la plateforme.&lt;/p&gt;

&lt;h3 id=&quot;installation-de-docker-ce&quot;&gt;Installation de Docker-CE&lt;/h3&gt;

&lt;p&gt;Je déroule les étapes d’installation de la documentation Docker; voilà les commandes en résumé,
histoire de vous montrer à quel point c’est compliqué:&lt;/p&gt;

&lt;pre&gt;
# apt-get update
# apt-get install \
    apt-transport-https \
    ca-certificates curl \
    software-properties-common
# curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add -
# apt-key fingerprint 0EBFCD88
# add-apt-repository \
   &quot;deb [arch=amd64] https://download.docker.com/linux/debian \
   $(lsb_release -cs) \
   stable&quot;
# apt-get update
# apt-get install docker-ce
&lt;/pre&gt;

&lt;p&gt;Plus quelques étapes pour la post-installation… et je crois qu’on peut commencer.&lt;/p&gt;

&lt;h3 id=&quot;initialisation-du-swarm&quot;&gt;Initialisation du swarm&lt;/h3&gt;

&lt;p&gt;Je lance le swarm avec l’adresse IP publique.&lt;/p&gt;

&lt;pre&gt;
docker swarm init --advertise-addr A.B.C.D
&lt;/pre&gt;

&lt;p&gt;Ce qui renvoie un token pour ajouter des serveurs par la suite.&lt;/p&gt;

&lt;p&gt;J’installe le docker-ce sur l’autre machine, et je fais les ouvertures
de flux, et maintenant je peux ajouter un worker sur la deuxième machine:&lt;/p&gt;

&lt;pre&gt;
docker swarm join --token SWMTKN-1-[very long token] A.B.C.D:2377
&lt;/pre&gt;

&lt;p&gt;Ce qui nous donne :&lt;/p&gt;

&lt;pre&gt;
docker node ls 
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS
3p8fiavr2pk1klo8rpze06qhe     sd-127251           Ready               Active              
4cqxlzxre3jt4nglzfo6a1bon *   sd-91418            Ready               Active              Leader
&lt;/pre&gt;

&lt;p&gt;Je crée un réseau chiffré interne :&lt;/p&gt;

&lt;pre&gt;
docker network create --opt encrypted --driver overlay swarm-net
&lt;/pre&gt;

&lt;p&gt;Nous voilà prêts à déployer des services sur le swarm.&lt;/p&gt;

&lt;h3 id=&quot;base-de-données-relationnelle&quot;&gt;Base de données relationnelle&lt;/h3&gt;

&lt;p&gt;Je pense qu’il faut faire des choix en ce qui concerne le stockage des données.
De toutes façons, la question de la haute disponibilité pourra être envisagée
par la suite; d’autant plus, et c’est un fait à prendre en considération, que
je n’ai jamais vu, en plus de 15 ans, une base MySQL s’arrêter purement et
simplement sans que quelqu’un ne lui en ai spécialement donner l’ordre, ou
sans qu’il se produise une coupure de courant. J’ai moins de recul avec MemCached
mais j’en ai avec Linux et ses daemons, en général: ils sont suffisament
robustes pour que la question du secours puisse être remise à plus tard.&lt;/p&gt;

&lt;p&gt;Je ne veux pas paraître léger sur cette question: mais avant de lutter pour
obtenir un mécanisme de haute disponibilité hors de prix, mieux vaut veiller
à faire de bonnes sauvegardes régulièrement. Comme pour les sessions, la
sauvegarde doit s’envisager globalement, avec une réflexion fonctionnelle.&lt;/p&gt;

&lt;p&gt;J’ai donc décidé de placer MariaDB dans un container docker,
en partant des images qui se trouvent sur le Docker Hub:&lt;/p&gt;

&lt;p&gt;MariaDB : &lt;a href=&quot;https://hub.docker.com/\_/mariadb/&quot;&gt;https://hub.docker.com/_/mariadb/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;La commande pour faire tourner un docker MariaDB est habituellement celle-ci:&lt;/p&gt;

&lt;pre&gt;
docker run --name some-mariadb -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mariadb:tag
&lt;/pre&gt;

&lt;p&gt;Convertie en mode swarm, cela donne :&lt;/p&gt;

&lt;pre&gt;
docker service create \
    --name mariadb \
    --publish 3306:3306 \
    --replicas 1 \
    --detach=true \
    --network swarm-net \
    mariadb:10.3.1 -e MYSQL_ROOT_PASSWORD=my-secret-pw
&lt;/pre&gt;

&lt;p&gt;Tiens, ça ne se passe pas tout à fait comme prévu, n’est-ce pas ?&lt;/p&gt;

&lt;pre&gt;
docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
4o0mzu5ost9v        mariadb             replicated          0/1                 mariadb:10.3.1      *:3306-&amp;gt;3306/tcp
&lt;/pre&gt;

&lt;p&gt;C’est au niveau de &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;REPLICAS : 0/1&lt;/code&gt; que je suis un peu déçu.&lt;/p&gt;

&lt;p&gt;Apparement, il est en train de galérer pour créer le service que je lui ai demandé.&lt;/p&gt;

&lt;pre&gt;
# docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                         PORTS               NAMES
d764e5a6a822        mariadb:10.3.1      &quot;docker-entrypoint...&quot;   13 seconds ago      Exited (1) 4 seconds ago                           mariadb.1.xnsegqjpes3apbfk6qrex9jiq
1e046cd4a62e        mariadb:10.3.1      &quot;docker-entrypoint...&quot;   33 seconds ago      Exited (1) 24 seconds ago                          mariadb.1.l2pzfoulbdbgn5fdbbpaqsyfz
f06cadb7b245        mariadb:10.3.1      &quot;docker-entrypoint...&quot;   53 seconds ago      Exited (1) 43 seconds ago                          mariadb.1.fpj6sg1p1mcaby0mpuer2yxdu
&lt;/pre&gt;

&lt;p&gt;On voit qu’il les lance, qu’ils s’arrêtent, et retour au départ…&lt;/p&gt;

&lt;p&gt;Au bout d’un moment, il avoue son échec :&lt;/p&gt;

&lt;pre&gt;
dockerd[5728]: time=&quot;2017-09-03T17:34:42.910510537+02:00&quot; level=error msg=&quot;fatal task error&quot;
error=&quot;task: non-zero exit (1)&quot; module=&quot;node/agent/taskmanager&quot; 
node.id=4cqxlzxre3jt4nglzfo6a1bon 
service.id=4o0mzu5ost9vuzhmy3rq3lyrz 
task.id=nyd85l2npn4xq86bg38di85hj
&lt;/pre&gt;

&lt;p&gt;Enfin, il n’a pas avoué publiquement: j’ai du aller chercher le message dans les logs…
Peut-être qu’on a vu trop loin trop vite. Essayons d’abord avec un container tout simple.&lt;/p&gt;

&lt;pre&gt;
# docker service create --replicas 1 --name helloworld --network swarm-net alpine ping docker.com 
ruo8azkw5uq1ogjm91ep9bwr7

# docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
ruo8azkw5uq1        helloworld          replicated          1/1                 alpine:latest
&lt;/pre&gt;

&lt;p&gt;Si je change le nombre de réplicas à 5, 3 d’entre eux se mettent à tourner sur le noeud secondaire,
c’est la preuve que le réseau fonctionne correctement.&lt;/p&gt;

&lt;p&gt;Après lecture de quelques articles, il semble que la version standard de MariaDB ne soit pas prévue
pour tourner en mode swarm. Il existe cependant d’autres versions qui permettent de le faire,
et la beauté du système, c’est que ces versions ne sont que des surcharges de l’image MariaDB
de base.&lt;/p&gt;

&lt;p&gt;J’en trouve notamment une qui se prête correctement à l’exercice:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://hub.docker.com/r/toughiq/mariadb-cluster/&quot;&gt;https://hub.docker.com/r/toughiq/mariadb-cluster/&lt;/a&gt;&lt;/p&gt;

&lt;pre&gt;
docker service create --name dbcluster \
--network swarm-net \
--replicas=1 \
--env DB_SERVICE_NAME=dbcluster \
--env MYSQL_ROOT_PASSWORD=my-secret-pw 
--detach=true \
toughiq/mariadb-cluster
&lt;/pre&gt;

&lt;p&gt;Donne le résultat suivant:&lt;/p&gt;

&lt;pre&gt;
docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE                            PORTS
q8tvec720iio        dbcluster           replicated          1/1                 toughiq/mariadb-cluster:latest   
&lt;/pre&gt;

&lt;p&gt;Une fois lancé correctement, je peux scaler le service :&lt;/p&gt;

&lt;pre&gt;
docker service scale dbcluster=3
&lt;/pre&gt;

&lt;p&gt;Ce qui donne le résultat suivant :&lt;/p&gt;

&lt;pre&gt;
docker service ps dbcluster
ID                  NAME                IMAGE                            NODE                DESIRED STATE       CURRENT STATE                ERROR               PORTS
wftd2utfvot5        dbcluster.1         toughiq/mariadb-cluster:latest   sd-127251           Running             Running about a minute ago                       
z71cwizr3ynu        dbcluster.2         toughiq/mariadb-cluster:latest   sd-91418            Running             Running 9 seconds ago                            
0zpxmam3b9tp        dbcluster.3         toughiq/mariadb-cluster:latest   sd-91418            Running             Running 10 seconds ago
&lt;/pre&gt;

&lt;p&gt;Je lance maintenant un service qui va agir comme un proxy pour l’accès à ce cluster.
Il n’est en rien indispensable, mais il apporte des fonctionnalités interressantes.&lt;/p&gt;

&lt;p&gt;Plus de détails ici : &lt;a href=&quot;https://github.com/toughIQ/docker-maxscale&quot;&gt;https://github.com/toughIQ/docker-maxscale&lt;/a&gt;&lt;/p&gt;

&lt;pre&gt;
docker service create --name maxscale \
--network swarm-net \
--env DB_SERVICE_NAME=dbcluster \
--env ENABLE_ROOT_USER=1 \
--publish 3306:3306 \
--detache=true
toughiq/maxscale
&lt;/pre&gt;

&lt;p&gt;Les services pour l’instant :&lt;/p&gt;

&lt;pre&gt;
docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE                            PORTS
q8tvec720iio        dbcluster           replicated          3/3                 toughiq/mariadb-cluster:latest   
t5a8wq4bn3hw        maxscale            replicated          1/1                 toughiq/maxscale:latest          *:3306-&amp;gt;3306/tcp
&lt;/pre&gt;

&lt;p&gt;Par contre pour se connecter sur la base… pour l’instant, c’est pas ça…
En interrogeant le maxscale (j’avais bien senti qu’il serait utile) :&lt;/p&gt;

&lt;pre&gt;
docker exec -it maxscale.1.owmjn2blt8i43rh534hilnq8n maxadmin -pxxxx list servers
Servers.
-------------------+-----------------+-------+-------------+--------------------
Server             | Address         | Port  | Connections | Status              
-------------------+-----------------+-------+-------------+--------------------
10.0.0.5           | 10.0.0.5        |  3306 |           0 | Down
10.0.0.3           | 10.0.0.3        |  3306 |           0 | Running
10.0.0.4           | 10.0.0.4        |  3306 |           0 | Down
-------------------+-----------------+-------+-------------+--------------------
&lt;/pre&gt;

&lt;p&gt;Au bout d’un moment la situation s’améliore…&lt;/p&gt;

&lt;pre&gt;
docker exec -it maxscale.1.owmjn2blt8i43rh534hilnq8n maxadmin -pxxxx list servers
Servers.
-------------------+-----------------+-------+-------------+--------------------
Server             | Address         | Port  | Connections | Status              
-------------------+-----------------+-------+-------------+--------------------
10.0.0.5           | 10.0.0.5        |  3306 |           0 | Master, Synced, Running
10.0.0.3           | 10.0.0.3        |  3306 |           0 | Running
10.0.0.4           | 10.0.0.4        |  3306 |           0 | Down
-------------------+-----------------+-------+-------------+--------------------
&lt;/pre&gt;

&lt;p&gt;Et finalement:&lt;/p&gt;

&lt;pre&gt;
-------------------+-----------------+-------+-------------+--------------------
Server             | Address         | Port  | Connections | Status              
-------------------+-----------------+-------+-------------+--------------------
10.0.0.5           | 10.0.0.5        |  3306 |           0 | Master, Synced, Master Stickiness, Running
10.0.0.3           | 10.0.0.3        |  3306 |           0 | Slave, Synced, Running
10.0.0.4           | 10.0.0.4        |  3306 |           0 | Slave, Synced, Running
-------------------+-----------------+-------+-------------+--------------------
&lt;/pre&gt;

&lt;p&gt;Bon, ce n’est pas super rassurant dans la mesure où je n’ai pas la moindre idée
de la façon dont cela fonctionne, mais… on va faire avec pour l’instant.&lt;/p&gt;

&lt;p&gt;En me connectant via maxscale et en créant une base de données, j’ai pu vérifier
qu’elle était répliquée sur les autres noeuds, ce qui est plutôt une bonne chose.&lt;/p&gt;

&lt;p&gt;La capacité de l’ensemble à supporter l’arrêt du master, par exemple, devra être testée
par la suite, mais en attendant, j’ai fait le test d’arrêter le service docker sur
le worker, et la base a changé de place, sans s’arrêter.&lt;/p&gt;

&lt;p&gt;Je vais être honnête avec vous; je n’avais pas vraiment l’intention de faire
ce test. Voilà comment on en est arrivé là: l’utilisateur d’un site - j’entends
par là, l’utilisateur technique d’un site, servant à la connexion à la base de données,
s’en en vu refusé l’accès depuis le moment où j’ai mis en place le dit worker.&lt;/p&gt;

&lt;p&gt;Ne sachant d’où venait le problème précisément, j’ai arrêté le service docker sur
le worker - ce qui a eu deux effets : l’accès a été de nouveau permis à cet utilisateur,
et je me suis aperçu que le service de cluster de MariaDB n’en a pas souffert. Il faut
dire aussi qu’il n’y a pas de données: cela a du faciliter la migration.&lt;/p&gt;

&lt;p&gt;J’ai eu tôt fait de comprendre que le problème venait des paramêtres d’accès à la base,
soit 127.0.0.1, au lieu de localhost. Je l’ai compris, pour ne rien vous cacher, parce que
dans le même temps, d’autres sites continuaient de fonctionner parfaitement. En comparant
les configurations, j’ai pu corriger, et relancer le service docker sur le worker.&lt;/p&gt;

&lt;p&gt;Seule chose qui m’ennuie un peu, c’est que les services sont depuis sur le manager
et ne sont pas revenus sur le worker. Que diable, c’est sans doute passager, ou
bien l’affaire de quelque commande que je ne connais pas encore.&lt;/p&gt;

&lt;p&gt;Je file noter en bas de page deux choses à voir dans le prochain épisode, et je reviens.&lt;/p&gt;

&lt;h3 id=&quot;key-value-storage&quot;&gt;Key-Value Storage&lt;/h3&gt;

&lt;p&gt;Le but de ce stockage, c’est de contenir la configuration des services, notamment
de Traefik. Mais aussi les certificats qu’il va générer à la volée.&lt;/p&gt;

&lt;p&gt;Je choisis donc d’utiliser Consul pour cela :&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.consul.io/&quot;&gt;https://www.consul.io/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Il offre les fonctionnalités suivantes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Service Discovery: enregistrement de services, découverte de services, via HTTP ou DNS,&lt;/li&gt;
  &lt;li&gt;Multi Data-center: bon là, c’est un peu du luxe… pour l’instant !&lt;/li&gt;
  &lt;li&gt;Failure Detection: c’est pertinent pour éviter de donner l’adresse de services en panne,&lt;/li&gt;
  &lt;li&gt;Key-Value Storage: et là, c’est ce qu’il faut pour notre traefik.&lt;/li&gt;
&lt;/ul&gt;

&lt;pre&gt;
docker service create \
  --name swarm_consul \
  --publish 8400:8400 --publish 8500:8500 --publish 8600:53/udp \
  --replicas 1 \
  --detach=true \
  --constraint=node.role==manager \
  --network swarm-net \
  progrium/consul -server -bootstrap -log-level debug -ui-dir /ui
&lt;/pre&gt;

&lt;p&gt;Pour l’instant on a pas la possibilité de le scaler (du moins je ne sais pas
le faire, mais je sais que c’est possible…) mais il tourne.&lt;/p&gt;

&lt;p&gt;J’ouvre les flux pour la console (mais uniquement depuis chez moi)&lt;/p&gt;

&lt;pre&gt;
ufw allow from W.X.Y.Z/32 to 163.172.29.216 port 8500
&lt;/pre&gt;

&lt;p&gt;À noter que Consul offre une UI (écrite avec EmberJS) qui permet de parcourir
son contenu ET de le modifier: en gros on pourrait modifier la configuration de
Traefik directement ! Ça ressemble à ceci :&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/s01e05consului.png&quot; alt=&quot;La console de Consul&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Et quand je dis “on pourrait”, c’est un fait qu’un composant - par exemple le saas -
pourrait profiter de son accès local au KV pour effectivement paramètrer des services
mais aussi récupérer des métriques, vérifier les états de santé, etc.&lt;/p&gt;

&lt;h3 id=&quot;le-registry-docker&quot;&gt;Le Registry Docker&lt;/h3&gt;

&lt;p&gt;Le registry docker, c’est l’endroit on l’on va stocker les images fabriquées sur un
des noeuds pour les mettre à disposition de l’ensemble du Swarm.&lt;/p&gt;

&lt;p&gt;Quand on commence à avoir plusieurs machines dans le swarm, il faut s’assurer que
chacune aura accès aux images, pour éventuellement instancier un des services
localement. La question ne s’est pas posée pour MariaDB, me direz-vous. Certes.
La raison est toute simple: l’image de MariaDB n’est pas locale, elle est sur le
Docker Hub, sur internet. Mais les images que nous allons fabriquer (nos applications,
l’instance particulière de traefik, etc.), elles, n’y seront pas.&lt;/p&gt;

&lt;p&gt;Il est possible de créer un compte sur Docker Hub, et d’après ce que j’ai compris,
d’y stocker des images, etc. Mais pour l’instant, je vais me simplifier les choses
en créant un service de registry local.&lt;/p&gt;

&lt;p&gt;Création du service :&lt;/p&gt;

&lt;pre&gt;
# docker service create --name registry --publish 5000:5000 registry:2
&lt;/pre&gt;

&lt;p&gt;Je le teste (au moins son accès)&lt;/p&gt;

&lt;pre&gt;
# curl 127.0.0.1:5000/v2/_catalog
{&quot;repositories&quot;:[]}
&lt;/pre&gt;

&lt;p&gt;Nous allons utiliser ce registry… très bientôt !&lt;/p&gt;

&lt;h3 id=&quot;le-frontal&quot;&gt;Le frontal&lt;/h3&gt;

&lt;p&gt;À présent, nous allons faire tourner Traefik dans le swarm, écoutant, pour le service public,
sur le port 443 - il va donc falloir du SSL; et un nom. Je file m’occuper du DNS
pour faire pointer saas.joe-linux.org sur mon IP publique.&lt;/p&gt;

&lt;p&gt;Le trafic venant sur le port 80 sera impitoyablement routé vers le port 443.
Le HTTP sans SSL, c’est so 2000, on peut plus se permettre ça.&lt;/p&gt;

&lt;p&gt;Je mets en place la génération automatique des certificats par
Let’s Encrypt… qui devraient se faire au moment où on déclare un backend
repondant à une rule spécifique. Pour l’instant je fais pointer vers
le server staging de Let’s Encrypt, histoire de pas casser quelque chose.&lt;/p&gt;

&lt;pre&gt;
[entryPoints]
  [entryPoints.http]
  address = &quot;:80&quot;
    [entryPoints.http.redirect]
    entryPoint = &quot;https&quot;
  [entryPoints.https]
  address = &quot;:443&quot;
    [entryPoints.https.tls]

[acme]
email = &quot;dvaldenaire@gmail.com&quot;
storage = &quot;traefik/acme/account&quot;
entryPoint = &quot;https&quot;
# Enable certificate generation on frontends Host rules.
OnHostRule = true
# The Staging Server - be careful before goind to prod !
caServer = &quot;https://acme-staging.api.letsencrypt.org/directory&quot;

[docker]
endpoint = &quot;unix:///var/run/docker.sock&quot;
domain = &quot;traefik&quot;
watch = true
swarmmode = true

[consul]
  endpoint = &quot;swarm_consul:8500&quot;
  watch = true
  prefix = &quot;traefik&quot;

[web]
  address = &quot;:8080&quot;
  readonly = true
&lt;/pre&gt;

&lt;p&gt;Notons qu’en théorie, cette configuration sera chargée dans le KV Store.&lt;/p&gt;

&lt;p&gt;Ok, je mets ça dans le fichier de configuration &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;traefik.toml&lt;/code&gt; en compagnie du Dockerfile,
et je construis une image.&lt;/p&gt;

&lt;pre&gt;
# docker build -t swarm_traefik .
&lt;/pre&gt;

&lt;p&gt;Je tagge l’image du swarm :&lt;/p&gt;

&lt;pre&gt;
# docker tag swarm_traefik 127.0.0.1:5000/swarm_traefik
&lt;/pre&gt;

&lt;p&gt;Et je la pousse dans le registry:&lt;/p&gt;

&lt;pre&gt;
# docker push 127.0.0.1:5000/swarm_traefik
The push refers to a repository [127.0.0.1:5000/swarm_traefik]
0bfa2764fbd0: Pushed 
96e939c31d1a: Pushed 
3b10c345cfdd: Pushed 
latest: digest: sha256:e41276b674[...]82ab67d4f0f size: 946
&lt;/pre&gt;

&lt;p&gt;On oublie pas d’ouvrir les flux… (pour la console, uniquement depuis chez moi)&lt;/p&gt;

&lt;pre&gt;
# ufw allow 80/tcp
# ufw allow 443/tcp
# ufw allow from W.X.Y.Z/32 to 163.172.29.216 port 8500
&lt;/pre&gt;

&lt;p&gt;C’est parti:&lt;/p&gt;

&lt;pre&gt;
docker service create \
    --name running_traefik \
    --publish 80:80 --publish 443:443 --publish 8080:8080 \
    --replicas 1 \
    --detach=true \
    --constraint=node.role==manager \
    --network swarm-net \
    --mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
    127.0.0.1:5000/swarm_traefik
&lt;/pre&gt;

&lt;p&gt;Je consulte la console de Traefik, elle est vide, il n’y a pas de services
pour le moment, mais elle fonctionne.&lt;/p&gt;

&lt;p&gt;Il faut pas oublier de “vraiment” stocker la configuration dans le KV. Cela se
fait par une commande lancée avec &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker exec&lt;/code&gt; et qui initialise ce stockage.&lt;/p&gt;

&lt;pre&gt;
docker exec -it running_traefik.1.lqnz2fy73yoc8km954ek8n9q3 /traefik storeconfig
&lt;/pre&gt;

&lt;p&gt;Histoire de valider le fonctionnement, je vais juste lancer un docker NGinX&lt;/p&gt;

&lt;pre&gt;
docker service create --name running_test \
  --replicas 1 \
  --label traefik.backend=test \
  --label traefik.frontend.rule=&quot;Host:saas.joe-linux.org&quot; \
  --network swarm-net \
  --detach=true \
  --label traefik.port=80 nginx:1.13.5-alpine
&lt;/pre&gt;

&lt;p&gt;Je le vois dans l’UI de Traefik, mais c’est à présent le moment de vérité !
Je vais accèder sur http://saas.joe-linux.org/ qui doit renvoyer vers https://,
lequel va générer le certificat à la volée, et nous montrer la page par défaut de NGinX.&lt;/p&gt;

&lt;p&gt;Et…&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/s01e05staticsaas.png&quot; alt=&quot;Ça marche !&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Ne me croyez pas sur parole, vous pouvez regarder par vous même:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://saas.joe-linux.org&quot;&gt;http://saas.joe-linux.org&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attention !!!&lt;/strong&gt; Le certificat n’est pas valide parce que je suis sur le serveur de
test de LetsEncrypt - je le passerai en production dans l’épisode suivant.&lt;/p&gt;

&lt;h2 id=&quot;à-suivre-&quot;&gt;À suivre !&lt;/h2&gt;

&lt;p&gt;Dans le prochain épisode, je propose que nous regardions de plus près:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;comment fonctionne le cluster de base de données,&lt;/li&gt;
  &lt;li&gt;comment faire revenir des services sur le worker,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;D’autre part, je vais faire tourner ce blog (qui ne contient que des fichiers statiques)
et sa partie analytics (une installation de Piwik) sur le cluster.&lt;/p&gt;

&lt;p&gt;À bientôt pour la suite des aventures avec Docker !&lt;/p&gt;
</description>
        <pubDate>Sun, 03 Sep 2017 06:00:00 -0500</pubDate>
        <link>http://0.0.0.0:4000/it/docker/linux/2017/09/03/adventures-with-docker-part-5.html</link>
        <guid isPermaLink="true">http://0.0.0.0:4000/it/docker/linux/2017/09/03/adventures-with-docker-part-5.html</guid>
        
        
        <category>it</category>
        
        <category>docker</category>
        
        <category>linux</category>
        
      </item>
    
      <item>
        <title>Adventures with Docker - S01E04 - The Swarm</title>
        <description>&lt;h2 id=&quot;résumé-des-épisodes-précédents&quot;&gt;Résumé des épisodes précédents&lt;/h2&gt;

&lt;p&gt;Je suis arrivé à un ensemble brouillon, mais qui nous a permis de voir comment
fonctionnait Docker, et de rencontrer les premières limitations.&lt;/p&gt;

&lt;p&gt;Je vous encourage à retourner voir les premiers articles que j’ai légerement modifié,
et où j’ai rajouté des schémas. Le plus récent était celui-ci, qui vous
donnera normalement une bonne idée de la situation courante:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/s01e03withtraefik.png&quot; alt=&quot;Architecture avec Traefik&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Le dernier problème en date est de déterminer comment les containers
vont se voir réciproquement. En particulier, comment les clients vont
parler aux serveurs, et pour cela, comment ils vont avoir connaissance des
adresses de ces serveurs.&lt;/p&gt;

&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;Avant d’aller dans le vif du sujet, je voudrais d’abord signaler que j’ai
commencé un projet “Saas” sur Gitlab : &lt;a href=&quot;https://gitlab.com/associative/saas&quot;&gt;https://gitlab.com/associative/saas&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Vous pourrez trouver plus d’informations sur le projet “Associative” sur le site web:
&lt;a href=&quot;https://www.associative.fr/&quot;&gt;https://www.associative.fr/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Quoiqu’il en soit, cette version du Saas devrait vous permettre de l’installer
chez vous, si vous voulez faire quelques expériences. Elle n’est pas encore fonctionnelle,
mais il n’est pas impossible de la faire marcher: si vous êtes motivés,
faîtes-moi signe, je vous aiderai dans la mesure du possible.&lt;/p&gt;

&lt;h3 id=&quot;digression-sur-les-sessions&quot;&gt;Digression sur les sessions&lt;/h3&gt;

&lt;p&gt;Une question qui vient assez vite se poser lorsqu’on envisage d’installer plusieurs
serveurs pour une même application (ce que l’on appelle aujourd’hui “Le redimensionnement
horizontal” et autrefois “L’attaque en rang d’oignons”) est celle de la gestion des sessions.&lt;/p&gt;

&lt;p&gt;Hé oui, qui dit applications web dit sessions (en première approximation, il est vrai)
et qui dit sessions, dit qu’il va falloir que les serveurs partagent l’accès aux sessions,
c’est-à-dire que si l’utilisateur passe du serveur 167 au serveur 326, il ne perdra pas
sa précieuse session - même si en général (mais il y a plusieurs écoles, c’est évident)
cette session ne contient pas grand chose de plus qu’un jeton d’authentification.&lt;/p&gt;

&lt;h4 id=&quot;où-donc-mettre-ces-sessions-&quot;&gt;Où donc mettre ces sessions ?&lt;/h4&gt;

&lt;p&gt;La première idée qui nous viendrait à l’esprit c’est la base de données !
Car une application web vient souvent avec une base de données, pour ne pas dire “toujours”.
Et que ce soit du relationnel ou pas ne change pas grand chose à l’affaire.&lt;/p&gt;

&lt;p&gt;On a une base de données, pourquoi ne pas y stocker les sessions ?&lt;/p&gt;

&lt;p&gt;Les avis divergent à ce sujet. La base de données peut être “lente”, 
ou disons, plus lente que le système par défaut, qui
est la lecture/écriture dans un fichier, directement sur le disque du serveur.&lt;/p&gt;

&lt;p&gt;Les autres solutions sont basées sur des technologies comme Memcached ou Redis.&lt;/p&gt;

&lt;p&gt;Memcached n’est pas un cluster, c’est une base distribuée. La répartition se
fait suivant une règle fixe, ce qui donne le fonctionnement suivant:&lt;/p&gt;

&lt;p&gt;Chaque client connait l’ensemble des instances de Memcached, et la librairie
memcached décide, sans concertation avec les autres clients, où stocker telle
ou telle clé. “Sans concertation” signifie qu’un autre client avec une liste
identique de memcached ira chercher les clés, en fonction de leur nom, au bon
endroit. Si la liste n’est pas dans le même ordre : cela ne fonctionne pas.&lt;/p&gt;

&lt;p&gt;On a donc le problème suivant: si on a 3 serveurs memcached et que le deuxième
tombe, 1/3 des clés sont perdues - et bien perdues, puisque c’est stocké en mémoire&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;et les clients vont rencontrer des problèmes: impossible de se connecter sur
le serveur numéro 2 ce qui provoque des timeouts (pas terrible quand le but est
d’aller vite, n’est-ce pas ?). Si on reconfigure les clients (une intervention
humaine, ou un script prévu à cet effet) pour restreindre la liste aux deux
serveurs restants, alors la règle de répartition change, donc certaines clés sont
perdues dans la redistribution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A ma connaissance, memcached ne dispose pas de mécanisme permettant de faire de
la haute disponibilité. D’ailleurs, ce n’est pas vraiment son but. Souvenez-vous
que par défaut, il écoutait uniquement sur l’interface 127.0.0.1. Ce simple
fait contenait un message…&lt;/p&gt;

&lt;p&gt;Redis propose un système de cluster: mais la rançon à payer pour une haute
disponibilité, c’est 6 serveurs au total, 3 maîtres et 3 esclaves.&lt;/p&gt;

&lt;h4 id=&quot;en-conclusion-provisoire-sur-la-digression&quot;&gt;En conclusion provisoire sur la digression…&lt;/h4&gt;

&lt;p&gt;Avant d’examiner la solution sous l’angle technique, il faut d’abord se poser les
questions en termes fonctionnels. Quels seront les profils des utilisateurs ? Dans notre
cas, une plateforme de Saas, tous seront connectés. La session sera donc indispensable.
L’utilisation pourra varier selon les cas, par exemple, le logiciel de comptabilité ne
sera employé que sur de courtes périodes, si l’on suppose que le volume de l’activité
ne nécessite pas un emploi à temps plein. Si on considère le logiciel de gestion
de ludothèque, il sera employé tout au long des journées où celle-ci est ouverte.&lt;/p&gt;

&lt;p&gt;Pour l’instant, je vais me contenter de laisser le Memcached sur le serveur.
Mais la question reste ouverte.&lt;/p&gt;

&lt;h3 id=&quot;le-vif-du-sujet--docker-swarm&quot;&gt;Le vif du sujet : Docker Swarm&lt;/h3&gt;

&lt;p&gt;Je vais donc mettre en place le Docker Swarm, qui normalement est la solution
miracle à tous les problèmes (non, je ne me fais pas d’illusions).&lt;/p&gt;

&lt;p&gt;Pour commencer je pense qu’il vaudrait mieux que j’arrête tout ce qui est en
train de tourner sur ma machine en ce moment.&lt;/p&gt;

&lt;p&gt;La commande pour faire tourner le Swarm est celle-ci. Elle doit comprendre
une adresse IP, et comme je suis sur un portable, je vais mettre 127.0.0.1 parce
que les autres sont de nature changeante.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker swarm init --advertise-addr 127.0.0.1&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;
Swarm initialized: current node (8mu904sfrqkyv5jv0nbo2r6zh) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-6ceng4c89ie5[...]424m0w7s4plm 127.0.0.1:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
&lt;/pre&gt;

&lt;p&gt;Voilà, j’ai un manager. Normalement, il faut rajouter des workers, mais bon, vu
la taille de machine, je vais tout faire tourner sur le même. Au pire, on
pourra toujours rajouter des workers par la suite, et mettre à l’épreuve le
dimensionnement dynamique.&lt;/p&gt;

&lt;pre&gt;
docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS
8mu904sfrqkyv5jv0nbo2r6zh *   alderaan            Ready               Active              Leader
&lt;/pre&gt;

&lt;h2 id=&quot;déployer-des-services-sur-le-swarm&quot;&gt;Déployer des services sur le Swarm&lt;/h2&gt;

&lt;p&gt;Le déploiement de service est assez simple; en gros c’est une commande pour
démarrer un docker, sauf qu’il est inclus dans un Swarm.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker service create --replicas 1 --name helloworld alpine ping docker.com&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Il y a ensuite différentes possibilités pour faire de multiples réplicats, pour
arrêter des services, pour rajouter des noeuds, etc. etc.&lt;/p&gt;

&lt;p&gt;Ce qui nous intéresse ici, c’est comment peut-on faire démarrer deux docker dans
le Swarm et les faire se connecter ensemble sans avoir besoin de calculer
des adresses IP…&lt;/p&gt;

&lt;p&gt;Il faut deux ports ouverts entre tous les nodes:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Port 7946 TCP/UDP for container network discovery.
Port 4789 UDP for the container ingress network.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Et pour quand on va publier des services pour l’extérieur, il faudra aussi
que les ports soient ouverts.&lt;/p&gt;

&lt;p&gt;Pour l’instant le frontal en amont sera Traefik; je vais donc lancer l’image avec
une commande de ce genre :&lt;/p&gt;

&lt;pre&gt;
docker service create \
  --name &amp;lt;SERVICE-NAME&amp;gt; \
  --publish &amp;lt;PUBLISHED-PORT&amp;gt;:&amp;lt;TARGET-PORT&amp;gt; \
  &amp;lt;IMAGE&amp;gt;
&lt;/pre&gt;

&lt;pre&gt;
docker service create \
    --name traefik \
    --publish 8081:80 --publish 8080:8080 \
    --replicas 1 \
    saas_traefik
&lt;/pre&gt;

&lt;p&gt;Ça semble tourner, j’ai deux avertissements:&lt;/p&gt;

&lt;p&gt;Le premier concerne la version de traefik: vu qu’elle n’est pas fixe, les différentes
versions qui s’exécutent peuvent diverger.&lt;/p&gt;

&lt;pre&gt;
image saas_traefik:latest could not be accessed on a registry to record
its digest. Each node will access saas_traefik:latest independently,
possibly leading to different nodes running different
versions of the image.
&lt;/pre&gt;

&lt;p&gt;Ça c’est le retour normale de la commande :
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vhyh7o8mt7rg98uapi6cu1pzy&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Et ça c’est le deuxième avertissement; il faut que je modifie ma
commande pour rajouter –detach=true, sinon ça marchera plus dans le futur.&lt;/p&gt;

&lt;pre&gt;
Since --detach=false was not specified, tasks will be created in the background.
In a future release, --detach=false will become the default.
&lt;/pre&gt;

&lt;p&gt;Les commandes pour voir ce qui tourne :&lt;/p&gt;

&lt;pre&gt;
docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
vhyh7o8mt7rg        traefik             replicated          1/1                 saas_traefik:latest   *:8081-&amp;gt;80/tcp,*:8080-&amp;gt;8080/tcp
&lt;/pre&gt;

&lt;p&gt;Pour en voir un en particulier :&lt;/p&gt;

&lt;pre&gt;
docker service ps traefik
ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE           ERROR               PORTS
v5uuti8xtsmi        traefik.1           saas_traefik:latest   alderaan            Running             Running 6 minutes ago
&lt;/pre&gt;

&lt;p&gt;La commande &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inspect&lt;/code&gt; (sans &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--pretty&lt;/code&gt;, elle renvoie du json.) :&lt;/p&gt;

&lt;pre&gt;
docker service inspect --pretty traefik

ID:		vhyh7o8mt7rg98uapi6cu1pzy
Name:		traefik
Service Mode:	Replicated
 Replicas:	1
Placement:
UpdateConfig:
 Parallelism:	1
 On failure:	pause
 Monitoring Period: 5s
 Max failure ratio: 0
 Update order:      stop-first
RollbackConfig:
 Parallelism:	1
 On failure:	pause
 Monitoring Period: 5s
 Max failure ratio: 0
 Rollback order:    stop-first
ContainerSpec:
 Image:		saas_traefik:latest
Resources:
Endpoint Mode:	vip
Ports:
 PublishedPort = 8081
  Protocol = tcp
  TargetPort = 80
  PublishMode = ingress
 PublishedPort = 8080
  Protocol = tcp
  TargetPort = 8080
  PublishMode = ingress
&lt;/pre&gt;

&lt;p&gt;Intéressant: on peut publier un port d’un service qui existe déja:&lt;/p&gt;
&lt;pre&gt;
docker service update \
  --publish-add &amp;lt;PUBLISHED-PORT&amp;gt;:&amp;lt;TARGET-PORT&amp;gt; \
  &amp;lt;SERVICE&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Le traefik est joignable sur le port 8080 pour l’admin, mais comme il n’y a pas
de backend, du coup… 404 sur le port 8081.&lt;/p&gt;

&lt;p&gt;Donc, maintenant, il faut rajouter un service. Commençons par le CDN. Je mets deux
replicas pour la manière…&lt;/p&gt;

&lt;pre&gt;
docker service create --name running_cdn \
  --replicas 2 \
  -l traefik.backend=cdn \
  -l traefik.frontend.rule=&quot;Host:alderaan;PathPrefixStrip:/cdn/&quot; \
  -l traefik.port=80 cdn
&lt;/pre&gt;

&lt;p&gt;Maintenant le doit apparaître sur traefik: ce n’est pas le cas. C’est quoi le
problème ?&lt;/p&gt;

&lt;p&gt;Il y a une option &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--docker.swarmmode&lt;/code&gt; ; d’autre part, il semble qu’il faille
créer un reseau spécifique pour le swarm…&lt;/p&gt;

&lt;p&gt;Je recommence, déjà en arrêtant les services:&lt;/p&gt;

&lt;pre&gt;
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
4ffd8bbcb282        cdn:latest          &quot;nginx -g 'daemon ...&quot;   9 minutes ago       Up 9 minutes        80/tcp              running_cdn.2.2swjz0xkzulnos3cu2eykxrne
42e51714614c        cdn:latest          &quot;nginx -g 'daemon ...&quot;   9 minutes ago       Up 9 minutes        80/tcp              running_cdn.1.lol52dsy7v8fh1sl4nuwkoc9h
a10b47867961        saas_traefik:latest   &quot;/traefik&quot;               29 minutes ago      Up 29 minutes       80/tcp              traefik.1.v5uuti8xtsmihmg29e6073ezi
&lt;/pre&gt;

&lt;pre&gt;
$ docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
n8ljuywcox6k        running_cdn         replicated          2/2                 cdn:latest          
vhyh7o8mt7rg        traefik             replicated          1/1                 saas_traefik:latest   *:8081-&amp;gt;80/tcp,*:8080-&amp;gt;8080/tcp

$ docker service rm running_cdn
running_cdn

$ docker service rm traefik
traefik

$ docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
&lt;/pre&gt;

&lt;p&gt;On relance le service traefik, qui doit tourner sur le manager, binder la socket
de docker, et tourner avec l’option –docker.swarmmode.&lt;/p&gt;

&lt;pre&gt;
docker service create \
    --name running_traefik \
    --publish 8081:80 --publish 8080:8080 \
    --replicas 1 \
    --detach=true \
    --constraint=node.role==manager \
    --mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
    saas_traefik --docker.swarmmode
&lt;/pre&gt;

&lt;p&gt;Puis les instances de CDN:&lt;/p&gt;

&lt;pre&gt;
docker service create --name running_cdn \
  --replicas 2 \
  --label traefik.backend=cdn \
  --label traefik.frontend.rule=&quot;Host:alderaan;PathPrefixStrip:/cdn/&quot; \
  --label traefik.port=80 cdn
&lt;/pre&gt;

&lt;p&gt;Et… ça fonctionne ! Bon enfin, on voit les CDN sur le frontend admin de traefik.&lt;/p&gt;

&lt;p&gt;Pour le web : internal server error - comment diable…&lt;/p&gt;

&lt;p&gt;Le problème, c’est le réseau. Dans l’admin de traefik, je vois ça:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;server-running_cdn-1 	http://:80 	0
server-running_cdn-2 	http://:80 	0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ce qui signifie sans doute que traefik n’a pas la moindre idée où joindre les
deux cocos. Ok, on arrête tout le monde, on crée un réseau pour leurs 
petites affaires, et c’est reparti:&lt;/p&gt;

&lt;p&gt;Le réseau:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker network create --driver=overlay traefik-net&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;On rajoute l’option &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--network traefik-net&lt;/code&gt; pour les services:&lt;/p&gt;

&lt;pre&gt;
docker service create \
    --name running_traefik \
    --publish 8081:80 --publish 8080:8080 \
    --replicas 1 \
    --detach=true \
    --constraint=node.role==manager \
    --network traefik-net \
    --mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
    saas_traefik --docker.swarmmode
&lt;/pre&gt;

&lt;p&gt;Puis les instances de CDN:&lt;/p&gt;

&lt;pre&gt;
docker service create --name running_cdn \
  --replicas 2 \
  --label traefik.backend=cdn \
  --label traefik.frontend.rule=&quot;Host:alderaan;PathPrefixStrip:/cdn/&quot; \
  --label traefik.port=80 cdn \
  --network traefik-net \
  --detach=true
&lt;/pre&gt;

&lt;p&gt;Il m’a redonné la main, mais il semble qu’il n’arrive pas à les lancer…&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
3360n9b8c2a4        running_cdn         replicated          0/2                 cdn:latest          
3qig0s7rop9g        running_traefik     replicated          1/1                 saas_traefik:latest   *:8081-&amp;gt;80/tcp,*:8080-&amp;gt;8080/tcp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;O/2 replicas, c’est pas bon signe…&lt;/p&gt;

&lt;p&gt;Il y a pas mal d’erreurs dans le fichier daemon.log, mais j’arrive pas à savoir
lesquelles sont arrivées en premier.&lt;/p&gt;

&lt;p&gt;Je vais donc regarder le fichier et lancer les services en même temps.&lt;/p&gt;

&lt;p&gt;En instanciant le service traefik, j’ai ces erreurs:&lt;/p&gt;

&lt;pre&gt;
Aug  5 11:10:34 alderaan dockerd[31840]: time=&quot;2017-08-05T11:10:34.114186403+02:00&quot; level=error msg=&quot;Not continuing with pull after error: errors:\ndenied: requested access to the resource is denied\nunauthorized: authentication required\n&quot;
Aug  5 11:10:34 alderaan dockerd[31840]: time=&quot;2017-08-05T11:10:34.114293122+02:00&quot; level=info msg=&quot;Ignoring extra error returned from registry: unauthorized: authentication required&quot;
Aug  5 11:10:34 alderaan dockerd[31840]: time=&quot;2017-08-05T11:10:34.114389085+02:00&quot; level=info msg=&quot;Translating \&quot;denied: requested access to the resource is denied\&quot; to \&quot;repository saas_traefik not found: does not exist or no pull access\&quot;&quot;
Aug  5 11:10:34 alderaan dockerd[31840]: time=&quot;2017-08-05T11:10:34.114490356+02:00&quot; level=error msg=&quot;pulling image failed&quot; error=&quot;repository saas_traefik not found: does not exist or no pull access&quot; module=&quot;node/agent/taskmanager&quot; node.id=8mu904sfrqkyv5jv0nbo2r6zh service.id=6abtofqlelhhzqahp5mhekawi task.id=ltbbh1sdb1m31qyyl9abtawu1
Aug  5 11:10:34 alderaan systemd-udevd[5350]: Could not generate persistent MAC address for veth75ebdae: No such file or directory
Aug  5 11:10:34 alderaan systemd-udevd[5349]: Could not generate persistent MAC address for veth57c1524: No such file or directory
Aug  5 11:10:34 alderaan systemd-udevd[5363]: Could not generate persistent MAC address for veth220f0a0: No such file or directory
Aug  5 11:10:34 alderaan systemd-udevd[5362]: Could not generate persistent MAC address for veth1505fd5: No such file or directory
Aug  5 11:10:35 alderaan systemd-udevd[5385]: link_config: could not get ethtool features for vx-001001-eop8p
Aug  5 11:10:35 alderaan systemd-udevd[5385]: Could not set offload features of vx-001001-eop8p: No such device
Aug  5 11:10:35 alderaan systemd-udevd[5385]: Could not generate persistent MAC address for vx-001001-eop8p: No such file or directory
Aug  5 11:10:35 alderaan systemd-udevd[5403]: Could not generate persistent MAC address for vetha4eb34a: No such file or directory
Aug  5 11:10:35 alderaan systemd-udevd[5404]: Could not generate persistent MAC address for vethb073e9b: No such file or directory
Aug  5 11:10:37 alderaan avahi-daemon[503]: Joining mDNS multicast group on interface veth220f0a0.IPv6 with address fe80::249c:72ff:fe51:94ea.
Aug  5 11:10:37 alderaan avahi-daemon[503]: New relevant interface veth220f0a0.IPv6 for mDNS.
Aug  5 11:10:37 alderaan avahi-daemon[503]: Registering new address record for fe80::249c:72ff:fe51:94ea on veth220f0a0.\*.
&lt;/pre&gt;

&lt;p&gt;Ça n’empèche pas traefik de tourner, mais bon…&lt;/p&gt;

&lt;p&gt;Bref, regardons ce qu’il se passe quand on lance les CDN :&lt;/p&gt;

&lt;p&gt;J’ai les même message de pull qui marche pas, mais ça c’est pas tant grave.&lt;/p&gt;

&lt;p&gt;J’ai aussi les histoires de &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;udevd&lt;/code&gt; qui se plaint des MAC address, mais pareil,
ça semble pas être un soucis pour traefik. Enfin, peut-être que c’en est pas un pour
lui mais que pour la communication entre lui et les backends, c’est pas la même.&lt;/p&gt;

&lt;p&gt;Par contre, j’ai ça :&lt;/p&gt;

&lt;pre&gt;
Aug  5 11:15:16 alderaan dockerd[31840]: time=&quot;2017-08-05T11:15:16.17292655+02:00&quot; level=error msg=&quot;containerd: start container&quot; error=&quot;oci runtime error: container_linux.go:262: starting container process caused \&quot;exec: \\\&quot;--network\\\&quot;: executable file not found in $PATH\&quot;\n&quot; id=215619fc8a85876d5a94e16a77f6cb7741c087fc375c2868b93902de6f0c0b3b
Aug  5 11:15:16 alderaan dockerd[31840]: time=&quot;2017-08-05T11:15:16.175587123+02:00&quot; level=error msg=&quot;Create container failed with error: oci runtime error: container_linux.go:262: starting container process caused \&quot;exec: \\\&quot;--network\\\&quot;: executable file not found in $PATH\&quot;\n&quot;
Aug  5 11:15:16 alderaan dockerd[31840]: time=&quot;2017-08-05T11:15:16.220435654+02:00&quot; level=error msg=&quot;containerd: start container&quot; error=&quot;oci runtime error: container_linux.go:262: starting container process caused \&quot;exec: \\\&quot;--network\\\&quot;: executable file not found in $PATH\&quot;\n&quot; id=21f33015c9a7014cd66a8c96f906bd60f822a5e0f5a5571bfcde82120d67a71d
Aug  5 11:15:16 alderaan dockerd[31840]: time=&quot;2017-08-05T11:15:16.221481542+02:00&quot; level=error msg=&quot;Create container failed with error: oci runtime error: container_linux.go:262: starting container process caused \&quot;exec: \\\&quot;--network\\\&quot;: executable file not found in $PATH\&quot;\n&quot;
Aug  5 11:15:16 alderaan dockerd[31840]: time=&quot;2017-08-05T11:15:16.988712765+02:00&quot; level=error msg=&quot;fatal task error&quot; error=&quot;starting container failed: oci runtime error: container_linux.go:262: starting container process caused \&quot;exec: \\\&quot;--network\\\&quot;: executable file not found in $PATH\&quot;\n&quot; module=&quot;node/agent/taskmanager&quot; node.id=8mu904sfrqkyv5jv0nbo2r6zh service.id=a9rqfx3e8cg6ej2ntq6hk9vlp task.id=41f84plc2rblv9pwazoiefkyl
Aug  5 11:15:17 alderaan dockerd[31840]: time=&quot;2017-08-05T11:15:17.068196369+02:00&quot; level=error msg=&quot;fatal task error&quot; error=&quot;starting container failed: oci runtime error: container_linux.go:262: starting container process caused \&quot;exec: \\\&quot;--network\\\&quot;: executable file not found in $PATH\&quot;\n&quot; module=&quot;node/agent/taskmanager&quot; node.id=8mu904sfrqkyv5jv0nbo2r6zh service.id=a9rqfx3e8cg6ej2ntq6hk9vlp task.id=pfbi6qylga0pjpn0agnq6hjl5
&lt;/pre&gt;

&lt;p&gt;Et ça, ça me semble pas du tout bon. On dirait qu’il pense que –network est
une commande qu’il doit exécuter (ofc ce n’est pas le cas…)&lt;/p&gt;

&lt;p&gt;Mais pourquoi donc ? AHhhhhhhhh diantre ! J’ai mis le –network après cdn !!!&lt;/p&gt;

&lt;pre&gt;
docker service create --name running_cdn \
  --replicas 2 \
  --label traefik.backend=cdn \
  --label traefik.frontend.rule=&quot;Host:alderaan;PathPrefixStrip:/cdn/&quot; \
  --network traefik-net \
  --detach=true \
  --label traefik.port=80 cdn
&lt;/pre&gt;

&lt;p&gt;Là ça semble aller tout de suite mieux: les backend CDN sont lancés, ils sont
dans l’interface de traefik avec des adresses correctes, et … le service
web répond !&lt;/p&gt;

&lt;p&gt;Est-ce que ça load-balance ??? Ah oui, ça marche ! En suivant les logs de part
et d’autre, on le voit bien.&lt;/p&gt;

&lt;p&gt;Le screenshot obligatoire :&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/s01e04traefikscreenshot.png&quot; alt=&quot;Screenshot Obligatoire&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Ok, suffit la plaisanterie, on va lancer une instance du saas.&lt;/p&gt;

&lt;pre&gt;
docker service create --name running_saas \
  --replicas 1 \
  --label traefik.backend=saas \
  --label traefik.frontend.rule=&quot;Host:alderaan;PathPrefixStrip:/saas&quot; \
  --label traefik.port=80 \
  --network traefik-net \
  --detach=true saas
&lt;/pre&gt;

&lt;p&gt;J’allais dire, la machine est un peu essouflée, en fait, atom et firefox prennent
plus de mémoire à eux deux que tout le docker…&lt;/p&gt;

&lt;p&gt;Le saas est lancé, ça ok. Mais vous savez quoi ? Je suis sur qu’il va pas arriver
à retrouver la base de données. Purée, il l’a trouvé, comment il fait ça ?
 Je veux même pas le savoir à ce stade.&lt;/p&gt;

&lt;p&gt;Ce qui me m’interesse surtout c’est comment les applis vont joindre le saas dans
le swarm ! Apparemment il n’y a qu’un seul moyen de le savoir !&lt;/p&gt;

&lt;p&gt;Il faut construire une image avec le logiciel ludo. Après, comme pour le saas,
on ira bidouiller la conf à l’intérieur jusqu’à ce que ça marche;&lt;/p&gt;

&lt;p&gt;Le Dockerfile:&lt;/p&gt;

&lt;pre&gt;
FROM generic-php-saas
COPY webroot/ /var/www/html/
COPY webroot/config/config-saas.php /var/www/html/config/config.php
&lt;/pre&gt;

&lt;p&gt;Build:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/www/ludotm/ludotheque$ docker build -t ludotheque .&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Create service :&lt;/p&gt;

&lt;pre&gt;
docker service create --name ludotm_ludotheque \
  --replicas 1 \
  --label traefik.backend=ludotm_ludotheque \
  --label traefik.frontend.rule=&quot;Host:alderaan;PathPrefixStrip:/ludotm/ludotheque&quot; \
  --label traefik.port=80 \
  --network traefik-net \
  --detach=true ludotheque
&lt;/pre&gt;

&lt;p&gt;Nickel ! Le logiciel de lutotheque fonctionne; et c’est bien le saas qui
stocke les sessions grâce à l’adresse running_saas.&lt;/p&gt;

&lt;p&gt;Petit bemol : j’ai construit une image nommée ludotheque avec la config en dur
d’une lutotheque en particulier, alors que l’instance devrait être parametrée
au lancement.&lt;/p&gt;

&lt;p&gt;Essayons de plus en plus fort:&lt;/p&gt;

&lt;p&gt;Mettre un deuxième docker pour rendre le service ludotheque.&lt;/p&gt;

&lt;p&gt;Première façon, juste histoire de se chauffer: on arrête le service et
on en remet un autre avec deux replicas.&lt;/p&gt;

&lt;pre&gt;
$ docker service rm ludotm_ludotheque

$ docker service create --name ludotm_ludotheque \
  --replicas 2 \
  --label traefik.backend=ludotm_ludotheque \
  --label traefik.frontend.rule=&quot;Host:alderaan;PathPrefixStrip:/ludotm/ludotheque&quot; \
  --label traefik.port=80 \
  --network traefik-net \
  --detach=true ludotheque
&lt;/pre&gt;

&lt;p&gt;Mettons que j’en veuille un troisième !&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker service scale ludotm_ludotheque=3&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Et voilà, j’ai cru que ça pourrait pas être aussi simple, mais si.
Je peux scaler en plus ou en moins, c’est top.&lt;/p&gt;

&lt;p&gt;Ça load-balance entre les deux ludotheques, mais dans la réalité absolue, c’est
plutôt le saas qu’il faudrait scaler, non ?&lt;/p&gt;

&lt;p&gt;Je scale !&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker service scale running_saas=2&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Le service des sessions est load-balancé entre les deux instances du saas, est-ce
que c’est pas BEAUTIFUL ?&lt;/p&gt;

&lt;h2 id=&quot;pour-la-suite&quot;&gt;Pour la suite&lt;/h2&gt;

&lt;p&gt;C’est pas fini, voilà les trucs qui restent à faire:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Construire une image générique de la ludotheque qui puisse se paramétrer
lors du lancement du service.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Accès MySQL: du coup l’accès se fait dans les fichier de config avec
l’adresse IP en 172.17… et ça c’est pas correct. Il faut que je trouve moyen
de faire tourner un docker MySQL qui se base sur les fichiers du host mais qui
propose le service mysql_master, par exemple, pour les autres services du swarm.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Accès memcached: comme je l’ai déjà dit, c’est pas bon non plus. Il faut que
je mette en place des serveurs redis sous forme de docker, que je pourrai scaler.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;La suite, c’est ici, dans l’épisode 5 :
&lt;a href=&quot;/it/docker/linux/2017/09/03/adventures-with-docker-part-5.html&quot;&gt;Adventures with Docker - Part 5 - The Testing Environment&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Thu, 31 Aug 2017 04:00:00 -0500</pubDate>
        <link>http://0.0.0.0:4000/it/docker/linux/2017/08/31/adventures-with-docker-part-4.html</link>
        <guid isPermaLink="true">http://0.0.0.0:4000/it/docker/linux/2017/08/31/adventures-with-docker-part-4.html</guid>
        
        
        <category>it</category>
        
        <category>docker</category>
        
        <category>linux</category>
        
      </item>
    
      <item>
        <title>Adventures with Docker - S01E03 - Logs</title>
        <description>&lt;h2 id=&quot;résumé-des-épisodes-précédents&quot;&gt;Résumé des épisodes précédents&lt;/h2&gt;

&lt;p&gt;J’ai installé Docker-ce (Community Edition) sur mon portable, et j’ai construit
une image qui fait tourner du PHP. Pour l’instant, le MySQL est toujours sur le
serveur et non dans un container docker. D’ailleurs, c’est pareil pour le memcached.&lt;/p&gt;

&lt;p&gt;La persistance des données est un problème sous Docker. Pas un problème dans le
sens que c’est impossible de s’en sortir, c’est juste qu’il faut bien réflechir
à ce point et surtout le considérer différement. On y viendra
plus tard.&lt;/p&gt;

&lt;p&gt;J’en suis resté à cette histoire d’erreur dans mon session handler (je l’ai
redéfini pour qu’il stocke les sessions dans memcached), et comme je vois
pas les logs, je suis bien ennuyé.&lt;/p&gt;

&lt;p&gt;Et pour rappel, le micro schéma d’architecture :&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/s01e01schemadraft.png&quot; alt=&quot;Brouillon d'architecture&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;construction-de-limage-du-saas&quot;&gt;Construction de l’image du Saas&lt;/h2&gt;

&lt;p&gt;J’ai placé les directives suivantes dans le php.ini (qui se trouve dans un
endroit inhabituel)&lt;/p&gt;

&lt;pre&gt;
$ cat /usr/local/etc/php/php.ini
log_errors = On
error_log = /dev/stderr
&lt;/pre&gt;

&lt;p&gt;Il faut donc que je rajoute cela à mon Dockerfile.&lt;/p&gt;

&lt;p&gt;1 - créer un fichier de configuration php.ini pour la construction du docker.&lt;/p&gt;

&lt;p&gt;2 - une commande pour créer le fichier php.ini au bon endroit:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;COPY generic-php.ini /usr/local/etc/php/php.ini&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;C’est pour mon image générique, mais pour la configuration du saas lui-même,
j’ai aussi un fichier de configuration à emmener.&lt;/p&gt;

&lt;p&gt;D’autre part, j’utilise un CDN (Content Deliver Network, pour l’instant, ce sera un 
container qui embarque avec lui les fichiers javascript et css dont les autres 
ont besoin - jQuery, Bootstrap, etc..) et pour ça je pense qu’un
container avec uniquement NGinX peut suffire.&lt;/p&gt;

&lt;p&gt;En tout cas, il faut absolument que je le mette en place.&lt;/p&gt;

&lt;p&gt;Donc, je commence par arrêter et effacer les images. Ensuite, je reconstruis l’image générique
et l’image qui en hérite;&lt;/p&gt;

&lt;p&gt;(Que le diable m’emporte, la construction de l’image générique a utilisé une
sorte de cache sorti de nulle part et a mis deux secondes !)&lt;/p&gt;

&lt;p&gt;Qu’importe. J’ai reconstruit à l’identique et je me trouve dans la même situation.&lt;/p&gt;

&lt;p&gt;Je ne peux pas me connecter, parce que la connexion implique du jquery, et comme
je l’ai dit au début, je n’ai pas de CDN.&lt;/p&gt;

&lt;p&gt;On a donc ici un choix comme dans la plupart des aventures:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Mettre le CDN directement dans l’image et on verra ça plus tard&lt;/li&gt;
  &lt;li&gt;Contruire le NGinX qui va héberger le CDN&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On le joue au dé 6 : 1,2,3 =&amp;gt; solution 1, sinon solution 2.&lt;/p&gt;

&lt;p&gt;(je vais un rand(1,6) parce que là j’ai pas dé sous la main.)&lt;/p&gt;

&lt;p&gt;3 ! Allez, va pour le CDN directement dans l’image. Au-delà de la plaisanterie,
j’ai plein de choses à considérer, il faut donc faire des choix pour pouvoir 
avancer, sachant que ces choix vont peut-être être remis en cause par la suite.&lt;/p&gt;

&lt;pre&gt;
Step 4/4 : COPY ../cdn /var/www/html
COPY failed: Forbidden path outside the build context: ../cdn ()
&lt;/pre&gt;

&lt;p&gt;Tiens, c’est à savoir: les fichiers qui font partie de l’image doivent
se trouver dans le repertoire de build. Bon, je le recopie directement.&lt;/p&gt;

&lt;p&gt;Maintenant, ça marche nickel (pour ce qui est du CDN) et pour la connexion ?
Marche aussi.&lt;/p&gt;

&lt;p&gt;Il y a quelques bugs (les préférences de l’utilisateur mène vers un lien absolu
qui ne marche pas (forcément)) et puis le passage à PHP7, j’ai quelques Warning
parce que mes contrôleurs sont pas super bien écrits (shame on me), mais on s’en
moque pour l’instant.&lt;/p&gt;

&lt;p&gt;Je me contenterai de souligner que ce mode de fonctionnement en services séparés
permets de voir plus rapidement ce qui va merdouiller quand on installer l’appli
sur plusieurs machines et que c’est globalement une bonne chose.&lt;/p&gt;

&lt;p&gt;Ceci dit, installer le CDN sur directement sur l’image a permis de valider
les choses, mais ce n’est pas une solution tenable, c’est juste parce que le
dé est tombé sur 3.&lt;/p&gt;

&lt;p&gt;Mais: si j’installe le CDN dans un NGinX, il va falloir accéder aux fichiers
et donc pour ça, je devrais alors écouter sur le port
8081, par exemple, et ça suppose que je modifie mes vues (enfin la vue template)
pour ajouter le lien vers ce nouveau serveur. Je n’aime pas trop cette solution,
elle m’a l’air de necéssiter ce truc qu’on appelle le travail.&lt;/p&gt;

&lt;p&gt;Je pense que je ne peux pas plus longtemps éviter le routeur.&lt;/p&gt;

&lt;p&gt;En gros, il me faut un truc qui va écouter sur le port 8080 et orienter les requêtes
vers les bons containers.&lt;/p&gt;

&lt;h2 id=&quot;traefik&quot;&gt;Traefik&lt;/h2&gt;

&lt;p&gt;Ce truc, c’est traefik. Je l’ai pas sorti de nulle part, on me l’a conseillé: un collègue
qui m’a donné pas mal de conseil sur Docker et que je citerai ici quand il m’aura donné
un profil pour le mentionner…&lt;/p&gt;

&lt;p&gt;Voilà ce que ça modifie sur le schéma :&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/s01e03withtraefik.png&quot; alt=&quot;Architecture avec Traefik&quot; /&gt;&lt;/p&gt;

&lt;p&gt;En plus, ça tourne sur et avec docker. Le programme lui-même peut tourner dans un docker,
et de plus, il écoute sur la socket &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/run/docker.sock&lt;/code&gt; pour
voir si des containers proposent un nouveau service, et automatiquement,
route les requêtes vers le bon endroit.&lt;/p&gt;

&lt;p&gt;Donc je crée un fichier de configuration traefik.toml:&lt;/p&gt;

&lt;pre&gt;
logLevel = &quot;DEBUG&quot;
defaultEntryPoints = [&quot;http&quot;, &quot;https&quot;]
[web]
address = &quot;:8080&quot;
[docker]
endpoint = &quot;unix:///var/run/docker.sock&quot;
# alderaan it's my machine - yes it will eventually explode
domain = &quot;alderaan&quot;

# Enable watch docker changes
watch = true
&lt;/pre&gt;

&lt;p&gt;Je ramène une image&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker pull traefik&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Je le build avec ça :&lt;/p&gt;

&lt;pre&gt;
FROM traefik:latest
COPY config/traefik.toml /etc/traefik/traefik.toml
&lt;/pre&gt;

&lt;p&gt;Lancer le build :&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker build -t saas_traefik .&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Démarrer une instance de Traefik&lt;/p&gt;

&lt;pre&gt;
docker run -d -p 8080:8081 -p 443:443 -p 80:80 \
  -v /var/run/docker.sock:/var/run/docker.sock --name running_traefik saas_traefik
&lt;/pre&gt;

&lt;p&gt;L’interface web est dispo sur 8080 - pour l’instant il n’y a pas de backend.&lt;/p&gt;

&lt;h2 id=&quot;démarrage-dun-nginx&quot;&gt;Démarrage d’un nginx&lt;/h2&gt;

&lt;p&gt;Builder le NGinX en CDN avec le Dockerfile suivant :&lt;/p&gt;

&lt;pre&gt;
FROM nginx:latest
COPY css /usr/share/nginx/html/css
COPY js /usr/share/nginx/html/js
&lt;/pre&gt;

&lt;p&gt;Et la commande :&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker build -t cdn .&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;On démarre le container, cette fois en lui passant des paramètres spécifiques
qui seront utilisés par Traefik. En détail, la solution que j’ai choisie,
c’est &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Host:alderaan;PathPrefixStrip:/cdn/&lt;/code&gt;, ce qui signifie que le &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Host:&lt;/code&gt;
de la requête http doit être alderaan, et que les requêtes commençant par le
prefix &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/cdn/&lt;/code&gt; doivent être envoyées vers ce container en “strippant” &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/cdn&lt;/code&gt;
de l’URL.&lt;/p&gt;

&lt;pre&gt;
docker run -d -l traefik.backend=cdn \
  -l traefik.frontend.rule=&quot;Host:alderaan;PathPrefixStrip:/cdn/&quot; \
  -l traefik.port=80 \
  --name running_cdn cdn
&lt;/pre&gt;

&lt;p&gt;Il faut d’abord que je rebuild l’image du saas sans le CDN…&lt;/p&gt;

&lt;p&gt;je supprime le CDN que j’avais recopié dans le webroot, je rebuild, et je
relance :&lt;/p&gt;

&lt;pre&gt;
docker run -d -l traefik.backend=saas \
  -l traefik.frontend.rule=&quot;Host:alderaan;PathPrefixStrip:/saas&quot; \
  -l traefik.port=80 \
  --name running_saas saas
&lt;/pre&gt;

&lt;p&gt;Ça marche !&lt;/p&gt;

&lt;p&gt;En gros, les requetes qui arrivent sur /cdn partent vers le NGinX et les
autres vers le container saas. Nickel.&lt;/p&gt;

&lt;p&gt;Pour l’instant tout le site tourne sur le port 8081 en http, ce qui n’est pas
top, mais ensuite, il est prévu de mettre un serveur apache en frontal qui
portera le SSL. Ou bien, tant qu’à faire, traefik pourra porter le certificat
SSL, puisqu’aussi bien, il sait le faire. Je crois même qu’il est possible
de l’interfacer avec LetsEncrypt pour avoir des certificats automatiquement.&lt;/p&gt;

&lt;p&gt;Maintenant le but, c’est de faire tourner une appli qui va utiliser le Saas.&lt;/p&gt;

&lt;p&gt;En gros, les applications ont besoin du saas pour deux choses:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;elles utilisent l’API pour l’authentification (spoil: c’est pas terrible comme solution)&lt;/li&gt;
  &lt;li&gt;elles y stockent leurs sessions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Le coup de l’API pour l’authentification c’est pas une super idée. Le mieux,
c’est que si les utilisateurs ne sont pas authentifiés, alors ils sont renvoyés
vers le saas qui se charge de gérer ces aspects (authentification, mais aussi
les trucs du genre i-forgot-my-password, i-want-a-new-account, etc.)&lt;/p&gt;

&lt;p&gt;Qu’importe, je vais fabriquer une image avec le logiciel ludo;&lt;/p&gt;

&lt;p&gt;Pour la configuration, c’est là que les problèmes commencent. Comment je fais,
lorsque j’installe une application, pour lui dire où se trouve le saas ? Au niveau
du container appli, pour se connecter sur le saas il faut passer par son adresse
interne. Aujourd’hui c’est 172.17.0.2 mais demain ?&lt;/p&gt;

&lt;p&gt;D’après certaines rumeurs, il conviendrait de passer par des variables d’environnement.
C’est à dire qu’au moment de lancer l’appli, il faut lui donner l’adresse du saas.&lt;/p&gt;

&lt;p&gt;C’est bien joli, mais si j’en ai plusieurs instances ? Je peux toujours lui
donner plusieurs adresses, j’imagine. Oui, mais si j’ai de nouvelles instances ?
Et si l’ancienne est remplacée par une nouvelle ?&lt;/p&gt;

&lt;p&gt;Il est clair qu’il me faut aussi un routeur pour les micro-services. Du coup,
est-ce que traefik ne peut pas s’occuper de ça aussi ?&lt;/p&gt;

&lt;p&gt;La solution se trouve plutôt dans un outil supplémentaire, le swarm. C’est un
sur ensemble de docker qui permet de faire du service discovery.&lt;/p&gt;

&lt;p&gt;Par contre, j’ai un autre soucis, c’est le memcached. De par son fonctionnement
les clients ont besoin de connaitre l’intégralité des membres du cluster memcached
(qui d’ailleurs n’est pas un cluster), ce qui fait que ce n’est pas l’outil idéal
pour stocker les sessions. Peut-être qu’il vaudrait mieux employer redis pour ça ?&lt;/p&gt;

&lt;p&gt;Je vais dessiner un schéma pour mieux expliquer tout ça, comment je pense que
ça doit fonctionner, etc.&lt;/p&gt;

&lt;p&gt;Et ben voilà, j’ai du boulot ! Rendez-vous très bientôt pour l’épisode 4 :
ce sera principalement un épisode théorique, avec des considérations sur les
choix des moyens et des politiques à mettre en oeuvre pour arriver à nos
fins.&lt;/p&gt;

&lt;p&gt;(Et je mettrai des captures d’écrans !)&lt;/p&gt;

&lt;p&gt;Update : La suite, c’est ici, dans l’épisode 4 :
&lt;a href=&quot;/it/docker/linux/2017/08/31/adventures-with-docker-part-4.html&quot;&gt;Adventures with Docker - Part 4 - The Swarm&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Fri, 25 Aug 2017 11:00:00 -0500</pubDate>
        <link>http://0.0.0.0:4000/it/docker/linux/2017/08/25/adventures-with-docker-part-3-logs.html</link>
        <guid isPermaLink="true">http://0.0.0.0:4000/it/docker/linux/2017/08/25/adventures-with-docker-part-3-logs.html</guid>
        
        
        <category>it</category>
        
        <category>docker</category>
        
        <category>linux</category>
        
      </item>
    
      <item>
        <title>Adventures with Docker - S01E02 - One Container</title>
        <description>&lt;h2 id=&quot;résumé&quot;&gt;Résumé&lt;/h2&gt;

&lt;p&gt;J’ai installé Docker sur mon portable. J’ai essayé de faire tourner un container
avec une de mes applis, en lien et place d’une installation ordinaire; d’habitude
l’application tourne sur le serveur apache local, se connecte sur la base de données
locale, etc.&lt;/p&gt;

&lt;p&gt;Ici l’idée, c’est de faire tourner l’application dans un container, qui est comme
une sorte de VM très légère, pour en faire une sorte de brique logicielle
indépendante.&lt;/p&gt;

&lt;h2 id=&quot;construction-dune-image-générique&quot;&gt;Construction d’une image générique&lt;/h2&gt;

&lt;p&gt;J’ai compris à l’usage que la construction des images docker fonctionnait avec 
des couches successives; en gros, je devrais commencer par me baser sur des images
existantes (elles sont fournies par le Docker Hub: &lt;a href=&quot;https://hub.docker.com/&quot;&gt;https://hub.docker.com/&lt;/a&gt; par différentes sources.) pour construire une image générique qui
va contenir les dépendances dont mes applications se servent en général: dans mon
cas particulier, il s’agit des librairies pour faire des images, de la classe d’accès
à MariaDB/MySQL, et de l’extension MemCached.&lt;/p&gt;

&lt;p&gt;Voilà donc mon Dockerfile :&lt;/p&gt;

&lt;pre&gt;
FROM php:7.1-apache
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y libmemcached-dev zlib1g-dev \
    &amp;amp;&amp;amp; pecl install memcached-3.0.3 \
    &amp;amp;&amp;amp; docker-php-ext-enable memcached
RUN apt-get install -y \
            libfreetype6-dev \
            libjpeg62-turbo-dev \
            libmcrypt-dev \
            libpng12-dev \
            libmariadbd-dev \
        &amp;amp;&amp;amp; docker-php-ext-install -j$(nproc) iconv mcrypt \
        &amp;amp;&amp;amp; docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
        &amp;amp;&amp;amp; docker-php-ext-install -j$(nproc) gd \
        &amp;amp;&amp;amp; docker-php-ext-install -j$(nproc) mysqli
&lt;/pre&gt;

&lt;p&gt;Je construit l’image générique :&lt;/p&gt;

&lt;pre&gt;
docker build -t generic-php-saas .
[...]
Removing intermediate container 96d995faa15a
Successfully built 4799c12f1067
Successfully tagged generic-php-saas:latest
&lt;/pre&gt;

&lt;p&gt;Ce qui fait que pour construire l’image du Saas, j’ai un nouveau Dockerfile, plus simple… (et plus rapide, du coup)&lt;/p&gt;

&lt;pre&gt;
FROM generic-php-saas
COPY webroot/ /var/www/html/
&lt;/pre&gt;

&lt;p&gt;Le build prend deux secondes ! C’est la bonne solution.&lt;/p&gt;

&lt;p&gt;Maintenant on va essayer de faire un fichier de configuration de l’application
adapté à son nouvel environnement.&lt;/p&gt;

&lt;p&gt;Pour ça je vais essayer la stratégie qui me semble le plus évidente:
lancer le container, essayer de se connecter “dedans” (je sais pas trop si ça
a un sens au moment où je formule cette tactique…) et modifier le fichier
jusqu’à temps que ça fonctionne.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker run -d -p 8080:80 --name running-saas saas&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Pour se connecter “dedans” ?&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker exec -it running-saas bash&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Ok, donc ya pas d’éditeur. Mais bonne nouvelle, je peux en installer un.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apt-get install vim&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;J’ai donc besoin de me connecter au MySQL du host, comment je fais exactement ?
C’est-à-dire, comment je trouve l’adresse du host (la machine) depuis le container.&lt;/p&gt;

&lt;p&gt;En fouinant sur SO, &lt;a href=&quot;https://stackoverflow.com/questions/41897077/connect-to-mysql-on-localhost-from-docker-container&quot;&gt;connect-to-mysql-on-localhost-from-docker-container&lt;/a&gt; je trouve la commande suivante:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ip route show | grep &quot;default&quot; | awk '{print $3}'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;En mettant l’adresse IP résultante dans le fichier de configuration, ça fonctionne,
ce qui signifie que j’ai été un peu large dans les permissions de connexion.
(mais baste! c’est la dev…) Bon on verra ce point un peu plus tard.&lt;/p&gt;

&lt;p&gt;L’adresse du host, pour les container, c’est donc la passerelle par défaut. Je ne sais
pas si c’est toujours le cas, mais pour l’instant, oui.&lt;/p&gt;

&lt;p&gt;D’autant qu’il semble que la bonne solution soit plutôt de passer par un container
qui joue le rôle de MySQL. Encore un truc à voir plus tard.&lt;/p&gt;

&lt;p&gt;Maintenant la connexion vers memcached: comme il tourne sur le host, son adresse est la
même que celle de MariaDB. Note: ça n’a pas marché du premier coup, pour une raison
toute simple: memcached n’écoute que sur l’adresse 127.0.0.1 du host. 
J’édite le fichier de conf de memcached, je relance, et voilà, c’est mieux.&lt;/p&gt;

&lt;p&gt;J’ai encore une erreur:&lt;/p&gt;

&lt;pre&gt;
Warning: Unknown: Failed to write session data using user defined save handler. 
    (session.save_path: ) in Unknown on line 0
&lt;/pre&gt;

&lt;p&gt;Cette erreur se produit à un endroit bien particulier: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;in Unknown on line 0&lt;/code&gt;,
cela signifie que c’est après l’exécution du programme PHP - au moment où il ferme
et enregistre la session.&lt;/p&gt;

&lt;p&gt;Il faut à tout prix que je puisse voir les logs d’erreur du container.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker logs --follow running-saas&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Je vois bien les logs du serveur Apache, mais je ne vois pas les logs de php.&lt;/p&gt;

&lt;p&gt;Et malgré différents essais, je ne vois toujours pas de log arriver. Je me rappelle
c’est ça qui m’avait arrêté, la dernière fois que j’ai essayé de me servir de Docker.
L’accès aux logs est crucial pour développer, et comme j’étais pressé, je n’avais pas
poursuivi.&lt;/p&gt;

&lt;h2 id=&quot;pour-la-suite&quot;&gt;Pour la suite&lt;/h2&gt;

&lt;p&gt;Bon, j’ai avancé. J’ai un système de stockage de session assez compliqué, en
fait, ce qui complexifie l’installation.&lt;/p&gt;

&lt;p&gt;La prochaine étape, c’est de pouvoir accéder aux logs d’erreurs pour que je puisse voir
ce qu’il se passe.&lt;/p&gt;

&lt;p&gt;Dans un futur pas encore bien visible pour le moment, il faudra que les logs soient stockés
non pas dans les containers mais dans un endroit centralisé. Par exemple un syslog ? Ou un
fluentd ? J’aimerai pouvoir le consulter en temps réel en fonction de leur catégorie (info,
warning, error, etc.)&lt;/p&gt;

&lt;p&gt;La suite, c’est ici, dans l’épisode 3 :
&lt;a href=&quot;/it/docker/linux/2017/08/25/adventures-with-docker-part-3-logs.html&quot;&gt;Adventures with Docker - Part 3 - Logs&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Wed, 23 Aug 2017 10:00:00 -0500</pubDate>
        <link>http://0.0.0.0:4000/it/docker/linux/2017/08/23/adventures-with-docker-part-2.html</link>
        <guid isPermaLink="true">http://0.0.0.0:4000/it/docker/linux/2017/08/23/adventures-with-docker-part-2.html</guid>
        
        
        <category>it</category>
        
        <category>docker</category>
        
        <category>linux</category>
        
      </item>
    
      <item>
        <title>Adventures with Docker - S01E01 - The Draft</title>
        <description>&lt;p&gt;Voici le premier d’une série d’articles sur Docker. L’idée de cette série
d’article est de servir de journal de bord d’installation, ainsi que,
comme le nom le veut suggérer, d’exploration des possibilités qu’offrent les
architectures basées sur Docker.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Vous&lt;/em&gt; pourrez, à défaut d’être le héros (le poste est déjà pris), participer
à ces aventures ! 
Vous pouvez me contacter par Google+ : 
&lt;a href=&quot;https://plus.google.com/u/0/+DenisVALDENAIRE&quot;&gt;&lt;span class=&quot;icon icon--twitter&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot;&gt;&lt;path d=&quot;M7 11v2.4h3.97c-.16 1.029-1.2 3.02-3.97 3.02-2.39 0-4.34-1.979-4.34-4.42 0-2.44 1.95-4.42 4.34-4.42 1.36 0 2.27.58 2.79 1.08l1.9-1.83c-1.22-1.14-2.8-1.83-4.69-1.83-3.87 0-7 3.13-7 7s3.13 7 7 7c4.04 0 6.721-2.84 6.721-6.84 0-.46-.051-.81-.111-1.16h-6.61zm0 0 17 2h-3v3h-2v-3h-3v-2h3v-3h2v3h3v2z&quot; fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt; &lt;span class=&quot;username&quot;&gt;DenisVALDENAIRE&lt;/span&gt;&lt;/a&gt;
,
sur Twitter :
&lt;a href=&quot;https://twitter.com/ValdenaireDenis&quot;&gt;&lt;span class=&quot;icon icon--twitter&quot;&gt;&lt;svg viewBox=&quot;0 0 16 16&quot;&gt;&lt;path fill=&quot;#828282&quot; d=&quot;M15.969,3.058c-0.586,0.26-1.217,0.436-1.878,0.515c0.675-0.405,1.194-1.045,1.438-1.809c-0.632,0.375-1.332,0.647-2.076,0.793c-0.596-0.636-1.446-1.033-2.387-1.033c-1.806,0-3.27,1.464-3.27,3.27 c0,0.256,0.029,0.506,0.085,0.745C5.163,5.404,2.753,4.102,1.14,2.124C0.859,2.607,0.698,3.168,0.698,3.767 c0,1.134,0.577,2.135,1.455,2.722C1.616,6.472,1.112,6.325,0.671,6.08c0,0.014,0,0.027,0,0.041c0,1.584,1.127,2.906,2.623,3.206 C3.02,9.402,2.731,9.442,2.433,9.442c-0.211,0-0.416-0.021-0.615-0.059c0.416,1.299,1.624,2.245,3.055,2.271 c-1.119,0.877-2.529,1.4-4.061,1.4c-0.264,0-0.524-0.015-0.78-0.046c1.447,0.928,3.166,1.469,5.013,1.469 c6.015,0,9.304-4.983,9.304-9.304c0-0.142-0.003-0.283-0.009-0.423C14.976,4.29,15.531,3.714,15.969,3.058z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;
&lt;/span&gt;&lt;span class=&quot;username&quot;&gt;ValdenaireDenis&lt;/span&gt;&lt;/a&gt;
, etc.
(voyez en bas de page les moyens de me contacter par mail ou LinkedIn)
et je ferai mon possible pour répondre à vos questions par des expérimentations,
utiliser vos suggestions, etc.&lt;/p&gt;

&lt;h3 id=&quot;introduction&quot;&gt;Introduction&lt;/h3&gt;

&lt;p&gt;J’ai décidé d’utiliser Docker pour héberger les applications que je développe.
Au moment précis où je commence, je ne sais pas du tout où je vais atterrir, bien que
j’aie tout de même un but précis en tête. Pour commencer je vais donc décrire l’architecture
cible de l’ensemble d’applications et comment elles devraient fonctionner de manière
coopérative.&lt;/p&gt;

&lt;h3 id=&quot;un-premier-brouillon-de-larchitecture&quot;&gt;Un premier brouillon de l’architecture&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/s01e01schemadraft.png&quot; alt=&quot;Brouillon d'architecture&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Les applications en question sont des applications “web”, mot fourre-tout qui signifie
qu’elles s’utilisent avec un navigateur.&lt;/p&gt;

&lt;p&gt;Il y a d’abord une base de données, qui fonctionnera sous MariaDB (une base de données
qui remplace MySQL).&lt;/p&gt;

&lt;p&gt;Les applications seront écrites en PHP; pour l’instant ce sera du MVC assez simple,
par exemple avec un framework CakePHP, mais dans le futur, j’aimerai autant qu’elles
soient en deux parties, une partie purement HTML/Javascript (avec React, par exemple),
et une partie purement API (sans doute toujours en PHP, mais pourquoi pas en javascript,
par exemple sur NodeJS avec Express ?)&lt;/p&gt;

&lt;p&gt;Une partie commune à toutes ces applications sera un composant applicatif central qui s’occupera
de gérer l’authentification, ainsi que les sessions, et sans doute d’autres services (envoyer du
mail, stocker des fichiers, etc.)&lt;/p&gt;

&lt;p&gt;D’après ce que je vois, l’accès aux applications se fera sur un serveur HTTP (à mon avis Apache,
quoique NGinX ne me semble pas mal non plus, mais je le connais peu) qui portera le certificat SSL
avec un nom de domaine unique.&lt;/p&gt;

&lt;p&gt;Derrière ce premier frontal, un routeur prendra place. Il distribuera les requètes vers
les applications.&lt;/p&gt;

&lt;h3 id=&quot;installation-de-docker-ce&quot;&gt;Installation de Docker CE&lt;/h3&gt;

&lt;p&gt;Pour commencer, je vais installer Docker sur mon portable. Je n’ai pas une machine
de course, c’est un vieux Dell Latidude E4300, avec un dual-core à 2.4GHz,
et 4 Go de RAM. Espérons que ça suffira… L’OS est Debian 9, 64 bits.&lt;/p&gt;

&lt;p&gt;Je suis donc les instructions ici :&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.docker.com/engine/installation/linux/docker-ce/debian/#install-docker-ce&quot;&gt;https://docs.docker.com/engine/installation/linux/docker-ce/debian/#install-docker-ce&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;J’installe quelques paquets, la clé du Docker repository, le repository stable, je mets
à jour la liste des paquets et j’installe docker-ce.&lt;/p&gt;

&lt;p&gt;Ça s’installe très vite, d’où je conclue que ça n’installe pas grand chose (pour l’instant).&lt;/p&gt;

&lt;p&gt;Je lance la commande de test :&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker run hello-world&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Et ça marche !&lt;/p&gt;

&lt;p&gt;Je passe par la page de post-installation; sur mon poste, j’aimerai autant pouvoir
manipuler les trucs sans être &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;root&lt;/code&gt;, on verra ensuite pour la production.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.docker.com/engine/installation/linux/linux-postinstall/&quot;&gt;https://docs.docker.com/engine/installation/linux/linux-postinstall/&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;quelques-essais&quot;&gt;Quelques essais&lt;/h3&gt;

&lt;p&gt;Je commence direct à essayer de faire tourner une de mes applis dans un docker.
Je crée un &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dockerfile&lt;/code&gt; à la racine de mon projet, contenant:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;FROM php:7.1-apache
COPY webroot/ /var/www/html/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;webroot&lt;/code&gt; étant le répertoire qui contient les sources, bien sur.&lt;/p&gt;

&lt;p&gt;Je lance le build:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker build -t saas .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Note&lt;/em&gt; : c’est pas plus mal quand on enregistre &lt;em&gt;réellement&lt;/em&gt; le Dockerfile…&lt;/p&gt;

&lt;p&gt;Le build télécharge des modèles qu’il empile les uns sur les autres. C’est assez
volumineux, quoique pour un runtime complet apache+php, ça reste normal.&lt;/p&gt;

&lt;p&gt;Je tente de le lancer :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run -d --name running-saas saas
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Il tourne. Enfin, je crois. Aucun signe. Où le joindre ? Je l’ignore.&lt;/p&gt;

&lt;p&gt;Je le stoppe avec :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker stop running-saas
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Je crois avoir trouvé un paramètre supplémentaire à passer à l’exécution:&lt;/p&gt;

&lt;pre&gt;
$ docker run -p 8080:80 -d --name running-saas saas
docker: Error response from daemon: Conflict. The container name &quot;/running-saas&quot;
is already in use by container &quot;2d8ee...73d&quot;. You have to remove (or rename)
that container to be able to reuse that name.
&lt;/pre&gt;

&lt;p&gt;Bon visiblement run, c’est pas la bonne commande quand le container existe déjà.&lt;/p&gt;

&lt;pre&gt;
docker rm running-saas
docker run -d -p 8080:80 --name running-saas saas
&lt;/pre&gt;

&lt;p&gt;Est-ce que je suis &lt;em&gt;vraiment&lt;/em&gt; obligé de l’effacer juste pour changer un paramètre ?
On dirait que oui, mais j’essaierai d’approfondir ce point plus tard.&lt;/p&gt;

&lt;p&gt;Maintenant le container est joignable sur le port 8080 de ma machine.&lt;/p&gt;

&lt;p&gt;Il y a des problèmes de configuration; l’adresse du serveur de session (c’est lui-même)
n’est pas la bonne. D’après ce que je comprends, il va falloir que je reconstruise
l’image jusqu’à arriver à un résultat.&lt;/p&gt;

&lt;p&gt;D’autre part, il n’y a pas l’extension mysqli - et c’est tout pour l’instant.&lt;/p&gt;

&lt;p&gt;Je vais avoir besoin de memcached. Je modifie donc le Dockerfile:&lt;/p&gt;

&lt;pre&gt;
FROM php:7.1-apache
COPY webroot/ /var/www/html/
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y libmemcached-dev zlib1g-dev \
    &amp;amp;&amp;amp; pecl install memcached-3.0.3 \
    &amp;amp;&amp;amp; docker-php-ext-enable memcached
RUN apt-get install -y \
            libfreetype6-dev \
            libjpeg62-turbo-dev \
            libmcrypt-dev \
            libpng12-dev \
            libmariadbd-dev \
        &amp;amp;&amp;amp; docker-php-ext-install -j$(nproc) iconv mcrypt \
        &amp;amp;&amp;amp; docker-php-ext-configure gd --with-freetype-dir=/usr/include/ \
            --with-jpeg-dir=/usr/include/ \
        &amp;amp;&amp;amp; docker-php-ext-install -j$(nproc) gd \
        &amp;amp;&amp;amp; docker-php-ext-install -j$(nproc) mysqli
&lt;/pre&gt;

&lt;p&gt;J’arrête, j’efface le docker et l’image… et je rebuilde.&lt;/p&gt;

&lt;pre&gt;
$ docker stop running-saas
$ docker rm running-saas
$ docker rmi saas
$ docker build -t saas .
&lt;/pre&gt;

&lt;p&gt;C’est long, mais au final, ça fonctionne, l’extention MySQLi est activée. 
Par contre la configuration est toujours pas la bonne.&lt;/p&gt;

&lt;h3 id=&quot;pour-la-suite&quot;&gt;Pour la suite&lt;/h3&gt;

&lt;p&gt;Comment je peux modifier en mettant juste à jour un fichier de configuration ?&lt;/p&gt;

&lt;p&gt;Je dirais qu’il faut construire d’abord un modèle avec tout ce qui va bien, 
et ENSUITE, construire des container avec les applications héritant de ces modèles.&lt;/p&gt;

&lt;p&gt;Par contre, je me demande si c’est vraiment utile d’avoir apache dans le docker ?
Est-ce que juste PHP ça pourrait pas suffire ?&lt;/p&gt;

&lt;p&gt;La réponse à ces questions (ou comment ne pas se les poser) dans l’épisode 2 :
&lt;a href=&quot;/it/docker/linux/2017/08/23/adventures-with-docker-part-2.html&quot;&gt;Adventures with Docker - Part 2&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Wed, 23 Aug 2017 09:00:00 -0500</pubDate>
        <link>http://0.0.0.0:4000/it/docker/linux/2017/08/23/adventures-with-docker-part-1.html</link>
        <guid isPermaLink="true">http://0.0.0.0:4000/it/docker/linux/2017/08/23/adventures-with-docker-part-1.html</guid>
        
        
        <category>it</category>
        
        <category>docker</category>
        
        <category>linux</category>
        
      </item>
    
      <item>
        <title>Le Ballon Ingouvernable</title>
        <description>&lt;p&gt;Le BI (prononcez Bihaille) pour Ballon Ingouvernable, est une activité ludique pouvant s’exercer sous tous les climats, mais on préfèrera un ciel ensoleillé si l’on est naturellement frileux. Il s’agit de construire un ballon volumineux (plusieurs mètres cubes) avec divers matériaux, le plastique de sac poubelle et le scotch marron à 10 sous le rouleau étant les matériaux les plus usités. Une fois l’enveloppe construite, elle est gonflée (libre choix de la méthode) à l’air (Composition approximative : 20% oxygène, 80% azote + quantités d’autres trucs), puis chauffée au soleil (le choix de la couleur du plastique a ici son importance). Le tout doit normalement prendre de l’altitude suivant une série de lois physiques encore imparfaitement maitrisées. On aura pris soin, pour éviter des complications avec l’aviation civile ou l’armée américaine (suivant l’endroit du globe ou l’on pratique), d’arrimer solidement le ballon. Celui-ci flottera alors au gré de son humeur, ou de celle du vent, ou pas du tout. Il pourra aussi se dégonfler (s’il y a des trous).&lt;/p&gt;

&lt;h3 id=&quot;a-quoi-ça-sert-&quot;&gt;A quoi ça sert ?&lt;/h3&gt;

&lt;p&gt;A rien. Si, on s’amuse. Les enfants aiment bien. Et puis aussi, si on y réfléchit, c’est toute une métaphore sur la société, etc. etc. Mais en général on réfléchit pas, on court après la ficelle. Pourtant j’avais bien dit de l’arrimer solidement.&lt;/p&gt;

&lt;h3 id=&quot;est-ce-autorisé-&quot;&gt;Est-ce autorisé ?&lt;/h3&gt;

&lt;p&gt;En tout cas, ce n’est pas encore formellement interdit. Sur le territoire où l’État français exerce le monopole de la violence, je ne suis pas au courant de lois l’interdisant. Mais tout le monde n’a pas cette chance (relative).&lt;/p&gt;

&lt;p&gt;Sur les autres territoires, je vais tenter de réunir ici les données que m’enverront les éventuels adhérents de la Fédération.&lt;/p&gt;

&lt;h3 id=&quot;une-fédération-&quot;&gt;Une Fédération ?&lt;/h3&gt;

&lt;p&gt;Je pense qu’il serait intéressant de réunir, au-delà des frontières inutiles dont nous encombrent nos dirigeants respectifs, les différentes personnes passionnées par ce type d’activités dites AHCI (À Haut Coefficient d’Inutilité). Cette fédération devrait donc dès le début être clairement internationale; je propose pour commencer les principe de fonctionnement suivants :&lt;/p&gt;

&lt;h3 id=&quot;adhésion&quot;&gt;Adhésion&lt;/h3&gt;

&lt;p&gt;L’adhésion se fait de manière spontanée et totalement informelle. Une simple revendication exprimée à voix haute, comme, par exemple, l’apostrophe suivante :&lt;/p&gt;

&lt;blockquote&gt;
  &lt;ul&gt;
    &lt;li&gt;Môssieur, surveillez votre langage, je vous prie ! J’appartiens à la FIBI !&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;en présence de témoins (des animaux peuvent éventuellement faire l’affaire faute de mieux) suffit amplement.&lt;/p&gt;

&lt;h3 id=&quot;conséquences-juridiques-de-ladhésion&quot;&gt;Conséquence(s) juridique(s) de l’adhésion&lt;/h3&gt;

&lt;p&gt;Strictement aucune. Être adhérent n’en fait pas moins de vous un être humain libre de ses mouvements, décisions et paroles. Vous pourrez, à loisir, si le coeur vous en dit, maudire allégrement l’ensemble des membres de la FIBI, ou seulement une partie (voir plus bas le paragraphe concernant les instances dirigeantes) sans être inquiété le moins du monde. Inversement, à aucun moment, la FIBI n’endossera tout ou partie de la moindre parcelle de responsabilité de vos actes.&lt;/p&gt;

&lt;h3 id=&quot;instances-dirigeantes&quot;&gt;Instances dirigeantes&lt;/h3&gt;

&lt;p&gt;Les dirigeants étant en général des baudruches, je propose de mettre à l’air un ballon coloré que nous appellerons “Monsieur le président”, et qui restera muet pendant toute la durée de son implacable règne.&lt;/p&gt;

&lt;p&gt;Par suite, tout adhérent de la FIBI peut prendre sur lui de promouvoir auprès du public de son choix cette activité ou au contraire, de cracher dans la soupe de la manière la plus éhontée qui soit. Il peut également prendre toute sorte d’engagements farfelu ou saugrenu (ou les deux à la fois) au nom de la fédération; ceux-ci seront naturellement sans aucune valeur et n’engageront que ceux qui y ont prêté une oreille attentive, et bien sur, le ballon coloré “Monsieur le président” mentionné en amont.&lt;/p&gt;
</description>
        <pubDate>Thu, 09 Jul 2009 15:13:40 -0500</pubDate>
        <link>http://0.0.0.0:4000/sports/air/2009/07/09/le-ballon-ingouvernable.html</link>
        <guid isPermaLink="true">http://0.0.0.0:4000/sports/air/2009/07/09/le-ballon-ingouvernable.html</guid>
        
        
        <category>sports</category>
        
        <category>air</category>
        
      </item>
    
      <item>
        <title>Linux ou Windows ? La liberté du loup ou le collier du chien ?</title>
        <description>&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Un linuxien réduit à sa console
Car sa carte graphique n'était pas supportée,
Rencontre un jour un windowsien égaré,
Jouant à solitaire sur l'interface multicolore.
L'humilier par demonstration de puissance,
Le linuxien l'aurait pu facilement ;
Seulement il trouvait ces combats fatiguants,
Et il prefera donc lier connaissance,
Plutôt que de l'ignorer simplement.
Le linuxien donc lui parle gentiment,
Evite les termes techniques,
Et dire qu'il trouve la déco fantastique.
&quot;Mais il ne tient pourtant qu'à vous, cher ami,
D'avoir cette interface conviviale,
Au lieu de cet austère terminal.
Laissez donc aux ancêtres tout ceci,
Venez vers le progrès, la modernité,
En un mot, les joies des nouvelles techno,
Car quoi ? Point de jeux, point de videos,
Tout à la ligne de commande ! Allez,
Faites comme moi : c'est plus facile.&quot;
Le linuxien séduit : &quot;Par quoi dois-je commencer ?
- Presque rien, dit le windowsien : accepter
un contrat de licence, se tenir tranquille ;
Fuir et dénoncer les pirates de logiciels,
Payer enfin une dîme aux fabricants,
Et pour tout le reste, comme avant,
Sauf une interface exceptionnelle,
Force jeux, et grandes distractions,
Des couleurs et des animations.&quot;
Le linuxien, affamé devant cet enoncé,
En savoure d'avance les délices,
Et cède à l'envie autant qu'à la curiosité :
Il voit déjà son écran comme un feu d'artifice.
&quot;Prêtez-moi le CD, lui dit-il - Non. - Quoi ? Non ? 
- Je ne peux pas. - Je vous demande bien pardon ?
- J'ai accepté le contrat, qui interdit le prêt,
le don, la copie et autres formes de générosité .
- Comment, dit le linuxien, mais pourtant je croyais
Que ce système était votre propriété ?
Et que vous en usiez en toute liberté !
- Pas vraiment ; mais qu'importe ?
- Il importe si bien, que de tous vos jeux,
vos programmes, surtout celui de l'écran bleu,
Je ne veux en aucune sorte,
Et ne voudrais pas même à ce prix un trésor.&quot;
Cela dit, le linuxien s'enfuit, et court encore.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Merci aux auteurs des versions originales, quels qu’ils soient ! On continue le travail…&lt;/p&gt;
</description>
        <pubDate>Mon, 23 Feb 2009 18:13:40 -0600</pubDate>
        <link>http://0.0.0.0:4000/linux/2009/02/23/le-loup-ou-le-chien.html</link>
        <guid isPermaLink="true">http://0.0.0.0:4000/linux/2009/02/23/le-loup-ou-le-chien.html</guid>
        
        
        <category>linux</category>
        
      </item>
    
      <item>
        <title>Le Chat</title>
        <description>&lt;p&gt;J’ai écrit cette nouvelle d’après le récit des péripéties d’une nuit arrosée. Ecrire des histoires dans ce genre, bon, je me suis pris pour Hemmingway, sauf que lui il était vraiment fort. Seulement j’ai pas pu resister. Finalement j’ai pas beaucoup de mérite. C’est les gens. Ils sont trop forts.&lt;/p&gt;

&lt;p&gt;La licence est la suivante :&lt;a href=&quot;http://creativecommons.org/licenses/by-nd/2.0/fr/&quot;&gt;Creative Commons Paternité - Pas de Modification ou plus simplement BY-ND&lt;/a&gt;. Le texte intégral de la license se trouve quelque part dans le lien.&lt;/p&gt;

&lt;h3 id=&quot;le-chat&quot;&gt;Le Chat&lt;/h3&gt;

&lt;p&gt;Illiah entrebailla doucement la porte d’entrée, passa la main pour allumer la lampe du hall, et rentra dans la maison, avec la main devant les yeux pour se protéger de la lumière. Derrière lui, dans l’obsurité, Erié termina sa cigarette par une longue inspiration, et jeta le mégot dans les buissons mouillés qui bordaient la maison. Au moment de refermer la porte, il entendit un miaulement rauque et plaintif qui semblait provenir du côté droit du jardin.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Illiah, attends-moi une seconde, je vais voir dans le jardin, je crois que j’entends le chat de mes parents.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Je rentre, j’ai froid. Je te sors une bière ? »&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sans répondre, Erié haussa les épaules. Une bière. Après avoir avalé une quantité pareille de vodka. Rien que la pensée n’était pas loin de lui donner envie de vomir. Il avança dans le noir en regardant vers le sol. A l’angle droit du jardin, le chat était couché sur le côté, dans l’herbe humide de la première rosée, laissant échapper de petits cris mélangés de sifflements. Illiah se pencha et lui caressa la tête.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Qu’est-ce qu’il y a, le minou ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Il n’arrivait pas l’appeler par son nom, un nom ridicule pour un chat, comme le sont tous les prénoms humains lorsqu’on les applique à des animaux. De toutes façons, celui-ci ne semblait pas pouvoir répondre ; ses yeux étaient mis-clos, (ce qui est inhabituel chez un chat, dans un jardin, vers les trois heures et demie du matin). Erié tenta de le ramasser comme il put, en formant une sorte de pelle avec ses deux mains. Le chat se débattit en miaulant, et retomba sur le sol, où il resta immobile, un peu sonné, et silencieux. Comprenant que le chat était blessé pour de bon (sans doute un choc avec une voiture, pas une de ces habituelles batailles de chats qui ne provoquent que quelques oreilles déchirées et touffes de poils arrachées), Erié se précipita vers la maison. Illiah avait terminé sa bière et commençait à s’endormir sur le canapé.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Illiah, réveilles-toi, on a un problème avec le chat !&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Hein ? Quoi ? Quel chat ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Le chat de mes parents. Il est là, dehors, dans le jardin. On dirait qu’il est blessé, et que c’est grave. Il faut l’emmener au véto.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Maintenant ? A trois heures et demie du matin ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Oui, je sais, mais il y a des urgences vétérinaires. Je ne sais pas où, mais on va appeler les renseignements, et on ira emporter le chat pour le soigner. Mes parents adorent cette bestiole. Je vais pas le laisser crever.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Ecoute, je te laisse faire, pour les renseignements, parce que je ne sais pas si je saurais me faire comprendre.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Est-ce que tu peux pendant ce temps aller près du chat ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Mais j’y connais rien en chat, moi !&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Pas besoin de connaître les chats ! Je voudrais juste être sur qu’il ne va pas se planquer quelque part.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Ah, ok, d’accord. J’y vais.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Merci, vieux.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Erié appela les renseignements, qui mirent un temps anormalement long à répondre.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Renseignements, bonjour.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Bonjour… euh, bonsoir, voilà, j’aurai aimé avoir le numéro des urgences vétérinaires.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Quelle ville, monsieur ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Paris 18e.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Veuillez patienter. (Quelques bruits de touches). Ah, voilà. Je vous mets en correspondance ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Oui, s’il vous plait.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Bonne soirée, monsieur, merci d’avoir appelé.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Une voix de synthèse un peu métallique epella le numéro deux fois, puis le composa. Après quelques sonneries, le message vocal des urgences vétérinaires débita son refrain.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Urgences vétérinaire, bonjour, ne quittez pas, un opérateur va vous rép…&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Allô, fit soudain une voix féminine très jeune, interrompant le message. Urgences vétérinaires, Annie à votre service, bonsoir.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Oui, bonjour… euh bonsoir, voilà, j’ai un problème avec un chat.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Il vous a mordu, griffé ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Non, non, ce n’est pas ça, non. Bon, écoutez, je ne sais pas trop ce qui lui est arrivé, mais je pense qu’il s’est fait accrocher par une voiture. Il est couché sur le côté, il souffle, il miaule, et je…&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Je vois. Il va falloir venir avec l’animal, monsieur.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;C’est un chat, reprit Erié. Comment fait-on pour le transporter ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Vous n’avez pas un panier ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Non.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Alors trouvez un carton. Il saigne ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Je ne sais pas trop. Il est dans le jardin, il fait sombre. Je… Je n’ose pas le porter.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Faîtes très attention. Prenez un grand carton. Venez vite avec lui.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;D’accord, nous arrivons. Où c’est que vous vous situez ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Rue des Abesses. Au 17.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;D’accord. Je pense qu’à pied, on en aura pour un quart d’heure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Erié salua, raccrocha, et sortit dans le jardin. Il faisait noir, d’autant plus que ces yeux s’était habitués à la clarté de l’intérieur. Le chat était toujours à sa place. Sa respiration sifflait plus fort. Illiah n’était pas à côté, par contre.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Illiah ! appela-t-il.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Oui, répondit une voix semblant venir du fond du jardin.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Mais qu’est-ce que tu fais ? Tu devais pas venir voir le chat ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Oui, ben, je le cherche, ton chat. Si tu crois que c’est facile, en pleine nuit !&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Viens par là. Ici. Voilà. Il est là, tu le vois ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Là, oui. Oulah, effectivement, il a pas l’air bien du tout, le pauvre.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Je vais chercher un carton, dit Erié. On va le mettre dedans. J’ai dit à la clinique vétérinaire qu’on arrivait dans un quart d’heure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;C’est où, ta clinique ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Rue des Abesses. C’est pas très loin.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Bon, d’accord. Je t’attends, mais fait vite, j’ai froid.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Erié se précipita en direction de la maison. Il fut encore une fois aveuglé par la lampe de l’entrée. Comme il avait bu, ses pupilles semblaient avoir encore plus de mal à se réadapter. Dans un placard en face de la porte d’entrée, il trouva un carton blanc qui avait du contenir une cototte-minute, s’il fallait en croire la décoration. Le fond ne semblait pas très solide, mais il n’avait rien d’autre sous la main, et pas le temps de faire beaucoup mieux pour l’instant. Le carton dans les bras, il sortit dans le jardin et retrouva Illiah en train de fumer à côté du chat.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Bon, aides-moi à le mettre dans le carton. Attention, il risque de se débattre.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;D’accord. Comment on le prend ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Prend le devant, et moi je soulève l’arrière train. Fais doucement.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ils le soulevèrent délicatement et le posèrent au fond du carton. Le chat semblait un peu assomé, mais pas endormi pour autant.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Je commence à le porter pour l’instant, dit Erié, comme ils quittaient la maison. Mais on se relaiera. Le fond n’est pas très solide, il faut le soutenir avec les mains, tu feras gaffe, d’accord ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Oui, ok, soutenir le fond, d’accord, acquiesca Illiah.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Les rues étaient désertes, sans aucune circulation. Ils marchèrent sans parler, portant le carton tour à tour, sur les avant bras. On voyait que le fond se teintait du sang qui traversait l’épaisseur.&lt;/p&gt;

&lt;p&gt;Devant la station de métro des Abesses, il firent une courte pause pour fumer une cigarette. Sous le porche d’entrée, quelques clochards dormaient.&lt;/p&gt;

&lt;p&gt;Arrivés devant les urgences, un panneau indiquait : “Sonnez et entrez”. Illiah, qui avait les mains libres, sonna, mais la porte ne s’ouvrit pas comme il s’y attendait. Il commença à forcer en appuyant sur la porte avec son épaule.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Attendez, ne forcez pas, c’est fermé, dit une voix derrière la porte.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Ah, d’accord, dit Illiah en se reculant.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Que désirez-vous ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Nous avons un chat, avec nous, qui… j’ai appelé tout-à-l’heure. Ca doit faire à peine dix minutes. J’ai prévenu.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Ah, oui, je me souviens. Je vous ouvre.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;La jeune fille qui ouvrit la porte semblait n’avoir même pas quinze ans. Elle était petite et frèle. Ses cheveux bruns sur son visage pâle accentuait encore cet air d’adolescente. Elle tenait un café dans les mains.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Vous êtes le vétérinaire de garde ? lui demanda Erié, regrettant presqu’aussitôt cette question.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Oui, c’est moi. Je vous semble un peu trop jeune ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Oui, c’est-à-dire, non, enfin, ne le prenez pas mal, balbutia Erié. C’est vrai que vous semblez toute jeune.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Je suis le docteur Annie Dessois, j’ai mon diplôme de vétérinaire, et j’ai plus de cinq ans de métier. Suivez-moi, s’il vous plaît.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Elle semblait un peu vexée. Il y eut un court silence pendant qu’ils la suivaient, dans un couloir ; puis ils arrivèrent enfin dans une pièce qui semblait être une salle d’opération. La table d’examen elle-même était en inox, et en son centre, la tôle était percée de très nombreux trous. Sur le côté, un amoncellement d’outils qui faisait ressembler l’ensemble à l’établi d’un menuisier. Annie posa son café brulant sur le petit bureau au fond de la pièce.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Je suis désolé, dit alors Erié, en posant le carton sur la table. Je ne voulais pas être malpoli, ni vous faire de la peine.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Il se sentait soudain plein d’admiration pour ce petit bout de femme qui faisait des gardes nocturnes, s’exposant à la rencontre de drôles d’individus.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;N’en parlons plus, lui répondit la jeune vétérinaire. Elle souriait à présent. Aidez-moi à mettre l’animal sur la table, je vais l’examiner.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;C’est un chat », murmura Erié, mais elle ne semblait pas l’entendre.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Pourquoi s’obstinait-elle à dire “l’animal”, et non pas “le chat”, se demanda-t-il alors. Il lui présenta le carton en l’inclinant, et elle se saisit du chat avec des gestes qui montraient assez qu’elle avait l’habitude des animaux. Du bout de ses doigts gantés, elle lui palpa l’ensemble du corps, sans que le chat ne laisse échapper le moindre miaulement de douleur. Le sourire avait disparu du visage d’Annie, pour laisser la place à une mine concentrée, puis inquiète. Quand elle releva la tête, ses yeux étaient brillants, comme si elle retenait ses larmes.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Bon, je… on dirait qu’il a été heurté par une voiture, effectivement. Il y a des nombreuses fractures par tout le corps. Cela dit, il y a des chances, très faibles, mais des chances, pour qu’il s’en sorte. Je vais lui faire une injection de calmant pour qu’il se tienne tranquille et que celà favorise sa guérison, si possible.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Une injection ? demanda Erié.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Une piqûre, oui. Il faudra que vous m’aidiez à le tenir pendant que je lui fait cette piqûre. Il risque de se débattre.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Ecoutez, je suis vraiment désolé, vous allez me trouver ridicule, mais je ne supporte pas la vue des aiguilles. J’ai peur de tourner de l’oeil. Je suis très fatigué, et… nous revenons d’une soirée assez…&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Votre collègue peut vous remplacer ? lui demanda-t-elle, le coupant dans son explication.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Mon collègue ? Ah, oui, Illiah ! Je vais le voir.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Illiah n’était pas entré dans la pièce avec eux. Il s’était vautré sur un des bancs de la pièce attenante, une sorte de salle d’attente. Il semblait dormir. Erié le secoua doucement par l’épaule.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Illiah ! Tu dors ? lui demanda-t-il.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Plus maintenant, marmonna Illiah, en clignant des yeux. Qu’est-ce qu’il y a, c’est fini ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Euh, non, au contraire. Il faudrait que tu aides la vétérinaire.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Quoi ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Oui, elle a besoin de quelqu’un pour lui tenir le chat pendant qu’elle lui fait une piqûre. Tu sais que je ne peux par supporter ces trucs-là, et même pas la vue d’une aiguille.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Bon, d’accord, pas de problème, répondit Illiah. Il étira les jambes, puis les bras, comme pour se dégourdir, puis se leva. Tu restes ici ? demanda-t-il à Erié, comme celui-ci s’asseyait.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Oui, viens me chercher dès que c’est fini.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Pas de problème », dit encore Illiah.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Il passa dans la salle d’opérations. Le chat ne bougeait presque plus, seul son souffle, sur un rythme irrégulier, lui soulevait la poitrine en faisant un sifflement effrayant. La vétérinaire sortit une seringue de son étui, et y fixa un longue aiguille. « Cette aiguille est tellement longue, pense Illiah, qu’elle pourrait traverser le chat. » pensa Illiah, en frissonant à cette pensée. La vétérinaire prit le chat et lui fit faire demi-tour, en le laissant couché sur le côté.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Bon, voilà, c’est assez simple, lui dit-elle. Vous allez devoir lui tenir la tête, vous comprenez ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Pas de problèmes, répondit Illiah.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Allez-y, dites moi quand vous le tenez bien.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Illiah posa les mains sur le cou et sur la tête du chat, et appuya fermement sur la table. Le chat ne pouvait plus bouger.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Je crois que c’est bon, maintenant, dit-il à la vétérinaire.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Celle-ci appliqua l’aiguille de la seringue dans le haut de la cuisse, et la fit rentrer d’un centimètre ou deux. Elle pressa le haut de la seringue pour faire rentrer le liquide ; soit qu’elle touchat un point sensible, soit que Illiah fit un faux mouvement, en appuyant trop fort sur le chat, par exemple, le chat miaula de douleur et cracha devant lui, ce qui surprit beaucoup Illiah. Il relachat sa prise, et le chat, dans un dernier effort, se dégagea complêtement, et lui mordit l’index, enlevant un petit morceau de peau. Le sang se mit à couler de la blessure.&lt;/p&gt;

&lt;p&gt;Annie avait ôté la seringue de la cuisse du chat dès le premier mouvement, de peur que l’aiguille ne casse dans le muscle. L’animal avait retrouvé un peu de vigueur, et, se sentant libre, sauta de la table sur le sol. Il avait présumé de ses forces, car ses pattes se dérobèrent sous son poids, au lieu de le soutenir, et se replièrent sous lui, le faisant s’effondrer lourdement sur le côté en poussant de petits miaulements à peine audibles. Tout s’était passé assez vite, et Annie reprit ses esprits la première. Elle prit la main d’Illiah pour la regarder.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Il vous a mordu, non ? Faites-moi voir un peu ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Oui, au doigt.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Aïe ! C’est bien abîmé ! Ca vous fait mal ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Non, pas tellement.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Ne bougez pas, je vais chercher un désinfectant et des compresses. Nous allons faire un bon pansement pour éviter de mettre du sang partout.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Je veux bien, répondit Illiah.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cette dernière remarque lui sembla un peu curieuse. Il y avait du sang un peu partout, déjà. Le chat avait saigné sur le sol et les empreintes rouges de ses pattes traçait un chemin sanglant. Annie était partie dans la pièce adjacente, une sorte de petite réserve. Illiah ne savait pas trop quoi faire, avec son doigt qui saignait. Les doigts saignent beaucoup et celui-là comme tout les autres. Quelques gouttes étaient tombées sur le sol ; la vétérinaire revint avec une bouteille d’un liquide transparent qui semblait être de l’alcool, une boîte de compresses stériles et un rouleau de sparadrap.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Bien, dit-elle, je vais vous désinfecter. Ca risque de faire assez mal, mais pour une morsure comme celle-là, il vaut mieux désinfecter correctement.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;C’est vrai », dit Illiah.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Il commençait à avoir un peu mal, et tout ce sang, sans parler de l’odeur des désinfectants, commençait à le mettre mal-à-l’aise. Annie semblait assez peu dans son élément. Elle soignait plutôt des animaux, pas des humains. En désinfectant la blessure, elle avait saisi le poignet d’Illiah et le serrait pour le maintenir comme s’il allait se sauver. Elle n’avait pas une poigne extraordinaire, mais ses ongles commençaient à rentrer dans la peau du bras. Illiah grimaça de douleur ; elle le remarqua et dessera immédiatement son étreinte.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Je suis désolée, je vous ai fait mal, dit-elle.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Ce n’est rien », lui dit-il.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Il aurait vraiment voulu lui sourire, mais n’en avait plus vraiment la force. Annie acheva de désinfecter le doigt sans même tenir le poignet. Puis, au moment de confectionner le pansement, elle se souvint qu’il y en avait peut-être une boîte dans une des armoires de la réserve, qui conviendraient mieux pour cet usage.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Je reviens de suite, dit-elle, ne bougez pas.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Alors qu’elle quittait la pièce, le chat, qui jusque-là n’avait plus fait d’autres bruits que ceux de sa difficile respiration, commençat à râler, toujours allongé sur le côté. Illiah pressentit qu’il n’allait pas tarder à mourir. Son corps était parcouru de tremblements, comme s’il avait le hoquet. Ses râles devinrent de plus en plus faibles, et soudain, sur un dernier un peu plus long que les autres, ils cessèrent complêtement. C’était fini. Illiah n’aimait pas voir souffrir les animaux, et il lui semblait avoir retenu son souffle, sans pouvoir rien faire d’autre, pendant toute l’agonie du chat. Il inspira alors une grande bouchée d’air. Mais ce qu’il croyait être de l’air frais était chargé de milles odeurs qui lui montèrent à la tête. Ses yeux tombèrent alors sur l’extrémité de son index, qui avait recommencé de saigner, et la fatigue, l’alcool, les désinfectants, la blessure, la mort du chat à laquelle il venait d’assister, firent leur effet. Comme la vétérinaire rentrait de nouveau dans la pièce, il s’effondra sur le sol. Elle ne put le rattraper à temps, (il faisait sans doute le double de son poids) mais empêcha au moins sa tête de heurter le sol. Elle prit son pouls, qui était calme. Elle écouta sa respiration, qui était régulière et profonde. Il dormait.&lt;/p&gt;

&lt;p&gt;Elle le plaça sur le côté, de manière à prévenir le risque d’étouffement, s’il venait à vomir. Son regard tomba alors sur le cadavre du chat, dont la position ne laissait aucun doute quant à son état. Elle ouvrit un tiroir et y prit un grand sac de plastique noir. Délicatement, elle souleva l’animal et le déposa dans le sac, qu’elle ferma. Après l’avoir posé dans un coin de la salle, elle jeta un coup d’oeil dans la salle d’attente. Erié, allongé sur un des bancs, dormait, comme elle s’y attendait.&lt;/p&gt;

&lt;p&gt;Rien ne pressait, après tout. C’était le premier appel de la nuit, et ce serait sans doute le dernier. Elle passa un rapide coup de chiffon sur la table et sur le sol, plia et jeta le carton plein de sang. Elle vint s’assoir derrière un petit pupitre qui occupait le coin de la salle d’opération. Elle ouvrit le cahier de la main courante, tira un trait rouge et inscrivit en formules vagues le compte-rendu de l’intervention. Elle le relut, rajouta quelques accents, le signa, et tira un second trait rouge.&lt;/p&gt;

&lt;p&gt;Elle s’était servi un café, et il était resté sur le bureau. Elle porta la tasse à ses lèvres. Le liquide était froid comme la nuit.&lt;/p&gt;

&lt;h4 id=&quot;entre-paris-et-rambouillet-le-2-août-2004&quot;&gt;Entre Paris et Rambouillet, le 2 Août 2004.&lt;/h4&gt;
</description>
        <pubDate>Thu, 22 Feb 2007 16:56:00 -0600</pubDate>
        <link>http://0.0.0.0:4000/litterature/nouvelles/2007/02/22/le-chat.html</link>
        <guid isPermaLink="true">http://0.0.0.0:4000/litterature/nouvelles/2007/02/22/le-chat.html</guid>
        
        
        <category>litterature</category>
        
        <category>nouvelles</category>
        
      </item>
    
      <item>
        <title>Le Vieux Programmeur</title>
        <description>&lt;p&gt;J’ai découvert ce texte au hasard d’une signature, dans un message sur &lt;a href=&quot;http://slashdot.org/&quot;&gt;./ - Slashdot.org&lt;/a&gt; : “In Software Engineering, You Are Supposed To Go Mad !” - J’ai traduit ce court texte en français. Quel intérêt ? Aucun, c’est juste pour le plaisir. Commentaires, corrections, et surtout, autres anecdotes bienvenues !!!&lt;/p&gt;

&lt;h3 id=&quot;le-vieux-programmeur&quot;&gt;LE VIEUX PROGRAMMEUR&lt;/h3&gt;

&lt;p&gt;Ce texte est de Targo Tennisberg, la version originale se trouvait ici : &lt;a href=&quot;http://www.targotennisberg.org/kirjad/theoldprogrammer.htm&quot;&gt;The Old Programmer&lt;/a&gt; - mais le temps passe !&lt;/p&gt;

&lt;h4 id=&quot;introduction&quot;&gt;Introduction&lt;/h4&gt;

&lt;p&gt;Cette histoire a été écrite, à l’origine, en Janvier 1998, à Taru, en Estonie. Elle est basée sur le folklore estonien et sur la vie de tous les jours dans la société où je travaillais à l’époque. Nous faisions beaucoup d’analyse système, d’où les mentions des outils et des diagrammes CASE(1). C’était une rude compagnie, pas du genre de ce qu’on peut voir dans les sociétés de nos jours, et nous passions vraiment des nuits dans des sacs de couchage de façon assez régulière. Et nous avons réellement eu un gars qui donnait des décharges électriques à ses collègues. Je pourrai vous raconter de longues histoires à ce sujet…&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Le nouveau gars fut amené dans la société à la tombée de la nuit.&lt;/p&gt;

&lt;p&gt;“Alors, ils t’ont fait signer,” lui dit un programmeur, “Tu devais certainement être bourré, comme nous tous. Qui viendrait dans une société pareille en étant à jeun !”&lt;/p&gt;

&lt;p&gt;“Tu sais ce qu’il est arrivé au gars qui avait ta place avant ?” lui demanda un autre. “Le chef de projet l’a tué. Il l’a tabassé et tué sur place. Beaucoup ont trouvé la mort par ses poings.”&lt;/p&gt;

&lt;p&gt;“Ouais, c’est comme ça que ça doit se passer, non ?” dit alors le nouveau, calmement. “Qui a déjà vu une société où l’on ne tuait pas ! Je suis un vieux hacker, j’ai testé tous les systèmes d’exploitation moi-même, et il y a toujours eu des bagarres. Dans le temps, les hommes étaient de vrais hommes ! Tout le monde se promenait avec des cables haute tension dans les mains, électrocutant les autres chaque fois qu’ils le pouvaient !&lt;/p&gt;

&lt;p&gt;J’ai été le dernier a rester vivant, j’ai fini le projet, et j’ai été pris pour un nouveau boulot. Ouais, je les connais, les usages de l’ingénierie logicielle !”&lt;/p&gt;

&lt;p&gt;Et il se glissa dans son sac de couchage et s’endormi aussitôt, en ronflant bruyamment.&lt;/p&gt;

&lt;p&gt;Le lendemain matin, le chef de projet demanda à voir le nouveau.&lt;/p&gt;

&lt;p&gt;“Où se trouve-t-il ?” demanda-t-il à l’analyste. “Je vais lui expliquer les règles qui ont cours dans cette société.”&lt;/p&gt;

&lt;p&gt;L’analyste rougit et baissa les yeux.&lt;/p&gt;

&lt;p&gt;“Ben… comment dire… il est sur IRC (2)… je lui ai dit, mais…”&lt;/p&gt;

&lt;p&gt;“QUOI !!” hurla le chef de projet. “A rien foutre ! Mais il croit que cette société est une maison de repos ou quoi ! Par mille pointeurs non initialisés ! Montrez-moi où il est !”&lt;/p&gt;

&lt;p&gt;Le programmeur était effectivement sur IRC, et rigolait fortement de temps à autre.&lt;/p&gt;

&lt;p&gt;En voyant le chef de projet, il sourit d’un air un peu endormi.&lt;/p&gt;

&lt;p&gt;“Je me remémorais le temps où j’étais jeune,” lui dit-il. “Les hommes étaient faits d’acier pur. Il y a eu des tonnes de fois où j’ai du trouver des bugs dans le code machine brut. Un jour j’en ai trouvé un vraiment récalcitrant. Bon dieu ! Trois fois j’ai du tout recompiler. Mais finalement, j’ai été son pire cauchemar !”&lt;/p&gt;

&lt;p&gt;“Quoi !” dit le chef de projet. “Mais comment oses-tu raconter ces histoires, espèce de malotru !”&lt;/p&gt;

&lt;p&gt;“Eh là !” dit le vieux programmeur en colère, “Tu vas commencer par la fermer, quand tu parles à un vieux hacker. Je n’ai pas encore fini. Il y a eu une autre fois où on a du patcher un moteur de base de données. Tout le groupe est devenu fou à cause de ça, mais finalement, on a fini par trouver un enregistrement interressant. “Viens là, vieille carne” que j’ai dit à la base de donnée, “Maintenant, je vais te réduire en pièces !” C’etait un moment dont je me souviendrai toujours.”&lt;/p&gt;

&lt;p&gt;Le chef de projet écoutait et devenait fou de rage.&lt;/p&gt;

&lt;p&gt;“Mais tu sais à qui tu parles !” cria-t-il. “Tu parles à ton chef de projet en personne !”&lt;/p&gt;

&lt;p&gt;“Tu n’es pas un chef de projet ni quoi que ce soit.” lui répondit le vieux programmeur. “J’ai vu des chefs dans ton genre un sacré paquet de fois par le passé. Tu sais, à l’époque, on avait des vrais chefs de projets. Ils bondissaient dans les couloirs, les yeux exorbités, et juraient tant et si bien que toutes les machines subissaient des erreurs de protection générale. Les petits bricolos dans ton genre n’avaient même pas le droit de venir au même étage, parce que cela portait malheur. File dans ton bureau et restes-y ; je vais tout réparer ici. Je connais le C++ comme ma poche.”&lt;/p&gt;

&lt;p&gt;“Eh, toi, là,” dit-il à l’analyste. “Combien d’outils de CASE tu utilises ?”&lt;/p&gt;

&lt;p&gt;“Un,” répondit l’analyste, perplexe.&lt;/p&gt;

&lt;p&gt;Le vieux programmeur hocha la tête.&lt;/p&gt;

&lt;p&gt;“Qui a jamais vu une chose pareille !” dit-il. “Il doit y avoir au moins sept outils de CASE différents et tous les diagrammes doivent être refaits douze fois chaque jour. C’était toujours comme ça, dans le bon vieux temps. Envoyez tous les hommes au dessin !”&lt;/p&gt;

&lt;p&gt;“Je pense que…” commença le chef de projet confus, mais le vieux programmeur lui ordonna de fermer son clapet.&lt;/p&gt;

&lt;p&gt;Puis il utilisa rapidement toute la place disque disponible sur le serveur de fichier et ordonna au DF (3) d’acheter du nouveau matériel.&lt;/p&gt;

&lt;p&gt;“De cette façon, nous n’allons pas tarder à manquer d’argent,” osa dire le DF.&lt;/p&gt;

&lt;p&gt;“Les sociétés ne sont pas sensées avoir autant d’argent,” répondit fermement le vieux programmeur. “A l’époque, quand je codais sur FreeBSD, personne n’a été payé le moindre centime pendant sept mois. Mais qu’est-ce que vous tous connaissez à l’ingénierie logicielle ?”&lt;/p&gt;

&lt;p&gt;“A qui achetez-vous le logiciel ?” demanda-t-il alors à l’analyste.&lt;/p&gt;

&lt;p&gt;“Microsoft.” répondit celui-ci.&lt;/p&gt;

&lt;p&gt;“Effacez-moi ça sur le champ,” dit le vieux hacker. “Qui a jamais vu quelqu’un utiliser du logiciel de chez Microsoft ! Il est clair que cela ne peut apporter que de funestes augures. Tous les vieux programmeurs savent que les logiciels Microsoft sont peuplés d’esprits malins. Enlevez-moi ces logiciels des disques durs, vite ! Je me souviens d’un cas où un autre de ces stupides chefs de projet avait dit aux gars d’installer des logiciels Microsoft sur leurs ordinateurs. Les démons se sont glissés hors des ordinateurs, et ont sucé le sang des gars dans leurs sacs de couchage, la nuit. Je suis le seul qui s’en soit sorti vivant.”&lt;/p&gt;

&lt;p&gt;Les employés, horrifiés, reformatèrent tous les disques durs.&lt;/p&gt;

&lt;p&gt;“Bon dieu de bon dieu !” cria le chef de projet, en état de choc. “Maintenant nous ne pourrons plus tenir les délais ! Je deviens fou !”&lt;/p&gt;

&lt;p&gt;“Dans l’ingénierie logicielle, vous êtes supposés devenir fous, il n’y a pas d’autre choix,” acquiesca le vieux programmeur. “Dans le bon vieux temps, on a eu un cas où tous les gars de la société sont devenus fous.”&lt;/p&gt;

&lt;p&gt;Le chef de projet gémit et s’échappa vers son bureau.&lt;/p&gt;

&lt;p&gt;Mais le vieux programmeur tournait alentour et finalement trouva le bureau de l’administrateur système.&lt;/p&gt;

&lt;p&gt;“Le serveur ne marche pas correctement.” dit-il après un moment de réflexion.&lt;/p&gt;

&lt;p&gt;“J’ai une interface graphique ici qui surveille le serveur,” repondit l’administrateur.&lt;/p&gt;

&lt;p&gt;Le vieux hacker soupira.&lt;/p&gt;

&lt;p&gt;“Alors, c’est ça,” dit-il. “Une interface graphique ! Qui a jamais vu une chose pareille ! Personne n’avait d’interface graphique dans le bon vieux temps. La ligne de commande, c’est tout ce dont un hacker a besoin. Les interfaces graphiques ne font que d’induire les gens en erreur.”&lt;/p&gt;

&lt;p&gt;Il poussa l’administrateur, effaça X-Window, et, fatigué par son dur labeur, alla se coucher.&lt;/p&gt;

&lt;p&gt;Quelques instant plus tard, la secrétaire avertit tout le monde qu’il allait y avoir une coupure de courant. Le chef de projet, dont le visage indiquait que les dernières heures n’avaient pas été des plus faciles, sortit de son bureau et dit à tout le monde d’enregistrer son travail.&lt;/p&gt;

&lt;p&gt;“Foutaises !” s’exclama le vieux programmeur, “Qu’il en soit ainsi ! Fermez tous vos clapets, je m’occupe des données !”&lt;/p&gt;

&lt;p&gt;Bien sur, le courant fut coupé et les données furent détruites.&lt;/p&gt;

&lt;p&gt;La société collaborait avec Microsoft, et des représentants de Microsoft devaient vérifier l’état d’avancement du projet dans la soirée.&lt;/p&gt;

&lt;p&gt;Mais l’administrateur ne put faire son travail, et les données ne purent être restorées. Les gens de chez Microsoft étaient très en colère.&lt;/p&gt;

&lt;p&gt;“Maintenant, nous allons droit à la faillite,” se plaignit le PDG.&lt;/p&gt;

&lt;p&gt;“Une société est destinée à la faillite,” dit le vieux programmeur, ne départissant pas de son calme. “Il n’y a pas de société qui ne fasse pas faillite ! Aussi longtemps que j’ai bossé dans ce secteur, les sociétés l’ont fait. Dans le bon vieux temps…”&lt;/p&gt;

&lt;p&gt;Mais il ne put finir, car une troupe d’avocats de Microsoft arrivèrent à l’instant, s’emparèrent de lui et des autres, et les amenèrent à Bill Gates en personne.&lt;/p&gt;

&lt;p&gt;Bill était assis sur son trône, resplendissant de dignité, et ajustait ses lunettes rondes.&lt;/p&gt;

&lt;p&gt;“Rien de mauvais ne vous arrivera ici,” dit-il. “Vous allez juste devenir des codeurs sur mes projets, comme tout ceux qui ont mis les pieds dans mon domaine. A partir de maintenant vous serez tous mes serviteurs !”&lt;/p&gt;

&lt;p&gt;Il y eu un moment de silence, et immédiatement après ils purent entendre la voix du vieux programmeur qui patchait MS Word. “Qui a jamais vu tourner Word sur un PC ? Word doit tourner sur des mainframes !”&lt;/p&gt;

&lt;p&gt;“Ce ne serait pas le Vieux Programmeur ?” s’exclama Bill, clairement effrayé. “De nouveau ici !”&lt;/p&gt;

&lt;p&gt;“Oui, c’est moi,” repondit le hacker. “Et pourquoi diable avez-vous des lunettes rondes ? Elles ont toujours été rectangulaires.”&lt;/p&gt;

&lt;p&gt;“Emportez-le loin d’ici !” cria Bill. “Vite !!!”&lt;/p&gt;

&lt;p&gt;Et les avocats emportèrent le Vieux Programmeur au loin.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Le jour d’après, comme il était assis devant un terminal et jouait au MUD (Multi-Users Dongeons, jeux de rôles), deux hommes rentrèrent dans la pièce.&lt;/p&gt;

&lt;p&gt;“Notre société a besoin d’un programmeur expérimenté !” cria l’un deux.&lt;/p&gt;

&lt;p&gt;“Me voici,” répondit le Vieux Programmeur, et il sortit avec les deux hommes.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;(1) CASE : Computer Assisted Software Engineering - outil de mise au point de logiciels.&lt;/p&gt;

&lt;p&gt;(2) IRC : Internet Relay Chat - serveur permettant des conversations en mode interactif.&lt;/p&gt;

&lt;p&gt;(3) DF : Directeur Financier.&lt;/p&gt;
</description>
        <pubDate>Thu, 22 Feb 2007 14:13:40 -0600</pubDate>
        <link>http://0.0.0.0:4000/litterature/traductions/2007/02/22/le-vieux-programmeur.html</link>
        <guid isPermaLink="true">http://0.0.0.0:4000/litterature/traductions/2007/02/22/le-vieux-programmeur.html</guid>
        
        
        <category>litterature</category>
        
        <category>traductions</category>
        
      </item>
    
  </channel>
</rss>
