<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Xylphid]]></title><description><![CDATA[Expression de la pensée informatique]]></description><link>https://blog.xylphid.net/</link><image><url>https://blog.xylphid.net/favicon.png</url><title>Xylphid</title><link>https://blog.xylphid.net/</link></image><generator>Ghost 3.31</generator><lastBuildDate>Thu, 09 Apr 2026 03:47:30 GMT</lastBuildDate><atom:link href="https://blog.xylphid.net/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Automatiser le déploiement d'une application Docker]]></title><description><![CDATA[<p>Dans un précédent post, nous avons vu comment <a href="https://blog.xylphid.net/mettre-en-place-lintegration-continue-avec-gitlab/">mettre en place l'intégration continue sur un projet web</a> Dans ce second post, nous allons voir comment faire en sorte que notre projet soit automatiquement déployé sur une infrastructure de type Swarm.</p><!--kg-card-begin: markdown--><h1 id="contexte">Contexte</h1>
<p>Pour rappel, dans le précédent article sur le sujet</p>]]></description><link>https://blog.xylphid.net/automatiser-le-deploiement-dune-application-docker/</link><guid isPermaLink="false">5d0b46003cd3bd0001cdfb16</guid><category><![CDATA[Docker]]></category><category><![CDATA[CI/CD]]></category><category><![CDATA[Swarm]]></category><dc:creator><![CDATA[Anthony PERIQUET]]></dc:creator><pubDate>Wed, 26 Jun 2019 14:51:14 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1518331434749-e36149620126?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1518331434749-e36149620126?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ" alt="Automatiser le déploiement d'une application Docker"><p>Dans un précédent post, nous avons vu comment <a href="https://blog.xylphid.net/mettre-en-place-lintegration-continue-avec-gitlab/">mettre en place l'intégration continue sur un projet web</a> Dans ce second post, nous allons voir comment faire en sorte que notre projet soit automatiquement déployé sur une infrastructure de type Swarm.</p><!--kg-card-begin: markdown--><h1 id="contexte">Contexte</h1>
<p>Pour rappel, dans le précédent article sur le sujet nous avions configuré un projet dans l'objectif de mettre en place une intégration continue. A chaque commit, le <code>runner</code> générait une image docker de notre projet et la poussait sur un registry docker (public ou privé).<br>
Pour cela nous avions utilisé le fichier de configuration <code>gitlab-ci.yml</code> qui permet de décrire les différentes étapes de notre intégration continue.</p>
<p>C'est ce même fichier que nous allons éditer et compléter afin de faire en sorte que notre image, fraichement buildée, puisse être déployée sur un environnement de type <a href="https://docs.docker.com/engine/swarm/">Docker Swarm</a></p>
<p>Pour rappel nous utilisions le fichier <code>gitlab-ci.yml</code> suivant :</p>
<pre><code class="language-yml">image: docker:stable

variables:
  DOCKER_HOST: tcp://docker:2376/
  DOCKER_DRIVER: overlay2

services:
  - docker:dind

stages:
  - package

docker-build:
  stage: package
  script:
    - docker login -u gitlab -p ${CI_REGISTRY_TOKEN} ${CI_REGISTRY_HOST}
    - docker build --build-arg VERSION=${CI_COMMIT_TAG:-nightly} -t ${CI_REGISTRY_HOST}/${CI_BUILD_IMAGE}:${CI_COMMIT_TAG:-nightly} .
    - docker push ${CI_REGISTRY_HOST}/${CI_BUILD_IMAGE}:${CI_COMMIT_TAG:-nightly}
  tags:
    - docker
</code></pre>
<h1 id="livraisoncontinue">Livraison continue</h1>
<h2 id="principe">Principe</h2>
<p>Dans notre contexte actuel, la livraison continue n'est pas vraiment différente de l'intégration continue dans le sens où nous souhaitons faire exécuter des commandes <code>docker</code> à un <code>docker-engine</code>. La seule différence est que nous souhaitons faire exécuter ces commandes sur un <code>docker-engine</code> particulier (celui de notre Swarm).</p>
<p>Afin que cela soit possible, plusieurs solutions s'offrent à nous :</p>
<ul>
<li>Faire en sorte que notre <code>runner</code> se connecte à distance au <code>docker-engine</code> de notre Swarm.</li>
<li>Mettre en place un runner directement dans notre Swarm afin de pouvoir intéragir directement avec le <code>docker-engine</code></li>
</ul>
<p>Le mode de déploiement peut quand à lui se faire service par service ou via une stack, définie par le biais d'un fichier compose. C'est d'ailleurs cette dernière solution que nous choisirons par la suite car elle permet d'utiliser la même commande que ce soit pour un premier déploiement ou pour une mise à jour.</p>
<p><img src="https://images.unsplash.com/photo-1544816565-aa8c1166648f?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" alt="Automatiser le déploiement d'une application Docker"><br>
<small>Photo by <a href="https://unsplash.com/@jamesponddotco?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit">James Pond</a> / <a href="https://unsplash.com/?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit">Unsplash</a></small></p>
<h2 id="ajoutdunrunner">Ajout d'un runner</h2>
<p>Nous allons choisir d'ajouter un <code>runner</code> dans notre swarm. Il est important qu'il soit déployé sur un manager car, pour rappel, seuls les managers sont habilités à déployer de nouveaux services sur les nodes qui composent le Swarm.</p>
<p>Pour cela nous allons reprendre la configuration et le référencement d'un runner que nous avons vu dans le précédent article (en version plus concise).<br>
Créons dans un premier temps un script d'enregistrerment <code>register.sh</code> :</p>
<pre><code class="language-bash">#!/bin/sh

docker run --rm -it \
    -v $(pwd)/config:/etc/gitlab-runner:rw \
    -v /var/run/docker.sock:/var/run/docker.sock \
    gitlab/gitlab-runner:latest register \
    --non-interactive \
    --executor &quot;docker&quot; \
    --docker-image alpine:3.8 \
    --docker-privileged \
    --url &quot;https://&lt;GITLAB_INSTANCE&gt;/&quot; \
    --registration-token &quot;&lt;GITLAB_TOKEN&gt;&quot; \
    --description &quot;docker-runner-manager&quot; \
    --tag-list &quot;docker,manager,swarm&quot; \
    --run-untagged \
    --locked=&quot;false&quot;
</code></pre>
<p>Comme dans le précédent article, nous utlisons l'URL <code>GITLAB_URL</code> de notre gitlab et le token <code>GITLAB_TOKEN</code> de notre projet.<br>
Nous avons ajouté deux nouveaux tag (<code>manager</code> et <code>swarm</code>) qui nous seront utiles par la suite afin de cibler le bon runner, en l'occurence celui qui est un <code>manager</code> dans notre <code>swarm</code>.</p>
<p>Voici un petit rappel sur la méthode utilisée pour lancer le runner après sa configuration :</p>
<pre><code class="language-bash">#!/bin/sh

docker rm -f gitlab-runner
docker run -d --name gitlab-runner \
    -v $(pwd)/config:/etc/gitlab-runner \
    -v /var/run/docker.sock:/var/run/docker.sock \
    --restart always \
    --privileged \
    gitlab/gitlab-runner:latest
</code></pre>
<h2 id="etapedelivraison">Etape de livraison</h2>
<p>Maintenant que notre nouveau runner est en place, il faut modifier le fichier <code>gitlab-ci.yml</code> de notre projet afin de pouvoir y ajouter une nouvelle étape de pipeline et la configurer.</p>
<p>Nous avons donc le fichier de base suivant :</p>
<pre><code class="language-bash">image: docker:stable

variables:
  DOCKER_HOST: tcp://docker:2376/
  DOCKER_DRIVER: overlay2

services:
  - docker:dind

stages:
  - package

docker-build:
  stage: package
  script:
    - docker login -u gitlab -p ${CI_REGISTRY_TOKEN} ${CI_REGISTRY_HOST}
    - docker build --build-arg VERSION=${CI_COMMIT_TAG:-nightly} -t ${CI_REGISTRY_HOST}/${CI_BUILD_IMAGE}:${CI_COMMIT_TAG:-nightly} .
    - docker push ${CI_REGISTRY_HOST}/${CI_BUILD_IMAGE}:${CI_COMMIT_TAG:-nightly}
  tags:
    - docker
</code></pre>
<p>Nous allons ajouter la nouvelle étape <code>delivery</code> dans les <code>stages</code> afin de pouvoir déclarer un nouveau job de livraison. Celui-ci sera le suivant :</p>
<pre><code class="language-bash">docker-deploy:
  stage: delivery
  before_script:
    - unset DOCKER_HOST
    - unset DOCKER_DRIVER
  script:
    - docker login -u gitlab -p ${CI_REGISTRY_TOKEN} ${CI_REGISTRY_HOST}
    - docker stack deploy -c swarm-compose.yml --with-registry-auth ${CI_STACK_NAME}
  tags:
    - docker
    - manager
    - swarm
  only: 
    - master
</code></pre>
<p>A la différence de notre premier job qui avait pour objectif de construire l'image de notre projet en utilisant le service <code>docker</code>, nous avons besoin de nous connecter directement au <code>docker-engine</code> de notre manager.<br>
Pour cela, nous supprimons les variables <code>DOCKER_HOST</code> et <code>DOCKER_DRIVER</code> afin que le <code>runner</code> utilise directement le socket local c'est à dire celui du manager.</p>
<p>Maintenant que notre <code>docker-engine</code> est configuré, nous pouvons exécuter les instructions de déploiement. Dans un premier temps nous allons nous connecter au registry afin d'avoir accès à notre image puis nous allons simplement déployer notre stack avec la commande <code>docker stack deploy</code>.</p>
<p>Ci-dessous une petite explication des paramètres :</p>
<ul>
<li><code>-c swarm-compose.yml</code> permet d'identifier le fichier compose, disponible à la racine de notre projet, qui contient la définition de nos services (Un site et une base de données par exemple)</li>
<li><code>--with-registry-auth</code> permet de passer l'authentification à notre registry</li>
<li><code>${CI_STACK_NAME}</code> nous permet enfin de nommer notre stack. Les services qui le composent seront quant à eux nommés selon le pattern <code>&lt;stack-name&gt;_&lt;service-name&gt;</code>.</li>
</ul>
<p>Comme nous avons également pu le faire dans le premier <code>job</code>, nous allons appliquer une contrainte sur la sélection de notre <code>runner</code> afin d'être sûr de cibler celui présent dans notre swarm. Pour cela nous avons appliqué les tags <code>docker</code>, <code>manager</code> et <code>swarm</code> que nous avions spécifié lors de la configuration du <code>runner</code>.</p>
<p>Finalement, afin de ne pas déployer de version instable, nous appliquons également un filtre sur la branche de notre dépôt git. De cette manière, le service ne sera déployé que s'il s'agit d'une modification de la branche <code>master</code> ce qui permet de limiter grandement le nombre de déploiement tout en s'assurant un minimum de la pérénité de notre code.</p>
<h1 id="rfrences">Références</h1>
<ul>
<li><a href="https://blog.xylphid.net/mettre-en-place-lintegration-continue-avec-gitlab/">Mettre en place une solution d'intégration continue</a></li>
<li><a href="https://docs.docker.com/compose/compose-file/">Compose file</a></li>
<li><a href="https://docs.docker.com/engine/reference/commandline/stack_deploy/">Docker stack</a></li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Mettre en place une solution d'intégration continue avec Gitlab et Docker]]></title><description><![CDATA[En partant du postulat que l'on dispose déjà d'un Gitlab, voici la procédure pour se préparer à mettre en place l'intégration continue et le déploiement continu (CI/CD) ainsi que des exemples type pour la configuration d'un projet multi-technologies.]]></description><link>https://blog.xylphid.net/mettre-en-place-lintegration-continue-avec-gitlab/</link><guid isPermaLink="false">5cfe04bf83d22e0001fe4113</guid><category><![CDATA[Docker]]></category><category><![CDATA[CI/CD]]></category><dc:creator><![CDATA[Anthony PERIQUET]]></dc:creator><pubDate>Mon, 10 Jun 2019 15:55:56 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1527266237111-a4989d028b4b?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1527266237111-a4989d028b4b?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ" alt="Mettre en place une solution d'intégration continue avec Gitlab et Docker"><p>Tout le monde a entendu parler d'intégration continue et de déploiement continue. Mais comment réellement la mettre en place.<br>En partant du postulat que l'on dispose déjà d'un Gitlab et de connaissances en Docker, voici la procédure pour se préparer à mettre en place l'intégration continue et le déploiement continu (CI/CD) ainsi que des exemples type pour la configuration d'un projet multi-technologies.</p><!--kg-card-begin: markdown--><h1 id="gnralitssurlintgrationetledploiementcontinu">Généralités sur l'intégration et le déploiement continu</h1>
<p>La philosophie générale de la CI/CD est définie par l'exécution d'une action ou liste d'actions lors d'un événement particulier sur dépôt GIT. Des <code>runners</code> sont alors missionnés pour exécuter une tâche à partir des sources disponibles dans le dépôt à un état donné (un commit ou un tag précis ...).</p>
<p>Généralement, c'est le serveur qui prévient les runners qu'une nouvelle tâche est à réaliser or, dans la philosophie de Gitlab, ce sont les runners qui vont aller vérifier régulièrement auprès du serveur s'il n'y a pas de nouvelle tâche à prendre en charge. Cela permet entre-autre de disposer de runner dans un espace sécurisé, sur un réseau privé par exemple, et surtout sur n'importe quel type de machine.</p>
<h1 id="miseenplacedunrunner">Mise en place d'un runner</h1>
<p>Comme annoncé dans le postulat de départ, nous partons du principe qu'une instance de Gitlab est déjà en place et qu'elle est fonctionnelle.<br>
Nous allons donc configurer un <code>gitlab-runner</code> et le configurer pour qu'il puisse aller récupérer des tâches/jobs sur le serveur. Pour cela, nous allons utiliser <a href="https://docs.docker.com/install/#supported-platforms">Docker</a><br>
L'ajout d'un <code>runner</code> se déroule en deux étapes :</p>
<ul>
<li>Il faut le configurer et le référencer dans un premier temps</li>
<li>Ensuite il faut le démarrer</li>
</ul>
<h2 id="configurationetrfrencementdunrunner">Configuration et référencement d'un <code>runner</code></h2>
<p>Dans un premier temps, il est nécessaire de récupérer token d'authentification auprès de notre instance Gitlab. Il est possible de récupérer deux types de tokens :</p>
<ul>
<li>Un token projet qui permettra de référencer un <code>runner</code> dans le scope d'un unique projet. Il ne pourra alors exécuter que les tâches associées au projet pour lequel il a été référencé.</li>
<li>Un token global qui permettra de référencer un <code>runner</code> dans le scope global de Gitlab. Cela veut dire qu'il sera en mesure de réaliser les tâches de n'importe quel projet pour peu que sa configuration soit compatible.</li>
</ul>
<p>Dans cet article, nous allons nous limiter au scope d'un projet aussi, pour récupérer notre token, il suffit d'aller, dans notre projet, dans le menu <strong>Paramètres &gt; Intégration et livraison continue</strong> puis d'étendre la section <strong>Exécuteurs</strong>.<br>
Dans le colonne <strong>Exécuteurs spécifiques</strong> nous pouvons voir les informations nécessaires à la configuration de notre nouveau <code>runner</code> :</p>
<ul>
<li>L'adresse de notre instance Gitlab</li>
<li>Le token d'authentification spécifique à notre projet</li>
</ul>
<p>Le référencement d'un runner peut se faire de manière intéractive ou directement en ligne de commande. Quoi qu'il en soit, il est important de sauvegarder la configuration du <code>runner</code> pour s'en reservir ultérieurement.<br>
Afin de faciliter un peu la procédure, Nous allons réaliser un petit script qui permet d'enregistrer un <code>runner</code>. Pour cela, il faut créer le fichier <code>register.sh</code> :</p>
<pre><code class="language-bash">#!/bin/sh

docker run --rm -it \
    -v $(pwd)/config:/etc/gitlab-runner:rw \
    -v /var/run/docker.sock:/var/run/docker.sock \
    gitlab/gitlab-runner:latest register \
    --non-interactive \
    --executor &quot;docker&quot; \
    --docker-image alpine:3.8 \
    --docker-privileged \
    --url &quot;https://&lt;GITLAB_INSTANCE&gt;/&quot; \
    --registration-token &quot;&lt;GITLAB_TOKEN&gt;&quot; \
    --description &quot;docker-runner&quot; \
    --tag-list &quot;docker&quot; \
    --run-untagged \
    --locked=&quot;false&quot;
</code></pre>
<p>Plus de détails sur les paramètres de la commande <code>docker</code> :</p>
<ul>
<li><code>-v $(pwd)/config:/etc/gitlab-runner:rw</code> permet de sauvegarder la configuration du <code>runner</code> dans le dossier <code>config</code> du répertoire courant.</li>
<li><code>--non-interactive</code> désactive le mode intéractif</li>
<li><code>--executor &quot;docker&quot;</code> définit le type d'éxécuteur en mode docker</li>
<li><code>--docker-image alpine:3.8</code> définit l'image de base à partir de laquelle seront réalisées les tâches</li>
<li><code>--docker-privileged</code> permet de donner des droits plus étendus au <code>runner</code> dont celui d'exécuter un daemon <code>docker</code></li>
<li><code>--url &quot;https://&lt;GITLAB_INSTANCE&gt;/&quot;</code> précise l'URL de notre instance Gitlab pour pouvoir vérifier si de nouvelles tâches doivent être exécutées</li>
<li><code>--registration-token &quot;&lt;GITLAB_TOKEN&gt;&quot;</code> précise le token d'authentication de notre runner</li>
<li><code>--description &quot;docker-runner&quot;</code> définit le nom du <code>runner</code></li>
<li><code>--tag-list &quot;docker&quot;</code> définit une liste de tag, séparés par une virgule, qui peuvent être utilisés pour sélectionner un runner lors de la définition d'une tâche</li>
<li><code>--run-untagged</code> précise que le <code>runner</code> peut exécuter des tâche qui n'ont pas été taggées</li>
</ul>
<p>Si l'on souhaite pouvoir créer des images Docker dans un <code>runner</code>, il peut être également intéressant d'ajouter le flag <code>--privileged</code>.</p>
<p>Lorsque la commande a été exécutée, nous devrions nous retrouver avec le fichier <code>./config/config.toml</code> dont le contenu est le suivant :</p>
<pre><code class="language-toml">concurrent = 1
check_interval = 0

[session_server]
  session_timeout = 1800

[[runners]]
  name = &quot;docker-runner&quot;
  url = &quot;https://&lt;GITLAB_INSTANCE&gt;/&quot;
  token = &quot;&lt;GITLAB_TOKEN&gt;&quot;
  executor = &quot;docker&quot;
  [runners.docker]
    tls_verify = false
    image = &quot;alpine:3.8&quot;
    privileged = true
    disable_entrypoint_overwrite = false
    oom_kill_disable = false
    disable_cache = false
    volumes = [&quot;/cache&quot;]
    shm_size = 0
  [runners.cache]
    [runners.cache.s3]
    [runners.cache.gcs]

</code></pre>
<h2 id="excutiondurunner">Exécution du <code>runner</code></h2>
<p>Pour exécuter le <code>runner</code>, nous procédons de la même manière. Nous créons le script <code>run.sh</code> dont le contenu est le suivant :</p>
<pre><code class="language-bash">#!/bin/sh

docker rm -f gitlab-runner
docker run -d --name gitlab-runner \
    -v $(pwd)/config:/etc/gitlab-runner \
    -v /var/run/docker.sock:/var/run/docker.sock \
    --restart always \
    --privileged \
    gitlab/gitlab-runner:latest
</code></pre>
<p>Ce script supprime dans un premier temps tout conteneur nommé <code>gitlab-runner</code> puis en démarre un nouveau qui s'appellera à son tour <code>gitlab-runner</code>.<br>
Voici également le détail des paramètres de la commande <code>docker run</code> :</p>
<ul>
<li><code>-v $(pwd)/config:/etc/gitlab-runner</code> permet de monter dans le conteneur un volume qui contient la configuration créée précédemment</li>
<li><code>-v /var/run/docker.sock:/var/run/docker.sock</code> permet de monter le socker <code>docker</code> afin de pouvoir exécuter des commandes <code>docker</code> depuis le <code>runner</code></li>
<li><code>--restart always</code> permet de redémarrer le conteneur en cas d'erreur afin de reprendre les travaux en cours</li>
<li><code>--privileged</code> permet au <code>runner</code> de créer des conteneurs</li>
</ul>
<p>Maintenant que nous avons mis en place notre <code>runner</code>, on doit pouvoir le retrouver sur la page d'administration de notre projet.</p>
<h1 id="lintgrationcontinuesurunprojetweb">L'intégration continue sur un projet Web</h1>
<h2 id="prparationduprojet">Préparation du projet</h2>
<p>Avant de rentrer dans le vEn partant du postulat quif du sujet, il est nécessaire de préparer le projet (au sens Gitlab) pour l'intégration continue. Rien de bien compliqué mais il faut tout de même préparer les variables d'intégration continue.<br>
Dans notre cas, nous allons faire en sorte de générer des images <code>docker</code> et de les pousser sur un registry local.</p>
<p>Pour cela nous avons besoin de déclarer quelques variables dans notre projet. Il faut donc se rendre dans la section <strong>Paramètres &gt; Intégration et livraison continue</strong> puis étendre la section <strong>Variables</strong>.<br>
Nous allons saisir les variables suivantes :</p>
<ul>
<li><strong>CI_REGISTRY_HOST</strong> : Adresse du registry <code>docker</code></li>
<li><strong>CI_BUILD_IMAGE</strong> : Nom de l'image <code>docker</code> que l'on va builder</li>
<li><strong>CI_REGISTRY_TOKEN</strong> : Token d'authentification au registry pour un utilisateur donné (ici on utilisera l'utilisateur <code>gitlab</code>)</li>
</ul>
<p><img src="https://images.unsplash.com/photo-1533234427049-9e9bb093186d?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ#full" alt="Mettre en place une solution d'intégration continue avec Gitlab et Docker"><br>
<small>Photo by <a href="https://unsplash.com/@markusspiske?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit">Markus Spiske</a> / <a href="https://unsplash.com/?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit">Unsplash</a></small></p>
<h2 id="dfinitiondesrglesdintgrationcontinue">Définition des règles d'intégration continue</h2>
<p>Afin de définir les étapes de notre intégration continue, il est nécessaire d'ajouter à notre projet un fichier de configuration nommé <code>gitlab-ci.yml</code>. Ce fichier va contenir les instructions nécessaires au <code>runner</code> pour réaliser les tâches que nous lui demanderons de faire, selon le formalisme décrit dans la <a href="https://docs.gitlab.com/ce/ci/yaml/README.html">documentation</a> Gitlab.<br>
L'ensemble des instructions définies se nomme un <code>pipeline</code> et est découpé en étapes (build, test, ...) et en jobs.</p>
<p>Dans l'exemple que je vais donner et expliquer ci-dessous, je pars du postulat que le projet sur lequel nous travaillons est déjà configuré pour être packagé dans une image docker. Il y a donc dans l'arborescence un fichier <code>.Dockerfile</code> qui contient les instructions nécessaire au build de notre image.</p>
<p>Voici donc un exemple relativement simple de fichier <code>gitlab-ci.yml</code> :</p>
<pre><code class="language-yml">image: docker:stable

# Uncomment if runner version lower than 11.11
# See : https://docs.gitlab.com/ce/ci/docker/using_docker_build.html#use-docker-socket-binding
# variables:
#   DOCKER_HOST: tcp://docker:2376/
#   DOCKER_DRIVER: overlay2

# Uncomment if runner version lower than 11.11
# See : https://docs.gitlab.com/ce/ci/docker/using_docker_build.html#use-docker-socket-binding
# services:
#   - docker:dind

stages:
  - package

docker-build:
  stage: package
  script:
    - docker login -u gitlab -p ${CI_REGISTRY_TOKEN} ${CI_REGISTRY_HOST}
    - docker build --build-arg VERSION=${CI_COMMIT_TAG:-nightly} -t ${CI_REGISTRY_HOST}/${CI_BUILD_IMAGE}:${CI_COMMIT_TAG:-nightly} .
    - docker push ${CI_REGISTRY_HOST}/${CI_BUILD_IMAGE}:${CI_COMMIT_TAG:-nightly}
  tags:
    - docker
</code></pre>
<p>Ce fichier permet de définir la configuration de notre pipeline. Dans le détail, voici la description des différentes étapes qui sont réalisées :</p>
<ul>
<li><code>image: docker:stable</code> : permet de demander au <code>runner</code> d'instancier un conteneur à partir de l'image <code>docker:stable</code> pour réaliser les jobs qui vont être décris pas la suite</li>
<li><code>variables</code> : Ce bloc permet d'ajouter des variables qui seront disponible dans le scope global de l'intégration continue. Il est également possible d'en définir dans chacun des jobs. Elles ne seront alors pas disponibles en dehors du scope du job dans lequel elles ont été définies.</li>
<li><code>services</code> : Permet de définir un service complémentaire qui sera instancé et accessible pendant l'exécution du pipeline. Ici nous souhaitons utiliser le service <code>docker:dind</code> (docker in docker) afin de pouvoir exécuter n'importe quelle commande docker.</li>
<li><code>stages</code> : Permet de définir les étapes de notre pipeline. Elles seront traitées dans l'ordre de définition</li>
<li><code>docker-build</code> : Ce n'est pas un mot clé. Nous commençons ici la définition d'un job nommé <code>docker-build</code>
<ul>
<li><code>stage</code> : Permet d'associer un job à une étape du pipeline.<br>
<strong>Attention</strong>, les jobs associés à une même étape sont traités dans un ordre arbitraire.</li>
<li><code>script</code> : Permet de définir une liste de commandes qui seront exécutées par le conteneur.</li>
</ul>
</li>
<li><code>tags</code> : Permet de définir quel <code>runner</code> sera utilisé pour exécuter le job, en l'occurence n'importe quel <code>runner</code> disposant du tag <code>docker</code>.</li>
</ul>
<p>Concentrons-nous un peu plus sur le contenu de la section <code>script</code>.<br>
Nous nous connectons dans un premier temps à notre registry <code>CI_REGISTRY_HOST</code> avec le compte <code>gitlab</code> et le token <code>CI_REGISTRY_TOKEN</code>. Ensuite nous buildons notre image en définissant le paramètre <code>VERSION</code> avec la valeur <code>CI_COMMIT_TAG</code>, une variable de Gitlab correspondant au tag du commit, ou la valeur <code>nightly</code> dans le cas où cette variable n'est pas renseignée puis nous taggons l'image.<br>
Enfin nous poussons l'image générées sur le registry.</p>
<p>C'est un pipeline tout à fait élémentaire mais il présente bien le principe d'intégration continue. Il existe tout un tas d'options de configuration complémentaires qui permettent d'affiner le comportement du <code>runner</code>, en cas d'erreur ou de succès d'un job par exemple.</p>
<p>Il est également possible de compléter un pipeline avec des étapes complémentaire comme les tests ou le déploiement continue car au final, ce ne sont que des commandes qui sont exécutées dans une contexte bien précis.</p>
<h1 id="rfrences">Références</h1>
<ul>
<li><a href="https://www.docker.com/">Docker</a></li>
<li><a href="https://about.gitlab.com/">Gitlab</a></li>
<li><a href="https://docs.gitlab.com/ce/ci/yaml/README.html">Documentation gitlab-ci.yml</a></li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Installer Kodi sur un Raspberry]]></title><description><![CDATA[<p>Je dispose de quelques Raspberry à la maison et je suis également un grand adepte du serveur freebox. Comme j'essaie le plus possible de ne pas être dépendant de services qui peuvent m'être retirés j'ai envisagé la possibilité de remplacer le serveur freebox, plus particulièrement la fonctionnalité de lecture de</p>]]></description><link>https://blog.xylphid.net/installer-kodi-sur-un-raspberry/</link><guid isPermaLink="false">5cf7851c83d22e0001fe40bd</guid><category><![CDATA[Raspberry]]></category><category><![CDATA[Self Hosting]]></category><dc:creator><![CDATA[Anthony PERIQUET]]></dc:creator><pubDate>Wed, 05 Jun 2019 12:31:52 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1461151304267-38535e780c79?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1461151304267-38535e780c79?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ" alt="Installer Kodi sur un Raspberry"><p>Je dispose de quelques Raspberry à la maison et je suis également un grand adepte du serveur freebox. Comme j'essaie le plus possible de ne pas être dépendant de services qui peuvent m'être retirés j'ai envisagé la possibilité de remplacer le serveur freebox, plus particulièrement la fonctionnalité de lecture de contenu local (disque, NAS, ...) par l'application Kodi.</p><!--kg-card-begin: markdown--><h2 id="matrieldebase">Matériel de base</h2>
<p>Au niveau du matériel, je dispose donc d'un Raspberry Pi 3B+ sur lequel j'ai installé la dernière version de <a href="http://raspbian-france.fr/">Raspbian</a>.<br>
Il n'y a besoin de rien de plus que cela pour la suite de l'article.</p>
<h2 id="installationdekodi">Installation de Kodi</h2>
<p>Si comme moi vous utilisez <a href="http://raspbian-france.fr/">Raspbian</a> sans bureau, il convient d'installer au préalables les utilitaires qui permettent d'afficher des interface. En gros, un serveur x :</p>
<pre><code class="language-bash">sudo apt install xinit xutils
</code></pre>
<p>Ceci effectué, il suffit ensuite d'exécuter la commande suivante pour installer kodi :</p>
<pre><code class="language-bash">sudo apt install kodi
</code></pre>
<h2 id="dmarrageautomatiquedekodi">Démarrage automatique de kodi</h2>
<p>Si comme moi, vous souhaitez que kodi démarre automatiquement lorsque vous allumez votre raspberry, il est nécessaire de passer par les étapes ci-dessous :</p>
<ul>
<li>On commence par créer un utilisateur qui sera chargé de lancer le programme. Cet utilisateur ne nécessite pas de mot de passe et doit faire parti de différents groupes afin de pouvoir utiliser les ressources nécessaires.</li>
</ul>
<pre><code class="language-bash">sudo adduser --disabled-password --gecos &quot;User to run Kodi Media Center&quot; &lt;USER&gt;
sudo adduser &lt;USER&gt; audio
sudo adduser &lt;USER&gt; video
sudo adduser &lt;USER&gt; plugdev
sudo adduser &lt;USER&gt; input
</code></pre>
<ul>
<li>On créé le fichier <code>/etc/systemd/system/kodi.service</code> qui nous permet de déclarer le service kodi. Le contenu est le suivant :</li>
</ul>
<pre><code>[Unit]
Description = Kodi Media Center

# if you don't need the MySQL DB backend, this should be sufficient
After = systemd-user-sessions.service network.target sound.target

# if you need the MySQL DB backend, use this block instead of the previous
# After = systemd-user-sessions.service network.target sound.target mysql.service
# Wants = mysql.service

[Service]
User = &lt;USER&gt;
Group = &lt;USER&gt;
Type = simple
ExecStart = /usr/bin/kodi-standalone
Restart = always
RestartSec = 15

[Install]
WantedBy = multi-user.target
</code></pre>
<ul>
<li>Enfin on référence le service que nous venons de créer puis nous le démarrons :</li>
</ul>
<pre><code class="language-bash">systemctl daemon-reload
systemctl enable kodi.service
systemctl start kodi.service
</code></pre>
<p>Désormais, lorsque le Raspberry démarrera, Kodi se lancera automatiquement. Il sera alors possible de le configurer en fonction de votre installation.</p>
<h2 id="configurationcomplmentaire">Configuration complémentaire</h2>
<p>Si l'installation est configurée pour utiliser des dossiers réseaux (répertoires sur un NAS par exemple), il convient de configurer la gestion du cache comme l'indique la <a href="https://kodi.wiki/view/HOW-TO:Modify_the_video_cache">documentation</a>.<br>
Pour cela, il faut créer le fichier <code>/home/&lt;USER&gt;/.kodi/userdata/advancedsettings.xml</code> et y ajouter le contenu suivant :</p>
<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;advancedsettings&gt;
  &lt;cache&gt;
    &lt;memorysize&gt;104857600&lt;/memorysize&gt;
    &lt;buffermode&gt;1&lt;/buffermode&gt;
    &lt;readfactor&gt;4.0&lt;/readfactor&gt;
  &lt;/cache&gt;
&lt;/advancedsettings&gt;
</code></pre>
<p>Il est possible d'avoir plus de détails sur les options de configuration du cache dans cette <a href="https://kodi.wiki/view/Advancedsettings.xml#cache">documentation</a>.</p>
<h1 id="rfrences">Références :</h1>
<ul>
<li><a href="https://kodi.wiki/view/Main_Page">Wiki Kodi</a></li>
<li><a href="https://gist.github.com/Cyberek/33af1b92c071791a71aa8bccf87b8a3a#gistcomment-2304207">Starting Kodi automatically</a></li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Traefik : Router des requêtes vers des services non dockerisés]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Fervent utilisateur de Docker, il m'arrive parfois d'être confronté à des difficultés lorsque certains services que je souhaite utiliser ne fonctionnent pas sur ce type d'architecture. Afin de contourner cette difficulté, je me suis penché sur la configuration de Traefik afin de découvrir s'il était possible de rediriger des requêtes</p>]]></description><link>https://blog.xylphid.net/traefik-router-vers-des-services-non-dockerises-full/</link><guid isPermaLink="false">5b913e64c12f3e0001c50b4c</guid><category><![CDATA[Traefik]]></category><category><![CDATA[Swarm]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Traefik v1]]></category><dc:creator><![CDATA[Anthony PERIQUET]]></dc:creator><pubDate>Tue, 02 Oct 2018 07:27:21 GMT</pubDate><media:content url="https://blog.xylphid.net/content/images/2018/10/traefik-architecture.svg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://blog.xylphid.net/content/images/2018/10/traefik-architecture.svg" alt="Traefik : Router des requêtes vers des services non dockerisés"><p>Fervent utilisateur de Docker, il m'arrive parfois d'être confronté à des difficultés lorsque certains services que je souhaite utiliser ne fonctionnent pas sur ce type d'architecture. Afin de contourner cette difficulté, je me suis penché sur la configuration de Traefik afin de découvrir s'il était possible de rediriger des requêtes HTTP vers un service non dockerisé.</p>
<p>Spoiler, c'est possible !</p>
<h1 id="configurationdetraefik">Configuration de Traefik</h1>
<p>Voici la marche à suivre pour mettre en place cette fonctionnalité.<br>
Pour cela, nous avons besoin du  module <code>file</code> de traefik qui va nous permettre de prendre en compte la configuration de certains services via des fichiers. Nous avons également besoin de spécifier à traefik de surveiller ces configurations et d'indiquer le fichier ou le répertoire à surveiller.</p>
<p>Pour configurer ces options depuis la ligne de commande, il faut ajouter les labels suivants :</p>
<ul>
<li><code>--file</code></li>
<li><code>--file.watch</code></li>
<li><code>--file.filename</code> ET/OU <code>--file.directory</code></li>
</ul>
<p>Vous pouvez également activer ces options depuis le fichier de configuration de traefik. Il faudra pour cela écrire les lignes suivantes :</p>
<pre><code class="language-toml">[file]
  watch = true
  # Uncomment filename to specify a file path
  # filename = /path/of/my/configuration.toml
  # Uncomment directory if you have multiple files
  directory = /path/of/my/files/
</code></pre>
<p>Cela peut donner la configuration suivante dans un fichier compose :</p>
<pre><code class="language-yml">  traefik:
    command: --web \
      -c /dev/null \
      --docker \
      --docker.watch \
      --docker.exposedbydefault=false \
      --docker.domain=&quot;domain.tld&quot; \
      --accesslogsfile=/dev/stdout \
      --defaultentrypoints=&quot;http,https&quot; \
      --entryPoints=&quot;Name:http Address::80&quot; \
      --entryPoints=&quot;Name:https Address::443 TLS&quot; \
      --entryPoints=&quot;Name:traefik Address::8080&quot; \
      --api=true \
      --api.dashboard=true \
      --api.statistics=true \
      --api.statistics.recentErrors=20 \
      --file \
      --file.watch \
      --file.directory=/opt/traefik/config/ \
      --debug
    environment:
      - &quot;affinity:container!=traefik*&quot;
    image: traefik:latest
    networks:
      - docker-net
    ports:
      - &quot;80:80&quot;
      - &quot;443:443&quot;
      - &quot;8080:8080&quot;
    restart: always
    volumes:
      - //var/run/docker.sock:/var/run/docker.sock
      - ./traefik-datas/secure:/root/secure
      - ./traefik-datas/config:/opt/traefik/config:ro
</code></pre>
<h1 id="dfinitiondesservices">Définition des services</h1>
<p>Pour définir vos services, il est possible, selon la configuration que vous avez choisi d'implémenter, de tout configurer dans un fichier unique ou dans des fichiers séparés afin de gagner en lisibilité.<br>
Quoi qu'il en soit, vous trouverez ci-dessous une configuration type de service :</p>
<pre><code class="language-toml">[backends]
  # Name of your backend
  [backends.mybackend]
    # Name of your service
    [backends.mybackend.servers.myservice]
      url = &quot;http://127.0.0.1:9000&quot;

[frontends]
  [frontends.mybackend]
    backend = &quot;mybackend&quot;
    entryPoints = [&quot;http&quot;, &quot;https&quot;]
    passHostHeader = true

  # Define headers
  [frontends.mybackend.headers]
    SSLRedirect = true

  # Define rules
  [frontends.mybackend.routes.myservice]
    rule = &quot;Host:my-service.domain.tld&quot;
</code></pre>
<p>Cette feature vous permet donc d'intégrer à votre infrastructure Docker des services qui ne fonctionnent pas sur docker et qui peuvent éventuellement être sur des machines distantes.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Aggréger les logs d'un Swarm Docker]]></title><description><![CDATA[Docker swarm est une architecture qui, en plus de distribuer ses services sur plusieurs nodes, répartie aussi les logs de celles-ci.
Pour palier à ce problème je vous parle de log-pycker, ce petit projet qui permet d'aggéger les logs de vos services.]]></description><link>https://blog.xylphid.net/aggreger-les-logs-dun-swarm-docker/</link><guid isPermaLink="false">5b655a1547f99a0001652c5c</guid><category><![CDATA[Docker]]></category><category><![CDATA[Monitoring]]></category><category><![CDATA[Python]]></category><category><![CDATA[Swarm]]></category><dc:creator><![CDATA[Anthony PERIQUET]]></dc:creator><pubDate>Mon, 13 Aug 2018 12:08:23 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1526628953301-3e589a6a8b74?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ&amp;s=36bdffeb9b6eb1207bed4029b11725c3" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://images.unsplash.com/photo-1526628953301-3e589a6a8b74?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ&s=36bdffeb9b6eb1207bed4029b11725c3" alt="Aggréger les logs d'un Swarm Docker"><p>Depuis que je me suis mis sur docker swarm, ma plus grosse problématique a été la gestion des logs. Cette architecture qui répartie les services entre plusieurs machines répartie également les logs. Pour peu qu'un service redémarre ou qu'un problème survienne, on risque de perdre une partie des logs et ne plus pouvoir inspecter les erreurs d'une application.</p>
<h1 id="unpeudeprospection">Un peu de prospection</h1>
<p>J'ai rapidement cherché une solution pour centraliser les logs de mes applications et j'ai souvent été ramené vers Elastic Search et la populaire stack ELK<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup>. Ma contrainte étant la gratuité et la maitrise de mes données cela semblait être une bonne option.<br>
Cependant, le problème de cette stack, de mon point de vue, c'est sa grosse consommation de ressources, et sa configuration pénible à implémenter. De plus je n'ai pas trouvé que logstash soit assez flexible sur le formattage des logs.</p>
<p><img src="https://images.unsplash.com/photo-1494707924465-e1426acb48cb?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ&amp;s=25da7cc70c77cdaa41e3b21adef01952" alt="Aggréger les logs d'un Swarm Docker"><br>
<small>Photo by <a href="https://unsplash.com/@cikstefan?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Štefan Štefančík</a> / <a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Unsplash</a></small></p>
<p>J'ai également testé une autre stack à base d'Elastic Search mais cette fois avec Fluentd<sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup> à la place de Logstash. Dans la configuration que j'ai pu rencontrer il fallait configurer les driver de log sur chacun des services que je souhaitais surveiller. Au final ce n'était pas pratique car la moindre défaillance au niveau de <code>fluentd</code> faisait planter tous les services qui l'utilisaient.</p>
<h2 id="versunesolutionpartielle">Vers une solution partielle</h2>
<p>Du moment ou j'ai commencé à m'intéresser au sujet de centralisation des logs à aujourd'hui, il s'est passé un an. Sur toutes les solutions que j'ai pu observer, Elastic Search et Kibana en étaient très souvent des composants. Par chance, ces deux projets ont également évolué pendant cette période pour être plus faciles d'accès.<br>
Je me suis donc retrouvé avec une solution partielle pour stocker et visualiser mes données. Il ne me restait plus qu'à trouver de quoi collecter et traiter mes logs.</p>
<h2 id="crationdunoutildecollecte">Création d'un outil de collecte</h2>
<p>Finalement je me suis retourné vers mes propres compétences en développement pour mettre en place une solution qui corresponde à mon besoin ... et peut-etre à celui d'autres.<br>
J'ai alors développé LogPycker, un petit projet python qui se connecte au daemon docker pour aller lire les logs de chacun des conteneurs déployés. De base, il effectue un parsing sommaire afin de construire un message structuré, au mieux, de la manière suivante :</p>
<pre><code class="language-json">{
    &quot;date&quot;: &quot;August 4th 2018, 10:32:31.384&quot;,
    &quot;project&quot;: &quot;my-project&quot;,
    &quot;service&quot;: &quot;my-service&quot;,
    &quot;task&quot;: &quot;my-task-id&quot;,
    &quot;message&quot;: &quot;content of my message&quot;
}
</code></pre>
<p>Certains attributs du message peuvent ne pas être présent notamment lorsque le conteneur est lancé tout seul. En effet, les attributs <code>project</code>, <code>service</code> et <code>task</code> ne sont disponibles que si le conteneur est lancé par le biais d'une stack (compose ou swarm).</p>
<p><img src="https://images.unsplash.com/photo-1526577579646-2402908bcb48?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ&amp;s=efa80533ec40f34fdd20dfca320327c6#full" alt="Aggréger les logs d'un Swarm Docker"><br>
<small>Photo by <a href="https://unsplash.com/@oskarssylwan?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Oskars Sylwan</a> / <a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Unsplash</a></small></p>
<h1 id="utilisationdelogpycker">Utilisation de Log Pycker</h1>
<p>Comme je l'ai dit plus tôt, mon objectif premier était de disposer d'un aggrégateur de log simple à mettre en place. Outre la configuration du point daccès vers Elastic Search , s'il n'y a rien à configurer de particulier pour récupérer des logs, on ne peut que mieux se porter.</p>
<p>Je me suis donc basé sur la stack ELK<sup class="footnote-ref"><a href="#fn1" id="fnref1:1">[1:1]</a></sup> afin de mettre en place mon outil. Cela m'a faut aboutir à la stack suivante (en mode compose) :</p>
<pre><code class="language-yml">version: &quot;3.2&quot;

networks:
  logger-net:
  docker-net:
    external: true

services:
  elastic:
    environment:
      discovery.type: single-node
    image: docker.elastic.co/elasticsearch/elasticsearch:6.3.1
    networks:
      - logger-net
    restart: always

  pycker:
    depends_on:
      - elastic
    environment:
      elastic.url: http://elastic
    image: xylphid/log-pycker:latest
    links:
      - elastic
    networks:
      - logger-net
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

  kibana:
    depends_on:
      - elastic
    environment:
      ELASTICSEARCH_URL: http://elastic:9200
    image: docker.elastic.co/kibana/kibana:6.3.1
    labels:
      traefik.enable: &quot;true&quot;
      traefik.backend: &quot;Monitor&quot;
      traefik.docker.network: &quot;docker-net&quot;
      traefik.frontend.headers.SSLRedirect: &quot;true&quot;
      traefik.frontend.rule: &quot;Host: monitor.docker&quot;
      traefik.port: &quot;5601&quot;
    links:
      - elastic
    networks:
      - docker-net
      - logger-net
</code></pre>
<p><em>Note :</em> J'utilise Traefik<sup class="footnote-ref"><a href="#fn3" id="fnref3">[3]</a></sup> afin de pouvoir accéder à kibana via l'adresse <a href="http://monitor.docker">http://monitor.docker</a> (domaine local)</p>
<p>Comme on peut le voir, le service pycker est relativement facile à configurer. Il n'a besoin que de connaitre l'adresse d'Elastic Search et d'avoir accès, par le biais du volume, au daemon docker.</p>
<h2 id="pourallerunpeuplusloin">Pour aller un peu plus loin</h2>
<p>Le besoin d'analyse des logs pouvant être différent selon les services que l'on souhaite surveiller, j'ai souhaité mettre en place un mécanisme qui permette, depuis chaque conteneur, de spécifier un format d'extraction des données.<br>
Pour cela, il suffit de définir un label de <strong>conteneur</strong> avec une expression régulière spécifiant le pattern d'extraction.<br>
Voici un exemple de ce que j'utilise pour extraire le niveau de log d'un projet <a href="https://www.playframework.com/">Play Framework!</a> :</p>
<pre><code>version: &quot;3.2&quot;
services:
  web-app:
    image: my-web-app-play
    labels:
      log.pycker.pattern: &quot;(?P&lt;level&gt;(?:[[])?(?:INFO|DEBUG|ERROR)(?:[]])?)\\s*&quot;
    restart: always
</code></pre>
<p>Comment cela fonctionne ?<br>
Assez simplement au final. L'expression régulière utilise des groupes nommés qui serviront à définir les attributs complémentaires de notre objet message. Si le message match un des groupes il sera extrait et nettoyé.</p>
<p>Voici un message avant traitement :</p>
<pre><code class="language-yml">{
    &quot;message&quot;: &quot;INFO ~ [LogManager][before] Requested URI : GET:/&quot;
}
</code></pre>
<p>Et voici le même message après avoir été analysé avec le pattern :</p>
<pre><code class="language-yml">{
    &quot;level&quot;: &quot;INFO&quot;,
    &quot;message&quot;: &quot;~ [LogManager][before] Requested URI : GET:/&quot;
}
</code></pre>
<p>Il est également possible de détecter automatiquement les messages multiligne. Pour cela, il faut activer l'option via un label de <strong>conteneur</strong>.<br>
Dans cette première version de l'application, cette option est très fortement liée à la présence de la date. Ainsi un message avec une date sera systématiquement considéré comme un nouveau message alors qu'un message n'ayant pas cette information sera rattaché au précédent.</p>
<p>Si l'on reprend notre stack précédente, nous avons modifié la définition comme suit :</p>
<pre><code>version: &quot;3.2&quot;
services:
  web-app:
    image: my-web-app-play
    labels:
      log.pycker.pattern: &quot;(?P&lt;level&gt;(?:[[])?(?:INFO|DEBUG|ERROR)(?:[]])?)\\s*&quot;
      log.pycker.multiline.enabled: &quot;true&quot;
    restart: always
</code></pre>
<p>Ces deux options permettent d'améliorer la qualité des informations récupérées par l'outil de collecte et, pour peu que l'on manipule correctement les expressions régulières, d'avoir un détail relativement fin dans le découpage des logs.</p>
<h2 id="amliorationvenir">Amélioration à venir</h2>
<p>Voici les quelques évolutions que j'envisage de mettre en place à ce jour afin d'améliorer les performances de l'application :</p>
<ul>
<li>Utiliser une message queue pour stocker les messages en cas d'erreur réseau (avec un fallback en local)</li>
<li>Récupérer tout l'historique de log d'un conteneur.</li>
</ul>
<h1 id="remonterunbugoucontribuer">Remonter un bug ou contribuer</h1>
<p>Le projet est actuellement en phase de test sur une petite architecture qui héberge une vingtaine de services.<br>
Vous pouvez, si vous le souhaitez, le tester sur votre propre infrastructure et remonter vos bugs via <a href="https://github.com/xylphid/log-pycker/issues/new">github</a>.</p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>ELK est l'accronyme de trois projets open-source : Elastic Search, Logstash, Kibana. Elastic Search est un moteur de recherche et d'analyse. Logstash est un outil qui ingère des données, les traite et les transforme pour ensuite les envoyer vers Elastic Search. Kibana permet de visualiser les logs et de créer des graphiques à partir des données collectées dans Elastic Search. (<a href="https://www.elastic.co/elk-stack">Plus d'informations</a>) <a href="#fnref1" class="footnote-backref">↩︎</a> <a href="#fnref1:1" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn2" class="footnote-item"><p>Fluentd est un projet open-source qui collecte les données de logs. (<a href="https://www.fluentd.org/">Plus d'informations</a>) <a href="#fnref2" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn3" class="footnote-item"><p>Traefik est un reverse proxy et un load balencer dynamique qui s'intègre tout naturellement dans un environnement docker. Pour plus d'informations sur Traefik, je vous laisse vous reporter au <a href="https://traefik.io">site</a> ou à la <a href="https://docs.traefik.io/">documentation</a> <a href="#fnref3" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Valider un plan de marquage avec Testcafé]]></title><description><![CDATA[Il y a peu, j'ai dû m'intéresser au sujet de la mesure d'audience d'un site et plus particulièrement valider que le plan de marquage (clics, affichage, auto-promo) soit correctement implémenté.]]></description><link>https://blog.xylphid.net/valider-un-plan-de-marquage-avec-testcafe/</link><guid isPermaLink="false">5b4899119c7cf000018495d5</guid><category><![CDATA[e2e-testing]]></category><category><![CDATA[Docker]]></category><dc:creator><![CDATA[Anthony PERIQUET]]></dc:creator><pubDate>Tue, 17 Jul 2018 13:56:29 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1483706571191-85c0c76b1947?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ&amp;s=4d99ab09e090020e74fa6d50d20bd9a7" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://images.unsplash.com/photo-1483706571191-85c0c76b1947?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ&s=4d99ab09e090020e74fa6d50d20bd9a7" alt="Valider un plan de marquage avec Testcafé"><p>Il y a peu, j'ai dû m'intéresser au sujet de la mesure d'audience d'un site et plus particulièrement valider que le plan de marquage (clics, affichage, auto-promo) soit correctement implémenté.</p>
<p>La régie en question ? <em>AT-Internet</em><sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup>.</p>
<p>Comme énoncé en introduction, je me suis plus particulièrement penché sur les trois types de marqueurs suivants :</p>
<ul>
<li><em>Affichage :</em> Permet de mesurer l'audience d'une page (le nombre d'affichage). Il faut faire attention car dans certains cas il est aussi nécessaire de mesurer l'audience d'une partie de page chargée en AJAX.</li>
<li><em>Clics :</em> Permet de mesurer l'audience d'un bouton ou d'un lien</li>
<li><em>Auto-Promo :</em> Permet de mesurer n'importe quel élément dans l'optique générale d'en mesurer l'impact. On souhaite souvent savoir si un lien, ou un encart a plus d'impact qu'un autre selon sa position sur la page.</li>
</ul>
<p><img src="https://images.unsplash.com/photo-1516383274235-5f42d6c6426d?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ&amp;s=dd3614f4babf1a6bac4dac089b285648" alt="Valider un plan de marquage avec Testcafé"><br>
<small>Photo by <a href="https://unsplash.com/@olloweb?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Olloweb Solutions</a> / <a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Unsplash</a></small></p>
<h1 id="gnralits">Généralités</h1>
<h2 id="fonctionnementdeloutildemonitoringdaudience">Fonctionnement de l'outil de monitoring d'audience</h2>
<p>Afin de mettre en place des tests valides, il est important de comprendre le fonctionnement de l'outil et de connaitre les pré-requis au bon marquage d'une page, d'un lien ou de tout autre élément.<br>
Pour commencer, il faut rappeler que la mesure d'audience est réalisée par le biais de requêtes vers les-dis outils. Cela peut se faire en affichant un pixel transparent sur une page ou en effectuant une requête asynchrone. Ces requêtes comportent des paramètres particuliers qui permettent d'identifier le compte, l'action ainsi que d'autres données utiles. Ce sont ces paramètres qui nous permettrons de valider le plan de marquage d'une application.</p>
<h2 id="fonctionnementdetestcaf">Fonctionnement de Testcafé</h2>
<p><img src="https://images.unsplash.com/photo-1517249555-43185b6284aa?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ&amp;s=36cc1cb7366f505260a5b972505f811a" alt="Valider un plan de marquage avec Testcafé"><br>
<small>Photo by <a href="https://unsplash.com/@robb_leahy?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Robb Leahy</a> / <a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Unsplash</a></small></p>
<p>Testcafé<sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup> est un outil de test end-to-end utilisé pour valider une interface Web et son comportement selon le navigateur utilisé par un visiteur. Il permet de scénariser la visite d'un internaute avec des instructions simples afin de s'assurer que l'application se comporte correctement.<br>
Cet outil permet également de surveiller les requêtes émises par l'application comme un chargement de fichier ou un appel asynchrone. C'est cette fonction qui va plus particulièrement nous intéresser car elle va nous permettre de récupérer toutes les requêtes sortantes vers l'outils <em>Xiti</em>.</p>
<p>Je ne rentrerai pas dans le détail de la configuration de l'outil car il est relativement facile à prendre en main et dispose d'une solide documentation<sup class="footnote-ref"><a href="#fn3" id="fnref3">[3]</a></sup>. Je mettrai cependant suffisamment de commentaires pour que chaque portion de code soit compréhensible.</p>
<h1 id="miseenplacedestests">Mise en place des tests</h1>
<p><img src="https://images.unsplash.com/photo-1488590528505-98d2b5aba04b?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ&amp;s=6bfea06674b33a2332da1b315b31e16b" alt="Valider un plan de marquage avec Testcafé"><br>
<small>Photo by <a href="https://unsplash.com/@lucabravo?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Luca Bravo</a> / <a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Unsplash</a></small></p>
<h2 id="validerlaffichagedunepage">Valider l'affichage d'une page</h2>
<p>Pour valider l'affichage d'une page il est nécessaire de connaitre la valeurs de plusieurs variables :</p>
<ul>
<li><em>Nom de page</em></li>
<li><em>Chapitrage :</em> Il est facultatif et peut être découpé en trois parties (chapitre 1, chapitre 2 et chapitre 3). Généralement il s'apparente à l'arborescence du site.</li>
</ul>
<p>Une fois ces données connues, nous pouvons écrire notre test dans le fichier <code>load.js</code> :</p>
<pre><code class="language-javascript">// Import required modules
import { Selector, RequestLogger } from 'testcafe';

// Set URL pattern for request logger
const xiti = /xiti.com/
// Set request logger
const logger = RequestLogger(xiti, {
    logRequestHeaders: true,
    logResponseHeaders: true,
    logResponseBody: true
});

// Declare fixture
fixture `Page Load`
    // Attach logger for all tests
    .requestHooks( logger );

test
    // Navigate
    .page( 'http://www.example.com' )
    ('Test name', async t =&gt; {
        const pageName = 'homepage';

        await t
            // Wait 5 seconds until the page is loaded (libs, images, DOMContentLoaded event)
            .wait(5000)
            // Expect that ...
            .expect(
                // At least one HTTP request ...
                logger.contains(record =&gt; {
                    var url = require( 'url' );
                    var request = new url.URL(record.request.url);

                    // Contains parameters with the correct value
                    return request.searchParams.get(&quot;p&quot;) == pageName;
                })
            )
            // Validate test or display error
            .ok('Error : Tag failed');
    });
</code></pre>
<p>Pour résumer rapidement ce test, nous allons afficher une page et attendre 5 secondes qu'elle soit complètement chargée. Pendant son chargement nous allons capturer toutes les requêtes vers le domaine <em>xiti.com</em>. Nous allons ensuite parcourir toutes les requêtes vers <em>xiti.com</em> afin de vérifier qu'au moins l'une d'entre elle comporte le paramètre <code>p</code> avec la bonne valeur.</p>
<p>Pour lancer le test nous exécutons la commande suivante :</p>
<pre><code>$&gt; docker run \
    --rm \
    --name testcafe \
    -a stdout \
    -a stderr \
    -v $(pwd):/tests \
    testcafe/testcafe \
    firefox /tests/load.js
    
 Running tests in:
 - Firefox 57.0.0 / Linux 0.0.0

 Page Load
 ✓ Test name
</code></pre>
<h2 id="validerunclicdenavigation">Valider un clic de navigation</h2>
<p>Globalement, il ne va pas se passer grand chose de plus que sur l'affichage d'une page. La seule différence consiste à intéragir avec la page, à savoir cliquer sur un élément.<br>
Les pages d'un site étant relativement vivantes de nos jour, il n'est pas rare de devoir naviguer un peu avant d'atteindre l'élément que l'on souhaite tester si celui-ci n'est pas visible par défaut.</p>
<p>Il y a également des paramètres différents à valider :</p>
<ul>
<li><em>Chapitrage</em></li>
<li><em>Libellé du clic</em></li>
<li><em>Type de clic :</em> exit, download, action ou navigation</li>
<li><em>Identifiant</em></li>
</ul>
<p>Nous rédigeons notre test dans le fichier <code>click.js</code> :</p>
<pre><code class="language-javascript">// Import required modules
import { Selector, RequestLogger } from 'testcafe';
// Set URL pattern for request logger
const xiti = /xiti.com/
// Set request logger
const logger = RequestLogger(xiti, {
    logRequestHeaders: true,
    logResponseHeaders: true,
    logResponseBody: true
});
// Click type object
const clickType = {
    exit: 'S',
    download: 'T',
    navigation: 'N',
    action: 'A'
};

// Declare fixture
fixture `Click`
    // Attach logger
    .requestHooks( logger );

test
    // Navigate
    .page( 'http://www.example.com' )
    ('Click submenu1', async t =&gt; {
        const type = 'navigation',
            chapter1 = 'myChapter1',
            chapter3 = 'myChapter3',
            label = 'monitoredLink',
            marker = chapter1 + &quot;::&quot; + chapter3 + &quot;::&quot; + label,
            targets = [
                '#menu a.submenu1',
                '#submenu1 a.out'
                ];

        // Wait 5 seconds until the page is loaded (libs, images, DOMContentLoaded event)
        await t.wait(5000);
        
        // Clear logged requests
        logger.clear();
        
        // Navigate to the final target
        for (let i = 0; i &lt; targets.length; i++) {
            await t.click( targets[i] );    
        }
        
        // Test click tag
        await t
             // Expect that ...
            .expect(
                // At least one HTTP request ...
                logger.contains(record =&gt; {
                    var url = require( 'url' );
                    var request = new url.URL(record.request.url);

                    // Contains parameters with the correct value
                    return request.searchParams.get(&quot;p&quot;) == marker &amp;&amp;
                        request.searchParams.get(&quot;click&quot;) == clickType[type];
                })
            )
            // Validate test or display error
            .ok('Errorr : Tag failed');
    });
</code></pre>
<p>Il ne reste plus qu'à valider le test</p>
<p>Comme dans l'exemple précédent, nous allons afficher la page et attendre 5 secondes qu'elle soit complètement chargée. Nous nettoyons ensuite l'ensemble des requêtes capturées afin de ne pas polluer notre monitoring avec des données qui pourraient être fausses.<br>
Afin d'accéder à l'élément à tester, un sous-menu fictif non affiché par défaut, nous simulons les actions d'un utilisateur pour le rendre visible. Ensuite, comme pour le cas précédent, nous allons parcourir toutes les requêtes vers <em>xiti.com</em> afin de vérifier qu'au moins l'une d'entre elle comporte les bonnes valeurs de paramètre (ici nous allons tester les paramètres <code>p</code> et <code>click</code>).</p>
<p>Nous lançons ce nouveau test avec la commande suivante :</p>
<pre><code>$&gt; docker run \
    --rm \
    --name testcafe \
    -a stdout \
    -a stderr \
    -v $(pwd):/tests \
    testcafe/testcafe \
    firefox /tests/click.js
    
 Running tests in:
 - Firefox 57.0.0 / Linux 0.0.0

 Click
 ✓ Click submenu1
</code></pre>
<h2 id="valideruneautopromotion">Valider une auto-promotion</h2>
<p>Avant de rentrer dans le détail de l'implémentation il faut savoir qu'une auto-promotion n'est ni plus ni moins que la combinaison des deux types de marqueurs précédents à la différence qu'on ne teste pas les mêmes paramètres.<br>
En effet, une auto-promotion consiste à effectuer au moins un des événements suivants :</p>
<ul>
<li>Afficher un élément promu</li>
<li>Cliquer sur un élément promu</li>
</ul>
<p>Il y aura donc deux événements à surveiller. Le premier au chargement de la page et le second, s'il est activé, au clic sur un élément promu.</p>
<p>Nous rédigeons notre test dans le fichier <code>self-promoted.js</code> :</p>
<pre><code class="language-javascript">// Import required modules
import { Selector, RequestLogger } from 'testcafe';

// Set URL pattern for request logger
const xiti = /xiti.com/
// Set request logger
const logger = RequestLogger(xiti, {
    logRequestHeaders: true,
    logResponseHeaders: true,
    logResponseBody: true
});

// Declare fixture
fixture `Click`
    // Attach logger
    .requestHooks( logger );
    // Navigate
    .page( 'http://www.example.com' )

test
    ('Web callback', async t =&gt; {
        const id = 11,
            chapter1 = 'myChapter1',
            chapter2 = 'myChapter2',
            chapter3 = 'myChapter3',
            label = 'monitoredLink',
            marker = 'INT-' + id + '-[' + chapter1 + '::' + chapter2 + '::' + chapter3 + '::' + label + ']',
            target = '#callBack';

        // Test print tag
        await t
            // Wait 5 seconds until the page is loaded (libs, images, DOMContentLoaded event)
            .wait(5000)
            // Expect that ...
            .expect(
                // At least one HTTP request ...
                logger.contains(record =&gt; {
                    var url = require( 'url' );
                    var request = new url.URL(record.request.url);

                    // Contains parameters with the correct value (sort of :) )
                    return request.searchParams.get(&quot;ati&quot;).indexOf(marker) != -1;
                })
            )
            .ok('Error : Print tag failed');

        // Clear logged requests
        logger.clear();

        // Test click tag
        await t.click( target )
            // Expect that ...
            .expect(
                // At least one HTTP request ...
                logger.contains(record =&gt; {
                    var url = require( 'url' );
                    var request = new url.URL(record.request.url);

                    // Contains parameters with the correct value
                    return request.searchParams.get(&quot;atc&quot;) &amp;&amp; 
                        request.searchParams.get(&quot;atc&quot;).indexOf(marker) != -1;
                })
            )
            // Validate test or display error
            .ok('Error : Click tag failed');
    });
</code></pre>
<p>Nous allons donc dans un premier temps valider, après le chargement de la page qu'un <em>tag d'impression</em> soit envoyé puis nous allons simuler une action utilisateur afin de valider par la suite que le tag de clic soit bien envoyé à son tour.<br>
Il faut noter que les paramètres à tester ne sont plus les mêmes car il dépendent du type de tag mis en place.</p>
<p>Nous lançons maintenant le nouveau test avec la commande suivante :</p>
<pre><code>$&gt; docker run \
    --rm \
    --name testcafe \
    -a stdout \
    -a stderr \
    -v $(pwd):/tests \
    testcafe/testcafe \
    firefox /tests/self-promoted.js
    
 Running tests in:
 - Firefox 57.0.0 / Linux 0.0.0

 Click
 ✓ Web callback
</code></pre>
<h1 id="conclusion">Conclusion</h1>
<p>Avec quelques outils et très peu de configuration, il est possible de valider la mise en place d'une solution de suivi d'audience telle que <em>Xiti</em>. Il est à mon avis tout à fait possible de réaliser la même chose avec <em>Google Analytics</em>.</p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>AT Internet (Applied Technologies Internet) est une entreprise française créée en 1996 spécialisée dans la mesure d'audience et de performance de sites web, mobiles, applications et réseaux sociaux. AT Internet est l’éditeur historique de l’outil de mesure XiTi, connu dans les années 2000. (<a href="https://fr.wikipedia.org/wiki/AT_Internet">Wikipedia</a>) <a href="#fnref1" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn2" class="footnote-item"><p><a href="https://devexpress.github.io/testcafe/">Testcafé</a> <a href="#fnref2" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn3" class="footnote-item"><p><a href="https://devexpress.github.io/testcafe/documentation/getting-started/">Documentation Testcafé</a> <a href="#fnref3" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Naissance d'un blog]]></title><description><![CDATA[Allez on se lance.

Depuis le temps que je me dis qu'il faut que j'écrive quelques articles sur ce que je fais... me voila !]]></description><link>https://blog.xylphid.net/naissance-dun-blog/</link><guid isPermaLink="false">5b4898fe9c7cf000018495d3</guid><dc:creator><![CDATA[Anthony PERIQUET]]></dc:creator><pubDate>Sun, 15 Jul 2018 10:15:19 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1470114510979-0a6062f10aee?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ&amp;s=d070a98c5f8eccf52661a4b06f5a5744" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://images.unsplash.com/photo-1470114510979-0a6062f10aee?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ&s=d070a98c5f8eccf52661a4b06f5a5744" alt="Naissance d'un blog"><p>Allez on se lance.</p>
<p>Depuis le temps que je me dis qu'il faut que j'écrive quelques articles sur ce que je fais... me voila !</p>
<p><img src="https://images.unsplash.com/photo-1503551723145-6c040742065b?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ&amp;s=5ddc8ede2d2a0a1eb9a43d0c03c6a762" alt="Naissance d'un blog"><br>
<small>Photo by <a href="https://unsplash.com/@pperkins?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Patrick Perkins</a> / <a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Unsplash</a></small></p>
<p>Pour me présenter rapidement, je suis Anthony PERIQUET, un ingénieur en informatique plutôt orienté développement et agilité mais tout de même intéressé par le reste des possibilités qu'offre ce grand domaine technologique. Je me suis passionné dernièrement pour Docker, aussi je pense qu'une bonne partie des articles à venir abordera ce sujet ou ce qui tourne autour.</p>
<p>Globalement je suis assez curieux de comprendre comment fonctionne les choses et il n'est pas rare que je réinvente la roue afin d'être sûr de bien m'être approprié le sujet. Je suis aussi ce que l'on pourrait qualifier d'Artisan informatique car je préfère globalement tout faire moi-même afin de maitriser la totalité de mon travail. Ce n'est pas forcément optimal en terme de productivité c'est pourquoi j'adopte plutôt cette pratique sur mes projets personnels plutôt que professionnels :).</p>
<p>Bon, assez parlé de moi ...<br>
A une prochaine fois pour un sujet plus technique :)</p>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>