Etienne Magro (blog)https://blog.etienne-magro.fr/2023-04-24T22:00:00+02:00DevOpsL'auto-hébergement2023-04-11T15:00:00+02:002023-04-24T22:00:00+02:00Etienne Magrotag:blog.etienne-magro.fr,2023-04-11:/autohebergement.html<p>Pourquoi se mettre à l'auto-hébergement et comment ?</p><p>Petite note, je fais cet article pour plusieurs raisons :</p>
<ul>
<li>partager une de mes passions et un des trucs qui fait que les gens me regarde avec des yeux ronds :-D</li>
<li>suite à un repas entre amis, on m'a demandé conseil pour débuter dans la pratique de l'auto-hébergement. Alors pour en faire profiter un maximum de monde, j'ai couché tout ça par écrit</li>
<li>promouvoir à ma très petite échelle la pratique</li>
</ul>
<p>/!\ Attention, ce blogpost est touffu et très décousu. Il n'est en rien un tuto (je ne suis pas à 2-3 approximations près), mais plutôt des pensées écrites au fil de l'eau avec un semblant d'organisation. Si ça vous a donné envie de vous lancer, partagez-le-moi, ça fera toujours plaisir. Et si vous avez des questions, contactez-moi :-)</p>
<h2 id="aspect-politique-et-philosophique">Aspect politique et philosophique</h2>
<p>Je vais commencer ce blogpost par présenter un peu la vision politique et philosophique qu'a pris l'auto-hébergement pour moi. Au départ, c'était surtout pour "mettre les mains dans le cambouis", m'amuser à installer un serveur web par-ci ou un serveur git par-là. Et au fur et à mesure la pratique technique s'est également accompagniée d'une revendication politique. Détaillons un peu ça.</p>
<p>L'auto-hébergement, self-hosting en anglais, consiste en l'hébergement des services informatiques dont on a besoin (ou envie) par soi-même. On pourrait rajouter d'autres éléments, mais ça excluerait de fait autant de tentatives pourtant louable : serveurs en propre, par exemple. Pour un professionnel, c'est héberger son site web institutionnel sur sa propre infrastructure, ainsi que tous les outils utilisés par les employés. Pour un particulier, ça démarre généralement avec un PC installé dans la DMZ de la box ou sur un dédié ou un VPS chez un fournisseur.</p>
<p>L'idée derrière cette pratique, c'est de (re-)prendre le controle sur les services informatiques que l'on consomme, et d'être le plus autonome sur son propre environnement technologique. Au niveau étatique, c'est une composante importante de la "souveraineté technologique". En creux, on trouve l'envie de se soustraire aux controles exercés ou exerçable par les grandes entreprises de la tech et d'éventuels gouvernements présents ou futurs, et par là-même la réaffirmation du droit à la vie privée et son application la plus strict et personnelle. C'est particulièrement mon cas. On peut aller jusqu'à y trouver pour certains un acte de résistance contre la centralisation d'Internet.</p>
<p>Pour moi, cet aspect d'être maître de mes outils et de mes données est le plus important. Je ne veux pas que les photos de mes vacances familiales soient stockées sur des serveurs qui ne m'appartiennent pas et qui pourraient être exploitées. Mes photos de vacances sont mes souvenirs à moi. Je ne veux pas que quelqu'un se les approprie. On parle également de mon carnet d'adresse perso, de mes agendas, et ça va jusqu'au code que je produis pour mes hobbys ou mes habitudes de lecture de journaux/blog/BD/manga/comics/romans/etc...</p>
<p>Oui, les journaux/blog/BD/etc... que je lis ne regardent pas d'autres personnes (ou entreprises, ou états). Tous autant qu'ils sont, ils sont le reflet de mes opinions, revendications, incertitudes, humeurs, interrogations (et des changements dans celles-ci) et je veux que personne ne puisse les utiliser.</p>
<p>Avant de passer à la technique pure et dure, je vais ajouter que c'est aussi une manière d'apprendre de nouvelles choses et de ne pas rester rouiller sur mes acquis. Je suis obliger de constamment <em>me</em> mettre à jour pour toujours avoir des services à jour. C'est également un laboratoire à ciel ouvert et ça forge aussi une certaine expertise (qui s'est déjà fait poutrer son serveur parce qu'il ne l'avait pas sécurisé comme il faut ? C'est bibi :-D)</p>
<h2 id="aspect-materiel">Aspect matériel</h2>
<p>Ok, maintenant qu'on a abordé l'auto-hébergement sous son jour politique, passons au concret : comment fait-on ? En premier il va falloir un serveur. Une machine sur laquelle on va installer les services que l'on désire.</p>
<p>Le plus facile, c'est d'utiliser un vieux PC qu'on n'utilise plus et qui prend la poussière dans un coin. On installe Linux dessus, et hop, ça fait un serveur. Ou bien le Raspberry Pi dont on ne sait pas quoi faire. C'est comme ça qu'à titre perso j'ai commencé : quand j'étais étudiant, mes parents ont remplacé le PC famillial par un nouveau, j'ai récupéré l'ancien et j'ai y installé un Debian 3.</p>
<p>Si on ne dispose pas d'une machine libre, on peut se tourner vers la location d'une VM ou d'un serveur dédié. On trouve des VPS et des dédiés par cher (4-6€/mois) qui sont parfaits pour débuter. Voire même de manière pérenne.</p>
<p>Si vous voulez <em>vraiment</em> investir et ne savez pas quoi acheter, le champs des possibles est trop grand pour que je puisse vous conseiller comme ça. Si vous n'avez de workload prévu, tournez-vous vers des mini-PC (j'adore ce form-factor) et dites-vous que plus le CPU est récent, moins il consommera d'électricité. A titre personnel, j'ai acheté un mini-PC sur une base d'intel celeron J4125 à ~150€ il y a bientôt 2 ans. En terme de puissance, c'est laaaaargement suffisant pour le workload familial. J'ai juste augmenté sa RAM à 16Go, son disque à 2To de HDD et 120Go de SSD et il me fera au moins 5 ans, voire 7-8 si je n'ai pas de grosse surprise.</p>
<p>Si vous avez besoin d'un gros stockage, le sujet est pour une autre fois ;-)</p>
<h2 id="aspect-logiciel">Aspect logiciel</h2>
<h3 id="los">L'OS</h3>
<p>Maintenant que l'on a un matériel, il faut savoir quoi installer dessus. Si vous louez un VPS ou un dédié, vous avez le choix entre plusieurs linux (qui voudrait faire de l'hébergement avec autre chose, franchement <Troll Inside :-D>), prenez celui avec lequel vous êtes le plus à l'aise. Sinon, un ubuntu ou un debian fera très bien l'affaire.</p>
<p>Si vous êtes sur un hardware que vous maîtrisez, vous avez également la possibilité de partir sur de la virtualisation. ESXi, Proxmox sont les plus connus et recommandé. C'est aussi une manière de se faire une architecture n-tiers, ou de se faire une/des VM "propre" qui hébergera les services à demeure et une/des VM "bac à sable" pour les expérimentations et qui pourront être trashées/recréées autant que l'on veut.</p>
<p>Pour des débutants, le mieux, à mon avis, c'est de partir sur un Debian le plus simple possible, sans VM, sans fioritures, le plus classique et pour lequel on pourra trouver le plus facilement de l'aide en ligne.</p>
<h3 id="comment-installer-les-services">Comment installer les services</h3>
<p>La manière de gérer les services que l'on installe sur les serveurs est importante. Parce qu'elle doit être le reflet de l'organisation que l'on a ou souhaite.</p>
<p>On peut le faire en mode "roots" : <code>apt get install</code>. C'est parfait si l'on sait que l'on aura qu'un seul service d'installé ou que l'ensemble des services ne vont pas se marcher sur les pieds.<br>
On peut le faire en mode "docker à la main" : à chaque fois on se logue en SSH sur le serveur et on pratique la cli docker (<code>docker pull</code>, <code>docker run</code>, etc)<br>
On peut le faire avec docker-compose : on commence à organiser ses installations avec un/des fichiers docker-compose.<br>
On peut le faire avec ansible/puppet/salt : là, c'est le début de la professionnalisation<br>
On peut le faire avec un k8s ou k3s : on est pas là pour lancer une start-up, passez votre chemin :-D</p>
<p>A titre personnel, j'utilise ansible, profession oblige. Mais à mon avis, un/des docker-compose seront parfait pour des débutants et des moins-débutants.
Je vais ajouter également qu'à titre perso, je suis utilisateur de docker sur mon infra perso (notez que j'insiste sur le "perso". Et aussi, podman à la place de docker sur le dernier serveur en date, pour expérimenter un peu). Ainsi l'installation de mes services est pilotée par ansible, et les services eux-même tournent sur les serveurs dans des containeurs. C'est ce qui me semble le plus propre à l'échelle hobby/serveur familial, aujourd'hui.</p>
<h3 id="quels-services-installer">Quels services installer</h3>
<p>A ce niveau-là, ce que vous voulez !</p>
<p>Littéralement des <a href="https://github.com/awesome-selfhosted/awesome-selfhosted" title="Awesome selfhosted, annuaire de service auto-hébergeable sur github">milliers de possibilités</a> s'offrent à vous.</p>
<p>Je vais juste vous indiquer ce que j'auto-héberge, sur mon mini-PC :</p>
<ul>
<li><a href="https://nextcloud.com/">Nextcloud</a>, pour la synchronisation des fichiers, photos, contacts, agenda, notes, recettes de cuisine et messagerie familiale</li>
<li><a href="https://funkwhale.audio/">Funkwhale</a>, pour écouter ma musique où et quand je veux</li>
<li><a href="https://gitea.io/">Gitea</a>, un serveur git ultra-léger, mais aussi très complet</li>
<li><a href="https://pi-hole.net/">PiHole</a>, qui fait la résolution DNS dans la maison, ainsi qu'un premier niveau de blocage de pub et trackers</li>
<li><a href="https://kresus.org/">Kresus</a>, pour la gestion des finances familiales</li>
<li><a href="https://searx.github.io/searx/">Searx</a>, le meta-moteur de recherche</li>
<li><a href="https://github.com/janeczku/calibre-web">Calibre-web</a>, pour faciliter la gestion et la distribution de nos livres électroniques (pour nos téléphones, tablettes et liseuse)</li>
<li><a href="https://freshrss.org/">FreshRSS</a>, chouette lecteur de flux RSS, pour ma veille techno et non-techno</li>
<li>Mes <a href="https://github.com/docker-mailserver/docker-mailserver">mails</a>, avec une très jolie <a href="https://www.rainloop.net/">webUI</a>, même si ça devient de <a href="https://blog.dahanne.net/2021/10/01/auto-hebergement-des-courriels-en-2021-encore-possible-mais/">plus en plus compliqué</a> avec les années (cf <a href="https://framablog.org/tag/mail/">Framasoft</a>)</li>
<li>le site web de mon beau-frère</li>
<li>la webapp que j'ai codé pour lire mes mangas/comics</li>
<li><a href="https://blog.etienne-magro.fr">mon blog</a>, avec <a href="https://getpelican.com/">Pelican</a></li>
<li>le future blog de mon épouse</li>
<li><a href="http://info-medoc.fr/">Info-medoc</a>, un petit projet perso d'il y a quelques années</li>
<li>Plusieurs autres projets perso, futurs ou anciens</li>
</ul>
<h2 id="on-y-va">On y va ?</h2>
<h3 id="sur-le-serveur-avec-docker">Sur le serveur avec docker</h3>
<p>Forcément, je ne pourrais pas être exhaustif, alors voilà le postulat de départ :</p>
<ul>
<li>Vous disposez d'une machine debian minimaliste fraichement installée et à jour sur laquelle vous vous connectez en SSH</li>
<li>Vous avez choisi l'option de facilité en déployant vos services avec Docker + docker-compose</li>
<li>Vous êtes sur une machine avec une IP publique (chez un hébergeur ou si le serveur est chez vous, il doit y avoir un paramétrage dans votre box pour placer votre serveur dans la DMZ. Et hop, c'est tout pareil ensuite).</li>
<li>Il y aura moins de 10 personnes qui utiliserons votre service. Je pars sur une installation type familiale, plus d'utilisateurs pourrait nécessité une configuration différente.</li>
</ul>
<p>Commençons par installer docker (<a href="https://docs.docker.com/engine/install/debian/">doc officielle</a>) :</p>
<div class="highlight"><pre><span></span><code>sudo<span class="w"> </span>apt-get<span class="w"> </span>remove<span class="w"> </span>docker<span class="w"> </span>docker-engine<span class="w"> </span>docker.io<span class="w"> </span>containerd<span class="w"> </span>runc<span class="w"> </span><span class="c1"># On enlève ce qui pourrait gêner</span>
sudo<span class="w"> </span>apt-get<span class="w"> </span>update
sudo<span class="w"> </span>apt-get<span class="w"> </span>install<span class="w"> </span>ca-certificates<span class="w"> </span>curl<span class="w"> </span>gnupg<span class="w"> </span><span class="c1"># On installe les dépendances nécessaire à l'usage du dépôt de package docker</span>
sudo<span class="w"> </span>install<span class="w"> </span>-m<span class="w"> </span><span class="m">0755</span><span class="w"> </span>-d<span class="w"> </span>/etc/apt/keyrings
curl<span class="w"> </span>-fsSL<span class="w"> </span>https://download.docker.com/linux/debian/gpg<span class="w"> </span><span class="p">|</span><span class="w"> </span>sudo<span class="w"> </span>gpg<span class="w"> </span>--dearmor<span class="w"> </span>-o<span class="w"> </span>/etc/apt/keyrings/docker.gpg<span class="w"> </span><span class="c1"># On installe la clé GPG de docker qui va signer les packages</span>
sudo<span class="w"> </span>chmod<span class="w"> </span>a+r<span class="w"> </span>/etc/apt/keyrings/docker.gpg
<span class="nb">echo</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span><span class="s2">"deb [arch="</span><span class="k">$(</span>dpkg<span class="w"> </span>--print-architecture<span class="k">)</span><span class="s2">" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \</span>
<span class="s2"> "</span><span class="k">$(</span>.<span class="w"> </span>/etc/os-release<span class="w"> </span><span class="o">&&</span><span class="w"> </span><span class="nb">echo</span><span class="w"> </span><span class="s2">"</span><span class="nv">$VERSION_CODENAME</span><span class="s2">"</span><span class="k">)</span><span class="s2">" stable"</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="se">\</span>
<span class="w"> </span>sudo<span class="w"> </span>tee<span class="w"> </span>/etc/apt/sources.list.d/docker.list<span class="w"> </span>><span class="w"> </span>/dev/null<span class="w"> </span><span class="c1"># On configure le dépôt officiel de docker</span>
sudo<span class="w"> </span>apt-get<span class="w"> </span>update
sudo<span class="w"> </span>apt-get<span class="w"> </span>install<span class="w"> </span>docker-ce<span class="w"> </span>docker-ce-cli<span class="w"> </span>containerd.io<span class="w"> </span>docker-buildx-plugin<span class="w"> </span>docker-compose-plugin<span class="w"> </span><span class="c1"># Installation à proprement parler de docker</span>
</code></pre></div>
<h3 id="premier-service-heberge">Premier service hébergé</h3>
<p>Pour aller au plus simple, je vais partir sur l'installation d'un <a href="https://nextcloud.com/">Nextcloud</a> avec docker-compose. On va d'ailleur fortement s'inspirer de la <a href="https://hub.docker.com/_/nextcloud">documentation officielle</a>.
Mettez ce qui suit dans un fichier <code>docker-compose.yml</code> :</p>
<div class="highlight"><pre><span></span><code><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s">'3'</span>
<span class="nt">volumes</span><span class="p">:</span>
<span class="w"> </span><span class="nt">nextcloud</span><span class="p">:</span>
<span class="nt">services</span><span class="p">:</span>
<span class="w"> </span><span class="nt">nextcloud</span><span class="p">:</span>
<span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">nextcloud</span>
<span class="w"> </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">always</span>
<span class="w"> </span><span class="nt">ports</span><span class="p">:</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">8080:80</span>
<span class="w"> </span><span class="nt">volumes</span><span class="p">:</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">nextcloud:/var/www/html</span>
<span class="w"> </span><span class="nt">environment</span><span class="p">:</span>
<span class="w"> </span><span class="nt">SQLITE_DATABASE</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">nextcloud.sqlite3</span>
</code></pre></div>
<p>Puis lancez votre docker-compose :</p>
<div class="highlight"><pre><span></span><code>docker-compose<span class="w"> </span>up<span class="w"> </span>-d
</code></pre></div>
<p>Bravo, vous pouvez vous rendre sur l'URL <code>http://<IP_DE_VOTRE_SERVEUR>:8080</code> et vous devriez pouvoir complétez l'installation de votre nextcloud.</p>
<p>Facile, non ?</p>
<h3 id="reverse-proxy-et-https">Reverse-proxy et HTTPS</h3>
<p>Suposons maintenant que vous vouliez pouvoir accéder à votre serveur nextcloud avec un nom de domaine et sur le "bon" port. Il faut que vous achetiez/louiez un nom de domaine chez votre registrar préféré et que vous le fassiez pointer (enregistrement A pour l'IPv4 et AAAA pour l'IPv6) vers l'adresse IP publique de votre serveur (ou box). Un fois cela fait, on va rajouter le reverse-proxy <code>traefik</code> qui va s'occuper du certificat pour vous. Comme toujours, on va s'appuyer sur la <a href="https://doc.traefik.io/traefik/user-guides/docker-compose/acme-http/">doc officielle</a>.</p>
<p>En premier lieu, arrêtez votre premier service (<code>docker-compose down</code>), puis modifiez le fichier <code>docker-compose.yml</code> comme suit :</p>
<div class="highlight"><pre><span></span><code><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s">'3'</span>
<span class="nt">volumes</span><span class="p">:</span>
<span class="w"> </span><span class="nt">nextcloud</span><span class="p">:</span>
<span class="w"> </span><span class="nt">letsencrypt</span><span class="p">:</span>
<span class="nt">services</span><span class="p">:</span>
<span class="w"> </span><span class="nt">rproxy</span><span class="p">:</span>
<span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">traefik</span>
<span class="w"> </span><span class="nt">command</span><span class="p">:</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"--providers.docker=true"</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"--providers.docker.exposedbydefault=false"</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"--entrypoints.web.address=:80"</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"--entrypoints.websecure.address=:443"</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"--certificatesresolvers.myresolver.acme.httpchallenge=true"</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"--certificatesresolvers.myresolver.acme.email=<VOTRE_ADRESSE@EMAIL.com>"</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"</span>
<span class="w"> </span><span class="nt">ports</span><span class="p">:</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"80:80"</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"443:443"</span>
<span class="w"> </span><span class="nt">volumes</span><span class="p">:</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"letsencrypt:/letsencrypt"</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"/var/run/docker.sock:/var/run/docker.sock:ro"</span>
<span class="w"> </span><span class="nt">nextcloud</span><span class="p">:</span>
<span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">nextcloud</span>
<span class="w"> </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">always</span>
<span class="w"> </span><span class="nt">volumes</span><span class="p">:</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">nextcloud:/var/www/html</span>
<span class="w"> </span><span class="nt">environment</span><span class="p">:</span>
<span class="w"> </span><span class="nt">SQLITE_DATABASE</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">nextcloud.sqlite3</span>
<span class="w"> </span><span class="nt">labels</span><span class="p">:</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"traefik.enable=true"</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"traefik.http.routers.nextcloud.rule=Host(`moncloudprive.mondomaine.example.com`)"</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"traefik.http.routers.nextcloud.entrypoints=websecure"</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"traefik.http.routers.nextcloud.tls.certresolver=myresolver"</span>
</code></pre></div>
<p>On relance le docker-compose :</p>
<div class="highlight"><pre><span></span><code>docker-compose<span class="w"> </span>up<span class="w"> </span>-d
</code></pre></div>
<p>Et maintenant, rendez-vous sur <code>https://moncloudprive.mondomaine.example.com</code>. Vous devriez voir votre service nextcloud.</p>
<h3 id="un-serveur-git">Un serveur git ?</h3>
<p>Pour l'exercice, rajoutons un serveur git qui va nous permettre de stocker notre fichier <code>docker-compose.yml</code> comme il faut. Après avoir arrêté nos services avec <code>docker-compose down</code>, modifions le fichier comme suit :</p>
<div class="highlight"><pre><span></span><code><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s">'3'</span>
<span class="nt">volumes</span><span class="p">:</span>
<span class="w"> </span><span class="nt">nextcloud</span><span class="p">:</span>
<span class="w"> </span><span class="nt">letsencrypt</span><span class="p">:</span>
<span class="w"> </span><span class="nt">gitea</span><span class="p">:</span>
<span class="nt">services</span><span class="p">:</span>
<span class="w"> </span><span class="nt">traefik</span><span class="p">:</span>
<span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">traefik</span>
<span class="w"> </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">always</span>
<span class="w"> </span><span class="nt">command</span><span class="p">:</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"--providers.docker=true"</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"--providers.docker.exposedbydefault=false"</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"--entrypoints.web.address=:80"</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"--entrypoints.websecure.address=:443"</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"--certificatesresolvers.myresolver.acme.httpchallenge=true"</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"--certificatesresolvers.myresolver.acme.email=<VOTRE_ADRESSE@EMAIL.com>"</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"</span>
<span class="w"> </span><span class="nt">ports</span><span class="p">:</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"80:80"</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"443:443"</span>
<span class="w"> </span><span class="nt">volumes</span><span class="p">:</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"letsencrypt:/letsencrypt"</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"/var/run/docker.sock:/var/run/docker.sock:ro"</span>
<span class="w"> </span><span class="nt">nextcloud</span><span class="p">:</span>
<span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">nextcloud</span>
<span class="w"> </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">always</span>
<span class="w"> </span><span class="nt">volumes</span><span class="p">:</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">nextcloud:/var/www/html</span>
<span class="w"> </span><span class="nt">environment</span><span class="p">:</span>
<span class="w"> </span><span class="nt">SQLITE_DATABASE</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">nextcloud.sqlite3</span>
<span class="w"> </span><span class="nt">expose</span><span class="p">:</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"80"</span>
<span class="w"> </span><span class="nt">labels</span><span class="p">:</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"traefik.enable=true"</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"traefik.http.routers.nextcloud.rule=Host(`moncloudprive.mondomaine.example.com`)"</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"traefik.http.routers.nextcloud.entrypoints=websecure"</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"traefik.http.routers.nextcloud.tls.certresolver=myresolver"</span>
<span class="w"> </span><span class="nt">gitea</span><span class="p">:</span>
<span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">gitea/gitea</span>
<span class="w"> </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">always</span>
<span class="w"> </span><span class="nt">volume</span><span class="p">:</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"{{</span><span class="nv"> </span><span class="s">app_data_dir</span><span class="nv"> </span><span class="s">}}/gitea:/data"</span>
<span class="w"> </span><span class="nt">ports</span><span class="p">:</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"222:22"</span>
<span class="w"> </span><span class="nt">expose</span><span class="p">:</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"3000"</span>
<span class="w"> </span><span class="nt">env</span><span class="p">:</span>
<span class="w"> </span><span class="nt">RUN_MODE</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">prod</span>
<span class="w"> </span><span class="nt">SSH_DOMAIN</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">monserveurgit.mondomaine.example.com</span>
<span class="w"> </span><span class="nt">SSH_PORT</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">222</span>
<span class="w"> </span><span class="nt">SSH_LISTEN_PORT</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">22</span>
<span class="w"> </span><span class="nt">INSTALL_LOCK</span><span class="p">:</span><span class="w"> </span><span class="s">"true"</span>
<span class="w"> </span><span class="nt">SECRET_KEY</span><span class="p">:</span><span class="w"> </span><span class="s">"MaCleSuperPrivee"</span>
<span class="w"> </span><span class="nt">PROTOCOL</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">https</span>
<span class="w"> </span><span class="nt">DOMAIN</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">monserveurgit.mondomaine.com</span>
<span class="w"> </span><span class="nt">ROOT_URL</span><span class="p">:</span><span class="w"> </span><span class="s">"https://monserveurgit.mondomaine.example.com/"</span>
<span class="w"> </span><span class="nt">ENABLE_GZIP</span><span class="p">:</span><span class="w"> </span><span class="s">"true"</span>
<span class="w"> </span><span class="nt">DISABLE_REGISTRATION</span><span class="p">:</span><span class="w"> </span><span class="s">"true"</span>
<span class="w"> </span><span class="nt">OFFLINE_MODE</span><span class="p">:</span><span class="w"> </span><span class="s">"true"</span>
<span class="w"> </span><span class="nt">labels</span><span class="p">:</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"traefik.enable=true"</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"traefik.http.routers.gitea.rule=Host(`monserveurgit.mondomaine.example.com`)"</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"traefik.http.routers.gitea.tls=true"</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"traefik.http.routers.gitea.entrypoints=websecure"</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"traefik.http.routers.gitea.tls.certresolver=</span><span class="nv"> </span><span class="s">myresolver"</span>
</code></pre></div>
<p>On commence à avoir l'habitude, maintenant : on relance les services avec <code>docker-compose up -d</code>. Vous devriez toujours avoir votre nextcloud sur <code>https://moncloudprive.mondomaine.example.com</code> et en plus votre serveur git sur <code>https://monserveurgit.mondomaine.example.com</code>.</p>
<p>Le reste ne dépend plus que de vous !</p>
<h2 id="note-de-fin">Note de fin</h2>
<p>Attention, le présent blogpost ne parle pas du tout de la sécurisation du serveur, des services, ni des données stockées. Si vous vous lancez dans l'auto-hébergement sérieusement, il faut vous renseigner sur ces sujets (peut-être une idée pour un prochain blogpost, tien). Si vous ne voulez pas voir vos serveurs devenir des mineurs de cryptocoin sans votre aval ou des bots au sein d'un réseau de piratage, ni vous le voir "confisqué" par un crypto-locker, ou vos données volées, il faut que vous creusiez sérieusement ces sujets.</p>Btrfs, c'est de la fucking black magic2021-09-07T19:00:00+02:002021-10-10T19:00:00+02:00Etienne Magrotag:blog.etienne-magro.fr,2021-09-07:/btrfs.html<p>Je n'ai jamais utilisé le filesystem Btrfs. J'ai du rajouter du disque sur mon NAS perso, c'était l'occasion de l'expérimenter. Ici, quelques commandes utiles pour bien débuter avec Btrfs sous linux.</p><h2 id="contexte">Contexte</h2>
<p>J'ai chez moi un NAS fait avec un <a href="https://n40l.fandom.com/wiki/Base_Hardware_N54L" title="Spécifications techniques du serveur">HP Proliant Gen7 N54L</a>. C'est une belle bête, un peu vieille maintenant, mais toujours vaillante, avec en standard une capacité de 4 disques 3,5". J'ai mis l'OS sur un disque supplémentaire (on peut le tweaker pour en rajouter jusqu'à 4 en plus) et j'utilisais jusqu'à ce jour 3 disques ainsi :</p>
<ul>
<li>sda : 3To (xfs) pour les gros fichiers de la maison (pensez vieux films non dispo en VOD que j'ai rippé pour notre usage familial, fichiers pour de l'animation 3D, etc.., ce genre de gros fichiers)</li>
<li>sdb : 3To (xfs) pour toutes les photos et les documents familiaux à garder le plus précieusement</li>
<li>sdc : 2To (ext4) backup manuel (mais cronné quand même) du sdb</li>
</ul>
<p>Pour les trucs vraiment importants (présents sur le sdb), j'ai d'autres backups, avec la <a href="https://www.nextinpact.com/article/30278/109000-quest-ce-que-strategie-sauvegarde-3-2-1" title="Explications sur le principe des 3-2-1 pour les sauvegardes">règle des 3-2-1</a>. L'idée c'était de ne pas perdre mes données en cas de défaillance d'un disque (tous achetés à des moments différents en veillant bien à ce qu'ils ne fasse pas partie d'un même lot de fabrication).<br>
Dernièrement, le sda s'est retrouvé quasiment plein. J'ai donc acheté un nouveau disque de 6To pour le remplacer. C'est là que je me suis dit que je pourrais basculer sur du Btrfs, qui est le FS par défaut sur certaines distrib' linux depuis pas mal de temps, mais que je n'avais jamais choisi.<br>
Sur un disque tout seul, c'est relativement simple, et ça s'utilise comme les autres FS.</p>
<div class="highlight"><pre><span></span><code>mkfs.btrfs /dev/sdX
mount /dev/sdX /media/ou_vous_voulez # N'oubliez pas le fstab
</code></pre></div>
<p>Et après ça s'oublie.</p>
<h2 id="raid">Raid</h2>
<p>Notes avant la suite : je suis sur un serveur perso chez moi, du coup, les reboot ne sont pas un problème, ce n'est pas de la prod en disponibilité 99,999%. Et je vais parler de "partitions", par abus de langage, même si le vrai terme n'est pas forcément celui-là.</p>
<p>Depuis maintenant plus de 10 ans, je cherche un système qui me permettrait d'avoir un FS unique et quand on manque de place, on rajoute un disque ou on en remplace un par un plus gros, et ça fonctionne sans plus de complications. Je me suis toujours tenu à l'écart de LVM et de mdraid. Au niveau perso, je trouve que ça rajoute des couches inutilement compliquées, avec pas mal de contraintes. Ça se justifie totalement dans un contexte professionnel, mais pour le serveur familial qui doit juste marcher dans un coin de la maison, ça me rebutait.<br>
Et le problème, pour moi, au-dela de la complexité, c'était l'impossibilité d'utiliser un environnement hétérogène, en terme de disque. Je ne veux pas avoir à remplacer <em>tous</em> les disques du serveurs si je veux juste augmenter sa capacité. Je veux juste en acheter un plus gros et c'est tout.</p>
<p>Et c'est là qu'est la force de Btrfs. On lui donne un nombre de disque que l'on veut, de capacités différentes, il s'en fout, il fait au mieux (qui est même mieux que ce que je pourrais faire avec un LVM+mdraid). Et ça marche !</p>
<p>Du coup, voici ce que j'ai fait (en root, et de mémoire, donc vérifiez avant de copier-coller) :</p>
<div class="highlight"><pre><span></span><code>mkfs.btrfs /dev/sdd # formattage du dernier disque de 6To ajouté en Btrfs
mount /dev/sdd /media/sdd
rsync -a /media/gros_fichiers /media/sdd
vim /etc/fstab # suppression de la ligne de montage du /dev/sda, et rajout de la ligne de montage du /dev/sdd à la place du sda sur /media/gros_fichiers
reboot # Ainsi, le nouveau disque sdd prend la place du vieux sda pour l'opérationel
btrfs device add -f /dev/sda /media/gros_fichiers # le '-f' est là pour forcer, parce que sinon, ça ne se fait pas car l'outil détecte la présence de l'ancien formattage en xfs (et c'est bien)
screen btrfs balance start -dconvert=raid1 -mconvert=raid1 /media/gros_fichiers
</code></pre></div>
<p>Il faut que j'explique, là.<br>
Après le formattage du sdd en Btrfs, j'ai donc une partition de 6To (je passe outre les pertes diverses, là n'est pas mon propos). Quand on fait le <code>btrfs device add</code>, ça étend la partition en utilisant le(s) disque(s) ajouté(s). Du coup, là, je me retrouve avec une partition de 6+3=9To. Un bon petit RAID0 les doigts dans le nez, sans rien faire d'autre. C'est magique !</p>
<p>Et ce qui est encore plus magique, c'est le <code>btrfs balance start -dconvert=raid1 -mconvert=raid1</code>. Ça convertit le RAID0 (6+3 To) en RAID1 (6+3 / 2 = 4,5 To). (Je reprécise, je m'en fous des chiffres exacts, ce n'est pas mon propos). Ça veut dire que Btrfs va s'arranger au mieux pour qu'une même donnée soit présente 2 fois sur des disques différents. Dans mon cas, c'est possible car justement toutes les data tenaient sur le disque de 3To.<br>
Ça prend un peu de temps, car il faut tout dupliquer, mais quand j'y suis revenu le lendemain, c'était terminé.</p>
<p>La suite, c'est donc d'y rajouter le sdc de 2To. Oui, oui, c'est possible, et c'est ce que j'ai fait devant vos yeux ébahis :</p>
<div class="highlight"><pre><span></span><code>btrfs device add -f /dev/sdc /media/gros_fichiers # toujours le '-f' car le disque était utilisé avant
</code></pre></div>
<p>Du coup, ça me donne en une seule commande un RAID1 sur 3 disques différents (6+3+2 / 2 = 5,5 To). Franchement, j'ai pas de mots pour dire à quel point je trouve ça merveilleux.<br>
J'ai terminé en rajoutant tout le reste dessus et en reconfigurant le /etc/fstab et le /etc/exports pour que tout fonctionne avec une seule "partition".</p>
<div class="highlight"><pre><span></span><code>rsync -a /media/fichiers_importants /media/gros_fichiers
vim /etc/fstab # Pour monter la partition unique Btrfs sur /media, maintenant (relisez les rsync et vous comprendrez)
reboot
btrfs device add -f /dev/sdb
screen btrfs balance start /media
</code></pre></div>
<p>On attend un petit moment, que toutes les données soient rebalancées sur les 4 disques. Une fois la redistribution faite, on a donc une seule "partition" Btrfs d'une capacité de 6+3+3+2 / 2 = 7To. Tout ça en RAID1 sur 4 disques de capacitées différentes.</p>
<p>Ai-je mentionné qu'hormis les reboot, tout le reste s'est fait <strong>pendant</strong> que les disques étaient utilisés ? Je bossais sur des documents/fichiers présents sur ces disques, exportés en NFS depuis un autre PC, sans rien remarquer.</p>
<p>C'est scandaleux tellement c'est facile. À ce niveau-là, on frole la magie.</p>
<h2 id="futur-nouveau-disque">Futur nouveau disque</h2>
<p>Si d'aventure je voulais rajouter un nouveau disque, il me suffirait d'acheter celui qui me convient le mieux en terme de coût et capacité à ce moment-là et de faire un <code>btrfs device add</code>, éventuellement un <code>btrfs balance start</code> et c'est tout.</p>
<p>Si je veux augmenter la capacité en remplaçant un disque, c'est un peu plus compliqué et risqué, mais en planifiant ça comme il faut ça devrait fonctionner. Par exemple, si je veux remplacer le disque de 2To par un de 6To. Les commandes suivantes me le permettront (WARNING : non-testé, à l'inverse des commandes précédentes. Ca <em>devrait</em> marcher, mais je ne l'ai pas encore vérifié) :</p>
<div class="highlight"><pre><span></span><code>btrfs device delete /dev/sdc /media
# Enlèvement du disque physique, et branchement du nouveau à la place
btrfs device add /dev/sdX /media
screen btrfs balance start /media
</code></pre></div>
<p>Je répète, on ne sait jamais, je n'ai pas encore testé ça. Il y a des choses à considérer avant d'enlever un disque d'un RAID, quel qu'il soit. Par exemple (non-exhaustif, et en plus d'un backup), il faut que la "partition" Btrfs ne soit pas pleine et que tous les autres disques puissent se répartir ce que contient le disque qu'on enlève, sinon, problèmes. Il ne faut pas que les autres disques tombent en panne pendant l'opération, non plus, sinon, vous êtes bon pour la perte de données.</p>
<p>EDIT : Au final, j'ai décidé de rester sur 3 disques (donc 6+3+3 / 2 = 6To) pour le moment, pour faciliter l'augmentation de capacité par remplacement (au fait, le <code>btrfs device delete</code> marche super bien, même en cours d'utilisation de la "partition"). Plutôt que de faire un <code>delete</code> puis <code>add</code>, le moment venu, je ferais l'inverse <code>add</code> d'un nouveau disque plus gros puis <code>delete</code> d'un des anciens disques. Ainsi je n'aurais pas à me soucier de vérifier trop régulièrement si j'ai la possibilité/capacité de faire le <code>delete</code>. Et aussi, ça me simplifiera la vie en cas de défaillance d'un des disques actuels. Dans un cas comme dans l'autre, il suffira de brancher un nouveau disque dans le 4e compartiment vide et de l'<code>add</code>, puis de retirer le disque crashé ou trop petit. Rester sur 3 disques et faire ainsi, me semble le mieux, vu mon use-case.</p>
<h2 id="snapshot">Snapshot</h2>
<p>On verra la gestion des snapshot dans un autre temps, mais forcément, vu mon setup et mon besoin, ça ne sera pas forcément un truc très standard (pas envie de gérer des subvolumes, par exemple). Je ferais sûrement quelque chose à base de <code>cp -r --reflink=auto /media/fichiers_importants /media/snapshot/fichiers_importants-$(date +'%Y-%m-%d')</code>, écrasé/rotaté régulièrement (Oui, ce <code>cp --reflink=auto</code> est une autre magie de Btrfs).</p>
<p>Pour finir, un peu de doc qui m'a bien aidée : <a href="https://btrfs.wiki.kernel.org/index.php/Using_Btrfs_with_Multiple_Devices" title="Page officielle sur kernel.org à propos de l'utilisation de Btrfs sur plusieurs disques">https://btrfs.wiki.kernel.org/index.php/Using_Btrfs_with_Multiple_Devices</a> (en anglais). <br>
La page sur le wiki de Sebsauvage (en français) peut aussi apporter des info/explications/retour d'expérience : <a href="https://sebsauvage.net/wiki/doku.php?id=btrfs" title="Comprendre et utiliser btrfs chez Sebsauvage">https://sebsauvage.net/wiki/doku.php?id=btrfs</a></p>
<h2 id="edit-apres-quelques-mois-dutilisation">EDIT : après quelques mois d'utilisation</h2>
<p>Ca marche super bien.</p>
<p>J'ai besoin d'en dire plus ?</p>
<p>J'ai modifié mon <code>/etc/fstab</code> comme ça : <code>UUID=6......1-1f3a-4922-a...............6 /mnt btrfs defaults,noatime,autodefrag,compress=zstd 0 0</code>. Du coup, avec le <code>compress=zstd</code>, tout ce qui peut être compressé le sera de manière transparente (et c'est intelligent : ça n'essayera pas de re-compresser un fichier .zip ou .tgz ou autre). Mine de rien, ça se prend. Plus de place disque sans changer/rajouter de disque, c'est super !</p>
<p>Je cite le site <a href="https://fedoraproject.org/wiki/Changes/BtrfsTransparentCompression" title="Comprendre la compression transparente pour Btrfs">FedoraProject</a> : </p>
<blockquote>
<p>Compression saves space and can significantly increase the lifespan of flash-based media by reducing write amplification. It can also increase read and write performance.</p>
</blockquote>
<p>Du coup, effet de bord non envisagé/cherché spécifiquement : après vérification, grâce au RAID intégré à Btrfs + la compression, la vitesse des transferts depuis et vers le NAS a été décuplée. Youhou !</p>
<p>Pour la maintenance, j'ai juste cronné une défragmentation mensuelle (avec vérification de la compression) <code>btrfs filesystem defragment -r -v -czstd /mnt</code>. C'est l'un des rares points noirs de Btrfs : contrairement à ext4/xfs, ça fragmente. Même si on met l'option de montage <code>autodefrag</code>, il faut penser à dégramenter régulièrement.</p>
<h3 id="bilan">Bilan</h3>
<p>Ca tourne bien, ça tourne plus vite et avec plus d'espace disque dispo, en RAID1 compressé transparent sur des disques hérérogènes, sans rien avoir eu à faire depuis que c'est installé. Moi, ça me va !</p>
<p>Btrfs, c'est de la fucking black magic, sérieusement.</p>Utiliser Kolla-Ansible pour déployer un environnement openstack2021-05-20T15:00:00+02:002021-05-21T15:00:00+02:00Etienne Magrotag:blog.etienne-magro.fr,2021-05-20:/kolla-ansible.html<p>Revue rapide de Kolla-ansible pour voir comment déployer un openstack. Ce post n'a pas vocation a entrer dans les détails, mais est plutôt un quickstart.</p><p>A toutes fins utiles, il convient de toujours se reporter à la <a href="https://docs.openstack.org/kolla-ansible/latest/user/quickstart.html" title="Documentation officielle pour l'outil kolla-ansible">documentation officielle</a>.
Le blogpost qui suit n'en est d'ailleurs qu'une reprise, et un résumé, adaptée à la découverte de l'usage de kolla-ansible dans le cadre de l'installation d'un openstack d'exemple sur un serveur unique (dit "mono-node" ou "all-in-one").</p>
<p>Avant toute chose, il faut savoir que Kolla-Ansible est un ensemble de playbooks et de rôles ansible déjà tous prêt pour installer un openstack dans les règles de l'art. Ce dépôt ansible est écrit et maintenu par les équipes d'openstack pour représenter l'état de l'art de l'installation d'un openstack et peut être utilisé tel quel par tout un chacun.</p>
<h2 id="pre-requis">Pré-requis</h2>
<p>Avant d'utiliser Kolla-Ansible, il est important de connaitre ansible. Au moins des notions de cet outil et une connaissance même basique de son usage sont nécessaires pour ne pas être perdu.</p>
<p>Également, la machine sur laquelle vous allez installer openstack a besoin d'au-moins deux interfaces réseau. Sans cela, impossible (ou alors, vraiment compliqué et ça dépasse le cadre de ce post) d'installer openstack.</p>
<h2 id="installation-de-kolla-ansible">Installation de Kolla-ansible</h2>
<h3 id="dependances">Dépendances</h3>
<p>Comme pour tout, il faut commencer par installer les dépendances. Heureusement, c'est python qui propulse ansible, du coup, pour les dépendances, c'est relativement vite fait. Pour mon PC sous fedora, ça donne ça (pour d'autres distrib', voir la <a href="https://docs.openstack.org/kolla-ansible/latest/user/quickstart.html" title="Documentation officielle pour l'outil kolla-ansible">doc officielle kolla</a>) :</p>
<div class="highlight"><pre><span></span><code>sudo<span class="w"> </span>dnf<span class="w"> </span>install<span class="w"> </span>python3-devel<span class="w"> </span>libffi-devel<span class="w"> </span>gcc<span class="w"> </span>openssl-devel<span class="w"> </span>python3-libselinux
python3<span class="w"> </span>-m<span class="w"> </span>venv<span class="w"> </span>venv
.<span class="w"> </span>venv/bin/activate.fish<span class="w"> </span><span class="c1"># . venv/bin/activate si vous utilisez bash</span>
pip<span class="w"> </span>install<span class="w"> </span><span class="s1">'ansible<3.0'</span><span class="w"> </span>kolla-ansible
</code></pre></div>
<h3 id="preparation-de-kolla-ansible">Préparation de kolla-ansible</h3>
<div class="highlight"><pre><span></span><code>sudo<span class="w"> </span>mkdir<span class="w"> </span>-p<span class="w"> </span>/etc/kolla
sudo<span class="w"> </span>chown<span class="w"> </span><span class="nv">$USER</span>:<span class="nv">$USER</span><span class="w"> </span>/etc/kolla
cp<span class="w"> </span>-rv<span class="w"> </span>venv/share/kolla-ansible/etc_examples/kolla/*<span class="w"> </span>/etc/kolla
</code></pre></div>
<p>A noter que les deux fichiers <code>globals.yml</code> et <code>password.yml</code> qui sont copiés dans <code>/etc/kolla</code> sont vide de configuration pour le premier (faite un <code>grep -v '#'</code> dessus pour vérifier) et inutilisable pour le second (toutes les variables sont vides).</p>
<div class="highlight"><pre><span></span><code>cp<span class="w"> </span>-v<span class="w"> </span>venv/share/kolla-ansible/ansible/inventory/*<span class="w"> </span>.
</code></pre></div>
<p>Les deux fichiers copiés ne vont pas servir tels quels, il va falloir les modifier un peu avant usage. A savoir que dans le cadre de ce post, nous n'allons utiliser que le fichier d'inventaire <code>all-in-one</code>. Il va nous permettre d'installer openstack sur un seul serveur. C'est parfait pour découvrir kolla-ansible, mais ne l'utilisez surtout pas ainsi en prod.</p>
<h2 id="configuration">Configuration</h2>
<h3 id="configuration-ansible">Configuration ansible</h3>
<p>Avant d'utiliser Ansible, il faut bien sûr commener par préparer l'inventaire. Dans notre cas, il va s'agir du fichier <code>all-in-one</code> copié à l'étape précédente. Ce qui nous intéresse, ce sont uniquement les premières lignes de ce fichier. Les reste du fichier constitue des groupe et sous-groupes que nous n'avons pas d'intérêt à modifier ici, mais qui seront pratique/utilisés lors du déploiement et le configuration des service.</p>
<p>Si vous souhaitez déployer openstack en local (c'est à dire si toutes les opérations que vous avez faites depuis le début ont été faite sur la machine où vous souhaitez déployer openstack), vous n'avez pas besoin de modifier ce fichier <code>all-in-one</code>. Sinon, si comme moi, vous préférer exécuter ansible sur une machine dédiée ou votre poste et installer openstack ailleurs, il faut le modifier. Voilà ce que donnent les premières lignes modifiées pour moi :</p>
<div class="highlight"><pre><span></span><code><span class="err">#</span><span class="w"> </span><span class="n">These</span><span class="w"> </span><span class="n">initial</span><span class="w"> </span><span class="n">groups</span><span class="w"> </span><span class="k">are</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="k">only</span><span class="w"> </span><span class="n">groups</span><span class="w"> </span><span class="n">required</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="n">be</span><span class="w"> </span><span class="n">modified</span><span class="p">.</span><span class="w"> </span><span class="n">The</span>
<span class="err">#</span><span class="w"> </span><span class="n">additional</span><span class="w"> </span><span class="n">groups</span><span class="w"> </span><span class="k">are</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">more</span><span class="w"> </span><span class="n">control</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">environment</span><span class="p">.</span>
<span class="o">[</span><span class="n">control</span><span class="o">]</span>
<span class="n">sandbox</span><span class="o">-</span><span class="n">test</span><span class="o">-</span><span class="n">user1</span><span class="w"> </span><span class="n">ansible_host</span><span class="o">=</span><span class="mf">10.8</span><span class="p">.</span><span class="n">X</span><span class="p">.</span><span class="n">Y</span><span class="w"> </span><span class="n">ansible_user</span><span class="o">=</span><span class="n">debian</span><span class="w"> </span><span class="n">ansible_become</span><span class="o">=</span><span class="k">true</span>
<span class="o">[</span><span class="n">network:children</span><span class="o">]</span>
<span class="n">control</span>
<span class="o">[</span><span class="n">compute:children</span><span class="o">]</span>
<span class="n">control</span>
<span class="o">[</span><span class="n">storage:children</span><span class="o">]</span>
<span class="n">control</span>
<span class="o">[</span><span class="n">monitoring:children</span><span class="o">]</span>
<span class="n">control</span>
<span class="o">[</span><span class="n">deployment:children</span><span class="o">]</span>
<span class="n">control</span>
</code></pre></div>
<p>Et plus qu'à vérifier ça avec la commande classique ansible (ne faites pas attention au warning, ansible n'aime pas les "-" dans les noms des groupes) :</p>
<div class="highlight"><pre><span></span><code>><span class="w"> </span>ansible<span class="w"> </span>-i<span class="w"> </span>all-in-one<span class="w"> </span>all<span class="w"> </span>-m<span class="w"> </span>ping
<span class="o">[</span>WARNING<span class="o">]</span>:<span class="w"> </span>Invalid<span class="w"> </span>characters<span class="w"> </span>were<span class="w"> </span>found<span class="w"> </span><span class="k">in</span><span class="w"> </span>group<span class="w"> </span>names<span class="w"> </span>but<span class="w"> </span>not<span class="w"> </span>replaced,<span class="w"> </span>use<span class="w"> </span>-vvvv<span class="w"> </span>to<span class="w"> </span>see<span class="w"> </span>details
sandbox-test-user1<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nv">SUCCESS</span><span class="w"> </span><span class="o">=</span>><span class="w"> </span><span class="o">{</span>
<span class="w"> </span><span class="s2">"ansible_facts"</span>:<span class="w"> </span><span class="o">{</span>
<span class="w"> </span><span class="s2">"discovered_interpreter_python"</span>:<span class="w"> </span><span class="s2">"/usr/bin/python3"</span>
<span class="w"> </span><span class="o">}</span>,
<span class="w"> </span><span class="s2">"changed"</span>:<span class="w"> </span>false,
<span class="w"> </span><span class="s2">"ping"</span>:<span class="w"> </span><span class="s2">"pong"</span>
<span class="o">}</span>
</code></pre></div>
<h3 id="configuration-kolla">Configuration kolla</h3>
<p>Kolla a besoin que l'on configure le fichier <code>/etc/kolla/passwords.yml</code> copié précédemment. Ce fichier va contenir tous les mots de passe qui seront ensuite paramétré dans l'openstack. Il ne s'agit pas de mot de passe utilisateurs, mais plutôt des services qui doivent s'identifier et s'authentifier entre-eux. Pour rappel, openstack est codé en micro-services. Et ces micro-services ont besoin de s'authentifier entre-eux pour être sûr qu'un intrus ne vienne les parasiter.</p>
<p>Cette configuration serait rédibitoire à faire manuellement, alors kolla met un outils à notre disposition pour faire ça simplement :</p>
<div class="highlight"><pre><span></span><code>><span class="w"> </span>grep<span class="w"> </span>-v<span class="w"> </span><span class="s1">'#'</span><span class="w"> </span>/etc/kolla/passwords.yml<span class="w"> </span><span class="p">|</span><span class="w"> </span>head<span class="w"> </span><span class="c1"># Avant l'exécution, on voit bien que le fichier ne contient aucun mot de passe</span>
---
rbd_secret_uuid:
cinder_rbd_secret_uuid:
database_password:
mariadb_backup_database_password:
docker_registry_password:
vmware_dvs_host_password:
><span class="w"> </span>kolla-genpwd
><span class="w"> </span>head<span class="w"> </span>/etc/kolla/passwords.yml<span class="w"> </span><span class="c1"># Alors qu'après, ils sont tous là</span>
aodh_database_password:<span class="w"> </span>YPaA................................vhpL
aodh_keystone_password:<span class="w"> </span>DOHa................................b8q7
barbican_crypto_key:<span class="w"> </span>4KwA....................................FsY<span class="o">=</span>
barbican_database_password:<span class="w"> </span>PI73................................Fo7v
barbican_keystone_password:<span class="w"> </span>QE3Q................................zFv3
barbican_p11_password:<span class="w"> </span>HfWS................................6xOn
bifrost_ssh_key:
<span class="w"> </span>private_key:<span class="w"> </span><span class="err">'</span>-----BEGIN<span class="w"> </span>PRIVATE<span class="w"> </span>KEY-----
<span class="w"> </span>MIIJ........................................................kg64
</code></pre></div>
<p>Nous devons maintenant configurer le second fichier copié précédemment : <code>/etc/kolla/globals.yml</code>. Il s'agit du fichier de configuration principal pour kolla. C'est de lui que vont être extrait tous les paramètres nécessaires à l'installation d'un openstack. La configuration réseau, quels services déployer, etc...</p>
<p>VOici le strict minimum à vérifier/configurer (pour plus d'information, se reporter à la <a href="https://docs.openstack.org/kolla-ansible/latest/user/quickstart.html" title="Documentation officielle pour l'outil kolla-ansible">doc officielle kolla</a>):</p>
<div class="highlight"><pre><span></span><code><span class="nx">network_interface</span><span class="p">:</span><span class="w"> </span><span class="s">"ens3"</span><span class="w"> </span><span class="err">#</span><span class="w"> </span><span class="nx">mettez</span><span class="w"> </span><span class="nx">ici</span><span class="w"> </span><span class="nx">l</span><span class="err">'</span><span class="kd">interface</span><span class="w"> </span><span class="nx">réseau</span><span class="w"> </span><span class="nx">principale</span><span class="w"> </span><span class="nx">de</span><span class="w"> </span><span class="nx">la</span><span class="w"> </span><span class="nx">machine</span><span class="w"> </span><span class="nx">sur</span><span class="w"> </span><span class="nx">laquelle</span><span class="w"> </span><span class="nx">l</span><span class="err">'</span><span class="nx">openstack</span><span class="w"> </span><span class="nx">sera</span><span class="w"> </span><span class="nx">déployé</span><span class="p">.</span><span class="w"> </span><span class="nx">C</span><span class="err">'</span><span class="nx">est</span><span class="w"> </span><span class="nx">l</span><span class="err">'</span><span class="kd">interface</span><span class="w"> </span><span class="nx">réseau</span><span class="w"> </span><span class="nx">qui</span><span class="w"> </span><span class="nx">sera</span><span class="w"> </span><span class="nx">utilisée</span><span class="w"> </span><span class="nx">pour</span><span class="w"> </span><span class="nx">l</span><span class="err">'</span><span class="nx">administration</span><span class="w"> </span><span class="nx">de</span><span class="w"> </span><span class="nx">l</span><span class="err">'</span><span class="nx">openstack</span>
<span class="nx">neutron_external_interface</span><span class="p">:</span><span class="w"> </span><span class="s">"ens4"</span><span class="w"> </span><span class="err">#</span><span class="w"> </span><span class="nx">Il</span><span class="w"> </span><span class="nx">s</span><span class="err">'</span><span class="nx">agit</span><span class="w"> </span><span class="nx">de</span><span class="w"> </span><span class="nx">l</span><span class="err">'</span><span class="kd">interface</span><span class="w"> </span><span class="nx">réseau</span><span class="w"> </span><span class="nx">qui</span><span class="w"> </span><span class="nx">sera</span><span class="w"> </span><span class="nx">utilisée</span><span class="w"> </span><span class="nx">pour</span><span class="w"> </span><span class="nx">les</span><span class="w"> </span><span class="nx">communications</span><span class="w"> </span><span class="nx">entres</span><span class="w"> </span><span class="nx">les</span><span class="w"> </span><span class="nx">VM</span><span class="w"> </span><span class="nx">et</span><span class="w"> </span><span class="nx">l</span><span class="err">'</span><span class="nx">extérieur</span><span class="w"> </span><span class="nx">de</span><span class="w"> </span><span class="nx">l</span><span class="err">'</span><span class="nx">openstack</span>
<span class="nx">kolla_internal_vip_address</span><span class="p">:</span><span class="w"> </span><span class="s">"172.20.2.121"</span><span class="w"> </span><span class="err">#</span><span class="w"> </span><span class="nx">Il</span><span class="w"> </span><span class="nx">faut</span><span class="w"> </span><span class="nx">indiquer</span><span class="w"> </span><span class="nx">une</span><span class="w"> </span><span class="nx">adresse</span><span class="w"> </span><span class="nx">IP</span><span class="w"> </span><span class="nx">non</span><span class="o">-</span><span class="nx">utilisée</span><span class="w"> </span><span class="nx">sur</span><span class="w"> </span><span class="nx">l</span><span class="err">'</span><span class="kd">interface</span><span class="w"> </span><span class="s">"network_interface"</span><span class="w"> </span><span class="nx">ci</span><span class="o">-</span><span class="nx">dessus</span><span class="p">.</span><span class="w"> </span><span class="nx">Elle</span><span class="w"> </span><span class="nx">servira</span><span class="w"> </span><span class="nx">de</span><span class="w"> </span><span class="nx">vIP</span><span class="w"> </span><span class="p">(</span><span class="nx">IP</span><span class="w"> </span><span class="nx">flotante</span><span class="p">)</span><span class="w"> </span><span class="nx">pour</span><span class="w"> </span><span class="nx">accéder</span><span class="w"> </span><span class="nx">aux</span><span class="w"> </span><span class="nx">API</span><span class="w"> </span><span class="nx">de</span><span class="w"> </span><span class="nx">l</span><span class="err">'</span><span class="nx">openstack</span><span class="p">,</span><span class="w"> </span><span class="nx">ainsi</span><span class="w"> </span><span class="nx">que</span><span class="w"> </span><span class="nx">la</span><span class="w"> </span><span class="nx">webUI</span><span class="w"> </span><span class="nx">horizon</span>
</code></pre></div>
<p>Vous pouvez parcourir ce fichier, les variables importantes sont commentées et toutes ont des valeurs par défaut qui vous satisferont en première utilisation.</p>
<h2 id="deploiement">Déploiement</h2>
<h3 id="le-deploiement-proprement-dit">Le déploiement proprement dit</h3>
<p>Dans l'ordre, on va installer les dépendances bas-niveau sur l'host, puis y déployer openstack après quelques vérifications.</p>
<div class="highlight"><pre><span></span><code>kolla-ansible<span class="w"> </span>-i<span class="w"> </span>./all-in-one<span class="w"> </span>bootstrap-servers
kolla-ansible<span class="w"> </span>-i<span class="w"> </span>./all-in-one<span class="w"> </span>prechecks
kolla-ansible<span class="w"> </span>-i<span class="w"> </span>./all-in-one<span class="w"> </span>deploy
</code></pre></div>
<p>A savoir, j'ai eu cette erreur, lors de la première commande, sur un host en Debian 10 :</p>
<div class="highlight"><pre><span></span><code><span class="n">TASK</span><span class="w"> </span><span class="p">[</span><span class="n">baremetal</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="n">Install</span><span class="w"> </span><span class="n">docker</span><span class="w"> </span><span class="n">apt</span><span class="w"> </span><span class="n">gpg</span><span class="w"> </span><span class="n">key</span><span class="p">]</span><span class="w"> </span><span class="o">*************************************************************************************************************************************</span>
<span class="nl">fatal</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="n">etienne</span><span class="o">-</span><span class="n">kolla</span><span class="o">-</span><span class="n">test1</span><span class="p">]</span><span class="o">:</span><span class="w"> </span><span class="n">FAILED</span><span class="o">!</span><span class="w"> </span><span class="o">=></span><span class="w"> </span><span class="p">{</span><span class="s">"changed"</span><span class="o">:</span><span class="w"> </span><span class="nb">false</span><span class="p">,</span><span class="w"> </span><span class="s">"msg"</span><span class="o">:</span><span class="w"> </span><span class="s">"Failed to find required executable gpg in paths: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"</span><span class="p">}</span>
</code></pre></div>
<p>Ça se règle avec l'installation de gnupg dessus à la main (<code>apt install gnupg</code> sur l'host openstask).</p>
<h3 id="la-verification">La vérification</h3>
<p>Un fois que tout est terminé, vous pouvez vous rendre sur l'interface webUI horizon http://172.20.2.121/, où l'adresse IP est celle renseignée dans la variable <code>kolla_internal_vip_address</code> précédemment. Vous devriez arriver sur la page d'authentification d'horizon. Le login par défaut est <code>admin</code> et le mot de passe associé est indiqué dans la variable <code>keystone_admin_password</code> présente dans le fichier <code>/etc/kolla/passwords.yml</code>.</p>
<p>Il est possible de récupérer simplement et rapidement le fichier RC pour intéragir avec le nouvel openstack, sans devoir passer par la webUI horizon. Pour cela, lancez cette commande :</p>
<div class="highlight"><pre><span></span><code>kolla-ansible -i ./all-in-one -e 'node_config=/where/you/want' post-deploy
</code></pre></div>
<p>A savoir, l'argument <code>-e 'node_config=...</code> n'est pas obligatoire, mais sans cela, kolla-ansible va essayer de créer le fichier RC dans <code>/etc/kolla</code>. A titre perso, je pense que c'est moyen, d'où cet ajout. Deuxième chose, vous pourriez avoir une erreur selinux (ça a été mon cas, sur ma Fedora). Ca se règle avec un <code>pip install selinux</code>.</p>Instancier une VM dans un openstack en CLI2021-05-19T15:00:00+02:002021-05-19T15:00:00+02:00Etienne Magrotag:blog.etienne-magro.fr,2021-05-19:/vm-openstack-cli.html<p>Voici un tuto rapide sur comment instancier une VM dans un openstack, sans (trop) passer par Horizon (la web-GUI). Ca sera fait en CLI pure depuis votre propre PC, avec exemples.</p><p>On va partir du principe qu'on vous a fourni un accès à une plateforme <a href="https://www.openstack.org/" title="Site officiel du logiciel OpenStack">openstack</a>.
Cela veut dire qu'on vous a donné ces informations-ci, au minimum :</p>
<ul>
<li>une URL (généralement celle de la web-GUI horizon<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup>, du style https://horizon.openstack.example.org/)</li>
<li>un login</li>
<li>et son mot de passe associé.</li>
</ul>
<p>Il faut savoir que les informations ci-dessus sont le strict minimum pour se connecter à la web-GUI, mais ne sont pas suffisantes pour vraiment travailler avec openstack. En effet, il manque d'autres informations, pour pouvoir utiliser l'API d'openstack. Typiquement, l'URL du keystone<sup id="fnref:2"><a class="footnote-ref" href="#fn:2">2</a></sup>, un "project id", etc...</p>
<p>La bonne pratique est la création d'un "RC file", un simple fichier sh qui mettra en place les bonnes variables d'environnement dont la CLI d'openstask a besoin, et de faire un <code>source</code> de ce fichier avant de travailler. Vous pouvez le <a href="https://docs.openstack.org/ocata/admin-guide/common/cli-set-environment-variables-using-openstack-rc.html#create-and-source-the-openstack-rc-file" title="Documentation officielle d'openstack sur la création du fichier RC">créer vous-même</a>, mais il faudra qu'on vous fournisse quand même les info manquantes. Le plus aisé, pour cette étape, est quand même de passer par la Web-GUI Horizon.</p>
<h2 id="le-passage-quasi-oblige-de-la-web-gui-pour-le-fichier-rc">Le passage quasi-obligé de la web-GUI pour le fichier RC</h2>
<p>La première chose à faire, donc, est de récupérer ces informations supplémentaires. Ça se passe sur la web-GUI à cette adresse : https://horizon.openstack.example.org/project/api_access/. Téléchargez simplement le fichier RC que vous propose, en haut à droite :</p>
<p><img alt="Où cliquer pour télécharger le fichier RC d'openstack" src="https://blog.etienne-magro.fr/images/openstack-rc-file-web-gui.png"></p>
<p>Jetons un œil au contenu de ce fichier :</p>
<div class="highlight"><pre><span></span><code>><span class="w"> </span>grep<span class="w"> </span>-v<span class="w"> </span><span class="s1">'#'</span><span class="w"> </span>openstack-rc.sh
<span class="nb">export</span><span class="w"> </span><span class="nv">OS_AUTH_URL</span><span class="o">=</span>https://horizon.openstack.example.org:5000/v3
<span class="nb">export</span><span class="w"> </span><span class="nv">OS_PROJECT_ID</span><span class="o">=</span><span class="m">718336</span>....................b8453d
<span class="nb">export</span><span class="w"> </span><span class="nv">OS_PROJECT_NAME</span><span class="o">=</span><span class="s2">"sandbox"</span>
<span class="nb">export</span><span class="w"> </span><span class="nv">OS_USER_DOMAIN_NAME</span><span class="o">=</span><span class="s2">"mycompany"</span>
<span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span>-z<span class="w"> </span><span class="s2">"</span><span class="nv">$OS_USER_DOMAIN_NAME</span><span class="s2">"</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span><span class="w"> </span><span class="nb">unset</span><span class="w"> </span>OS_USER_DOMAIN_NAME<span class="p">;</span><span class="w"> </span><span class="k">fi</span>
<span class="nb">export</span><span class="w"> </span><span class="nv">OS_PROJECT_DOMAIN_ID</span><span class="o">=</span><span class="s2">"19e48......................8c18a"</span>
<span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span>-z<span class="w"> </span><span class="s2">"</span><span class="nv">$OS_PROJECT_DOMAIN_ID</span><span class="s2">"</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span><span class="w"> </span><span class="nb">unset</span><span class="w"> </span>OS_PROJECT_DOMAIN_ID<span class="p">;</span><span class="w"> </span><span class="k">fi</span>
<span class="nb">unset</span><span class="w"> </span>OS_TENANT_ID
<span class="nb">unset</span><span class="w"> </span>OS_TENANT_NAME
<span class="nb">export</span><span class="w"> </span><span class="nv">OS_USERNAME</span><span class="o">=</span><span class="s2">"myusername"</span>
<span class="nb">echo</span><span class="w"> </span><span class="s2">"Please enter your OpenStack Password for project </span><span class="nv">$OS_PROJECT_NAME</span><span class="s2"> as user </span><span class="nv">$OS_USERNAME</span><span class="s2">: "</span>
<span class="nb">read</span><span class="w"> </span>-sr<span class="w"> </span>OS_PASSWORD_INPUT
<span class="nb">export</span><span class="w"> </span><span class="nv">OS_PASSWORD</span><span class="o">=</span><span class="nv">$OS_PASSWORD_INPUT</span>
<span class="nb">export</span><span class="w"> </span><span class="nv">OS_REGION_NAME</span><span class="o">=</span><span class="s2">"region1"</span>
<span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span>-z<span class="w"> </span><span class="s2">"</span><span class="nv">$OS_REGION_NAME</span><span class="s2">"</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span><span class="w"> </span><span class="nb">unset</span><span class="w"> </span>OS_REGION_NAME<span class="p">;</span><span class="w"> </span><span class="k">fi</span>
<span class="nb">export</span><span class="w"> </span><span class="nv">OS_INTERFACE</span><span class="o">=</span>public
<span class="nb">export</span><span class="w"> </span><span class="nv">OS_IDENTITY_API_VERSION</span><span class="o">=</span><span class="m">3</span>
</code></pre></div>
<p>Si comme moi, vous être plutôt adepte du shell <a href="https://fishshell.com/" title="Fish shell est un shell linux que j'apprécie particulièrement">fish</a>, voici le fichier rc transposé pour ce shell, c'est cadeau :</p>
<div class="highlight"><pre><span></span><code><span class="nb">set</span><span class="w"> </span>-gx<span class="w"> </span>OS_AUTH_URL<span class="w"> </span>https://horizon.openstack.example.org:5000/v3
<span class="nb">set</span><span class="w"> </span>-gx<span class="w"> </span>OS_PROJECT_ID<span class="w"> </span><span class="m">718336</span>....................b8453d
<span class="nb">set</span><span class="w"> </span>-gx<span class="w"> </span>OS_PROJECT_NAME<span class="w"> </span>sandbox
<span class="nb">set</span><span class="w"> </span>-gx<span class="w"> </span>OS_USER_DOMAIN_NAME<span class="w"> </span>mycompany
<span class="nb">set</span><span class="w"> </span>-gx<span class="w"> </span>OS_PROJECT_DOMAIN_ID<span class="w"> </span>19e48......................8c18a
<span class="nb">set</span><span class="w"> </span>-e<span class="w"> </span>OS_TENANT_ID
<span class="nb">set</span><span class="w"> </span>-e<span class="w"> </span>OS_TENANT_NAME
<span class="nb">set</span><span class="w"> </span>-gx<span class="w"> </span>OS_USERNAME<span class="w"> </span>user
<span class="nb">echo</span><span class="w"> </span><span class="s2">"Please enter your OpenStack Password for project </span><span class="nv">$OS_PROJECT_NAME</span><span class="s2"> as user </span><span class="nv">$OS_USERNAME</span><span class="s2">: "</span>
<span class="nb">read</span><span class="w"> </span>-s<span class="w"> </span>OS_PASSWORD_INPUT
<span class="nb">set</span><span class="w"> </span>-gx<span class="w"> </span>OS_PASSWORD<span class="w"> </span><span class="nv">$OS_PASSWORD_INPUT</span>
<span class="nb">set</span><span class="w"> </span>-gx<span class="w"> </span>OS_REGION_NAME<span class="w"> </span>region1
<span class="nb">set</span><span class="w"> </span>-gx<span class="w"> </span>OS_INTERFACE<span class="w"> </span>public<span class="w"> </span>
<span class="nb">set</span><span class="w"> </span>-gx<span class="w"> </span>OS_IDENTITY_API_VERSION<span class="w"> </span><span class="m">3</span>
</code></pre></div>
<h2 id="installation-de-la-cli-cliente-openstask">Installation de la CLI cliente openstask</h2>
<p>Ça paraît évident, mais vous allez avoir besoin d'un shell linux (bash généralement ou fish pour moi), et de python. Sur mon poste (<a href="https://getfedora.org/fr/" title="Site officiel de la distribution Linux Fedora, en français">Fedora</a>), l'installation en virtualenv se passe comme ça :</p>
<div class="highlight"><pre><span></span><code>sudo<span class="w"> </span>dnf<span class="w"> </span>install<span class="w"> </span>python3-devel<span class="w"> </span>python3-pip<span class="w"> </span>python3-virtualenv
virtualenv<span class="w"> </span>-p<span class="w"> </span>python3<span class="w"> </span>venv
.<span class="w"> </span>venv/bin/activate.fish<span class="w"> </span><span class="c1"># ou . venv/bin/activate avec bash</span>
pip<span class="w"> </span>install<span class="w"> </span>python-openstackclient
</code></pre></div>
<p>Testons maintenant que tout est bon :</p>
<div class="highlight"><pre><span></span><code>.<span class="w"> </span>openstack-rc.fish<span class="w"> </span><span class="c1"># ou . openstack-rc.sh avec bash</span>
openstack<span class="w"> </span>server<span class="w"> </span>list
</code></pre></div>
<p>Si tout se passe bien, vous devriez vous voir répondre quelque chose dans ce genre :</p>
<div class="highlight"><pre><span></span><code>+--------------------------------------+---------------------+---------------+-----------------------------+---------------------+--------------+
<span class="p">|</span><span class="w"> </span>ID<span class="w"> </span><span class="p">|</span><span class="w"> </span>Name<span class="w"> </span><span class="p">|</span><span class="w"> </span>Status<span class="w"> </span><span class="p">|</span><span class="w"> </span>Networks<span class="w"> </span><span class="p">|</span><span class="w"> </span>Image<span class="w"> </span><span class="p">|</span><span class="w"> </span>Flavor<span class="w"> </span><span class="p">|</span>
+--------------------------------------+---------------------+---------------+-----------------------------+---------------------+--------------+
<span class="p">|</span><span class="w"> </span>8a0...46-4..1-4..7-8..b-7b........ea<span class="w"> </span><span class="p">|</span><span class="w"> </span>sandbox-test-test1<span class="w"> </span><span class="p">|</span><span class="w"> </span>ACTIVE<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nv">internal</span><span class="o">=</span><span class="m">172</span>.20.X.Y<span class="w"> </span><span class="p">|</span><span class="w"> </span>Debian-10<span class="w"> </span><span class="p">|</span><span class="w"> </span>c2.medium<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>35c...57-a..3-4..1-b..0-30........f0<span class="w"> </span><span class="p">|</span><span class="w"> </span>int-ubu-user39--6<span class="w"> </span><span class="p">|</span><span class="w"> </span>ACTIVE<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nv">provider</span><span class="o">=</span><span class="m">10</span>.8.X.Y<span class="w"> </span><span class="p">|</span><span class="w"> </span>Ubuntu-20.04<span class="w"> </span><span class="p">|</span><span class="w"> </span>c2.tiny<span class="w"> </span><span class="p">|</span>
<span class="o">[</span>...<span class="o">]</span>
+--------------------------------------+---------------------+---------------+-----------------------------+---------------------+--------------+
</code></pre></div>
<p>Bravo !</p>
<h2 id="commandes-utiles-avant-de-creer-votre-premiere-vm-dans-openstack">Commandes utiles avant de créer votre première VM dans openstack</h2>
<p>Avant de créer sa première VM au sein d'openstack, il faut savoir qu'elle image de base on peut utiliser, quelle taille de VM est posible, quel réseau, etc... Pour savoir tout cela, voici quelques commandes qui vont vous y aider.</p>
<h3 id="quelle-taille-flavor">Quelle taille (flavor) ?</h3>
<p>Quelle taille de VM voulez-vous, et surtout, que propose votre fournisseur openstack ? Vous pouvez le savoir avec cette commande : </p>
<div class="highlight"><pre><span></span><code>><span class="w"> </span>openstack<span class="w"> </span>flavor<span class="w"> </span>list
+--------------------------------------+-----------------+-------+------+-----------+-------+-----------+
<span class="p">|</span><span class="w"> </span>ID<span class="w"> </span><span class="p">|</span><span class="w"> </span>Name<span class="w"> </span><span class="p">|</span><span class="w"> </span>RAM<span class="w"> </span><span class="p">|</span><span class="w"> </span>Disk<span class="w"> </span><span class="p">|</span><span class="w"> </span>Ephemeral<span class="w"> </span><span class="p">|</span><span class="w"> </span>VCPUs<span class="w"> </span><span class="p">|</span><span class="w"> </span>Is<span class="w"> </span>Public<span class="w"> </span><span class="p">|</span>
+--------------------------------------+-----------------+-------+------+-----------+-------+-----------+
<span class="p">|</span><span class="w"> </span><span class="m">11</span>....94-6629-444d-ab2d-f6........83<span class="w"> </span><span class="p">|</span><span class="w"> </span>c2.medium<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="m">4096</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="m">20</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="m">0</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="m">2</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>True<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>1a....34-47f6-4417-a559-13........92<span class="w"> </span><span class="p">|</span><span class="w"> </span>c8.large<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="m">12288</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="m">80</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="m">0</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="m">8</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>True<span class="w"> </span><span class="p">|</span>
<span class="o">[</span>...<span class="o">]</span>
+--------------------------------------+-----------------+-------+------+-----------+-------+-----------+
</code></pre></div>
<h3 id="quelle-image-de-base">Quelle image de base ?</h3>
<p>Quelle image de base pouvez-vous utiliser ? Généralement, vous ne pouvez pas utiliser toutes les distributions linux existantes, mais juste quelques unes parmis un catalogue, catalogue qui se récupère ainsi :</p>
<div class="highlight"><pre><span></span><code>><span class="w"> </span>openstack<span class="w"> </span>image<span class="w"> </span>list
+--------------------------------------+---------------------------+--------+
<span class="p">|</span><span class="w"> </span>ID<span class="w"> </span><span class="p">|</span><span class="w"> </span>Name<span class="w"> </span><span class="p">|</span><span class="w"> </span>Status<span class="w"> </span><span class="p">|</span>
+--------------------------------------+---------------------------+--------+
<span class="p">|</span><span class="w"> </span>4d....db-cdee-4f08-b37e-71........fd<span class="w"> </span><span class="p">|</span><span class="w"> </span>CentOS-7<span class="w"> </span><span class="p">|</span><span class="w"> </span>active<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>1b....63-27de-4b0c-bca9-c6........52<span class="w"> </span><span class="p">|</span><span class="w"> </span>CentOS-8<span class="w"> </span><span class="p">|</span><span class="w"> </span>active<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>e4....3a-00e1-432d-b0eb-8d........69<span class="w"> </span><span class="p">|</span><span class="w"> </span>Ubuntu-20.04<span class="w"> </span><span class="p">|</span><span class="w"> </span>active<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>ea....5f-e72d-4da3-8f04-5d........3c<span class="w"> </span><span class="p">|</span><span class="w"> </span>Debian-10<span class="w"> </span><span class="p">|</span><span class="w"> </span>active<span class="w"> </span><span class="p">|</span>
<span class="o">[</span>...<span class="o">]</span>
+--------------------------------------+---------------------------+--------+
</code></pre></div>
<h3 id="quel-reseau-lui-attacher">Quel réseau lui attacher ?</h3>
<p>Votre VM va avoir besoin d'être connectée sur un réseau pour pouvoir communiquer et que vous puissiez vous y connecter. Pour avoir la liste, je pense que maintenant, vous devez commencer à deviner :</p>
<div class="highlight"><pre><span></span><code>><span class="w"> </span>openstack<span class="w"> </span>network<span class="w"> </span>list
+--------------------------------------+-------------------+--------------------------------------+
<span class="p">|</span><span class="w"> </span>ID<span class="w"> </span><span class="p">|</span><span class="w"> </span>Name<span class="w"> </span><span class="p">|</span><span class="w"> </span>Subnets<span class="w"> </span><span class="p">|</span>
+--------------------------------------+-------------------+--------------------------------------+
<span class="p">|</span><span class="w"> </span><span class="m">07</span>....1a-09b9-49e6-9b09-1b........5c<span class="w"> </span><span class="p">|</span><span class="w"> </span>internal<span class="w"> </span><span class="p">|</span><span class="w"> </span>1a....5e-536d-4889-ae47-ca........02<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span><span class="m">08</span>....56-76ca-4c85-b0d9-bb........6e<span class="w"> </span><span class="p">|</span><span class="w"> </span>public<span class="w"> </span><span class="p">|</span><span class="w"> </span>ff....59-43b0-4620-a225-da........8b<span class="w"> </span><span class="p">|</span>
<span class="o">[</span>...<span class="o">]</span>
+--------------------------------------+-------------------+--------------------------------------+
</code></pre></div>
<h3 id="quel-filtrage-security-group">Quel filtrage (security group) ?</h3>
<p>Il faut que vous puissiez vous connecter en SSH à votre VM, il faut donc lui appliquer un "security group" qui l'autorise. Dans mon cas, il en existe un bien nommé "ssh".</p>
<div class="highlight"><pre><span></span><code>><span class="w"> </span>openstack<span class="w"> </span>security<span class="w"> </span>group<span class="w"> </span>list
+--------------------------------------+-------------+------------------------+----------------------------------+---------+
<span class="p">|</span><span class="w"> </span>ID<span class="w"> </span><span class="p">|</span><span class="w"> </span>Name<span class="w"> </span><span class="p">|</span><span class="w"> </span>Description<span class="w"> </span><span class="p">|</span><span class="w"> </span>Project<span class="w"> </span><span class="p">|</span><span class="w"> </span>Tags<span class="w"> </span><span class="p">|</span>
+--------------------------------------+-------------+------------------------+----------------------------------+---------+
<span class="p">|</span><span class="w"> </span>0f....ee-497b-4917-8569-ae........ea<span class="w"> </span><span class="p">|</span><span class="w"> </span>default<span class="w"> </span><span class="p">|</span><span class="w"> </span>Default<span class="w"> </span>security<span class="w"> </span>group<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="m">71</span>............................3d<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="o">[]</span><span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span><span class="m">29</span>....82-f29a-470b-a7bf-cd........20<span class="w"> </span><span class="p">|</span><span class="w"> </span>ssh<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="m">71</span>............................3d<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="o">[]</span><span class="w"> </span><span class="p">|</span>
<span class="o">[</span>...<span class="o">]</span>
+--------------------------------------+-------------+------------------------+----------------------------------+---------+
</code></pre></div>
<h3 id="quelle-cle-ssh">Quelle clé SSH ?</h3>
<p>On sait tous que se connecter en SSH sur un serveur avec un mot de passe, c'est pas génial. Du coup, il faut indiquer une pair de clé à openstask. Si vous en avez déjà inscrites dans openstack, c'est facile, un coup de <code>openstack keypair list</code> devrait suffir, mais si non, il faut le faire :</p>
<div class="highlight"><pre><span></span><code>><span class="w"> </span>openstack<span class="w"> </span>keypair<span class="w"> </span>list
><span class="w"> </span>openstack<span class="w"> </span>keypair<span class="w"> </span>create<span class="w"> </span>--public-key<span class="w"> </span>~/.ssh/id_rsa.pub<span class="w"> </span>ssh-rsa-etienne
+-------------+------------------------------------------------------------------+
<span class="p">|</span><span class="w"> </span>Field<span class="w"> </span><span class="p">|</span><span class="w"> </span>Value<span class="w"> </span><span class="p">|</span>
+-------------+------------------------------------------------------------------+
<span class="p">|</span><span class="w"> </span>created_at<span class="w"> </span><span class="p">|</span><span class="w"> </span>None<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>fingerprint<span class="w"> </span><span class="p">|</span><span class="w"> </span>d9:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:cb<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>id<span class="w"> </span><span class="p">|</span><span class="w"> </span>ssh-rsa-etienne<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>is_deleted<span class="w"> </span><span class="p">|</span><span class="w"> </span>None<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>name<span class="w"> </span><span class="p">|</span><span class="w"> </span>ssh-rsa-etienne<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span><span class="nb">type</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>ssh<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>user_id<span class="w"> </span><span class="p">|</span><span class="w"> </span>8e............................................................00<span class="w"> </span><span class="p">|</span>
+-------------+------------------------------------------------------------------+
><span class="w"> </span>openstack<span class="w"> </span>keypair<span class="w"> </span>list
+-----------------+-------------------------------------------------+------+
<span class="p">|</span><span class="w"> </span>Name<span class="w"> </span><span class="p">|</span><span class="w"> </span>Fingerprint<span class="w"> </span><span class="p">|</span><span class="w"> </span>Type<span class="w"> </span><span class="p">|</span>
+-----------------+-------------------------------------------------+------+
<span class="p">|</span><span class="w"> </span>ssh-rsa-etienne<span class="w"> </span><span class="p">|</span><span class="w"> </span>d9:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:cb<span class="w"> </span><span class="p">|</span><span class="w"> </span>ssh<span class="w"> </span><span class="p">|</span>
+-----------------+-------------------------------------------------+------+
</code></pre></div>
<h2 id="creons-notre-premier-vm-avec-openstack">Créons notre premier VM avec openstack</h2>
<p>Voici la commande qu'il faut utiliser pour créer une VM dans votre openstack, ainsi que sa syntaxe.</p>
<div class="highlight"><pre><span></span><code>openstack<span class="w"> </span>server<span class="w"> </span>create<span class="w"> </span>--flavor<span class="w"> </span><span class="o">{</span>FlavorID-or-FlavorName<span class="o">}</span><span class="w"> </span>--image<span class="w"> </span><span class="o">{</span>ImageID-or-ImageName<span class="o">}</span><span class="w"> </span>--nic<span class="w"> </span>net-id<span class="o">={</span>NetworkID-or-NetworkName<span class="o">}</span><span class="w"> </span>--security-group<span class="w"> </span><span class="o">{</span>SecurityGroupID-SecurityGroupName<span class="o">}</span><span class="w"> </span>–key-name<span class="w"> </span><span class="o">{</span>KeypairName<span class="o">}</span><span class="w"> </span><VM_Name>
</code></pre></div>
<p>Exemple :</p>
<div class="highlight"><pre><span></span><code>><span class="w"> </span>openstack<span class="w"> </span>server<span class="w"> </span>create<span class="w"> </span>--flavor<span class="w"> </span>c2.medium<span class="w"> </span>--image<span class="w"> </span>Debian-10<span class="w"> </span>--nic<span class="w"> </span>net-id<span class="o">=</span>internal<span class="w"> </span>--security-group<span class="w"> </span>ssh<span class="w"> </span>-–key-name<span class="w"> </span>ssh-rsa-etienne<span class="w"> </span>sandbox-test-user1
+-----------------------------+------------------------------------------------------------------+
<span class="p">|</span><span class="w"> </span>Field<span class="w"> </span><span class="p">|</span><span class="w"> </span>Value<span class="w"> </span><span class="p">|</span>
+-----------------------------+------------------------------------------------------------------+
<span class="p">|</span><span class="w"> </span>OS-DCF:diskConfig<span class="w"> </span><span class="p">|</span><span class="w"> </span>MANUAL<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>OS-EXT-AZ:availability_zone<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>OS-EXT-STS:power_state<span class="w"> </span><span class="p">|</span><span class="w"> </span>NOSTATE<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>OS-EXT-STS:task_state<span class="w"> </span><span class="p">|</span><span class="w"> </span>scheduling<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>OS-EXT-STS:vm_state<span class="w"> </span><span class="p">|</span><span class="w"> </span>building<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>OS-SRV-USG:launched_at<span class="w"> </span><span class="p">|</span><span class="w"> </span>None<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>OS-SRV-USG:terminated_at<span class="w"> </span><span class="p">|</span><span class="w"> </span>None<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>accessIPv4<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>accessIPv6<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>addresses<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>adminPass<span class="w"> </span><span class="p">|</span><span class="w"> </span>xxxxxxxxxxxx<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>config_drive<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>created<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="m">2021</span>-05-19T15:44:50Z<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>flavor<span class="w"> </span><span class="p">|</span><span class="w"> </span>c2.medium<span class="w"> </span><span class="o">(</span><span class="m">11</span>....94-6629-444d-ab2d-f6........83<span class="o">)</span><span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>hostId<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>id<span class="w"> </span><span class="p">|</span><span class="w"> </span>af....c2-562e-49b3-9848-f0........8d<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>image<span class="w"> </span><span class="p">|</span><span class="w"> </span>Debian-10<span class="w"> </span><span class="o">(</span>ea....5f-e72d-4da3-8f04-5d........3c<span class="o">)</span><span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>key_name<span class="w"> </span><span class="p">|</span><span class="w"> </span>ssh-rsa-etienne<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>name<span class="w"> </span><span class="p">|</span><span class="w"> </span>sandbox-test-user1<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>progress<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="m">0</span><span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>project_id<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="m">71</span>............................3d<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>properties<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>security_groups<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nv">name</span><span class="o">=</span><span class="s1">'29....92-f29a-470b-a7bf-cd........20'</span><span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>status<span class="w"> </span><span class="p">|</span><span class="w"> </span>BUILD<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>updated<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="m">2021</span>-05-19T15:44:50Z<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>user_id<span class="w"> </span><span class="p">|</span><span class="w"> </span>8e............................................................00<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>volumes_attached<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="p">|</span>
+-----------------------------+------------------------------------------------------------------+
</code></pre></div>
<p>Vérification du résultat :</p>
<div class="highlight"><pre><span></span><code>><span class="w"> </span>openstack<span class="w"> </span>server<span class="w"> </span>list<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>sandbox-test-user1
<span class="p">|</span><span class="w"> </span>af....c2-562e-49b3-9848-f0........8d<span class="w"> </span><span class="p">|</span><span class="w"> </span>sandbox-test-user1<span class="w"> </span><span class="p">|</span><span class="w"> </span>ACTIVE<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nv">internal</span><span class="o">=</span><span class="m">172</span>.20.X.Y<span class="w"> </span><span class="p">|</span><span class="w"> </span>Debian-10<span class="w"> </span><span class="p">|</span><span class="w"> </span>c2.medium<span class="w"> </span><span class="p">|</span>
</code></pre></div>
<p>La nouvelle VM est bien présente sur l'openstack.</p>
<div class="highlight"><pre><span></span><code>><span class="w"> </span>openstack<span class="w"> </span>server<span class="w"> </span>show<span class="w"> </span>sandbox-test-user1
+-----------------------------+------------------------------------------------------------------+
<span class="p">|</span><span class="w"> </span>Field<span class="w"> </span><span class="p">|</span><span class="w"> </span>Value<span class="w"> </span><span class="p">|</span>
+-----------------------------+------------------------------------------------------------------+
<span class="p">|</span><span class="w"> </span>OS-DCF:diskConfig<span class="w"> </span><span class="p">|</span><span class="w"> </span>MANUAL<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>OS-EXT-AZ:availability_zone<span class="w"> </span><span class="p">|</span><span class="w"> </span>nova<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>OS-EXT-STS:power_state<span class="w"> </span><span class="p">|</span><span class="w"> </span>Running<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>OS-EXT-STS:task_state<span class="w"> </span><span class="p">|</span><span class="w"> </span>None<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>OS-EXT-STS:vm_state<span class="w"> </span><span class="p">|</span><span class="w"> </span>active<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>OS-SRV-USG:launched_at<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="m">2021</span>-05-19T15:44:56.000000<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>OS-SRV-USG:terminated_at<span class="w"> </span><span class="p">|</span><span class="w"> </span>None<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>accessIPv4<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>accessIPv6<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>addresses<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nv">internal</span><span class="o">=</span><span class="m">172</span>.20.X.Y<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>config_drive<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>created<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="m">2021</span>-05-19T15:44:50Z<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>flavor<span class="w"> </span><span class="p">|</span><span class="w"> </span>c2.medium<span class="w"> </span><span class="o">(</span><span class="m">11</span>....94-6629-444d-ab2d-f6........83<span class="o">)</span><span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>hostId<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="m">93</span>....................................................99<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>id<span class="w"> </span><span class="p">|</span><span class="w"> </span>af....c2-562e-49b3-9848-f0........8d<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>image<span class="w"> </span><span class="p">|</span><span class="w"> </span>Debian-10<span class="w"> </span><span class="o">(</span>ea....5f-e72d-4da3-8f04-5d........3c<span class="o">)</span><span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>key_name<span class="w"> </span><span class="p">|</span><span class="w"> </span>ssh-rsa-etienne<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>name<span class="w"> </span><span class="p">|</span><span class="w"> </span>sandbox-test-user1<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>progress<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="m">0</span><span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>project_id<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="m">71</span>............................3d<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>properties<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>security_groups<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nv">name</span><span class="o">=</span><span class="s1">'ssh'</span><span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>status<span class="w"> </span><span class="p">|</span><span class="w"> </span>ACTIVE<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>updated<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="m">2021</span>-05-19T15:44:56Z<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>user_id<span class="w"> </span><span class="p">|</span><span class="w"> </span>8e............................................................00<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span>volumes_attached<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="p">|</span>
+-----------------------------+------------------------------------------------------------------+
</code></pre></div>
<p>Et son status est bien <code>ACTIVE</code>. Bravo.</p>
<h2 id="comment-sy-connecter">Comment s'y connecter ?</h2>
<p>Pour s'y connecter, dans l'immédiat, le plus facile est de lui assigner une floating IP. C'est une IP "publique" qu'on attache à la VM pour pouvoir la joindre depuis l'extérieur d'openstack. Sans cela, la VM existe, certes, mais vous ne pouvez pas vous y connecter directement.</p>
<p>J'imagine que maintenant, vous devez savoir comment procéder. On commence par lister les floating IP dosponible, pour ensuite en attacher une à la VM.</p>
<div class="highlight"><pre><span></span><code>><span class="w"> </span>openstack<span class="w"> </span>floating<span class="w"> </span>ip<span class="w"> </span>list
+--------------------------------------+---------------------+------------------+--------------------------------------+--------------------------------------+----------------------------------+
<span class="p">|</span><span class="w"> </span>ID<span class="w"> </span><span class="p">|</span><span class="w"> </span>Floating<span class="w"> </span>IP<span class="w"> </span>Address<span class="w"> </span><span class="p">|</span><span class="w"> </span>Fixed<span class="w"> </span>IP<span class="w"> </span>Address<span class="w"> </span><span class="p">|</span><span class="w"> </span>Port<span class="w"> </span><span class="p">|</span><span class="w"> </span>Floating<span class="w"> </span>Network<span class="w"> </span><span class="p">|</span><span class="w"> </span>Project<span class="w"> </span><span class="p">|</span>
+--------------------------------------+---------------------+------------------+--------------------------------------+--------------------------------------+----------------------------------+
<span class="p">|</span><span class="w"> </span><span class="m">02</span>....03-ad67-40f8-b580-cb........d1<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="m">10</span>.8.X.Y<span class="w"> </span><span class="p">|</span><span class="w"> </span>None<span class="w"> </span><span class="p">|</span><span class="w"> </span>None<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="m">08</span>....56-76ca-4c85-b0d9-bb........6e<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="m">71</span>............................3d<span class="w"> </span><span class="p">|</span>
<span class="p">|</span><span class="w"> </span><span class="m">19</span>....32-72b4-494e-9bcb-2e........c0<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="m">10</span>.8.X.Y<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="m">192</span>.168.X.Y<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="m">04</span>....16-0e56-4ad5-ae87-ac........c1<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="m">08</span>....56-76ca-4c85-b0d9-bb........6e<span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="m">71</span>............................3d<span class="w"> </span><span class="p">|</span>
<span class="o">[</span>...<span class="o">]</span>
+--------------------------------------+---------------------+------------------+--------------------------------------+--------------------------------------+----------------------------------+
</code></pre></div>
<p>Ici, on voit que la 1e floating IP est disponible, alors que la seconde non.</p>
<div class="highlight"><pre><span></span><code>><span class="w"> </span>openstack<span class="w"> </span>server<span class="w"> </span>add<span class="w"> </span>floating<span class="w"> </span>ip<span class="w"> </span>sandbox-test-user1<span class="w"> </span><span class="m">10</span>.8.X.Y
</code></pre></div>
<p>Plus qu'à s'y connecter :</p>
<div class="highlight"><pre><span></span><code>><span class="w"> </span>ssh<span class="w"> </span>debian@10.8.X.Y
Linux<span class="w"> </span>sandbox-test-user1<span class="w"> </span><span class="m">4</span>.19.0-10-amd64<span class="w"> </span><span class="c1">#1 SMP Debian 4.19.132-1 (2020-07-24) x86_64</span>
The<span class="w"> </span>programs<span class="w"> </span>included<span class="w"> </span>with<span class="w"> </span>the<span class="w"> </span>Debian<span class="w"> </span>GNU/Linux<span class="w"> </span>system<span class="w"> </span>are<span class="w"> </span>free<span class="w"> </span>software<span class="p">;</span>
the<span class="w"> </span>exact<span class="w"> </span>distribution<span class="w"> </span>terms<span class="w"> </span><span class="k">for</span><span class="w"> </span>each<span class="w"> </span>program<span class="w"> </span>are<span class="w"> </span>described<span class="w"> </span><span class="k">in</span><span class="w"> </span>the
individual<span class="w"> </span>files<span class="w"> </span><span class="k">in</span><span class="w"> </span>/usr/share/doc/*/copyright.
Debian<span class="w"> </span>GNU/Linux<span class="w"> </span>comes<span class="w"> </span>with<span class="w"> </span>ABSOLUTELY<span class="w"> </span>NO<span class="w"> </span>WARRANTY,<span class="w"> </span>to<span class="w"> </span>the<span class="w"> </span>extent
permitted<span class="w"> </span>by<span class="w"> </span>applicable<span class="w"> </span>law.
debian@sandbox-test-user1:~$
</code></pre></div>
<p>Bravo !</p>
<p>Ça sera tout pour aujourd'hui.</p>
<div class="footnote">
<hr>
<ol>
<li id="fn:1">
<p>Openstack étant pensé en architecture micro-service, Horizon est le nom donné au service qui fait tourner la webui. <a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:2">
<p>Keystone est le micro-service chargé de tout ce qui a rapport à l'authentification au sein d'openstack. <a class="footnote-backref" href="#fnref:2" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
</ol>
</div>La passion dans l'informatique n'est pas synonyme de qualité de code2020-03-23T19:00:00+01:002020-03-23T19:00:00+01:00Etienne Magrotag:blog.etienne-magro.fr,2020-03-23:/passion-profession-informatique.html<p>Il est de coutume de demander à ses employés "informaticiens" d'être des passionnés. Je le suis moi-même, mais il faut toujours apporter un peu de raison dans cette passion. Ici, je vais démontrer que coder par passion n'est pas pareil que coder en environnement professionnel.</p><h2 id="je-suis-un-passionne">Je suis un passionné</h2>
<p>J'adore coder.</p>
<p>Que ça soit dit et compris, j'adore l'informatique.</p>
<p>Ce que je trouve génial, en tant que développeur, c'est qu'on peut batir un monde. On peut <a href="https://fr.wikipedia.org/wiki/Second_Life" title="Page Wikipedia du logiciel Second Life">littéralement</a> <a href="https://fr.wikipedia.org/wiki/R%C3%A9alit%C3%A9_virtuelle" title="Page Wikipedia sur la réalité virtuelle">construire</a> des <a href="https://fr.wikipedia.org/wiki/Myst" title="Page Wikipedia sur le jeu vidéo Myst">mondes</a>, des <a href="https://fr.wikipedia.org/wiki/Final_Fantasy" title="Page Wikipedia sur les jeux vidéos Final Fantasy">univers</a> qui <a href="https://fr.wikipedia.org/wiki/BioShock_Infinite" title="Page Wikipedia sur les jeux vidéos Bioshock Infinite">n'existent pas</a>, et <a href="https://fr.wikipedia.org/wiki/The_Legend_of_Zelda:_Breath_of_the_Wild" title="Page Wikipedia sur le jeu video Zelda Breath of the Wild">cela</a> comme <a href="https://fr.wikipedia.org/wiki/The_Elder_Scrolls" title="Page Wikipedia sur les jeux vidéos the Elder Scrolls">on</a> <a href="https://fr.wikipedia.org/wiki/A_Blind_Legend" title="Page Wikipedia sur le jeu vidéo Blind Legend">l'entend</a>. C'est encore mieux qu'un architecte, qu'un chef de chantier, qu'un manoeuvre, qu'un ouvrier, même tous ensemble. C'est la transposition des jeux de <a href="https://fr.wikipedia.org/wiki/Lego" title="Page Wikipedia sur les Lego">lego</a> et <a href="https://fr.wikipedia.org/wiki/Meccano" title="Page Wikipedia sur les Meccano">meccano</a> de mon enfance à l'age adulte et à l'échelle supérieure. Je ne suis plus limité par la taille de ma chambre, la fortune (ou plutôt son absence) de mes parents ou la mienne. Mon imagination devient ma seule limite.</p>
<p>Je code sur mon temps libre (enfin, beaucoup moins depuis que je suis marié puis papa). Et je batis des trucs. Vraiment.</p>
<p>A un moment, j'ai codé un SGBD, juste parce que je voulais savoir comment MySQL marchait (à ne pas utiliser en prod, SVP). A un autre moment, j'ai codé un moteur de recherche, juste parce que je voulais savoir comment google fonctionnait. Et il utilisait le SGBD précédemment cité. Et puis, j'ai aussi codé un site web d'aggrégation d'articles d'actualité, parce que je voulais créer un robot-écrivain et que je me suis dit que ça serait bête de perdre toute la matière première. J'ai même <a href="https://blog.etienne-magro.fr/un-temps-pour-tout-bis.html">gagné de l'argent avec</a>. J'ai commencé un nombre incalculable de projets perso, juste parce que je pouvais le faire : un <a href="https://blog.etienne-magro.fr/hdf5-python.html">jeu vidéo de sous-marin pour aveugle</a>, un livre (oui, je <a href="https://pandoc.org/epub.html" title="Comment créer un livre électronique avec pandoc">code un livre</a>, et alors), un système pour pouvoir lire mes BD/Comics/Mangas depuis n'importe où sans perdre l'avancement, un bot de trading de crypto-monnaie (à ne pas utiliser en vrai), des sites web pour la famille et les amis, plusieurs jeux vidéos mobiles qui ne sont jamais arrivés en prod, et pleins d'autres choses.</p>
<p>Alors, oui, j'aime vraaaaiiiiment coder. Mais ce n'est pas pour ça que je vais passer ma vie à le faire. Et encore moins coder 12h par jour pour un employeur.</p>
<h2 id="je-suis-aussi-un-professionnel">Je suis aussi un professionnel</h2>
<p>Mais au fait, pourquoi ne suis-je pas développeur ?</p>
<p>Aujourd'hui, je suis AdminSys Linux/SRE/DevOps, selon la définition que vous accordez à chacun de ces mots. En gros, je ne code pas tant que ça, au niveau professionnel. Je "code" pour industrialiser et automatiser de l'infra, mais je ne code pas d'appli/site web/logiciel que des clients finaux utiliseront.<br>
Il y a plusieurs raisons simple à cela. Le première, c'est que j'aime coder, certes, mais le code ne fait pas tout. Le code doit tourner sur quelque chose, et ce quelque chose m'intéresse tout autant. La seconde, c'est que parfois "passion" et "profession" ne font pas bon ménage.</p>
<p>Tout le monde connait cette maxime :</p>
<blockquote>
<p>Choisis un travail que tu aimes, et tu n'auras pas à travailler un seul jour dans ta vie.</p>
</blockquote>
<p>Oui mais non, en fait.</p>
<p>Prenons l'exemple de la construction d'un pont. Construire un pont en lego/meccano/imprimante 3D peut être génial, apporter du fun, être une passion, etc... Mais ça n'a rien à voir avec la construction d'un pont sur lequel des trains ou des semi-remorques pleins devront passer. Les contraintes ne sont pas les mêmes. Les responsabilités aussi. L'écroulement d'un pont en lego dans ma chambre n'aura pas les mêmes conséquence que l'écroulement d'un pont autoroutier. Il en va de même pour beaucoup de passions. J'aime écouter et jouer de la musique (>30ans d'expérience d'instrument) et sonoriser pour mon association, et pourtant je ne sonoriserais jamais pour la radio/télé/enregistrement ou un concert live ou ne jouerais de la musique contre de l'argent.</p>
<p>C'est pareil pour le code. J'adore coder, mais coder en environnement professionnel n'est pas pareil que coder par passion.</p>
<p>Quand je code mon projet pour le fun, je n'ai pas la responsabilité de la vie de gens. On reparle de Boeing et du MCAS de ses 737-Max ? Ou des programmes en milieux hospitalier ? Dans ma vie professionnelle, j'ai eu à administrer des serveurs informatiques hospitaliers. <em>Si ça ne marche pas, des gens </em><em>meurent</em><em>.</em></p>
<p>Je ne pense pas qu'une passion devrait avoir ce genre de responsabilités.</p>
<p>L'informatique est aussi une industrie, comme l'industrie nucléaire ou l'industrie automobile. Quand on pratique une activité <em>professionnelle</em>, certaines pratiques viennent se greffer. C'est aussi le cas dans l'informatique. En simplifiant, il y a beaucoup de bonnes pratiques, en informatique, qui devraient être des contraintes fortes quand on code de manière professionnelle. Pour l'industrie automobile, il y des crash-tests, des obligations de ceintures, d'airbags et d'émission de CO2, des vérifications de ceci et de cela. Il existe des <em>bonnes pratiques</em> qui correspondent à la même chose : tests unitaires, d'intégrations, d'intrusion, test-driven development, CI/CD, etc... et j'en passe.</p>
<p>Et ces bonnes pratiques devraient, à mon avis, être autant obligatoires et contraignantes que ne le sont les obligations et tests dans l'automobile. Et toutes ses choses, sont <em>nécessaires</em>.</p>
<p>Mais elle ne sont pas fun (au moins pour moi). Elles ne sont pas passionnantes, mais il <em>faut</em> les faire.<br>
Je ne cherche pas à m'en convaincre, je le suis déjà et autant que je le peux, j'essaye d'obliger mes co-équipiers à les pratiquer. Mais ce n'est pas comme ça que je code pour la <em>passion</em>. Oui, c'est un fait, la plupart de mes projets perso n'ont pas de tests unitaires. Même si mes projets perso sont en partie en CI/CD, ils ne sont pas "test-driven", etc...</p>
<p>C'est pour ça que coder n'est pas mon activité professionnelle principale.</p>
<p><strong>On ne code pas de la même manière pour une passion que pour son job.</strong></p>
<p>D'un autre côté, c'est également pour ça aussi, qu'ayant bien compris l'importance de ces contraintes, mon poste actuel me permet d'essayer de les faire appliquer autant que je le peux. Au travail. Pas sur mes projets perso chez moi.</p>
<h2 id="pour-que-la-passion-reste-une-passion">Pour que la passion reste une passion</h2>
<p>Dernière raison que je vais détailler ici : il faut que la passion reste.</p>
<p>Coder est <em>une</em> de mes passions. J'en ai d'autres. Et j'aimerais qu'elles restent à leurs place de passion. Je ne veux pas perdre la passion de coder. Car à imposer des contraintes (même si elles sont nécessaire), la passion peut partir. C'est pour cela que mes projets perso n'ont (pour la plupart) pas de tests unitaires, etc... et que je m'impose à moi une règle impérieuse : je fais mes horaires.</p>
<p>Ca ne veut pas dire que je serais pointilleux à la minutes près et sous-performant au travail, loin de là. Ca ne veut pas dire que je ne ferais pas un peu d'heure sup' de temps en temps, ou en cas de coup dur, mais ça doit rester anecdotique. Ca ne veut pas dire que je n'aime pas mon travail, pas du tout.</p>
<p>Faire uniquement mes heures, ça veut dire plusieurs choses pour moi :</p>
<ul>
<li>Ca veut dire que je respecte un contrat. Parfois, on a tendance à oublier, mais un contrat n'est pas à prendre à la légère, il a une valeur juridique et comporte des clauses contraignantes pour le bon profit de 2 parties : moi et mon employeur. Par exemple, je reçois une compensation financière pour le temps dont mon employeur profite de ma science. Travailler plus pour le même salaire rompt virtuellement le contrat en cela qu'il devient plus profitable pour mon employeur que pour moi.</li>
<li>Ca veut dire que je dispose de temps pour me reposer et profiter de ma famille. Je serais donc plus performant (en qualité de travail, ainsi qu'en quantité de travail par unité de temps), si je suis reposé.</li>
<li>Ca veut finalement dire que je disposerais aussi de temps pour assouvir et entretenir mes passions, y compris le code.</li>
</ul>
<p>In fine, ça me permet de pratiquer ma passion et donc d'être meilleur dans ma passion. Pour mon code. Et donc, aussi pour mon travail.</p>
<p>En conclusion, quand je vois des offres d'emplois qui demandent des gens "passionnés", j'ai toujours tendance à me méfier. Pas parce que je ne suis pas passionné, loin de là. Mais parce que coder par passion, n'est pas pareil que coder pour un employeur. Et s'ils ne veulent que des développeurs passionnés, je ne veux pas imaginer la qualité de ce qu'ils produisent.</p>
<p><strong>Je ne code pas de la même manière pour le fun ou pour le travail.</strong></p>
<p>Si vous m'embauchez, vous embauchez un <em>professionnel</em>, qui fera un travail de qualité <em>professionnelle</em>. Avec tests unitaires, "test-driven", et cetera... Pas un amateur qui code par <em>passion</em>. Ca, je le réserve pour l'amusement chez moi.</p>L'interview qui dérappe2019-08-29T15:00:00+02:002019-08-29T16:00:00+02:00Etienne Magrotag:blog.etienne-magro.fr,2019-08-29:/wrong-interview.html<p>Petit retour d'expérience sur un entretien qui m'a fait un peu halluciner</p><h2 id="les-premices">Les prémices</h2>
<p>Dernièrement, je passais un entretien pour un job de "DevOps Senior" (hors SSII, précision importante). Au bout de 3 entretiens, on me prévient que le prochain sera technique. Normal, me direz-vous. Je tique quand même un peu qu'il faille autant d'entretiens : on en sera au 4e, toujours sans avoir vu de RH, et sans l'ombre d'une proposition.</p>
<p>Là où ça se gâte, c'est que je devrais passer cet entretien <em>technique</em> <strong>après 20h</strong>, en remote synchrone, de chez moi. Donc, après ma journée de travail normale, et après mon second "boulot" de <strong>papa</strong>. Oui, j'ai 2 enfants en bas-âge dont je m'occupe en rentrant du boulot : préparation du souper, distribution du souper aux enfants, bains des enfants, coucher des enfants (avec l'histoire et tout le rituel). Tout parent de jeunes enfants sait que ce n'est pas de tout repos. A 20h, on n'a qu'une envie (pire qu'une envie, j'oserais parler même de capacité physique et intellectuelle), c'est de s'écrouler dans un canapé après une douche régénératrice.</p>
<h2 id="lentretien">L'entretien</h2>
<p>Voici le problème que m'a proposé la personne au téléphone et sur un tableau blanc partagé via internet :<br>
Etant donnée cette entrée : <code>[2, 98, 67, 33, 42, 50, 58, 50, 50]</code>, codez la fonction qui donne cette sortie : <code>[(2,98), (67,33), (42,58), (50,50)]</code>.</p>
<p>J'ai bien demandé avant de coder, il ne regardait pas à la qualité du code pour cet entretien, il faut juste que ça marche. OK.
Je propose une première solution quick&dirty fonctionnelle au bout de 10 minutes (je rappelle qu'il est passé 20h, après une journée bien remplie de support).</p>
<p>Ca marche, parfait. L'examinateur change alors l'énoncé avec cette entrée : <code>[2, 98, 67, 33, 42, 50, 58, 50, 50, 50]</code> pour cette sortie : <code>[(2,98), (67,33), (42,58), (50,50), (50,50)]</code>. En changeant l'entrée ainsi, mon code précédent ne renvoie pas la bonne sortie.</p>
<p>Je passe donc les 20-30 prochaines minutes à galérer pour corriger/produire un code qui répond au nouveau problème, tout en répondant aux questions techniques (de programmation, ce détail à son importance) de l'examinateur.</p>
<p>Il est 20h30-20h40 quand je sors enfin un code fonctionnel répondant au problème. Voici mon code final :</p>
<div class="highlight"><pre><span></span><code><span class="nb">input</span> <span class="o">=</span> <span class="p">[</span><span class="mi">2</span><span class="p">,</span> <span class="mi">98</span><span class="p">,</span> <span class="mi">67</span><span class="p">,</span> <span class="mi">33</span><span class="p">,</span> <span class="mi">42</span><span class="p">,</span> <span class="mi">50</span><span class="p">,</span> <span class="mi">58</span><span class="p">,</span> <span class="mi">50</span><span class="p">,</span> <span class="mi">50</span><span class="p">,</span> <span class="mi">50</span><span class="p">]</span>
<span class="n">output</span> <span class="o">=</span> <span class="p">[]</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'input'</span><span class="p">,</span> <span class="nb">input</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">input</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">counter_index</span> <span class="o">=</span> <span class="nb">input</span><span class="o">.</span><span class="n">index</span><span class="p">(</span><span class="mi">100</span><span class="o">-</span><span class="n">i</span><span class="p">)</span>
<span class="n">counter</span> <span class="o">=</span> <span class="nb">input</span><span class="o">.</span><span class="n">pop</span><span class="p">(</span><span class="n">counter_index</span><span class="p">)</span>
<span class="n">output</span><span class="o">.</span><span class="n">append</span><span class="p">((</span><span class="n">i</span><span class="p">,</span> <span class="mi">100</span><span class="o">-</span><span class="n">i</span><span class="p">))</span>
<span class="k">except</span><span class="p">:</span>
<span class="k">pass</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'output'</span><span class="p">,</span> <span class="n">output</span><span class="p">)</span>
</code></pre></div>
<p>Sortie :</p>
<div class="highlight"><pre><span></span><code>etienne@computer:~/code$<span class="w"> </span>python3<span class="w"> </span>temp.py
input<span class="w"> </span><span class="o">[</span><span class="m">2</span>,<span class="w"> </span><span class="m">98</span>,<span class="w"> </span><span class="m">67</span>,<span class="w"> </span><span class="m">33</span>,<span class="w"> </span><span class="m">42</span>,<span class="w"> </span><span class="m">50</span>,<span class="w"> </span><span class="m">58</span>,<span class="w"> </span><span class="m">50</span>,<span class="w"> </span><span class="m">50</span>,<span class="w"> </span><span class="m">50</span><span class="o">]</span>
output<span class="w"> </span><span class="o">[(</span><span class="m">2</span>,<span class="w"> </span><span class="m">98</span><span class="o">)</span>,<span class="w"> </span><span class="o">(</span><span class="m">67</span>,<span class="w"> </span><span class="m">33</span><span class="o">)</span>,<span class="w"> </span><span class="o">(</span><span class="m">42</span>,<span class="w"> </span><span class="m">58</span><span class="o">)</span>,<span class="w"> </span><span class="o">(</span><span class="m">50</span>,<span class="w"> </span><span class="m">50</span><span class="o">)</span>,<span class="w"> </span><span class="o">(</span><span class="m">50</span>,<span class="w"> </span><span class="m">50</span><span class="o">)]</span>
</code></pre></div>
<h2 id="la-suite">La suite</h2>
<p>Quelle a été la suite ?<br>
Hé bien, d'après le ton de sa voix et les questions qu'il a posé ensuite, l'examinateur doutait clairement de mes compétences et de mon CV.</p>
<p>C'était il y a plus d'un mois, et je n'ai jamais eu de retour. Pas de mail, ni d'appel téléphonique. Rien. Nada. Silence radio.</p>
<p>Pourquoi ça a foiré, de mon point de vue de candidat ?</p>
<ul>
<li>
<p><em>faire un entretien après 20h est une très mauvaise idée</em>. Pour de multiples raisons :</p>
<ul>
<li><em>organisation familiale</em> : un "senior" a au moins 10 ans d'expérience, et donc plus de 30 ans et une forte probabilité d'avoir des enfants. Passer un entretien en journée, ça se fait bien, en terme d'organisation. En soirée, il faut composer avec le conjoint et les enfants et l'organisation qui tourne autour (repas de la famille à cuisiner/manger/faire manger, toilettes des petits enfants, devoirs des grands enfants, moments familliaux, etc...).</li>
<li><em>fatigue</em> : à 20h, la fatigue se fait sentir après une journée normale et complète de travail. Et de vie de famille.</li>
</ul>
</li>
<li>
<p><em>l'entretien technique n'était pas du tout adapté ni au candidat (moi), ni au poste</em> (de ce que j'avais pu en comprendre dans les 3 entretiens précédents) :</p>
<ul>
<li>juger un adminsys/devops (c'est mon job actuel, et depuis 2012 (pour moi, DevOps/SRE, c'est de l'adminsys 2.0 (oui, ça se discute))) sur ses compétences en programmation/algorythmie est une très mauvaise idée. Pour paraphraser Albert, c'est comme juger un poisson sur sa capacité à grimper un arbre.</li>
<li>juger quelqu'un qui a 10 ans d'expérience avec un problème théorique scolaire (clairement, le problème posé est ce qu'on donne en exercice aux étudiants) et attendre une réponse scolaire (répondre au tableau ou sur papier en 2 minutes avec un code fonctionnel pendant que l'examinateur pose des questions), c'est faire un oubli total et volontaire de son expérience et de tout ce qu'il a fait depuis qu'il est sorti d'école. </li>
</ul>
</li>
</ul>
<p>Effectivement, vu comme ça, j'ai été particulièrement mauvais : j'ai mis 40 minutes à pondre un bout de code répondant à un problème scolaire d'algorythmie pour développeurs débutants.</p>
<p>En regardant le contexte, on comprend pourquoi : il était 20h (11h du matin pour mon examinateur), j'étais fatigué de ma journée de travail normale et de ma seconde vie de papa, et je n'ai pas codé comme ça/ce genre de chose depuis plus de 10 ans.</p>
<p>Mon <a href="https://www.linkedin.com/in/sathish-chindarankandy-3009a87/" title="Profile LinkedIn de mon examinateur">examinateur</a> n'a pas du tout vu le contexte et m'a catalogué/black-listé.</p>
<p>Dommage.</p>Programmer l'exécution d'un script en avance sur linux2019-07-30T12:00:00+02:002019-07-30T12:00:00+02:00Etienne Magrotag:blog.etienne-magro.fr,2019-07-30:/tache-cron-autodestructible.html<p>Petite astuce. On doit parfois lancer un script sur des serveurs linux à une heure donnée, mais pour divers raisons, on ne peut pas le lancer nous-même, ni même utiliser un jenkins/rundeck/whatever centralisé. Voici comment je fais. (SPOILER : tâche cron autodestructible)</p><h2 id="besoin-lancer-un-script-sur-des-serveurs-linux-a-une-heure-donnee-sans-jenkins">Besoin : lancer un script sur des serveurs linux à une heure donnée, sans jenkins</h2>
<p>On doit parfois lancer un script bash sur des serveurs linux (par exemple pour faire de la maintenance ou redémarrer un service hors horaires d'utilisation). Si on a un jenkins avec ansible et une connexion directe aux serveurs, on va dire que c'est facile.</p>
<p>Mais comment faire quand on a pas de jenkins ou pas une connexion stable. On peut utiliser le cron local à chaque serveur.</p>
<p>L'astuce, c'est de lancer le script avec une tâche cron et de supprimer cette tâche cron immédiatement après son lancement.</p>
<h2 id="solution-tache-cron-auto-destructible">Solution : tâche cron auto-destructible</h2>
<p>Passons au code directement, les explications après :</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>crontab<span class="w"> </span>-l
<span class="m">0</span><span class="w"> </span><span class="m">20</span><span class="w"> </span>*<span class="w"> </span>*<span class="w"> </span>*<span class="w"> </span>bash<span class="w"> </span>/opt/cron/mon_script.sh
$<span class="w"> </span>cat<span class="w"> </span>/opt/cron/mon_script.sh
<span class="c1">#!/usr/bin/env sh</span>
<span class="c1"># Remove self from cron</span>
crontab<span class="w"> </span>-l<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-v<span class="w"> </span><span class="s1">'/opt/cron/mon_script.sh'</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>crontab<span class="w"> </span>-
<span class="c1"># Changement de la version dans la conf nrpe</span>
sed<span class="w"> </span>-i<span class="w"> </span><span class="s1">'s#command\[check_app_version\].*#command\[check_app_version\]=/usr/lib64/nagios/plugins/check_app_version 1.9.45#'</span><span class="w"> </span>/etc/nrpe.d/common.cfg
<span class="c1"># Restart le service nrpe</span>
systemctl<span class="w"> </span>restart<span class="w"> </span>nrpe
<span class="c1"># do the stuff here</span>
<span class="c1"># par exemple, met à jour l'appli</span>
<span class="c1"># blablabla</span>
</code></pre></div>
<p>Ok, décortiquons.</p>
<p>En premières lignes, on voit que le script bash <code>/opt/cron/mon_script.sh</code> est croné tous les jours à 20h.<br>
Ensuite, on a le détail du-dit script.</p>
<p>Et sa première action est de s'enlever de cron.<br>
Voilà.<br>
Fin de l'astuce.</p>
<p>Bon, comme on essaye de faire des choses bien, et qu'il n'y aura pas retour sur l'action entreprise, la deuxième chose faite est de modifier la <a href="https://fr.wikipedia.org/wiki/NRPE" title="Wikipedia NRPE">supervision</a> pour dire "on s'attend à être dans <em>cet</em> état-là". Comme ça, si jamais la suite ne se passe pas bien, un coup d'oeil à la <a href="https://www.nagios.org/" title="Site officiel de Nagios">supervision</a> nous l'indiquera avec une belle alerte.</p>Découverte du format HDF5 en python2019-07-08T15:00:00+02:002019-07-08T15:00:00+02:00Etienne Magrotag:blog.etienne-magro.fr,2019-07-08:/hdf5-python.html<p>Un de mes side-project est le développement d'un petit jeu type open-world en python. A un moment, s'est présenté la problématique du stockage d'une map du jeu. Retour sur 3 format de stockage essayés dans cette optique : json, sqlite3, hdf5.</p><h2 id="la-map-dun-jeu-open-world-ca-represente-quoi">La map d'un jeu open-world, ça représente quoi ?</h2>
<p>Ceux qui me connaissent savent que je code sur mon temps libre. Et il y a quelques temps, je me suis mis en tête de coder pour le fun un petit jeu open-world sur le thème du monde sous-marin. Très vite, la problématique du stockage de la carte de ce jeu s'est posée. Comment stocker ça correctement.</p>
<p>En gros, voilà comment ça se présente : on est dans un jeu sous-marin, donc je dois pouvoir stocker la profondeur (on utilisera un integer pour faire simple) de n'importe quel endroit sur la carte. En partant sur une granularité de 1m², si je veux une carte de 5km*5km, ça donne 5000*5000=25 millions de points à stocker<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup>.</p>
<p>25 millions d'integer, ça prend grosso-modo 50Mo. Sans structure et en utilisant des short int de 2 octets. Cool !</p>
<p>En RAM, ça passe large, même sur un raspberry pi !
Plus qu'à stocker ça sur disque.</p>
<h2 id="place-au-code">Place au code</h2>
<h3 id="flat">Flat</h3>
<p>Pour le dev, on va commencer par stocker ça dans des fichiers "flat", tout simple.</p>
<p>Admettons que j'ai une fonction <code>depth(x,y)</code> qui pour chaque coordonnée (x,y) me génère une profondeur.</p>
<p>Génération de ce fichier map flat :</p>
<div class="highlight"><pre><span></span><code><span class="n">data</span> <span class="o">=</span> <span class="s1">''</span>
<span class="k">for</span> <span class="n">y</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">int</span><span class="p">(</span><span class="n">GAME_HEIGHT</span><span class="p">)):</span>
<span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">int</span><span class="p">(</span><span class="n">GAME_WIDTH</span><span class="p">)):</span>
<span class="n">data</span> <span class="o">+=</span> <span class="s1">'</span><span class="si">{}</span><span class="s1"> '</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">depth</span><span class="p">(</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">)))</span>
<span class="n">data</span> <span class="o">+=</span> <span class="s1">'</span><span class="se">\n</span><span class="s1">'</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">output_file</span><span class="p">,</span> <span class="s2">"w"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</code></pre></div>
<p>Facile !</p>
<p>Et ça donne un fichier d'un peu moins de 100Mo. Et 12Mo gzippé. Par contre, ça veut dire que pour l'utiliser, il faut éventuelle le dézipper et écrire toute la mécanique d'import du fichier en RAM, et de requêtage.</p>
<p>Ca reste clairement jouable !</p>
<h3 id="sqlite">Sqlite</h3>
<p>Et si on avait envie d'utiliser un format de BDD parce qu'on aime ça ? genre du <a href="https://sqlite.org/">sqlite</a>, ça donnerait quoi ? En plus, c'est <a href="https://docs.python.org/3/library/sqlite3.html">fourni en standard dans python</a>.</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">sqlite3</span>
<span class="n">conn</span> <span class="o">=</span> <span class="n">sqlite3</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">output_file</span><span class="o">+</span><span class="s1">'.tmp'</span><span class="p">)</span>
<span class="n">cur</span> <span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="n">cursor</span><span class="p">()</span>
<span class="n">cur</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s1">'CREATE TABLE map (x SMALLINT, y SMALLINT, h SMALLINT);'</span><span class="p">)</span>
<span class="k">for</span> <span class="n">y</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">int</span><span class="p">(</span><span class="n">GAME_HEIGHT</span><span class="p">)):</span>
<span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">int</span><span class="p">(</span><span class="n">GAME_WIDTH</span><span class="p">)):</span>
<span class="n">c</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">depth</span><span class="p">(</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">))</span>
<span class="n">cur</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">"INSERT INTO map VALUES (?,?,?)"</span><span class="p">,</span> <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">c</span><span class="p">))</span>
<span class="n">conn</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
<span class="n">cur</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s1">'VACUUM;'</span><span class="p">)</span>
<span class="n">conn</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</code></pre></div>
<p>Bon, par contre, ça nous donne un fichier d'un peu plus de 400Mo, pour les mêmes données que précédemment. Plutôt embêtant. Et même inenvisageable.</p>
<h3 id="hdf5">HDF5</h3>
<p>Devant les résultats obtenus pour Sqlite, je me suis mis en tête de chercher un autre format de fichier à-la sqlite, mais qui donnerait des fichiers plus petits. Et j'ai fini par trouvé le <a href="https://www.hdfgroup.org/">HDF5</a>. En python, ça s'utilise avec la lib <code>h5py</code> ou <code>pytables</code>. Je suis parti avec le dernier.</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">tables</span>
<span class="n">db_struct</span> <span class="o">=</span> <span class="p">{</span>
<span class="s1">'x'</span><span class="p">:</span> <span class="n">tables</span><span class="o">.</span><span class="n">Int16Col</span><span class="p">(),</span>
<span class="s1">'y'</span><span class="p">:</span> <span class="n">tables</span><span class="o">.</span><span class="n">Int16Col</span><span class="p">(),</span>
<span class="s1">'h'</span><span class="p">:</span> <span class="n">tables</span><span class="o">.</span><span class="n">Int16Col</span><span class="p">()</span>
<span class="p">}</span>
<span class="n">h5file</span> <span class="o">=</span> <span class="n">tables</span><span class="o">.</span><span class="n">open_file</span><span class="p">(</span><span class="n">output_file</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="s2">"w"</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s1">'Map'</span><span class="p">)</span>
<span class="n">filters</span> <span class="o">=</span> <span class="n">tables</span><span class="o">.</span><span class="n">Filters</span><span class="p">(</span><span class="n">complevel</span><span class="o">=</span><span class="mi">9</span><span class="p">)</span> <span class="c1"># petit truc ici : je demande la compression au max avec les algo de commpresison standards par defaut de hdf5</span>
<span class="n">group</span> <span class="o">=</span> <span class="n">h5file</span><span class="o">.</span><span class="n">create_group</span><span class="p">(</span><span class="s1">'/'</span><span class="p">,</span> <span class="s1">'group'</span><span class="p">,</span> <span class="s1">'Group'</span><span class="p">)</span>
<span class="n">table</span> <span class="o">=</span> <span class="n">h5file</span><span class="o">.</span><span class="n">create_table</span><span class="p">(</span><span class="n">group</span><span class="p">,</span> <span class="s1">'map'</span><span class="p">,</span> <span class="n">db_struct</span><span class="p">,</span> <span class="n">filters</span><span class="o">=</span><span class="n">filters</span><span class="p">)</span>
<span class="n">heights</span> <span class="o">=</span> <span class="n">table</span><span class="o">.</span><span class="n">row</span>
<span class="k">for</span> <span class="n">y</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">int</span><span class="p">(</span><span class="n">GAME_HEIGHT</span><span class="p">)):</span>
<span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">int</span><span class="p">(</span><span class="n">GAME_WIDTH</span><span class="p">)):</span>
<span class="n">heights</span><span class="p">[</span><span class="s1">'x'</span><span class="p">]</span> <span class="o">=</span> <span class="n">x</span>
<span class="n">heights</span><span class="p">[</span><span class="s1">'y'</span><span class="p">]</span> <span class="o">=</span> <span class="n">y</span>
<span class="n">heights</span><span class="p">[</span><span class="s1">'h'</span><span class="p">]</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">depth</span><span class="p">(</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">))</span>
<span class="n">heights</span><span class="o">.</span><span class="n">append</span><span class="p">()</span>
<span class="n">table</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
<span class="n">table</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
<span class="n">h5file</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</code></pre></div>
<p>Là, c'est tout de suite mieux ! On a directement un fichier de 12Mo, et qui s'utilise peu ou prou <a href="http://www.pytables.org/cookbook/hints_for_sql_users.html">comme un fichier Sqlite</a> :</p>
<div class="highlight"><pre><span></span><code><span class="n">h5file</span> <span class="o">=</span> <span class="n">tables</span><span class="o">.</span><span class="n">open_file</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">m5p</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="s1">'r'</span><span class="p">)</span>
<span class="n">rows</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">h5file</span><span class="o">.</span><span class="n">root</span><span class="o">.</span><span class="n">group</span><span class="o">.</span><span class="n">map</span><span class="o">.</span><span class="n">row</span><span class="o">.</span><span class="n">table</span>
<span class="n">depth</span> <span class="o">=</span> <span class="n">rows</span><span class="o">.</span><span class="n">read_where</span><span class="p">(</span><span class="s1">'((x==</span><span class="si">{}</span><span class="s1">) & (y==</span><span class="si">{}</span><span class="s1">))'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">))[</span><span class="s1">'h'</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span>
<span class="n">h5file</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</code></pre></div>
<p>Banco pour HDF5, alors !</p>
<div class="footnote">
<hr>
<ol>
<li id="fn:1">
<p>5km*5km, c'est juste le début, j'espère bien en générer des plus grande, mais là, je bloque sur d'autres choses :-) <a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
</ol>
</div>Faire une demande d'intervention2019-05-10T09:00:00+02:002019-05-15T09:00:00+02:00Etienne Magrotag:blog.etienne-magro.fr,2019-05-10:/faire-une-demande-d-intervention.html<p>On connait tous les relous qui nous demandent d'intervenir sur un serveur de prod parce que c'est urgent et que "ça marche pas" sans rien dire de plus. Voici la règle que j'impose aux membres de mon équipe quand ils veulent que j'intervienne sur un serveur. En résumé : un contexte.</p><h2 id="contexte-professionnel">Contexte professionnel</h2>
<p>Hier, un dev m’a taggé sur un ticket relatif à la prod bleue. Avec « Prod Bleue » indiqué dans le titre et dans les commentaires/tags/whatever.<br>
Il m’a demandé de regarder un problème de connexion. Je regarde et ne trouve rien. Je creuse les logs, les services systèmes, toujours rien. Je l’appelle par skype, on cherche ensemble, j’indique à l’oral toutes les opérations que je fais et sur quels serveurs je regarde, mais je ne trouve rien alors que lui oui. Jusqu’à ce qu’il me dise que son problème était sur un serveur copieprod-jaune, 15-20 minutes plus tard.</p>
<p>Certes ce cas est extrême. Mais il s'est bien produit et n'aurait jamais dû arrivé. Et ce n'était pas la première fois depuis que je suis arrivé à mon poste actuel.<br>
Avec les bonnes informations tout de suite, le problème aurait été résolu en quelques secondes, au lieu de la bonne demi-heure du cas ci-dessus qui a tendance à se reproduire de plus en plus dernièrement.</p>
<p>Cet état de fait me pose problème pour 3 raisons :</p>
<ul>
<li>Je perds du temps à faire des choses, alors qu’avec les bonnes informations, ça pourrait être résolu rapidement</li>
<li>C’est du temps que je ne passe pas à améliorer notre infra/supervision/scripts de déploiement/etc…</li>
<li>Ça m’énerve (littéralement) que certains gens ne sachent pas ce qui me semblait être la base d’une demande d’intervention</li>
</ul>
<p>Du coup, j’ai créé une page sur le wiki de l'équipe avec le contenu ci-après.</p>
<p>L’idée derrière tout ça est de formaliser le minimum d'information à me fournir pour toute demande d’intervention (« ça marche pas, tu peux regarder ? »), d’en informer tout le monde, et de renvoyer vers cette page sans vergogne toutes les demandes ne fournissant pas ce dont j’ai besoin pour ne pas perdre du temps<sup id="fnref:tempsargent"><a class="footnote-ref" href="#fn:tempsargent">1</a></sup>.</p>
<p><em>Petite précision : dans mon environnement professionnel actuel, on déploie les appli/BDD/etc... directement sur des VMs, sans utiliser docker.</em></p>
<hr>
<p>Etant seul pour gérer toute l'administration système (cf métriques Nagios : pas loin de 100 serveurs dont 60 en production et on en rajoute 5-10 chaque mois) et faire du support aux développeurs/testeurs/etc... en plus des développements propres à l'adminsys/devops, j'ai besoin que vous suiviez quelques règles pour que contacter l'adminsys soit rapide et efficace pour vous comme pour moi.</p>
<h2 id="contexte-ou-quoi-pourquoi">Contexte : Où, Quoi, Pourquoi</h2>
<h3 id="la-regle-dor">La règle d'or</h3>
<p>Voici une règle d'or que doit contenir toute demande : un <strong>contexte</strong>.<br>
C'est <em>obligatoire</em> pour que la demande soit traitée efficacement.</p>
<blockquote>
<p><strong>Toute demande sans contexte se verra systématiquement rejetée avec un lien vers cette page</strong></p>
</blockquote>
<p>Le plus simple pour me fournir un contexte est de répondre à ces 3 questions : <strong>où ?, quoi ?, pourquoi ?</strong></p>
<ul>
<li>Où : quel environnement ? prod ou pas prod ? quel serveur ? le plus simple est de fournir son adresse IP ou l'URL sur laquelle apparaît le problème</li>
<li>Quoi : que se passe-t-il ? que voyez-vous ? quel est le message d'erreur ?</li>
<li>Pourquoi : Pourquoi est-ce une erreur pour vous ? à quoi vous attendiez-vous ?</li>
</ul>
<blockquote>
<h5 id="moyen-mnemotechnique">Moyen mnémotechnique :</h5>
<p>Pour vous rappeler cette règle d'or, dites-vous que l'adminsys est <em>occupé</em> à autre chose avant que vous ne le contactiez.</p>
<p><em>occupé => OQP => Où, Quoi, Pourquoi</em></p>
</blockquote>
<h2 id="precision">Précision</h2>
<p>Parce que le diable se cache dans les détails, il faut s'appliquer à être le plus précis possible.</p>
<h3 id="precision-du-ou">Précision du Où</h3>
<p><em>"Il y a un problème sur l'environnement fushia"</em> n'est pas précis : quel "fushia" (prod-fushia, test-fushia, uat-fushia) ? Quel serveur (serveur web, serveur bdd) ?</p>
<blockquote>
<h5 id="simple-et-precis-ladresse-ip">Simple et précis : l'adresse IP</h5>
<p>Les serveurs ont cela de facile qu'il dispose d'un identifiant absolument unique qui les caractérise bien plus qu'un nom : une adresse IP. Soyez précis, fournissez l'adresse IP du serveur où vous constatez l'erreur.</p>
</blockquote>
<h3 id="precision-du-quoi">Précision du Quoi</h3>
<p>Une capture d'écran seule n'est pas suffisante. Par contre, accompagnée d'une explication de ce qu'il faut regarder dessus, avec citation du texte important à lire est bien mieux.<br>
Pour qu'une ligne de log soit précise, elle doit contenir un horodatage (date + heure à la seconde près) et une indication succincte et explicite du problème.</p>
<p>Exemple de ce qu'il ne faut pas faire :<br>
<em>"Bonjour, il y a eu un problème hier sur le serveur 12.34.56.78. Voilà l'erreur : "java.lang.NullPointerException", tu peux regarder au plus vite ?"</em></p>
<p>Il n'y a pas d'horodatage précis (hier n'est pas précis quand on traite des opérations à la milliseconde près), et indiquer uniquement "java.lang.NullPointerException" ne permet pas d'analyser, donc de corriger le problème au plus vite, ce n'est donc pas une indication succincte et explicite.</p>
<h3 id="precision-du-pourquoi">Précision du Pourquoi</h3>
<p>Une description d'une erreur n'est pas suffisante s'il n'y a pas d'indication de l'état normal.</p>
<p>Exemple :<br>
<em>"Bonjour, quand j'interroge le webservice http://example.com/api/getToto, ça me renvoie {"result": "tata"}, tu pourrais corriger ça ?"</em></p>
<p>Pour corriger, encore faut-il savoir ce que le webservice aurait dû répondre. D'où la nécessité de l'indiquer pour permettre de corriger le problème au plus vite.</p>
<h2 id="pourquoi-imposer-cette-regle">Pourquoi imposer cette règle ?</h2>
<p>Trouver et résoudre un problème avec un contexte se fait généralement en quelques minutes, voire en quelques secondes.<br>
Sans contexte, il faut commencer à trouver le contexte, avant même de pouvoir chercher le problème, et cela prend souvent plusieurs dizaines de minutes (10 à 40 minutes).</p>
<p>Juste pour avoir un ordre d'idée :</p>
<ul>
<li>10m : temps de déploiement d'une copie-prod</li>
<li>20m : temps de build d'une nouvelle version de l'application</li>
<li>40m : temps qu'il faut pour télécharger un nouvel ear sur tous les serveurs en même temps</li>
</ul>
<p>Et tout ça, sans intervention de l'adminsys.<br>
Si trouver le contexte d'un problème prend plus de temps que de déployer une copie-prod-X, c'est que la demande n'a pas été formulée avec assez de précision.</p>
<div class="footnote">
<hr>
<ol>
<li id="fn:tempsargent">
<p>Le temps c'est de l'argent, et je coûte environ 1€/minute à l'entreprise pour laquelle je travaille. Alors, vous voulez la payer combien, votre résolution de bug ? en fournissant les bonnes informations tout de suite : 1€, sans fournir les bonnes informations : 30€. <a class="footnote-backref" href="#fnref:tempsargent" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
</ol>
</div>Watchtower, garder ses docker container à jour2019-03-19T15:00:00+01:002019-03-19T15:00:00+01:00Etienne Magrotag:blog.etienne-magro.fr,2019-03-19:/docker-watchtower.html<p>Retour d'expérience sur la grande question du "comment garder ses containers docker à jour"</p><h2 id="garder-ses-soft-tournant-avec-docker-a-jour">Garder ses soft tournant avec Docker à jour</h2>
<p>Docker, c'est chouette. On peut faire tourner en totale autarcie n'importe quelle logiciel. Plus besoin de dédier une VM à un serveur applicatif.</p>
<p>Problème : comment on fait pour garder tout ça à jour. En effet, dans le cas "VM", un <code>apt update && apt upgrade</code> ou un <code>yum update</code> suffit dans la plupart des cas. C'est toujours valable pour les serveurs sur lesquels tournent docker, ainsi que pour le daemon docker lui-même, mais pour les soft tournant sous docker ?</p>
<p>Jusque là, je le faisais à la main : je récupèrais le flux RSS des releases (sur github ou sur le gitlab ou sur le blog sur soft) dans mon super <a href="https://www.freshrss.org/" title="FreshRSS est le logiciel que j'utilise pour lire mes flux RSS">lecteur de flux RSS</a> autohébergé. Et quand une nouvelle release arrivait, j'exécutais à la main (ou via ansible) <code>docker stop MonServiceHerberge && docker pull editeur/mon_service_herberge:latest && docker run --blablabla MonServiceHeberge</code>.</p>
<p>Quand on a 2 ou 3 services hébergés, c'est encore gérable, mais quand on en a plusieurs dizaines et qu'en plus, ils ont chacun plusieurs briques (au hasard, nginx, par exemple), ça devient plus long à s'en occuper. Et je ne parle même pas des mises-à-jour des layers dockers. Alpine linux pour ne citer que lui.</p>
<h2 id="watchtower">Watchtower</h2>
<p>Et c'est là que j'ai découvert <a href="https://github.com/v2tec/watchtower" title="Le github officiel de Watchtower">Watchtower</a>.</p>
<p>Je vous copie-colle sa description :</p>
<blockquote>
<p>A process for watching your Docker containers and automatically restarting them whenever their base image is refreshed.</p>
</blockquote>
<p>Belle promesse ! et en plus, on peut le déployer dans un docker.</p>
<p>Ok, comment ça marche ? Repompons sans vergogne la documentation officielle :</p>
<div class="highlight"><pre><span></span><code><span class="n">docker</span><span class="w"> </span><span class="n">run</span><span class="w"> </span><span class="o">-</span><span class="n">d</span><span class="w"> </span>\
<span class="w"> </span><span class="o">--</span><span class="n">name</span><span class="w"> </span><span class="n">watchtower</span><span class="w"> </span>\
<span class="w"> </span><span class="o">-</span><span class="n">v</span><span class="w"> </span><span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="n">run</span><span class="o">/</span><span class="n">docker</span><span class="o">.</span><span class="n">sock</span><span class="p">:</span><span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="n">run</span><span class="o">/</span><span class="n">docker</span><span class="o">.</span><span class="n">sock</span><span class="w"> </span>\
<span class="w"> </span><span class="n">v2tec</span><span class="o">/</span><span class="n">watchtower</span>
</code></pre></div>
<h2 id="et-concretement">Et concrêtement</h2>
<p>Personnellement, en plus de serveurs git/jenkins/... codés par d'autres, j'héberge également des trucs que j'ai créé moi-même et que je mets moi-même à jour régulièrement. Et j'ai pu observé watchtower interférer dans ces derniers, les arrêter sans les redémarrer, etc..., alors qu'il est sans problème sur les images dockers récupérées depuis DockerHub.</p>
<p>Voici donc la configuration que j'utilise (codé dans ansible, non mais) :</p>
<div class="highlight"><pre><span></span><code><span class="n">docker</span><span class="w"> </span><span class="n">run</span><span class="w"> </span><span class="o">-</span><span class="n">d</span><span class="w"> </span>\
<span class="w"> </span><span class="o">--</span><span class="n">name</span><span class="w"> </span><span class="n">Watchtower</span><span class="w"> </span>\
<span class="w"> </span><span class="o">-</span><span class="n">v</span><span class="w"> </span><span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="n">run</span><span class="o">/</span><span class="n">docker</span><span class="o">.</span><span class="n">sock</span><span class="p">:</span><span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="n">run</span><span class="o">/</span><span class="n">docker</span><span class="o">.</span><span class="n">sock</span><span class="w"> </span>\
<span class="w"> </span><span class="o">--</span><span class="n">label</span><span class="o">=</span><span class="n">com</span><span class="o">.</span><span class="n">centurylinklabs</span><span class="o">.</span><span class="n">watchtower</span><span class="o">.</span><span class="n">enable</span><span class="o">=</span><span class="bp">true</span>
<span class="w"> </span><span class="n">v2tec</span><span class="o">/</span><span class="n">watchtower</span><span class="w"> </span><span class="o">--</span><span class="n">cleanup</span><span class="w"> </span><span class="o">--</span><span class="n">label</span><span class="o">-</span><span class="n">enable</span><span class="w"> </span><span class="o">-</span><span class="n">s</span><span class="w"> </span><span class="s1">'@weekly'</span>
</code></pre></div>
<p>Avec les variables d'environnement qui vont bien pour recevoir un rapport par e-mail à chaque update de docker. Ca lancera une vérification par semaine sur les docker containers qui ont le bon label (<code>com.centurylinklabs.watchtower.enable=true</code>) et il fera un peu de ménage dans les images qui ne sont plus utilisées.</p>
<p>Il ne me reste plus qu'à rajouter le label <code>com.centurylinklabs.watchtower.enable=true</code> à tous les docker containers dont je veux la mise-à-jour automatique. Les autres, ne seront pas toucher.</p>
<p>Ca me permet d'avoir l'esprit tranquille : j'ai des <a href="https://nextcloud.com/">nextcloud</a>, <a href="https://jenkins.io/">jenkins</a>, <a href="https://gitea.io/">gitea</a>, <a href="https://github.com/linuxserver/docker-openvpn-as">openvpn</a>, <a href="https://traefik.io/">traefik</a>, etc... qui sont toujours à jour. Et ça, c'est top !</p>SMTP-sink, tester l'envoi de mail2019-03-11T17:30:00+01:002019-03-11T17:30:00+01:00Etienne Magrotag:blog.etienne-magro.fr,2019-03-11:/smtp-sink-envoi-mail.html<p>Petite astuce pour mettre en place un serveur fake-smtp pour faire du test d'envoi de mail.</p><h2 id="la-demande-fake-smtp-pour-lenvoi-de-mail-pour-tests">La demande : fake-smtp pour l'envoi de mail pour tests</h2>
<p>A mon boulot actuel, j'ai eu une demande intéressante : les développeurs désiraient avoir un serveur SMTP pour tester l'envoi de mail, y compris avec des data de prod (et donc les <em>vraies</em> adresses e-mail des clients), mais sans que les e-mails soient reçus par les clients.</p>
<p>Le plus simple était de démarrer un serveur postfix en interne et de tout envoyer dans <code>/dev/null</code>.</p>
<p>Mais les dev voulaient aussi pouvoir accéder aux e-mails envoyés pour pouvoir vérifier le formatage, le MIME, etc... Tout envoyer dans <code>/dev/null</code> ne peut pas convenir. J'ai donc mis en place un combo smtp-sink + partage samba sur un petit serveur linux.</p>
<h2 id="la-solution-smtp-sink">La solution : smtp-sink</h2>
<p><a href="http://www.postfix.org/smtp-sink.1.html">SMTP-sink</a> est un soft de la suite postfix. Ca s'installe avec le package postfix (<code>apt-get</code> ou <code>yum</code>, à votre convenance), tout simplement.</p>
<p>Il a ça d'interessant qu'il est un serveur SMTP normal, qui accepte tout mail entrant, et le met dans un fichier sur disque. C'est tout ce qu'il fait. Il ne fera rien d'autre des e-mail que de les mettre dans un fichier. Il ne les enverra pas ailleurs. C'est parfait pour du debug !</p>
<p><strong>ATTENTION : à ne pas faire sur un serveur qui écoute sur internet. Uniquement en interne et pour tests.</strong></p>
<p>Ca s'utilise comme ça (en root pour pouvoir écouter sur le port 25) :</p>
<div class="highlight"><pre><span></span><code><span class="o">[</span><span class="n">root@outils-fakesmtp ~</span><span class="o">]</span><span class="err">#</span><span class="w"> </span><span class="n">smtp</span><span class="o">-</span><span class="n">sink</span><span class="w"> </span><span class="err">:</span><span class="mi">25</span><span class="w"> </span><span class="mi">100</span>
</code></pre></div>
<p>Dans mon cas, voici l'exacte ligne de commande utilisée. Bon, OK, j'aurais du en faire un service (pas inclu dans le package postfix), je note ça dans ma TODO liste.</p>
<div class="highlight"><pre><span></span><code><span class="p">[</span><span class="n">root</span><span class="err">@</span><span class="n">outils</span><span class="o">-</span><span class="n">fakesmtp</span><span class="w"> </span><span class="o">~</span><span class="p">]</span><span class="c1"># screen -dmSL smtp-sink smtp-sink -u mail -d '%Y%m%d-%Hh%M.' -R /var/mail/ -c :25 100</span>
</code></pre></div>
<p>Expliquons :</p>
<ul>
<li><code>screen</code> : pour le lancer dans un screen, comme ça on peut le détacher et ça continuera à fonctionner. C'est d'ailleur ce qui est fait avec les options suivantes.</li>
<li><code>-dmSL smtp-sink</code> : on crée puis détache le screen, avec du logging et comme nom de session "smtp-sink"</li>
<li><code>smtp-sink</code> : la commande en elle-même</li>
<li><code>-u mail</code> : le user avec lequel smtp-sink doit tourner, on ne va pas le laisser tourner en root</li>
<li><code>-d %Y%m%d-%Hh%M.</code> : comment on nomme les fichiers (${année}${mois}${jour}-${heure}h${minute}.hash dans ce cas-ci). A noter qu'un hash est toujours ajouté en fin de fichier</li>
<li><code>-R /var/mail</code> : où stocker les fichiers</li>
</ul>
<p>C'est chouette, on peut voir les e-mails dans <code>/var/mail</code> :</p>
<div class="highlight"><pre><span></span><code><span class="p">[</span><span class="n">root</span><span class="err">@</span><span class="n">outils</span><span class="o">-</span><span class="n">fakesmtp</span><span class="w"> </span><span class="o">~</span><span class="p">]</span><span class="c1"># ls /var/mail/</span>
<span class="mi">20190310</span><span class="o">-</span><span class="mi">08</span><span class="n">h15</span><span class="o">.</span><span class="mi">0</span><span class="n">ad30c02</span><span class="w"> </span><span class="mi">20190311</span><span class="o">-</span><span class="mi">08</span><span class="n">h46</span><span class="o">.</span><span class="mi">2</span><span class="n">dfc539b</span>
</code></pre></div>
<p>Il n'y a plus qu'à partager ce répertoire avec samba.</p>
<h2 id="lastuce-en-plus">L'astuce en plus</h2>
<p>Les dev peuvent parfois être et pointilleux et ignares (nul n'est parfait, et surtout pas moi 😜 ), et donc, quand ils ouvrent leur navigateur de fichier dans <code>\\outils-fakesmtp\mail\</code>, ils ne voient pas des "e-mails" qu'ils peuvent ouvrir en double-cliquant, uniquement des fichiers "bizarres".</p>
<p>Le plus simple est de renommer les fichiers pour leur ajouter une extension <code>.eml</code>, et comme on est DevOps, autant l'automatiser avec cette commande en tâche cron qui tournera toutes les minutes :</p>
<div class="highlight"><pre><span></span><code><span class="k">for</span><span class="w"> </span><span class="n">f</span><span class="w"> </span><span class="ow">in</span><span class="w"> </span><span class="err">`</span><span class="n">ls</span><span class="w"> </span><span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="n">mail</span><span class="o">/*</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">grep</span><span class="w"> </span><span class="o">-</span><span class="n">v</span><span class="w"> </span><span class="s1">'.eml'</span><span class="err">`</span><span class="p">;</span><span class="w"> </span><span class="n">do</span><span class="w"> </span><span class="n">mv</span><span class="w"> </span><span class="o">$</span><span class="n">f</span><span class="w"> </span><span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="n">mail</span><span class="o">/</span><span class="err">`</span><span class="n">basename</span><span class="w"> </span><span class="o">$</span><span class="n">f</span><span class="w"> </span><span class="err">`</span><span class="o">.</span><span class="n">eml</span><span class="p">;</span><span class="w"> </span><span class="n">done</span><span class="p">;</span><span class="w"> </span><span class="n">find</span><span class="w"> </span><span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="n">mail</span><span class="o">/</span><span class="w"> </span><span class="o">-</span><span class="n">type</span><span class="w"> </span><span class="n">f</span><span class="w"> </span><span class="o">-</span><span class="n">name</span><span class="w"> </span><span class="s1">'*.eml'</span><span class="w"> </span><span class="o">-</span><span class="n">mtime</span><span class="w"> </span><span class="o">+</span><span class="mi">1</span><span class="w"> </span><span class="o">-</span><span class="n">exec</span><span class="w"> </span><span class="n">rm</span><span class="w"> </span><span class="p">{}</span><span class="w"> </span>\\<span class="p">;</span>
</code></pre></div>
<p>(Pour ceux qui liront cette ligne de commande, vous noterez que la dernière commande est une commande de nettoyage pour éviter d'avoir un répertoire qui se rempli ad vitam æternam)</p>
<p>Je conviens que ce n'est pas optimal, mais je ne vais pas tout faire pour vous. 😉</p>Docker, LibVirt, Iptables2019-01-07T09:00:00+01:002019-01-07T09:00:00+01:00Etienne Magrotag:blog.etienne-magro.fr,2019-01-07:/docker-libvirt-iptables.html<p>Vous avez déjà essayer de faire tourner sur un même serveur des VM libvirt/KVM et du docker ? alors vous avez été confronté à un problème de pare-feu. Ici est de quoi corriger tout ça.</p><p>Je fais tourner pour mon plaisir et mon auto-formation un homelab sur lequel il y a des VMs KVM. Dernièrement, j'ai revu une partie de mon infrastructure est me suis tourné vers docker pour certains services.</p>
<p>J'aurais pu faire tourné ces services dockers dans leur propre VM, mais j'ai voulu les mettre directement en bar-metal.</p>
<p>Problème, docker vient foutre la grouille dans mes règles pare-feu iptables, rendant impossible l'accès au réseau à mes VM. Honnêtement, quand on ne sait pas, on se dit que la cohabitation docker+KVM va être compliquées. Mais en fait, non, il y a une solution simple.</p>
<h2 id="demander-a-docker-de-ne-pas-intervenir-dans-iptables">Demander à docker de ne pas intervenir dans iptables</h2>
<p>Première possibilité : demander à docker de ne pas toucher à iptables.
ça se fait relativement simplement en ajoutant <code>--iptables=false</code> à la ligne de commande qui démarre le daemon docker.
C'est plutôt bien documenté sie le blog <a href="https://fralef.me/docker-and-iptables.html">fralef.me</a>.</p>
<p>Ou ajouter une clef:valeur <code>"iptables": "false"</code> dans le fichier de configuration de docker <code>/etc/docker/daemon.json</code>.</p>
<p>Le problème maintenant, c'est que vous allez devoir gérer les redirection de ports, etc... normalement gérés par docker en sous-main vous-même. Cf <a href="https://docs.docker.com/network/iptables/#prevent-docker-from-manipulating-iptables">la doc officielle chez docker</a>.</p>
<h2 id="remettre-le-forward-accept-dans-iptables">Remettre le forward accept dans iptables</h2>
<p>La meilleur solution, à mon avis est de laisse docker son affaire, il le fait pas trop mal, mais juste remettre une seule rêgle iptables qui règlera tout (merci <a href="https://paulgorman.org/technical/blog/20181130111416.html">Paul Gorman</a>) :</p>
<div class="highlight"><pre><span></span><code>iptables<span class="w"> </span>-I<span class="w"> </span>FORWARD<span class="w"> </span>-i<span class="w"> </span>vbr0<span class="w"> </span>-o<span class="w"> </span>vbr0<span class="w"> </span>-j<span class="w"> </span>ACCEPT
</code></pre></div>
<p>N'oubliez-pas de sauver cette règle dans votre configuration iptables-persistant et vous serez bon.</p>Un temps pour tout (bis)2018-05-29T19:00:00+02:002018-05-29T19:00:00+02:00Etienne Magrotag:blog.etienne-magro.fr,2018-05-29:/un-temps-pour-tout-bis.html<p>On n'a qu'une vie. Quand on n'a plus le temps de s'occuper de quelque chose, autant l'arrêter proprement.</p><p>J'ai lancé <a href="https://blog.etienne-magro.fr/penser-linformation-autrement.html" title="Annonce lancement What.isup.ws">What.IsUp</a> il y a quelques temps, maintenant.</p>
<p>Au départ, c'était uniquement à usage personnel, pour remplacer mon lecteur de flux RSS. Et je me suis dit, "Si ça me sert, ça pourrait aussi servir à d'autres, autant le mettre public sur internet". C'est comme cela qu'est né What.IsUp.</p>
<p>Le temps passe, et le site reçoit vraiment des visiteurs, jusqu'à des pics à 1500 visiteurs par jours. Moi, ça me fait halluciner.</p>
<p>Aujourd'hui, le serveur souffre beaucoup, le temps d'accès à chaque page s'est allongé à plusieurs secondes, ce qui est inadmissible. Les visiteurs ne sont plus là, ça se comprends parfaitement.</p>
<p>Le truc, c'est que je l'avais dimensionné pour environ 1 visiteur de temps en temps, pas autant que cela. Le serveur souffre, alors pour corriger cela, il faut modifier l'architecture du site, la base de donnée, les scripts d'analyse, etc... J'y ai déjà pas mal passé de temps pour améliorer ça, mais ce n'est pas assez, il faut prendre encore plus de temps pour revoir le fonctionnement dans sa globalité.</p>
<p>Malheureusement, ce temps, je ne l'ai plus. Ou plutôt, le temps que j'ai, je le passe à d'autres choses. Genre mon épouse et les 2 enfants que j'ai eu entretemps.</p>
<p>La décision a aussi été précipité à cause d'une future loi européenne qui pourrait porter préjudice à ce site : <a href="https://fr.reuters.com/article/technologyNews/idFRKCN1IQ2TR-OFRIN">Les sites d'agrégation d'articles de journaux pourraient avoir à payer les journaux</a>. Vu le "bénéfice" que je fais sur ce site, autant le fermer.</p>
<p>Pour être totalement honnête, voici quelques chiffres au moment où je ferme ce site :</p>
<ul>
<li>en moyenne 300-400 visiteurs par jours, à comparer à la moyenne de 1200 il y a 2 ans.</li>
<li>101 journaux analysés toutes les heures</li>
<li>2748122 articles analysés depuis le debut du site (vous ne rêvez pas, ça fait bien 2,7 millions)</li>
<li>11Go de disque pris par la base de donnée.</li>
<li>120€ rapportés par la pub sur toute la durée de vie du site. Cela représente environ 5€/mois dans les meilleurs mois. Et moins d'1€/mois juste avant de le fermer. Ne pensez pas que je sois soudainement devenu riche, le serveur seul coute 12€/mois.</li>
</ul>
<p>Un site meurt proprement, mais d'autres choses naissent en parallèle.
A bientôt, peut-être, pour de nouvelles avantures ;-)</p>Servir son contenu statique avec Docker2018-05-17T19:00:00+02:002015-05-17T19:00:00+02:00Etienne Magrotag:blog.etienne-magro.fr,2018-05-17:/docker-serve-static-content.html<p>Comment servir du contenu web statique avec Docker sans prise de tête et avec minimalisme.</p><p><a href="https://www.docker.com/" title="Site officiel de Docker">Docker</a> est la technologie incontournable dans le monde de l'IT d'aujourd'hui. Elle peut être utilisée pour beaucoup de chose, mais ici, nous allons voir un cas d'usage simple : servir un contenu web statique en l'utilisant. En étant le plus minimaliste et le plus simple dans notre approche.</p>
<p>On va aussi se rajouter une contrainte supplémentaire, sinon, c'est pas drôle : je veux que le docker container soit aussi le plus minimaliste en termes d'utilisation ressources (disque, RAM, CPU), une fois en route. Et aussi, il faut que ça soit un peu rigolo à faire et sans prise de tête à configurer.</p>
<h2 id="la-voie-royale-nginx">La voie royale : Nginx</h2>
<p>Le plus simple et qui vient immédiatement à l'esprit est Nginx. Enfin, dans mon cas, car je le connais plutôt bien et qu'il est vraiment performant.</p>
<h3 id="pourquoi">Pourquoi</h3>
<p>Heu... vous connaissez <a href="https://nginx.org/" title="Site officiel Nginx">Nginx</a>, bien sûr, non ?</p>
<p>Ok, alors, présentons <a href="https://fr.wikipedia.org/wiki/Nginx" title="page Wikipedia sur Nginx">Nginx</a> :</p>
<blockquote>
<p>Nginx (pronounced "engine-x") is an open source reverse proxy server for HTTP, HTTPS, SMTP, POP3, and IMAP protocols, as well as a load balancer, HTTP cache, and a web server (origin server). The nginx project started with a strong focus on high concurrency, high performance and low memory usage.</p>
</blockquote>
<p>En gros, Nginx est <strong>ultra performant pour du servir du contenu statique</strong>, il a été conçu pour ça et est beaucoup plus performant qu'<a href="https://httpd.apache.org/" title="Site officiel du serveur web Apache">Apache</a> à tout point de vue <em>pour cet usage particulier</em>.</p>
<h3 id="code">Code</h3>
<p>Ok, comment fait-on, alors ? Simple : mettez juste le Dockerfile qui suit à la racine du contenu que vous voulez servir, buildez, et runnez, et fini.</p>
<div class="highlight"><pre><span></span><code>FROM nginx:alpine
COPY . /usr/share/nginx/html
</code></pre></div>
<p>En détail, placez un fichier <code>Dockerfile</code> avec le contenu précédent (oui, juste ces 2 lignes) à la racine de votre dépôt git contenant vos fichiers statiques, puis faites ceci :</p>
<div class="highlight"><pre><span></span><code>docker<span class="w"> </span>build<span class="w"> </span>-t<span class="w"> </span>static-content-server<span class="w"> </span>.
docker<span class="w"> </span>run<span class="w"> </span>-d<span class="w"> </span>-p<span class="w"> </span><span class="s2">"80:80"</span><span class="w"> </span>--name<span class="w"> </span>static-server<span class="w"> </span>static-content-server
</code></pre></div>
<p>Voilà.
Je peux pas faire plus simple, je vous promets.</p>
<h2 id="la-voie-de-lamusement">La voie de l'amusement</h2>
<h3 id="le-serveur-web-proprement-dit">Le serveur web proprement dit</h3>
<ul>
<li>"Dis papa, comment on fait les serveurs web ?"</li>
<li>"Regarde ma fille, c'est facile. Tu peux même le faire toi-même"</li>
</ul>
<p>Bon, c'est <a href="https://golang.org/doc/articles/wiki/#tmp_3">faisable en 15 lignes en go</a>, mais si on en veut plus, un serveur web, avec doc incluse et quelques options sympa, ça se fait en ~250 lignes de <a href="https://golang.org/" title="Site officiel du language Go">go</a> sur <a href="https://github.com/halverneus/static-file-server/blob/master/serve.go">https://github.com/halverneus/static-file-server/blob/master/serve.go</a></p>
<p>Il faut git-cloner ce code, le builder et ensuite, on peut l'utiliser en l'incluant facile dans nos propre dépôt git.</p>
<p>D'abord, on build :</p>
<div class="highlight"><pre><span></span><code>git<span class="w"> </span>clone<span class="w"> </span>https://github.com/halverneus/static-file-server.git
<span class="nb">cd</span><span class="w"> </span>static-file-server
docker<span class="w"> </span>build<span class="w"> </span>-t<span class="w"> </span>static-file-server<span class="w"> </span>.
</code></pre></div>
<p>On met ça à la racine de notre dépôt git contenant les ressources statiques à servir:</p>
<div class="highlight"><pre><span></span><code>FROM static-file-server
COPY . /web
</code></pre></div>
<p>Et ensuite, il faut builder et runner ce nouveau docker :</p>
<div class="highlight"><pre><span></span><code>docker<span class="w"> </span>build<span class="w"> </span>-t<span class="w"> </span>static-content-server<span class="w"> </span>.
docker<span class="w"> </span>run<span class="w"> </span>-d<span class="w"> </span>-p<span class="w"> </span><span class="s2">"80:8080"</span><span class="w"> </span>--name<span class="w"> </span>static-server<span class="w"> </span>static-content-server
</code></pre></div>
<h3 id="avec-ansible">Avec Ansible</h3>
<p>Et si on rajoutait du fun avec <a href="https://www.ansible.com/">Ansible</a> ?
Commençons par un état des lieux :</p>
<div class="highlight"><pre><span></span><code>user@computer:~/code$<span class="w"> </span>tree<span class="w"> </span>startpage/
startpage/
├──<span class="w"> </span>Dockerfile
├──<span class="w"> </span>index.html
└──<span class="w"> </span>robots.txt
<span class="m">0</span><span class="w"> </span>directories,<span class="w"> </span><span class="m">3</span><span class="w"> </span>files
user@computer:~/code$<span class="w"> </span>git<span class="w"> </span>-C<span class="w"> </span>startpage/<span class="w"> </span>remote<span class="w"> </span>-v
origin<span class="w"> </span>ssh://git@git.example.net/user/startpage.git<span class="w"> </span><span class="o">(</span>fetch<span class="o">)</span>
origin<span class="w"> </span>ssh://git@git.example.net/user/startpage.git<span class="w"> </span><span class="o">(</span>push<span class="o">)</span>
user@computer:/home/code$<span class="w"> </span>cat<span class="w"> </span>startpage/Dockerfile
FROM<span class="w"> </span>registry.example.net/static-file-server
COPY<span class="w"> </span>.<span class="w"> </span>/web
</code></pre></div>
<p>(Parce que je suis trop fainéant pour vous mettre des captures d'écran de dépôts git)</p>
<p>Voilà un petit playbook Ansible qui buildera, mettra dans une registry privée et fera tourner le serveur web pour vous servir le contenu de notre startpage contenu dans le dépôt git git.example.net/user/startpage.git.</p>
<div class="highlight"><pre><span></span><code><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">hosts</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">mon-super.serveur-perso.example.net</span>
<span class="w"> </span><span class="nt">tasks</span><span class="p">:</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">Get static-file-server code</span>
<span class="w"> </span><span class="nt">git</span><span class="p">:</span>
<span class="w"> </span><span class="nt">repo</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">https://github.com/halverneus/static-file-server.git</span>
<span class="w"> </span><span class="nt">dest</span><span class="p">:</span><span class="w"> </span><span class="s">"{{</span><span class="nv"> </span><span class="s">app_code_dir</span><span class="nv"> </span><span class="s">}}/static-file-server"</span>
<span class="w"> </span><span class="nt">register</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">git_pull_server</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">Build static-file-server</span>
<span class="w"> </span><span class="nt">docker_image</span><span class="p">:</span>
<span class="w"> </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="s">"{{</span><span class="nv"> </span><span class="s">app_code_dir</span><span class="nv"> </span><span class="s">}}/static-file-server"</span>
<span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">medusa</span>
<span class="w"> </span><span class="nt">tag</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">latest</span>
<span class="w"> </span><span class="nt">push</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">yes</span>
<span class="w"> </span><span class="nt">force</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">yes</span>
<span class="w"> </span><span class="nt">repository</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">registry.example.net/static-file-server</span>
<span class="w"> </span><span class="nt">when</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">git_pull_server.after != git_pull_server.before</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">Get StartPage code</span>
<span class="w"> </span><span class="nt">git</span><span class="p">:</span>
<span class="w"> </span><span class="nt">repo</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">ssh://git@git.example.net/user/startpage.git</span>
<span class="w"> </span><span class="nt">dest</span><span class="p">:</span><span class="w"> </span><span class="s">"{{</span><span class="nv"> </span><span class="s">app_code_dir</span><span class="nv"> </span><span class="s">}}/startpage"</span>
<span class="w"> </span><span class="nt">key_file</span><span class="p">:</span><span class="w"> </span><span class="s">"{{</span><span class="nv"> </span><span class="s">app_data_dir</span><span class="nv"> </span><span class="s">}}/.ssh/id_rsa"</span>
<span class="w"> </span><span class="nt">register</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">git_pull</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">Build Startpage</span>
<span class="w"> </span><span class="nt">docker_image</span><span class="p">:</span>
<span class="w"> </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="s">"{{</span><span class="nv"> </span><span class="s">app_code_dir</span><span class="nv"> </span><span class="s">}}/startpage"</span>
<span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">startpage</span>
<span class="w"> </span><span class="nt">tag</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">latest</span>
<span class="w"> </span><span class="nt">push</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">yes</span>
<span class="w"> </span><span class="nt">force</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">yes</span>
<span class="w"> </span><span class="nt">repository</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">registry.example.net/startpage</span>
<span class="w"> </span><span class="nt">when</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">git_pull.after != git_pull.before</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">Deploy StartPage</span>
<span class="w"> </span><span class="nt">docker_container</span><span class="p">:</span>
<span class="w"> </span><span class="nt">pull</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">yes</span>
<span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">StartPage</span>
<span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">registry.example.net/startpage:latest</span>
<span class="w"> </span><span class="nt">ports</span><span class="p">:</span><span class="w"> </span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">"80:8080"</span>
<span class="w"> </span><span class="nt">restart_policy</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">unless-stopped</span>
</code></pre></div>
<p>PS : le playbook ansible est moche, il faut le découper en rôles, avec des handler, et tout et tout, mais ça, je vous laisse le faire ;-)</p>Pourquoi une startup ne devrait pas se fermer à l'embauche d'expérimentés2018-01-30T19:00:00+01:002018-01-30T19:00:00+01:00Etienne Magrotag:blog.etienne-magro.fr,2018-01-30:/Emploi-expérimenté-startup.html<p>Je suis étonné d'entendre dire que les startups ne veulent pas embaucher d'informaticiens expérimentés à cause du prix des des contraintes horaires. Alors qu'ils devraient plutôt les convoiter. Disclaimer : <em>embauchez-moi !</em></p><p>Je suis depuis peu en écoute très forte du marché du travail. Les quelques entretiens téléphoniques que j'ai déjà eu m'ont amené à remettre en cause ma vision des startup et à sortir ce blogpost.</p>
<h1 id="startup-jeunes">Startup = jeunes</h1>
<p>Dans l'imaginaire collectif, la population qui monte une startup est généralement jeune. Genre fraichement sortie d'école de commerce ou d'ingénieur.
C'en est tellement caricatural que même les créateurs de startup vont chercher à n'embaucher que des jeunes.</p>
<h2 id="caricatures-de-jeunes">Caricatures de jeunes</h2>
<p>Voyons ensemble quels sont les raisons à cela. Généralement, les jeunes :</p>
<ul>
<li>coûtent moins cher (et le combo (véridique) : <em>on est une startup, donc on est cool, par contre, on paye pas beaucoup, mais regarde, on aime Star Wars</em>)</li>
<li>ont des horaires trèèèès flexibles (Les soirées pizza et les week-ends "hackaton", ils en sont même friants, autant leur proposer le plus possible)</li>
<li>sont facilement "enthousiasmable" (je vous invite d'ailleurs à lire l'origine de ce terme, c'est très intéressant : littérallement un <a href="https://fr.wikipedia.org/wiki/Enthousiasme" title="Wikipedia Enthousiasme : possédé par le divin">enthousiaste</a> est "en présence d'un dieu, possédé par un dieu")</li>
</ul>
<h2 id="les-vieux-sont-cons-dailleurs-ne-les-appelle-t-on-pas-vieux-cons">Les vieux sont cons. D'ailleurs, ne les appelle-t-on pas "vieux cons" ?</h2>
<p>A l'opposé, les moins jeunes (grosso-modo à partir du moment où le candidat est marié avec enfants) :</p>
<ul>
<li>coûtent plus cher (conjoint+enfants+crédit, ça coûte cher)</li>
<li>ont des horaires pas du tout flexible (les enfants à couduire/chercher à l'école, et je ne parle pas des petits à emmener aux urgences parce que ces cons-là sont tout le temps malades)</li>
<li>sont plus critiques. Genre à voir qu'arriver au produit final va prendre tant de temps et coûter autant plutôt que de se mettre à coder tout de suite.</li>
</ul>
<h1 id="et-si-le-monde-netait-pas-aussi-binaire">Et si le monde n'était pas aussi binaire</h1>
<p>Pourtant, avoir un "sénior" pourrait être d'une grande valeur pour une startup.
En effet, ils ne sont pas chers pour rien : leurs quelques années d'expérience leur permet d'être réalistes, de voir les problèmes arriver en avance et de coder plus vite et efficacement.</p>
<h2 id="experience-assurance">Expérience = Assurance</h2>
<p>L'expérimenté a déjà vu beaucoup de problèmes. Et surtout, leur correction. Et encore mieux : comment les <strong>éviter</strong>.
Je ne parle pas forcément de bug type "oubli de virgule", ça arrive même aux meilleurs.
Par contre, l'intérêt du CI/CD, des tests, la bonne manière de bien architecturer son projet et son code, etc... pour éviter d'éventuels problèmes futurs.</p>
<h2 id="experience-visibilite-sur-le-futur">Expérience = Visibilité sur le futur</h2>
<p>A partir d'un moment, le prototype fonctionne, on a une belle app/site/whatever, et il faut la mettre en prod. Les premiers clients arrivent, et là c'est le drâme.
Ca ne tient pas a charge. Sauf si vous avez prévu la <strong>scalabilité</strong> du machin.
Et ça, à moins d'être un dieu, un jeune diplômé ne sait pas. Par contre, l'expérience peut jouer.</p>
<h2 id="experience-bonne-planification">Expérience = Bonne Planification</h2>
<p>La planification fait aussi parti du savoir-faire du vieux con. Il sait <strong>combien de temps</strong> il va prendre pour faire telle fonctionnalité ou installer tel service. Par exemple, il vous faut combien de temps pour <a href="https://blog.etienne-magro.fr/ansible-WTF.html" title="Ansible">installer</a> un <a href="https://blog.etienne-magro.fr/elasticsearch-installation.html" title="Comment installer un cluster elasticsearch">cluster Elasticsearch</a> ?
Quite à faire plaisir à ces investisseur, c'est mieux de dire (et de le tenir) "un premier proto sera dispo dans 2 mois avec une mise en prod prévu pour dans 9 mois" que de sortir le classique "ça sera prêt quand ça sera prêt"...</p>
<h2 id="experience-efficacite">Expérience = Efficacité</h2>
<p>L'expérience si ch€r€ correspond à autant de try&fail et de leçons apprises par la personne qu'elle ne refera pas dans la startup.
Généralement, l'expérience produira moins de code jetable qu'il faudra tout recoder l'année d'après pour scaller.
Cela permet de coder plus vite et surtout plus efficace.</p>
<p>Et aussi cela lui évite de passer une éternité pour installer/coder quelque chose ou de rester bloqué sur une autre chose. La personne code <strong>droit au but</strong> et fonctionnel.</p>
<p>Être plus efficace permet au à l'expérimenté d'abattre la même <strong>quantité de travail</strong>, voir plus, entre 9h et 17h qu'un fraichement diplômé entre 11h et 23h.</p>
<h1 id="maries-2-enfants">Mariés, 2 enfants</h1>
<p>En résumé, un informaticien expérimenté :</p>
<ul>
<li>code plus vite</li>
<li>anticipe les problèmes et les résoud plus rapidement/facilement</li>
<li>anticipe la montée en charge et le dimensionnement de l'infra</li>
<li>planifie son travail</li>
<li>fourni plus de travail par unité de temps</li>
</ul>
<p><strong>En gros, un informaticien plus expérimenté est plus efficace. Et coûte moins cher sur le long terme.</strong></p>
<p><a href="https://www.linkedin.com/in/etienne-magro-75332217/" title="Mon profil Linkedin">Embauchez-moi</a> ?</p>Ansible, WTF2017-11-29T19:00:00+01:002017-11-30T19:00:00+01:00Etienne Magrotag:blog.etienne-magro.fr,2017-11-29:/ansible-WTF.html<p>Je découvre Ansible, un truc qu'il est à la mode. Et il y a quand même quelques conneries dedans que je vais ici vous montrer.</p><p><a href="https://www.ansible.com/" title="Site officiel d'Ansible">Ansible</a> est l'une des techno à la mode en ce moment dans le monde de l'IT. Je bosse avec depuis quelques semaines et j'ai quelques griefs à son encontre que je vous propose de voir ici.</p>
<p><em>NOTE : Ce blogpost est loin d'être impartial</em></p>
<h2 id="elk-as-a-service">ELK "as a service"</h2>
<p>Au niveau professionel, j'opère une plateforme <a href="/category/elasticsearch.html" title="Mes blogposts sur Elasticsearch">ELK</a>. Son déploiement s'est effectué avec des scripts bash, il y a quelques années. Il m'a été demandé il y a quelques semaines de revoir notre procédure d'installation de cette plateforme pour automatiser cela avec Ansible.</p>
<p>Notre infrastructure est basée sur celle décrite dans mon <a href="/elasticsearch-architexture-logs.html" title="Elasticsearch, Architecture pour les logs">blogpost idoine</a>. Nous sommes passé en 3 ans d'une infra à 5 serveurs à une infra à 17 serveurs (dont 12 serveurs pour le cluster Elasticsearch), avec une vision sur l'année prochaine de tout redéployer from scratch pour tout mettre à jour et monter ça à 20 serveurs (+3 pour le cluster ES).</p>
<p>Notre équipe installe cette infrastructure et la maintient en condition de fonctionnement, mais elle est ouverte à l'usage pour les équipes connexes à la notre. Notre chef n+2 aime parler de "as a service".</p>
<p>Pour ce "as a service", les autres équipes peuvent envoyer leurs logs dans notre ELK et les parser. Le point particulier du parsage est qu'il est de leur responsabilité d'écrire ces règles, et de les tester, avant de faire une <em>pull request</em> sur un dépôt git contenant toutes les règles de parsage utilisées.</p>
<p>Ce point est important pour la suite car il a été le déclencheur de ce billet d'humeur.</p>
<h2 id="ansible-est-tres-verbeux">Ansible est (très) verbeux</h2>
<p>Installer logstash, elasticsearch ou kibana en ansible, ça se fait. Surtout qu'on n'a que des serveurs <a href="https://www.ubuntu.com/server" title="Official Ubuntu server website">Ubuntu</a>, donc, pas besoin de s'occuper de la distro-agnosticité de notre ansible.</p>
<p>Il faut <em>juste</em> écrire <strong>1045 lignes</strong> de YAML répartis dans <strong>20 fichiers</strong>, sans compter les <a href="https://github.com/elastic/ansible-elasticsearch" title="Ansible playbook for Elasticsearch on github">rôles pour elasticsearch qui existent déjà</a>, et qu'on n'a pas à faire soit-même.
En tout, on a ça :</p>
<div class="highlight"><pre><span></span><code>etienne@computer:~/code/ansible_for_ELK$<span class="w"> </span>find<span class="w"> </span>roles/<span class="w"> </span>group_vars/<span class="w"> </span>*.yml<span class="w"> </span>-type<span class="w"> </span>f<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-v<span class="w"> </span><span class="s1">'.git'</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>wc<span class="w"> </span>-l
<span class="m">186</span>
etienne@computer:~/code/ansible_for_ELK$<span class="w"> </span>wc<span class="w"> </span>-l<span class="w"> </span><span class="k">$(</span>find<span class="w"> </span>roles/<span class="w"> </span>group_vars/<span class="w"> </span>*.yml<span class="w"> </span>-type<span class="w"> </span>f<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-v<span class="w"> </span><span class="s1">'.git'</span><span class="k">)</span>
<span class="o">[</span>...<span class="o">]</span>
<span class="m">8579</span><span class="w"> </span>total
</code></pre></div>
<p><strong>ARE YOU FUCKING SERIOUS ?</strong></p>
<p><strong>8579 lignes</strong> dans <strong>186 fichiers</strong> ? Sérieusement, <strong>WHAT THE FUCK ?</strong></p>
<p>Tout ça pour quelques <code>apt-get install</code> et quelques <code>sed</code> dans des fichiers de conf ?</p>
<p>Je veux bien que ça soit verbeux, mais quand même, c'est pire que du Java !</p>
<p>Et je ne parle pas d'ansible en lui-même (et du python qu'il faut installer sur la machine, plus le virtual env, etc...), sinon ça ne serait pas équitable.</p>
<p>Ha, oui, et ça ne contient pas non plus la configuration du logstash indexer (nous y reviendrons plus tard).</p>
<p>Comparons avec nos anciens scripts, à base de bash et de Dockerfile :</p>
<div class="highlight"><pre><span></span><code>etienne@computer:~/code$<span class="w"> </span>find<span class="w"> </span>elk_installation/<span class="w"> </span>-type<span class="w"> </span>f<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-v<span class="w"> </span><span class="s1">'.git'</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>wc<span class="w"> </span>-l
<span class="m">56</span>
etienne@computer:~/code$<span class="w"> </span>wc<span class="w"> </span>-l<span class="k">$(</span>find<span class="w"> </span>elk_installation/<span class="w"> </span>-type<span class="w"> </span>f<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>-v<span class="w"> </span><span class="s1">'.git'</span><span class="k">)</span>
<span class="m">1437</span><span class="w"> </span>total
</code></pre></div>
<p>Donc, en l'état actuel, on a 1437 lignes de code/config/commentaires/whatever dans 56 fichiers.</p>
<p>Et dedans, il est aussi compté les 104 lignes dans 4 fichiers README et surtout TOUTE la configuration du logstash indexer (300 lignes dans 21 fichiers).</p>
<p><em>Petite apparté : nous imposons un formalisme particulier pour notre configuration de logstash indexer pour éviter que toutes les équipes qui l'utilise ne se marchent dessus lors de l'ajout de leurs règles de parsage et qu'elles ne pète pas la conf d'une autre par inadvertance. Cela explique les 21 fichiers de conf pour le logstash indexer</em></p>
<p>Dis, Ansible, tu m'expliques un peu ?</p>
<h2 id="exemple">Exemple</h2>
<p>Prenons un exemple judicieusement choisi qui m'a fait m'arracher les cheveux cette semaine.
Voici ce que je cherche à faire :</p>
<ul>
<li>git cloner la configuration du logstash indexer sur le serveur</li>
<li>changer une chaine de caractère dans les fichiers de conf (il s'agit d'un changement de path entre l'ancienne infra et la nouvelle. Le truc, c'est qu'il faut maintenir les 2 en même temps, le temps de finir de migrer)</li>
<li>faire un lien symbolique entre le dépôt fraichement git cloné et le répertoire de conf normal</li>
</ul>
<p>Voici en bash ce qui était fait :</p>
<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal"> 1</span>
<span class="normal"> 2</span>
<span class="normal"> 3</span>
<span class="normal"> 4</span>
<span class="normal"> 5</span>
<span class="normal"> 6</span>
<span class="normal"> 7</span>
<span class="normal"> 8</span>
<span class="normal"> 9</span>
<span class="normal">10</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="c1"># Remove old conf, for clean setup/update</span>
rm<span class="w"> </span>-rf<span class="w"> </span>/etc/logstash/conf.d<span class="w"> </span>/srv/indexer_conf
<span class="c1"># Get the actual conf</span>
git<span class="w"> </span>clone<span class="w"> </span>ssh://git@git.entreprise.net/server_install/logstash-indexer.git<span class="w"> </span>/srv/indexer_conf
<span class="c1"># Install the conf in the right place</span>
ln<span class="w"> </span>-s<span class="w"> </span>/srv/indexer_conf/config<span class="w"> </span>/etc/logstash/conf.d
<span class="c1"># Replace the patterns_dir path to the right one</span>
sed<span class="w"> </span>-i<span class="w"> </span><span class="s1">'s#[\t ]+patterns_dir\s+=>\s+\[?".*"\]?#patterns_dir => "/etc/logstash/conf.d/patterns"'</span><span class="w"> </span>/etc/logstash/conf.d/*.conf
<span class="c1"># Restart logstash</span>
service<span class="w"> </span>logstash<span class="w"> </span>restart
</code></pre></div></td></tr></table></div>
<p>Et en Ansible :</p>
<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal"> 1</span>
<span class="normal"> 2</span>
<span class="normal"> 3</span>
<span class="normal"> 4</span>
<span class="normal"> 5</span>
<span class="normal"> 6</span>
<span class="normal"> 7</span>
<span class="normal"> 8</span>
<span class="normal"> 9</span>
<span class="normal">10</span>
<span class="normal">11</span>
<span class="normal">12</span>
<span class="normal">13</span>
<span class="normal">14</span>
<span class="normal">15</span>
<span class="normal">16</span>
<span class="normal">17</span>
<span class="normal">18</span>
<span class="normal">19</span>
<span class="normal">20</span>
<span class="normal">21</span>
<span class="normal">22</span>
<span class="normal">23</span>
<span class="normal">24</span>
<span class="normal">25</span>
<span class="normal">26</span>
<span class="normal">27</span>
<span class="normal">28</span>
<span class="normal">29</span>
<span class="normal">30</span>
<span class="normal">31</span>
<span class="normal">32</span>
<span class="normal">33</span>
<span class="normal">34</span>
<span class="normal">35</span>
<span class="normal">36</span>
<span class="normal">37</span>
<span class="normal">38</span>
<span class="normal">39</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="nn">---</span>
<span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">Remove old logstash conf</span>
<span class="w"> </span><span class="nt">file</span><span class="p">:</span>
<span class="w"> </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">absent</span>
<span class="w"> </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="s">"/etc/logstash/conf.d"</span>
<span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">git clone/pull lastest logstash-indexer conf</span>
<span class="w"> </span><span class="nt">git</span><span class="p">:</span>
<span class="w"> </span><span class="nt">repo</span><span class="p">:</span><span class="w"> </span><span class="s">"{{</span><span class="nv"> </span><span class="s">indexer_git_repo</span><span class="nv"> </span><span class="s">}}"</span>
<span class="w"> </span><span class="nt">dest</span><span class="p">:</span><span class="w"> </span><span class="s">"/srv/indexer_conf"</span>
<span class="w"> </span><span class="nt">force</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">yes</span>
<span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">Link indexer conf to logstash</span>
<span class="w"> </span><span class="nt">file</span><span class="p">:</span>
<span class="w"> </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">link</span>
<span class="w"> </span><span class="nt">src</span><span class="p">:</span><span class="w"> </span><span class="s">"/srv/indexer_conf/config"</span>
<span class="w"> </span><span class="nt">dest</span><span class="p">:</span><span class="w"> </span><span class="s">"/etc/logstash/conf.d"</span>
<span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">find the file to edit</span>
<span class="w"> </span><span class="nt">find</span><span class="p">:</span>
<span class="w"> </span><span class="nt">paths</span><span class="p">:</span><span class="w"> </span><span class="s">"/etc/logstash/conf.d"</span>
<span class="w"> </span><span class="nt">patterns</span><span class="p">:</span><span class="w"> </span><span class="s">"*.conf"</span>
<span class="w"> </span><span class="nt">register</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">tmp_glob</span>
<span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">Replace pattern path references</span>
<span class="w"> </span><span class="nt">replace</span><span class="p">:</span>
<span class="w"> </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="s">"{{</span><span class="nv"> </span><span class="s">item.path</span><span class="nv"> </span><span class="s">}}"</span>
<span class="w"> </span><span class="nt">regexp</span><span class="p">:</span><span class="w"> </span><span class="s">'[\t</span><span class="nv"> </span><span class="s">]+patterns_dir\s+=>\s+\[?".*"\]?'</span>
<span class="w"> </span><span class="nt">replace</span><span class="p">:</span><span class="w"> </span><span class="s">'patterns_dir</span><span class="nv"> </span><span class="s">=></span><span class="nv"> </span><span class="s">"/etc/logstash/conf.d/patterns"'</span>
<span class="w"> </span><span class="nt">with_items</span><span class="p">:</span><span class="w"> </span><span class="s">"{{</span><span class="nv"> </span><span class="s">tmp_glob.files</span><span class="nv"> </span><span class="s">}}"</span>
<span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">Restart logstash</span>
<span class="w"> </span><span class="nt">service</span><span class="p">:</span>
<span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">logstash</span>
<span class="w"> </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">restarted</span>
<span class="nn">...</span>
</code></pre></div></td></tr></table></div>
<p>Verbeux, n'est-ce pas ?</p>
<p>Mais ce n'est pas le point le plus intéressant. Non, le point intéressant, c'est que Ansible ne sait pas faire le <code>sed -i 'blabla' *.conf</code> d'un seul coup.</p>
<p>Naïvement, on peut penser que le <code>sed</code> pourrait se faire ainsi :</p>
<div class="highlight"><pre><span></span><code><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">Replace pattern path references</span>
<span class="w"> </span><span class="nt">replace</span><span class="p">:</span>
<span class="w"> </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="s">"{{</span><span class="nv"> </span><span class="s">item.path</span><span class="nv"> </span><span class="s">}}"</span>
<span class="w"> </span><span class="nt">regexp</span><span class="p">:</span><span class="w"> </span><span class="s">'[\t</span><span class="nv"> </span><span class="s">]+patterns_dir\s+=>\s+\[?".*"\]?'</span>
<span class="w"> </span><span class="nt">replace</span><span class="p">:</span><span class="w"> </span><span class="s">'patterns_dir</span><span class="nv"> </span><span class="s">=></span><span class="nv"> </span><span class="s">"/etc/logstash/conf.d/patterns"'</span>
<span class="w"> </span><span class="nt">with_fileglob</span><span class="p">:</span><span class="w"> </span><span class="s">"/etc/logstash/conf.d/*.conf"</span>
</code></pre></div>
<p>Mais ça ne marche pas. Et vous savez pourquoi ?</p>
<p>Parce que, c'est con, hein ?, mais cette instruction-là est exécutée en <strong>local</strong> et non en <strong>remote</strong>.</p>
<p>Vous m'avez bien lu. Toutes les instructions au-dessus (<code>file</code>, <code>git</code>, ...) et en-dessous (<code>service</code>) sont exécutées sur le serveur distant, mais pas le <code>replace</code>. A cause du <code>with_fileglob</code>.</p>
<p>Mais ça, rien ni personne ne nous le dit ! pas même la <a href="http://docs.ansible.com/ansible/latest/playbooks_loops.html">doc officielle</a>.
Et c'est pas l'erreur <code>file not found</code> renvoyée qui va vous aider à le découvrir.</p>
<p>Sérieusement, quel est le crétin qui s'est dit que cette instruction-là précisément, contrairement aux autres, serait exécutée localement ?</p>
<p>Et comme ça n'a pas été prévu de faire un <code>sed *</code> en remote, il faut utiliser le subterfuge des ligne 20-24 : faire un <code>ls *</code> remote, stocker la réponse dans une variable et itérer sur cette dernière dans la commande <code>replace</code>.</p>
<p>J'ai envie de dire "Bien joué" !</p>
<h2 id="recapitulons">Récapitulons</h2>
<ul>
<li>Ansible est <em>beaucoup</em> plus verbeux que du bash</li>
<li>Ansible exécute parfois des trucs en local, et parfois en remote, mais ne te le dit jamais</li>
<li>Ansible a <a href="http://docs.ansible.com/ansible/latest/intro_installation.html">besoin de python</a> + les <a href="https://github.com/ansible/ansible/blob/devel/requirements.txt">bonnes libs qui vont bien</a>. Et encore, en fonction de ce que l'on veut faire, il faut en rajouter. Soit en remote, soit en local. C'est toujours la même histoire</li>
<li>Les <a href="https://github.com/ansible/ansible/blob/devel/CHANGELOG.md">API d'Ansible ne sont pas stables</a>. Sérieusement, il y a des trucs qui sont ajoutés et surtout supprimés (ou des mot-clés qui changent, ce qui est pire) d'une version à une autre</li>
<li>Impossible d'exécuter uniquement une partie d'un script/role/playbook/whatever, c'est tout ou rien</li>
<li>Ansible a 5 ans, et avant, le truc à la mode, c'était Puppet (tout au moins, dans mon monde). Par quoi devrons-nous le remplacer dans 5 ans ?</li>
<li>Quel sera le coût de sorti d'Ansible ? Ansible est compatible uniquement avec Ansible. Le jour où on ne veut plus d'Ansible, comment fait-on ?</li>
</ul>
<p><em>(true story pour Puppet. A la mode pendant quelques années, avec formations, conférences, "workshops", etc... on passe tout à Puppet. Et depuis 1 an, Puppet s'est fait détrôné par Ansible)</em></p>
<p>De l'autre côté :</p>
<ul>
<li>Bash est plus concis, même avec des commentaires pour expliquer des trucs pas évidents (genre des regexp)</li>
<li>Bash est exécuté en remote ou en local, mais uniquement l'un <em>ou</em> l'autre, et c'est le user qui maitrise ça</li>
<li>Bash est installé en standard sur toutes les distrib, tous les serveurs du monde</li>
<li>Bash est stable. Et il n'y a pas d'API, ni de <a href="https://en.wikipedia.org/wiki/Domain-specific_language">DSL</a> à la con (vivent les buzzword)</li>
<li>Tu peux copier-coller le contenu d'un fichier <code>.sh</code> dans un terminal, ça juste marche. Même un petit morceau</li>
<li>Bash existe depuis 1989, et est fourni en standard partout. Il est juste pas à la mode des informaticiens bobo-POO-TDD-WTF</li>
</ul>
<p>Quitte à avoir du python, pourquoi se palucher un DSL en YAML ? On en perd toute la puissance de python...</p>
<p>Pour moi, c'est clair, il n'est pas encore arrivé celui qui détrônera bash.</p>
<p><strong><3 Bash</strong></p>Elasticsearch, suppression de SPOF2017-10-31T19:00:00+01:002017-12-01T19:00:00+01:00Etienne Magrotag:blog.etienne-magro.fr,2017-10-31:/elasticsearch-suppression-spof.html<p>A mon travail actuel, j'ai dû intervenir pour éviter un SPOF lors de la dernière update d'Atlassian Bitbucket. Story time.</p><p><em>Note : Pour plus de détails sur Elasticsearch, voyez ma <a href="/category/elasticsearch.html" title="Mes post sur Elasticsearch">série sur ce thème</a>.</em></p>
<h1 id="il-etait-une-fois-bitbucket">Il était une fois Bitbucket</h1>
<h2 id="contexte">Contexte</h2>
<h3 id="quest-ce">Qu'est-ce ?</h3>
<p>L'entreprise pour laquelle je travaille en ce moment utilise <a href="https://bitbucket.org/" title="Hum">Bitbucket</a> pour stocker tout son code source. Il s'agit d'un concurrent de <a href="https://github.com/" title="Faut-il le présenter ?">Github</a>, <a href="https://about.gitlab.com/" title="Un serveur git qui scale">Gitlab</a> ou <a href="https://gitea.io/" title="Un serveur git qui l'est bien">Gitea</a> que les entreprises peuvent héberger en interne.<br>
Tout le monde connait Github, tout au moins tout informaticien sorti de sa grotte il y a moins de 5 ans. Il est surtout connu pour son instance gratuite qu'utilise le monde de l'open source.<br>
Et, pour mon usage personnel, j'utilise Gitea dont je suis très content. </p>
<h3 id="le-pourquoi">Le pourquoi</h3>
<p>La version 4.6 de <a href="https://bitbucket.org/" title="Hum">Bitbucket</a> imposait l'utilisation d'un serveur <a href="https://www.elastic.co/products/elasticsearch" title="Page officiel d'Elasticsearch">Elasticsearch</a> pour fonctionner.<br>
La raison était la recherche de code.<br>
Avant, Bitbucket ne permettait de faire des recherches que sur le nom des projets ou des dépôts git.<br>
A partir de cette version, on pouvait enfin <a href="https://confluence.atlassian.com/bitbucketserver/bitbucket-server-4-6-release-notes-827109988.html" title="Note de version pour bitbucket 4.6">chercher dans le code</a>. On pouvait chercher une fonction, une variable, ce que l'on veut dans tout le code source stocké.<br>
Et c'est à cela que va servir Elasticsearch.</p>
<h3 id="absurdites">Absurdités</h3>
<p>Malheureusement, <a href="https://www.atlassian.com/" title="Un editeur de logiciel qui devrait revoir une bonne partie de son code">Atlassian</a> (l'éditeur de ce logiciel) ne sait pas comment fonctionne Elasticsearch, ni quelles sont ses capacités. Cela l'a poussé a divers absurdités :</p>
<ul>
<li><a href="https://confluence.atlassian.com/bitbucketserver/install-and-configure-a-remote-elasticsearch-instance-815577748.html">Imposer une version obsolète d'Elasticsearch (v2.3.5)</a>, <a href="https://www.elastic.co/support/eol" title="Date des fins de support des produits Elastic.co">totalement dépréciée</a> depuis le 30 Septembre 2017</li>
<li><a href="https://confluence.atlassian.com/bitbucketserver/use-an-elasticsearch-cluster-with-bitbucket-data-center-913477079.html">Recommander une installation mono-node d'Elasticsearch</a></li>
<li>Ou héberger son cluster <a href="https://confluence.atlassian.com/bitbucketserver/use-an-elasticsearch-cluster-with-bitbucket-data-center-913477079.html">Elasticsearch chez Amazon</a></li>
<li>Ou rajouter encore un SPOF supplémentaire avec un proxy devant (<a href="https://confluence.atlassian.com/bitbucketserver/use-an-elasticsearch-cluster-with-bitbucket-data-center-913477079.html">toujours la même page</a>)</li>
<li>Hardcoder le nombre de shard et de replica (respectivement 5 et 0)</li>
</ul>
<p>Prenez "recommander" dans le sens "si le client fait autrement, et que des merdes lui arrivent, même si elles sont causées parce qu'on ne sait pas coder, il se débrouille tout seul, même s'il paye un contrat de support d'1 million d'euros par an."</p>
<p>Je ne parlerais pas du "<a href="https://confluence.atlassian.com/bitbucketserver/use-an-elasticsearch-cluster-with-bitbucket-data-center-913477079.html#UseanElasticsearchclusterwithBitbucketDataCenter-SecureElasticsearchwithAtlassian'sBucklerplugin">il faut sécuriser votre elasticsearch avec notre propre plugin qui n'est compatible qu'avec telle version d'elasticsearch</a>. Et sinon, <a href="https://confluence.atlassian.com/bitbucketserver/use-an-elasticsearch-cluster-with-bitbucket-data-center-913477079.html#UseanElasticsearchclusterwithBitbucketDataCenter-SecureElasticsearchwithElastic%27sShieldplugin">y'a aussi le truc officiel, mais on est pas sûr que ça marchera et de tout façon, on le supporte pas</a>".</p>
<p>Voyons un peu plus les détails.</p>
<h2 id="architecture-du-service-bitbucket">Architecture du service Bitbucket</h2>
<p>Commençons par le début, comment ça marchait Bitbucket, avant.</p>
<h3 id="sans-elasticsearch">Sans Elasticsearch</h3>
<p>Dans le cadre d'une petite équipe, ou d'une petite entreprise, l'architecture du service Bitbucket, ressemblait à toute autre :</p>
<p><img alt="Architecture normal de Bitbucket" src="https://blog.etienne-magro.fr/images/archi_bitbucket_normal_1.svg"></p>
<h3 id="avec-elasticsearch">Avec Elasticsearch</h3>
<p>Avec l'ajout de la dépendance obligatoire Elasticsearch, ça donne ça :</p>
<p><img alt="Architecture normal de Bitbucket avec Elasticsearch" src="https://blog.etienne-magro.fr/images/archi_bitbucket_normal_2.svg"></p>
<h1 id="le-probleme">Le problème</h1>
<p>Le truc, avec mon fournisseur de travail actuel, c'est qu'il est trop gros pour utiliser Bitbucket comme cela.<br>
En effet, plusieurs centaines de développeurs git clonant et git pushant régulièrement tous les jours mettent par terre le service tel quel.</p>
<h2 id="grosse-archi">Grosse archi</h2>
<h3 id="bitbucket-datacenter">Bitbucket Datacenter</h3>
<p>Ce qu'il s'est passé, c'est que mon $JOB_ACTUEL a travaillé avec Atlassian pour avoir un truc qui scale un peu plus, et Atlassian a sorti une version dite "<a href="https://confluence.atlassian.com/bitbucketserver/bitbucket-data-center-872143591.html" title="Documentation officiel sur Bitbucket datacenter">datacenter</a>". En gros, ça donne ça :</p>
<p><img alt="Architecture datacenter de Bitbucket" src="https://blog.etienne-magro.fr/images/archi_bitbucket_datacenter_1.svg"></p>
<p>Et comme il fallait un truc bien robuste pour de la bonne grosse prod, le proxy est redondant en n+2 avec fail-over automatique (c'est <a href="https://blog.etienne-magro.fr/" title="Mon blog technique">bibi</a> qui s'en est occupé de cette partie-là).<br>
Pour la DB, j'imagine que les DBA ont fait la même chose, ainsi que l'équipe d'infra qui nous fourni le stockage.</p>
<h3 id="bitbucket-nouvelle-version">Bitbucket nouvelle version</h3>
<p>Bon, et voilà ce que ça donnerai avec les <a href="https://confluence.atlassian.com/bitbucketserver/use-an-elasticsearch-cluster-with-bitbucket-data-center-913477079.html">recommendations d'Atlassian</a>.</p>
<p><img alt="Architecture datacenter de Bitbucket avec un nouveau SPOF" src="https://blog.etienne-magro.fr/images/archi_bitbucket_datacenter_problem.svg"></p>
<h2 id="et-dans-la-vraie-vie">Et dans la vraie vie ?</h2>
<p>Le problème avec cela, c'est que dans la vraie vie, ça ne tient pas du tout la charge, ou alors à quel prix.<br>
Et surtout, il y a 2 problèmes immédiat :</p>
<ul>
<li>Ca ne scale pas du tout</li>
<li>Ca introduit un nouveau <a href="https://fr.wikipedia.org/wiki/Point_individuel_de_d%C3%A9faillance" title="Définition de SPOF sur Wikipedia">SPOF</a> dans notre belle infra critique de prod</li>
</ul>
<p>Voyons ensemble les contraintes que nous avions.</p>
<h3 id="les-besoins-de-stockage">Les besoins de stockage</h3>
<p>Commençons par le stockage.
Au moment de l'update, l'infra bitbucket occupait un peu moins d'1To de disque. Autant dire qu'1To réparti dans quelques milliers de dépôt git, c'est pas petit !</p>
<p>Notre équipe qui s'occupe de Bitbucket a procédé à quelques tests, pour savoir ce que cela prendrait comme disque une fois répliqué dans elasticsearch. Et elle a trouvé qu'il y avait un rapport 1.33 entre l'espace occupé brut et l'espace disque dont aura besoin elasticsearch.</p>
<p>Avec un peu de marge, on se retrouve avec un besoin immédiat d'1,5To. Avec une courbe de croissance estimée à environ +60Go/mois</p>
<h3 id="les-contraintes-de-notre-equipe-dinfra">Les contraintes de notre équipe d'infra</h3>
<p>Notre équipe d'infra a elle aussi quelques contraintes, avec ses +8000 serveurs gérés :</p>
<ul>
<li>Elle ne fourni que des VM, pas de machine bare-metal.</li>
<li>Chaque serveur ne pourra avoir que 500Go de disque attaché, au maximum</li>
<li>Le moins on utilise d'espace disque, le mieux c'est</li>
</ul>
<h3 id="dun-autre-cote-quelles-sont-les-possibilites-delasticsearch">D'un autre côté, quelles sont les possibilités d'Elasticsearch ?</h3>
<p>Comme déjà vu ensemble, Elasticsearch est hautement distribué et scalable.<br>
Et il a une fonctionnalité qui nous intéresse aussi, c'est son utilisation en tant que "proxy", avec une configuration "no-data,no-master".</p>
<h1 id="la-solution">La solution</h1>
<h2 id="larchi-actuelle">L'archi actuelle</h2>
<p><img alt="Architecture datacenter de Bitbucket avec elasticsearch" src="https://blog.etienne-magro.fr/images/archi_bitbucket_datacenter_elasticsearch.svg"></p>
<p>L'idée principale est de mettre un noeud elasticsearch "proxy" en local sur chaque noeud bitbucket. Ainsi Bitbucket se connecte au cluster elasticsearch sur l'URI http://localhost:9200/.</p>
<p>Avec cette configuration, la perte d'un noeud elasticsearch ou bitbucket, quelqu'il soit n'entraine pas de perturbation.</p>
<p>Ajouté à cela qu'après discussions avec Atlassian, nous avons pu avoir la possitilité de changer le nombre de shard et de replica, via des variables de configuration non-documentées.</p>
<h2 id="quelques-chiffres-clefs">Quelques chiffres clefs</h2>
<ul>
<li>Nous avons actuellement un cluster de 15 noeuds. Chaque noeud a 400Go de disque, 4vCPUs et 16Go de RAM.</li>
<li>Nous avons aujourd'hui de configuré 15 shards et 1 replica</li>
<li>3To de disque utilisés sur les 6To de disponible</li>
<li>>160 milliards de documents stockés</li>
<li>Temps de réponse moyen d'Elasticsearch <50ms</li>
<li>Pas de SPOF : en test, on a perdu 2 noeuds sur les 15 sans aucun impact (pas de perte de data, pas d'interruption de service)</li>
<li>Possibilité de scaler pour suivre l'utilisation du service</li>
</ul>
<p><em>Note : si c'était à refaire (et c'est ce qui sera fait prochainement), et que nous avions les ressources matérielles que nous voulions, nous aurions pris 45 shards et 2 replica répartis sur 20 VMs. Cela nous aurait permis de mieux scaler et sur plus longtemps. Dans notre cas, nous avons été surpris par un taux de croissance plus important que ce que nous prévoyions qui nous amène à changer nos plans dans peu de temps.</em></p>Elasticsearch, Creation d'un template d'index2017-10-20T19:00:00+02:002017-10-20T19:00:00+02:00Etienne Magrotag:blog.etienne-magro.fr,2017-10-20:/elasticsearch-creationtemplate.html<p>Lorsque l'on envoie des logs parsés par Logstash à Elasticsearch, si on ne spécifie pas de type lors de la création de l'index, tout sera considéré comme du texte par défaut. Mais cela nous empêche d'exploiter correctement certains champs (IP, nombres typiquement). Voyons comment arranger cela.</p><p>Lorsque l'on envoie des logs parsés par Logstash à Elasticsearch, si on ne spécifie pas de type lors de la création de l'index, tout sera considéré comme du texte par défaut. Mais cela nous empêche d'exploiter correctement certains champs (IP, nombres typiquement). Voyons comment arranger cela.</p>
<h1 id="log-parse">Log parsé</h1>
<p>Prenons une ligne d'access log nginx que l'on va parser avec Logstash :</p>
<div class="highlight"><pre><span></span><code><span class="mf">192.168.6.66</span><span class="w"> </span><span class="n">app</span><span class="mf">.</span><span class="n">serveur</span><span class="mf">.</span><span class="n">entreprise</span><span class="mf">.</span><span class="n">net</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="err">[</span><span class="mf">20</span><span class="o">/</span><span class="n">Oct</span><span class="o">/</span><span class="mf">2017</span><span class="p">:</span><span class="mf">08</span><span class="p">:</span><span class="mf">38</span><span class="p">:</span><span class="mf">04</span><span class="w"> </span><span class="o">+</span><span class="mf">0000</span><span class="err">]</span><span class="w"> </span><span class="s">"GET /page/visitee.php HTTP/1.1"</span><span class="w"> </span><span class="mf">200</span><span class="w"> </span><span class="mf">387</span><span class="w"> </span><span class="s">"http://app.serveur.entreprise.net/page/precedente.php"</span><span class="w"> </span><span class="s">"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36"</span><span class="w"> </span><span class="s">"-"</span>
</code></pre></div>
<p>Voici grosso-modo ce que l'on obtiendra après parsing par Logstash (article qui reste à écrire) :</p>
<div class="highlight"><pre><span></span><code><span class="p">{</span>
<span class="nt">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"192.168.6.66 app.serveur.entreprise.net - [20/Oct/2017:08:38:04 +0000] \"GET /page/visitee.php HTTP/1.1\" 200 387 \"http://app.serveur.entreprise.net/page/precedente.php\" \"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36\" \"-\""</span><span class="p">,</span>
<span class="nt">"@timestamp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2017-10-20T08:38:04.000Z"</span><span class="p">,</span>
<span class="nt">"file"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/var/log/nginx/access.log"</span><span class="p">,</span>
<span class="nt">"host"</span><span class="p">:</span><span class="w"> </span><span class="s2">"serveurhostname"</span><span class="p">,</span>
<span class="nt">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"nginx"</span><span class="p">,</span>
<span class="nt">"app"</span><span class="p">:</span><span class="w"> </span><span class="s2">"app.serveur"</span><span class="p">,</span>
<span class="nt">"env"</span><span class="p">:</span><span class="w"> </span><span class="s2">"production"</span><span class="p">,</span>
<span class="nt">"clientip"</span><span class="p">:</span><span class="w"> </span><span class="s2">"192.168.6.66"</span><span class="p">,</span>
<span class="nt">"domain"</span><span class="p">:</span><span class="w"> </span><span class="s2">"app.serveur.entreprise.net"</span><span class="p">,</span>
<span class="nt">"auth"</span><span class="p">:</span><span class="w"> </span><span class="s2">"-"</span><span class="p">,</span>
<span class="nt">"timestamp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"20/Oct/2017:08:38:04 +0000"</span><span class="p">,</span>
<span class="nt">"verb"</span><span class="p">:</span><span class="w"> </span><span class="s2">"GET"</span><span class="p">,</span>
<span class="nt">"pathrequest"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/page/visitee.php"</span><span class="p">,</span>
<span class="nt">"httpversion"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.1"</span><span class="p">,</span>
<span class="nt">"coderesponse"</span><span class="p">:</span><span class="w"> </span><span class="s2">"200"</span><span class="p">,</span>
<span class="nt">"bytesresponse"</span><span class="p">:</span><span class="w"> </span><span class="s2">"387"</span><span class="p">,</span>
<span class="nt">"referrer"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://app.serveur.entreprise.net/page/precedente.php"</span><span class="p">,</span>
<span class="nt">"useragent"</span><span class="p">:</span><span class="w"> </span><span class="s2">"\"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36\""</span>
<span class="p">}</span>
</code></pre></div>
<p>Vous pouvez remarquer que tous les champs et leur valeurs sont quotés, entre doubles-quotes. Même les valeurs de type IP et numérique. Elasticsearch risque de les stocker en tant que valeur textuelle.</p>
<h1 id="index-templating">Index templating</h1>
<p>Pour remédier à cela, il faut utiliser l'<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates.html" title="Documentation officielle sur l'index template sur elastic.co">index templating</a>.</p>
<p>Voyons ce que cela donnerait pour nous. le champ "clientip" est une adresse IP, le "bytesresponse" est un nombre. "httpversion" sera gardé comme texte ainsi que "coderesponse". Pour ce dernier, il s'agit d'un code de statut, on ne va pas l'additionné, le multiplier our faire ce genre d'opération. On aurait pu mettre "tout_bon" que cela aurait été exactement pareil.</p>
<p>Allons-y :</p>
<div class="highlight"><pre><span></span><code>curl<span class="w"> </span>-XPUT<span class="w"> </span><span class="s1">'http://ELASTICSEARCH_SERVER_ADDRESS:9200/_template/default_log?pretty'</span><span class="w"> </span>-d<span class="w"> </span><span class="s1">'</span>
<span class="s1">{</span>
<span class="s1"> "template": "logstash-*",</span>
<span class="s1"> "mappings": { </span>
<span class="s1"> "nginx": {</span>
<span class="s1"> "properties": {</span>
<span class="s1"> "bytesresponse": {"type": "long"},</span>
<span class="s1"> "clientip": {"type": "ip"}</span>
<span class="s1"> } </span>
<span class="s1"> }, </span>
<span class="s1"> }</span>
<span class="s1">}'</span>
</code></pre></div>
<p>Et voilà !</p>
<p>Ok, et en détail ?</p>
<ul>
<li><code>template</code>: indique à quels nom d'index seront appliqué ce template. Dans notre cas, si l'index créé commence par "logstash-", il s'appliquera, sinon, il ne s'appliquera pas.</li>
<li><code>mappings</code>: mot clef pour indiquer que l'on va mapper des champs avec des types</li>
<li><code>nginx</code>: correspond au champ "type" de vos enregistrement. Donc "nginx" dans notre exemple. Cela aurait pu être autre chose car (nous le verrons au § suivant), les rêgles ne s'appliqueront pas qu'aux enregistrements de type nginx.</li>
<li><code>properties</code>: mot clef</li>
</ul>
<p>Pour plus d'information, je vous invite à vous référer à la <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html">documentation très complète sur le site officiel</a>.</p>
<h1 id="a-savoir-globalite-et-disponibilite">A savoir : globalité et disponibilité</h1>
<p>Attention, il y a quelques subtilités à savoir quand on crée un index template : la globalité de la configuration et le fait qu'il ne s'applique aux futurs index créés, pas au anciens.</p>
<h2 id="potentiels-conflits-de-typage">Potentiels conflits de typage</h2>
<p>En effet, et il s'agit d'une <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html#field-conflicts">limitation connue et documentée</a>, tous les champs "bytesresponse" et "clientip" dans notre exemple <strong>doivent</strong> de type "long" et "ip" respectivement. Même s'ils sont dans le résultat de parsing d'une autre application.</p>
<p>Et méconnaire cette contrainte peut entrainer des problèmes : dans le cas où un enregistrement arriverait dans le cluster elasticsearch avec un champ "clientip" non castable en IP (s'il y a un reverse DNS appliqué et qu'il est fournit un hostname au lieu de l'ip, par exemple), l'enregistrement ne s'effectuera pas, sera perdu et une erreur sortira dans les logs d'elasticsearch.</p>
<h2 id="application-du-template-uniquement-sur-les-futurs-index">Application du template uniquement sur les futurs index</h2>
<p>La seconde chose à savoir est qu'un index template ne s'appliquera que sur les index qui seront créés <strong>après</strong> la création du template.
Ce n'est pas un problème si vous utilisez Elasticsearch comme une base de données comme une autre dans laquelle vous avez un schéma dès le départ (équivalent au <code>CREATE TABLE...</code> en SQL).</p>
<p>Si par contre, vous stockez des logs et n'avez pas défini de template <em>avant</em> de les envoyer dans elasticsearch, il va y avoir un petit temps de latence entre la création de cet index template et sa totale opérabilité.
En effet, les index créés avant la création du template n'en bénéficieront pas sauf à tout ré-indexer. Généralement, on attend plutôt que tous les vieux index sans template disparaissent avec leur durée de rétention.</p>Une erreur de débutant2017-07-30T19:00:00+02:002017-07-30T19:00:00+02:00Etienne Magrotag:blog.etienne-magro.fr,2017-07-30:/une-erreur-de-debutant.html<p>La semaine dernière, j'ai eu un problème sur une infrastructure ELK. Je vous propose de décortiquer içi ce qu'il en est ressorti. Spoiler : j'ai fait une erreur de débutant.</p><p>La semaine dernière, j'ai eu un problème sur une infrastructure ELK de logs. Je vous propose de décortiquer içi ce qu'il en est ressorti. Spoiler : j'ai fait une erreur de débutant.</p>
<p>Dabord, un petit rappel de l'architecture de notre infrastructure pour parser et stocker les logs.</p>
<p><img alt="Schéma de notre infra ELK" src="https://blog.etienne-magro.fr/images/Archi_complete_ELK.svg"></p>
<h1 id="symptomes">Symptômes</h1>
<p>En arrivant le matin au bureau, j'ai eu une alerte de mon <a href="http://www.shinken-monitoring.org/" title="Shinken est une solution de monitoring">monitoring</a> sur l'occupation RAM du serveur buffer.</p>
<p>Le scénario classique, quand la RAM monte sur le <a href="https://redis.io/" title="Site officiel de redis">redis</a>, qui sert de mémoire tampon pour lisser l'arrivées des logs dans notre infra, c'est :</p>
<ul>
<li>soit un gros pic de log. Comme nous connaissons les équipes qui nous envoi des logs, et surtout l'audience des appli qui nous envoient leurs logs, c'est souvent dû à un développeur qui active l'envoie de log en debug sur son instance de test/intégration/pré-prod/whatever qui subit des tests de perf/montée en charge. Ca se règle avec une petite gueulante parce qu'il "ne faut pas faire ça sur l'infra de prod". Ca se règle assez vite.</li>
<li>soit un/plusieurs noeuds de parsing est tombé. Dans l'urgence en relancer des nouveaux, et voir pourquoi certains sont mort. Cela prend un peu plus de temps.</li>
</ul>
<p>Notre monitoring nous montre aussi depuis quand ça monte : le matin à minuit.</p>
<h1 id="actions-entreprises">Actions entreprises</h1>
<h2 id="les-logstash-de-parsing">Les logstash de parsing</h2>
<p>Dans l'urgence, je me suis logué sur les serveurs de parsing et j'ai redémarré les logstash. Pendant qu'ils redémarraient, j'en ai profité pour lire les logs (du redémarrage et d'avant).</p>
<p>Et j'ai stoppé l'entrée de nouveau log dans le buffer redis. Histoire d'éviter que celui-là n'explose aussi.<br/>
Il n'y a rien a craindre pour les logs. Ceux qui sont sur le serveur de buffers seront traités quand tout sera remis en place et ceux qu'on empêche d'être envoyé restent bien au chaud sur les serveurs et rejoindront le buffer quand on rouvrira les vannes.</p>
<p>En attendant, les logs des logstash de parsing nous donnent : ils n'arrivent pas à envoyer les logs parsés au cluster elasticsearch. Eclair de génie (en vrai, j'ai honte, j'aurais dû le faire avant) : quel est l'état de notre cluster elasticsearch ?</p>
<h2 id="le-cluster-elasticsearch">Le cluster elasticsearch</h2>
<p>Un petit coup de <code>curl 'http://un_serveur_elasticsearch/_cluster/health?pretty=true'</code> => <code>"status" : "yellow",</code>. Aïe ! Il y a des <code>unassigned_shards</code>.</p>
<p>Sortons notre script magique, inspiré de cette réponse sur <a href="https://stackoverflow.com/a/26746211" title="Réponse sur Stackoverflow : script pour forcer l'assignation de shard sur un elasticsearch">Stackoverflow</a>. Ce script nous permet de forcer l'assignation de shard sur un noeud donné du cluster. Ca surcharge temporairement ce noeud-là, le temps que la magie du cluster fasse son effet et ne redispatche la charge sur l'ensemble du cluster.</p>
<p>Réponse du script : Erreur. Il ne peut pas forcer parce que... parce que QUOI ??? il ne reste que 85% d'espace disque, alors Elasticsearch refuse la création d'un nouvel index. WTF ? Connexion sur un des noeud pour vérifier, effectivement, il reste un peu moins de 15% d'espace libre.</p>
<p>"<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/disk-allocator.html">Expected behavior</a>". Bon, OK. Dans l'urgence, je supprime des vieux logs. On a normalement 30 jours de rétention, je n'en garderais que 28, ça devrait permettre de résorber temporairement cette situation.</p>
<p>Application de notre <a href="https://github.com/imperialwicket/elasticsearch-logstash-index-mgmt/blob/master/elasticsearch-remove-old-indices.sh">2e script magique, largement inspiré de celui-ci</a>. Résultat : l'occupation disque redescend à 55%. C'est à la fois cool, parce que tout repart, la machine se remet en branle, et à la fois pas cool, parce que 2 jours de rétention, ça ne fait pas 30% de notre capacité.</p>
<h1 id="la-suite">La suite</h1>
<h2 id="fin-de-lincident">Fin de l'incident</h2>
<p>A ce stade, je remets tout en route, j'attends que le buffer se vide un peu avant de rouvrir les vannes. En quelques minutes, tout est rentré dans l'ordre.</p>
<p>Fin de l'incident.</p>
<h2 id="la-vraie-raison">La vraie raison</h2>
<p>A ce stade, tout est fonctionnel, mais je n'ai toujours pas la root cause, la raison première de cet incident.</p>
<p>Je creuse un peu du côté des 30% de capacité en 2 jours. C'est louche.</p>
<p>Nous avons un script qui tourne toutes les nuits qui supprime les logs trop vieux, au delà des 30 jours de rétention. En regardant les graph de notre solution de monitoring, on peut voir clairement que l'espace à commencé à grossir il y a 2 semaines. Ce script n'avait pas tourné depuis 2 semaines. Pourquoi ?</p>
<p>Il requêtait un serveur appartenant au cluster qui a été décommissionné il y a 2 mois et supprimé il y a 2 semaines. Vous le voyez, le <a href="https://en.wikipedia.org/wiki/User_error#Acronyms_and_other_names" title="PEBKAC sur wikipedia">pebkac</a> ?</p>
<p><img alt="Pebkac on Dilbert" src="http://assets.amuniversal.com/583d3560af230132cfe8005056a9545d"></p>
<h1 id="comment-faire-pour-que-cela-ne-se-reproduise-plus">Comment faire pour que cela ne se reproduise plus ?</h1>
<p>Plusieurs choses ont été faites pour que je ne rencontre plus ce problème dont je suis indirectement en partie la cause.</p>
<ol>
<li>Descendre les seuils d'alerte pour l'espace disque du cluster elasticsearch, on est passé de 85-95 à 70-80.</li>
<li>Réfléchir à l'éventuelle modification du paramètre <code>cluster.routing.allocation.disk.watermark.low</code></li>
<li>Superviser l'exécution du script de suppression des vieux enregistrements</li>
<li>Superviser le nombre de jour de log stockés dans elasticsearch</li>
</ol>
<p>Les points 3. et 4. auraient déjà dû être fait bien avant, dès la mise en place de l'infra. C'est un gros oubli. C'est là qu'a été mon erreur.</p>Jouons avec les script-kiddies2017-07-18T19:00:00+02:002017-11-02T20:00:00+01:00Etienne Magrotag:blog.etienne-magro.fr,2017-07-18:/jouons-avec-les-script-kiddies.html<p>Les script kiddies, ces apprenti-hackeurs, devraient être les premiers à être automatiquement bloqués par une bonne conf sur un serveur de prod. Mais on peut aussi s'amuser un peu à leur dépend.</p><p>Depuis bien 10 ans, je gère des serveurs directement accessibles sur internet. J'ai commencé par auto-héberger quelques trucs pour moi quand j'étais étudiant sur un PC de récupération, avant d'en faire mon métier.</p>
<p>Aujourd'hui, j'auto-héberge toujours des outils/blog/projets perso pour le fun.</p>
<p>L'une des premières proccupations que l'on doit avoir quand on met un serveur sur le wild internet, c'est ça sécurisation. Il n'y a pas forcément besoin d'être expert en sécurité pour appliquer quelques règles minimales. On pense typiquement à la connexion SSH uniquement par clefs, quelques règles iptables, fail2ban, etc...</p>
<p>Je ne m'étendrais pas sur ces mesures nécessaires, mais plutôt sur un comportement très particulier de <a href="https://fr.wikipedia.org/wiki/Script_kiddie" title="Définition d'un script kiddie sur Wikipedia">script kiddies</a> et comment on peut s'amuser un peu avec eux (enfin, à leur dépend).</p>
<h1 id="je-nutilise-pas-wordpress">Je n'utilise pas Wordpress</h1>
<h2 id="constatations">Constatations</h2>
<p>Commençons par le commencement : je n'utilise pas wordpress. Par contre, ça ne m'empèche pas de voir ce genre de chose dans mes logs de serveur web (<a href="http://nginx.org" title="Nginx est un serveur web performant pour les fichier statiques et le reverse-proxying">Nginx</a> pour moi) :</p>
<div class="highlight"><pre><span></span><code><span class="nt">X</span><span class="p">.</span><span class="nc">X</span><span class="p">.</span><span class="nc">X</span><span class="p">.</span><span class="nc">X</span><span class="w"> </span><span class="nt">-</span><span class="w"> </span><span class="nt">-</span><span class="w"> </span><span class="cp">[</span><span class="mi">12</span><span class="p">/</span><span class="nx">Jul</span><span class="p">/</span><span class="nx">2017</span><span class="p">:</span><span class="mi">16</span><span class="p">:</span><span class="mi">23</span><span class="p">:</span><span class="mi">34</span><span class="w"> </span><span class="o">+</span><span class="mi">0200</span><span class="cp">]</span><span class="w"> </span><span class="s2">"GET /wp-login.php HTTP/1.1"</span><span class="w"> </span><span class="nt">404</span><span class="w"> </span><span class="nt">408</span><span class="w"> </span><span class="s2">"-"</span><span class="w"> </span><span class="s2">"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1"</span>
<span class="nt">X</span><span class="p">.</span><span class="nc">X</span><span class="p">.</span><span class="nc">X</span><span class="p">.</span><span class="nc">Y</span><span class="w"> </span><span class="nt">-</span><span class="w"> </span><span class="nt">-</span><span class="w"> </span><span class="cp">[</span><span class="mi">12</span><span class="p">/</span><span class="nx">Jul</span><span class="p">/</span><span class="nx">2017</span><span class="p">:</span><span class="mi">16</span><span class="p">:</span><span class="mi">27</span><span class="p">:</span><span class="mi">50</span><span class="w"> </span><span class="o">+</span><span class="mi">0200</span><span class="cp">]</span><span class="w"> </span><span class="s2">"GET /wp-login.php HTTP/1.1"</span><span class="w"> </span><span class="nt">404</span><span class="w"> </span><span class="nt">408</span><span class="w"> </span><span class="s2">"-"</span><span class="w"> </span><span class="s2">"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1"</span>
<span class="nt">X</span><span class="p">.</span><span class="nc">X</span><span class="p">.</span><span class="nc">X</span><span class="p">.</span><span class="nc">Z</span><span class="w"> </span><span class="nt">-</span><span class="w"> </span><span class="nt">-</span><span class="w"> </span><span class="cp">[</span><span class="mi">12</span><span class="p">/</span><span class="nx">Jul</span><span class="p">/</span><span class="nx">2017</span><span class="p">:</span><span class="mi">16</span><span class="p">:</span><span class="mi">32</span><span class="p">:</span><span class="mi">17</span><span class="w"> </span><span class="o">+</span><span class="mi">0200</span><span class="cp">]</span><span class="w"> </span><span class="s2">"GET /wp-login.php HTTP/1.1"</span><span class="w"> </span><span class="nt">404</span><span class="w"> </span><span class="nt">408</span><span class="w"> </span><span class="s2">"-"</span><span class="w"> </span><span class="s2">"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1"</span>
<span class="nt">X</span><span class="p">.</span><span class="nc">X</span><span class="p">.</span><span class="nc">X</span><span class="p">.</span><span class="nc">A</span><span class="w"> </span><span class="nt">-</span><span class="w"> </span><span class="nt">-</span><span class="w"> </span><span class="cp">[</span><span class="mi">12</span><span class="p">/</span><span class="nx">Jul</span><span class="p">/</span><span class="nx">2017</span><span class="p">:</span><span class="mi">16</span><span class="p">:</span><span class="mi">35</span><span class="p">:</span><span class="mi">37</span><span class="w"> </span><span class="o">+</span><span class="mi">0200</span><span class="cp">]</span><span class="w"> </span><span class="s2">"GET /wp-login.php HTTP/1.1"</span><span class="w"> </span><span class="nt">404</span><span class="w"> </span><span class="nt">408</span><span class="w"> </span><span class="s2">"-"</span><span class="w"> </span><span class="s2">"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1"</span>
<span class="nt">X</span><span class="p">.</span><span class="nc">X</span><span class="p">.</span><span class="nc">X</span><span class="p">.</span><span class="nc">B</span><span class="w"> </span><span class="nt">-</span><span class="w"> </span><span class="nt">-</span><span class="w"> </span><span class="cp">[</span><span class="mi">12</span><span class="p">/</span><span class="nx">Jul</span><span class="p">/</span><span class="nx">2017</span><span class="p">:</span><span class="mi">16</span><span class="p">:</span><span class="mi">51</span><span class="p">:</span><span class="mi">58</span><span class="w"> </span><span class="o">+</span><span class="mi">0200</span><span class="cp">]</span><span class="w"> </span><span class="s2">"GET /wp-login.php HTTP/1.1"</span><span class="w"> </span><span class="nt">404</span><span class="w"> </span><span class="nt">408</span><span class="w"> </span><span class="s2">"-"</span><span class="w"> </span><span class="s2">"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1"</span>
</code></pre></div>
<p>Décriptons vite fait pour ceux qui n'aurait jamais vu de log de serveur web : mon serveur web Nginx se voit demandé par plusieurs IP différentes de servir la page <code>wp-login.php</code>, mais comme elle n'existe pas renvoie une erreur <code>404</code>.</p>
<h2 id="decryptage">Décryptage</h2>
<p>Pourquoi ces logs ?</p>
<p>Parce que wordpress, dans sa configuration par défaut, utilise cette page <code>wp-login.php</code> pour authentifier les utilisateurs. D'autres pages propres à wordpress sont aussi demandées, toujours sans exister chez moi : <code>wp-content</code>, <code>xmlrpc.php</code>, etc...</p>
<p>Ces pages pouvant présenter des vulnérabilités sur d'anciennes version de Wordpress, si des apprentis hackeurs les trouvent, ils auront tôt fait de le vérifier et de s'approprier le site vulnérable.</p>
<h1 id="zip-bomb">Zip-Bomb</h1>
<h2 id="origine">Origine</h2>
<p>Suite à un article de <a href="https://korben.info">Korben</a>, <a href="https://korben.info/adapter-concept-de-zip-bomb-defendre-site-web-scripts-kiddies.html">adapter le concept de la Zip Bomb pour défendre son site web des scripts kiddies</a>, je me suis mis en tête de l'appliquer sur un de mes serveurs.</p>
<p>Problème, je n'utilise pas du tout PHP, sur aucun des sites que j'héberge sur le serveur choisi.</p>
<p>Qu'à cela ne tienne, il doit bien y avoir moyen de faire quelque chose quand même avec la configuration du serveur web directement.</p>
<h2 id="explication">Explication</h2>
<p>Le but du jeu est de faire crasher le logiciel ou carrément l'ordinateur du malheureux méchant qui voudrait accéder à la page <code>wp-login.php</code>.</p>
<p>Le principe de la Zip-Bomb est simple : on va donner à l'attaquant un petit fichier compressé de 10Mo (inofencif pour nous), mais qui une fois décompressé fait 10Go. Un petit ordre 1000, oui. La plupart du temps, les ordinateurs ne supporteront pas bien cette décompression par manque de RAM (qui à 10Go de RAM de libre ?) ou par manque de place disque.</p>
<h2 id="mise-en-place">Mise en place</h2>
<p>Créons la zip bomb. Sur votre serveur linux faites :</p>
<div class="highlight"><pre><span></span><code>dd<span class="w"> </span><span class="k">if</span><span class="o">=</span>/dev/zero<span class="w"> </span><span class="nv">bs</span><span class="o">=</span>1M<span class="w"> </span><span class="nv">count</span><span class="o">=</span><span class="m">10240</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>gzip<span class="w"> </span>><span class="w"> </span>/srv/badrobots/10G.gzip
</code></pre></div>
<p>Et voilà.<br/>
Vérifiez, le fichier compressé fait environ 10Mo.</p>
<p>La suite se trouve dans la configuration nginx :</p>
<div class="highlight"><pre><span></span><code><span class="nt">server</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="err">listen</span><span class="w"> </span><span class="err">80</span><span class="p">;</span>
<span class="w"> </span><span class="n">listen</span><span class="w"> </span><span class="cp">[</span><span class="p">::</span><span class="cp">]</span><span class="p">:</span><span class="mi">80</span><span class="p">;</span>
<span class="w"> </span><span class="err">server_name</span><span class="w"> </span><span class="err">mon_super_nom_de_domaine.tld</span><span class="p">;</span>
<span class="w"> </span><span class="err">...</span>
<span class="w"> </span><span class="err">location</span><span class="w"> </span><span class="err">~</span><span class="w"> </span><span class="err">(wp-login|wp-content|xmlrpc)</span><span class="w"> </span><span class="err">{rewrite</span><span class="w"> </span><span class="err">^/.*</span><span class="w"> </span><span class="err">/10G.gzip</span><span class="p">;}</span>
<span class="w"> </span><span class="nt">location</span><span class="w"> </span><span class="o">/</span><span class="nt">10G</span><span class="p">.</span><span class="nc">gzip</span><span class="w"> </span><span class="p">{</span><span class="err">alias</span><span class="w"> </span><span class="err">/srv/badrobots/10G.gzip</span><span class="p">;</span><span class="w"> </span><span class="err">more_set_headers</span><span class="w"> </span><span class="err">'</span><span class="n">Content-Type</span><span class="p">:</span><span class="w"> </span><span class="kc">text</span><span class="o">/</span><span class="n">html</span><span class="s1">' '</span><span class="n">Content-Encoding</span><span class="o">:</span><span class="w"> </span><span class="n">gzip</span><span class="err">'</span><span class="p">;}</span>
<span class="w"> </span><span class="o">...</span>
<span class="err">}</span>
</code></pre></div>
<p><em>Note : J'utilise ici le plugin nginx "<a href="https://github.com/openresty/headers-more-nginx-module">more_set_headers</a>" au lieu du simple "<a href="http://nginx.org/en/docs/http/ngx_http_headers_module.html">add_header</a>". Dans notre cas, il faut changer le champ "Content-Type", et l'usage de la directive "add_header" ne fait que rajouter un header "Content-Type" supplémentaire. Et au final ça ne marchera pas, le client web ne prenant pas le bon header "Content-Type". Sur système type Debian, installez le package "nginx-extras" plutôt que les "nginx-light" ou "nginx-full".</em></p>
<p>Expliquons : pour toute requête contenant <code>wp-login</code> ou <code>wp-content</code> ou <code>xmlrpc</code>, on va ré-écrire la requête pour demander à se faire servir le path "/10G.gzip". Pour ce path particulier, on va servir le fichier qui nous intéresse et ajouter/modifier des headers HTTP. On modifie le "Content-Type" pour indiquer que c'est une page HTML si on ne fait pas ça, Nginx indique un type binaire et donc le client demandera juste à l'utilisateur où il veut télécharger le fichier. On ajoute le header "Content-Encoding" pour indiquer au navigateur qu'il faut qu'il décompresse ce qu'on lui a envoyé.</p>
<p>Pour faire court : pour toute requête contenant <code>wp-login</code> ou <code>wp-content</code> ou <code>xmlrpc</code>, on va plutôt leur servir la zip bomb.</p>
<p>Et voilà !</p>
<h2 id="resultats">Résultats</h2>
<p>Pour voir ce que cela fait, vous pouvez soit tentez par vous-même, en allant http://blog.etienne-magro.fr/wp-login.php, soit en lisant le <a href="https://blog.haschek.at/post/f2fda" title="How to defend your website with ZIP bombs">blogpost de l'auteur de l'idée</a> qui a fait les tests pour vous ;-)</p>
<p>En résumé, ça fait planté certains navigateurs.</p>
<h2 id="fail2ban">Fail2ban</h2>
<p>Ha oui, aussi, n'oubliez pas de mettre à jour vos filtres <a href="http://www.fail2ban.org/wiki/index.php/Apache" title="Filtres fail2ban pour Apache">fail2ban</a>.</p>
<p>En effet, là où avant, nginx répondait une erreur <code>404 Not Found</code>, maintenant, il répond <code>200</code> en envoyant la zip bomb. Vos <a href="http://envyandroid.com/fail2ban-wordpress-login-attacks/">regex fail2ban pour wordpress</a> ne vont plus fonctionner.</p>
<p>Les logs maintenant :</p>
<div class="highlight"><pre><span></span><code><span class="nt">X</span><span class="p">.</span><span class="nc">X</span><span class="p">.</span><span class="nc">X</span><span class="p">.</span><span class="nc">X</span><span class="w"> </span><span class="nt">-</span><span class="w"> </span><span class="nt">-</span><span class="w"> </span><span class="cp">[</span><span class="mi">18</span><span class="p">/</span><span class="nx">Jul</span><span class="p">/</span><span class="nx">2017</span><span class="p">:</span><span class="mi">18</span><span class="p">:</span><span class="mi">20</span><span class="p">:</span><span class="mi">09</span><span class="w"> </span><span class="o">+</span><span class="mi">0200</span><span class="cp">]</span><span class="w"> </span><span class="s2">"GET /wp-login.php HTTP/1.1"</span><span class="w"> </span><span class="nt">200</span><span class="w"> </span><span class="nt">10420385</span><span class="w"> </span><span class="s2">"-"</span><span class="w"> </span><span class="s2">"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1"</span>
</code></pre></div>
<p>Récapitulons : non seulement on fait crasher l'ordinateur du script kiddie avec notre conf nginx, mais une fois crashé, on le ban avec fail2ban.</p>
<p>Qu'est-ce qu'on s'amuse :-)</p>
<h1 id="bonus">Bonus</h1>
<h2 id="bonus-1-mauvais-nom-de-domaine">Bonus 1 : Mauvais nom de domaine</h2>
<p>Petit bonus pour toutes les requêtes qui arrivent sur le serveur avec un mauvais nom de domain :</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>cat<span class="w"> </span>/etc/nginx/sites-enabled/default
server<span class="w"> </span><span class="o">{</span>
<span class="w"> </span>listen<span class="w"> </span><span class="m">80</span><span class="w"> </span>default_server<span class="p">;</span>
<span class="w"> </span>listen<span class="w"> </span><span class="o">[</span>::<span class="o">]</span>:80<span class="w"> </span>default_server<span class="p">;</span>
<span class="w"> </span>server_name<span class="w"> </span>_<span class="p">;</span>
<span class="w"> </span>location<span class="w"> </span>/<span class="w"> </span><span class="o">{</span>rewrite<span class="w"> </span>^/.*<span class="w"> </span>/10G.gzip<span class="p">;</span><span class="o">}</span>
<span class="w"> </span>location<span class="w"> </span>/10G.gzip<span class="w"> </span><span class="o">{</span><span class="nb">alias</span><span class="w"> </span>/srv/badrobots/10G.gzip<span class="p">;</span>more_set_headers<span class="w"> </span><span class="s1">'Content-Type: text/html'</span><span class="w"> </span><span class="s1">'Content-Encoding gzip'</span><span class="p">;</span><span class="o">}</span>
<span class="o">}</span>
</code></pre></div>
<h2 id="bonus-2-regles-plus-completes">Bonus 2 : Règles plus complètes</h2>
<p>Pour finir, quelques règles plus complètes qui prend aussi les phpmyadmin et 2-3 autres merdes.</p>
<p>Cadeau !</p>
<div class="highlight"><pre><span></span><code><span class="err">$</span><span class="w"> </span><span class="n">cat</span><span class="w"> </span><span class="o">/</span><span class="n">etc</span><span class="o">/</span><span class="n">nginx</span><span class="o">/</span><span class="n">conf</span><span class="p">.</span><span class="n">d</span><span class="o">/</span><span class="n">zipbomb</span><span class="p">.</span><span class="n">conf</span>
<span class="k">map</span><span class="w"> </span><span class="err">$</span><span class="n">location</span><span class="w"> </span><span class="err">$</span><span class="n">bad_location</span><span class="w"> </span><span class="err">{</span>
<span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span>
<span class="w"> </span><span class="o">~*</span><span class="p">(</span><span class="vm">?</span><span class="n">i</span><span class="p">)(</span><span class="n">wp</span><span class="o">-</span><span class="n">login</span><span class="o">|</span><span class="n">wp</span><span class="o">-</span><span class="n">content</span><span class="o">|</span><span class="n">wp</span><span class="o">-</span><span class="k">admin</span><span class="o">|</span><span class="n">wp</span><span class="o">-</span><span class="n">signup</span><span class="o">|</span><span class="n">xmlrpc</span><span class="o">|</span><span class="n">typo3</span><span class="o">|</span><span class="n">xampp</span><span class="o">|</span><span class="n">pma</span><span class="o">|</span><span class="p">(</span><span class="n">php</span><span class="p">)</span><span class="vm">?</span><span class="o">[</span><span class="n">Mm</span><span class="o">]</span><span class="n">y</span><span class="o">[</span><span class="n">Aa</span><span class="o">]</span><span class="n">dmin</span><span class="p">)</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span>
<span class="err">}</span>
<span class="err">$</span><span class="w"> </span><span class="n">cat</span><span class="w"> </span><span class="o">/</span><span class="n">etc</span><span class="o">/</span><span class="n">nginx</span><span class="o">/</span><span class="n">site</span><span class="o">-</span><span class="n">enabled</span><span class="o">/</span><span class="n">my</span><span class="o">-</span><span class="n">site</span><span class="p">.</span><span class="n">conf</span>
<span class="n">server</span><span class="w"> </span><span class="err">{</span>
<span class="w"> </span><span class="n">listen</span><span class="w"> </span><span class="mi">80</span><span class="p">;</span>
<span class="w"> </span><span class="n">listen</span><span class="w"> </span><span class="o">[</span><span class="n">::</span><span class="o">]</span><span class="err">:</span><span class="mi">80</span><span class="p">;</span>
<span class="w"> </span><span class="n">server_name</span><span class="w"> </span><span class="n">mon_super_nom_de_domaine</span><span class="p">.</span><span class="n">tld</span><span class="p">;</span>
<span class="w"> </span><span class="p">...</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="err">$</span><span class="n">bad_location</span><span class="p">)</span><span class="w"> </span><span class="err">{</span><span class="n">rewrite</span><span class="w"> </span><span class="o">^/</span><span class="p">.</span><span class="o">*</span><span class="w"> </span><span class="o">/</span><span class="mi">10</span><span class="n">G</span><span class="p">.</span><span class="n">gzip</span><span class="p">;</span><span class="err">}</span>
<span class="w"> </span><span class="n">location</span><span class="w"> </span><span class="o">/</span><span class="mi">10</span><span class="n">G</span><span class="p">.</span><span class="n">gzip</span><span class="w"> </span><span class="err">{</span><span class="k">alias</span><span class="w"> </span><span class="o">/</span><span class="n">srv</span><span class="o">/</span><span class="n">badrobots</span><span class="o">/</span><span class="mi">10</span><span class="n">G</span><span class="p">.</span><span class="n">gzip</span><span class="p">;</span><span class="n">more_set_headers</span><span class="w"> </span><span class="s1">'Content-Type: text/html'</span><span class="w"> </span><span class="s1">'Content-Encoding: gzip'</span><span class="p">;</span><span class="err">}</span>
<span class="w"> </span><span class="p">...</span>
<span class="err">}</span>
</code></pre></div>Un temps pour tout2017-07-12T12:00:00+02:002017-07-12T12:00:00+02:00Etienne Magrotag:blog.etienne-magro.fr,2017-07-12:/un-temps-pour-tout.html<p>Il faut régulièrement regarder en arrière, et ranger ce qu'il faut ranger.</p><p>Il y a maintenant 3 ans, je tentais de lancer un business sur internet : le monitoring de site web doublé d'un outils d'analitics.</p>
<p>Ce site, c'était isup.ws.</p>
<p>Aujourd'hui, 3 ans après, le site n'a pas fait un seul client, et surtout n'a pas de traffic (<5 visiteurs par mois).</p>
<p>Il est temps pour moi de tirer un trait dessus. D'arrêter de le développer ou même de le maintenir.</p>
<p>C'est pour cette raison que j'ai décidé de le retirer d'internet.</p>
<p>Le code reste bien au chaud dans mon dépôt git, comme dans un musée, mais restera dans cet état, et le domain redirige maintenant vers ce blogpost.</p>
<p>J'ai peut-être euthanasié ce site, mais d'autres tournent toujours plutôt bien :</p>
<ul>
<li><a href="https://what.isup.ws/" title="Le portail d'actualité">What.Isup</a> (lancé il y a plus de 2 ans) avec plus de 600 visiteurs par jour</li>
<li><a href="https://www.ventefilante.fr/" title="les ventes flash et promotions sur internet">Vente Filante</a> fait environ 50 visiteurs par jour depuis son lancement il y a quelques mois</li>
<li>le petit dernier <a href="https://www.info-medoc.fr/" title="Votre médicament est-il remboursables par la sécu ? nécessite-t-il une prescription médicale ?">Info-Medoc</a> qui existe depuis le mois dernier</li>
<li>et ce <a href="https://blog.etienne-magro.fr/" title="Mon blog">blog-ci</a></li>
</ul>Elasticsearch, Architecture pour les logs2017-07-03T19:00:00+02:002017-07-03T19:00:00+02:00Etienne Magrotag:blog.etienne-magro.fr,2017-07-03:/elasticsearch-architexture-logs.html<p>L'un des usages principaux d'Elasticsearch est le stockage de logs. Voyons sans détour comment bien architecturer et dimensionner une infrastructure complète à fin de parsing et stockage des logs.</p><p>L'un des usages principaux d'Elasticsearch est le stockage de logs. Voyons sans détour comment bien architecturer et dimensionner une infrastructure complète à fin de parsing et stockage des logs.</p>
<h1 id="decoupage-des-roles">Découpage des rôles</h1>
<p>On peut distinguer plusieurs étapes/rôles dans une architecture ELK :</p>
<ul>
<li>une étape facultative pour récupérer/centraliser les logs</li>
<li>il faut parser les logs</li>
<li>puis les stocker</li>
<li>et enfin pouvoir les consulter</li>
</ul>
<h2 id="lenvoi-des-logs">L'envoi des logs</h2>
<p>Le parsing des logs peut se faire en local sur chaque serveur. Dans le cas d'une petite infrastructure, pourquoi pas. Mais qui a envie de lancer du <a href="https://github.com/elastic/logstash" title="Logstash sur Github">code JRuby</a> sur son serveur applicatif/web/DB/whatever de production ?</p>
<p>Dans ce cas, le plus simple est de centraliser le parsing sur un/des serveurs qui seront dédiés à cela et ne pénaliseront pas la prod.</p>
<p>Le serveur qui centralisera les logs en attente de parsing sera un <a href="https://redis.io/" title="Site officiel de redis">redis</a>, et pour envoyer les logs vers lui, on utilisera <a href="https://www.elastic.co/products/beats/filebeat" title="Filebeat sur elastic.co">filebeat</a>, un petit <a href="https://github.com/elastic/beats" title="Filebeat sur github">utilitaire en Go</a>, minimaliste pour ne pas plomber la prod.</p>
<h2 id="le-parsing-des-logs">Le parsing des logs</h2>
<p>Nous allons partir sur un serveur dédié au parsing des logs. Cela sera le travail de <a href="https://www.elastic.co/products/logstash" title="Page Logstash sur elastic.co">Logstash</a>.</p>
<p>Nous pourrions envoyer les logs directement dans Elasticsearch, mais les parser va nous permettre de chercher plus facilement dedans, de faire des stats/board, de la corrélation, etc... Autant le faire, ça vaut vraiment le coup.</p>
<h2 id="le-stockage-des-logs">Le stockage des logs</h2>
<p><a href="https://www.elastic.co/products/elasticsearch" title="Page officielle Elasticsearch sur elastic.co">Elasticsearch</a> est le logiciel le plus performant pour stocker et interroger des logs. Autant partir dessus.</p>
<h2 id="la-consultation-des-logs">la consultation des logs</h2>
<p>En complément d'Elasticsearch, <a href="https://www.elastic.co/products/kibana" title="Page officielle Kibana sur elastic.co">Kibana</a> est le plus adapté à la consultation des logs.</p>
<h2 id="resume">Résumé</h2>
<p>Un bon schéma vaut mieux qu'on long discours, voici ce vers quoi nous nous dirigeons.
<img alt="Premier schéma basique de notre infra ELK" src="https://blog.etienne-magro.fr/images/Archi_basique_ELK.svg"></p>
<h1 id="dimensionnement">Dimensionnement</h1>
<p>Voici quelques estimations sur lesquelles vous pouvez vous baser pour faire un premier dimensionnement de votre architecture :</p>
<ul>
<li>Logstash parse ~1000 lignes de logs par seconde par GHz de CPU</li>
<li>Elasticsearch utilise 1Mo de disque dur pour 1000 lignes de logs par jour par jour de rétention</li>
</ul>
<p>Explicitons cela ensemble.</p>
<h2 id="redis">Redis</h2>
<p>Le redis n'a pas forcément besoin d'être particulièrement gros. Son rôle est la centralisation des log ainsi que le buffering.
Dans un cas optimal, le <a href="https://redis.io/" title="Site officiel de redis">redis</a> sera toujours vide car tout ce qui lui sera envoyé devrait être consommé par les logstash immédiatement.</p>
<h3 id="ram">RAM</h3>
<p>La chose à particulièrement surveiller va être la RAM. En effet, si vous avez un pic de production de log, il faut que le redis puisse l'absorbé, le temps que les logstash consomme ce pic. Si les logstash n'arrivent pas à consommer les logs suffisamment rapidement, votre redis va exploser. Dans ce cas, il fera office de fusible.</p>
<p>Quand vous choissirez la taille de votre VM/serveur (RAM surtout), il faudra impérativement le mettre en perspective avec la capacité que vous allouerez à votre logstash.</p>
<h2 id="logstashparsing">Logstash/Parsing</h2>
<p><a href="https://www.elastic.co/products/logstash" title="Page Logstash sur elastic.co">Logstash</a>, le service de parsing, va prendre les logs depuis le redis et les parser. Cela consistente basiquement en l'application d'une regexp pour extraire différentes informations de la ligne de log.</p>
<p>Ce service est <a href="http://whatis.techtarget.com/definition/stateless-app" title="Définition (en anglais) d'une application stateless">stateless</a>, car aussitôt les informations extraites, tout est envoyé dans l'elasticsearch. Logstash ne stocke aucune donnée.</p>
<p>Les paramètres important à regarder dans le dimensionnement d'un serveur de parsing logstash sont : le CPU (surtout) et la RAM (moins).</p>
<h3 id="ram_1">RAM</h3>
<p>Evacuons tout de suite la RAM. Pourquoi faut-il se préoccuper de la RAM ? parce que vous aller tourner un logiciel écrit en JRuby. En raccourci, du Ruby interprété en Java. Oui, une JVM. C'est mieux si elle est à l'aise.</p>
<h3 id="cpu">CPU</h3>
<p>Le CPU est directement relié à la vitesse avec laquelle vos logs seront analysées. Donc, plus il y a de CPU mieux c'est. Au doigt mouillé, en première estimation, vous pouvez utiliser cette règle : <strong>Logstash parse ~1000 lignes de logs par seconde par GHz de CPU</strong>. Bien entendu, cela va dépendre de la complexité de vos règles de parsing, etc... mais c'est une bonne approximation de départ.</p>
<h3 id="parallelisation">Parallélisation</h3>
<p>Ce qu'il y a de bien avec un logiciel stateless, c'est que vous pouvez en démarrer autant que vous le souhaitez en parallèle, l'analyse de vos logs n'iront que plus vite. 2 serveurs 2 CPU iront 4 foix plus vite qu'un serveur 1 CPU (cadensé à la même vitesse). Pas besoin d'avoir un seul gros serveur. Plusieurs petits feront autant le travail. Sans compter sur la résilience. Mais nous verrons cela plus tard.</p>
<h2 id="elasticsearch">Elasticsearch</h2>
<p><a href="https://blog.etienne-magro.fr/elasticsearch-whatisit.html">Elasticsearch</a> un système de base de donnée NoSQL distribué et hautement scalable. Donc, il stocke des données. Et nous allons stocker beaucoup de données. Que devons-nous regarder pour dimensionner notre service Elasticsearch ? Le disque dur, bien évidemment ! mais aussi la RAM et dans une moindre mesure le CPU.</p>
<p>Pour plus de détails, vous pouvez vous reporter à la <a href="https://www.elastic.co/guide/en/elasticsearch/guide/current/hardware.html" title="Recommandations hardware pour elasticsearch par elastic.co">page officielle dans la doc elasticsearch</a>.</p>
<h3 id="disque-dur">Disque dur</h3>
<p>Pour le stockage disque, pour faire votre dimensionnement prévisionnel, vous pouvez vous baser sur cette règle : <strong>Elasticsearch utilise 1Mo de disque dur pour 1000 lignes de logs par jour par jour de rétention</strong>. J'évacue volontairement la question de la réplication des données que nous verrons un peu plus tard. Comment appliquer cela ? Prenons comme hypothèses :</p>
<ul>
<li>que vous voulez garder votre historique de log pendant 15 jours avant de le supprimer</li>
<li>que vous allez stocker les logs d'appli qui produisent en moyenne 1 millions de lignes de log par jour</li>
</ul>
<p>Avec cela, vous allez avoir besoin de 15Go de disque dur.</p>
<h3 id="ram_2">RAM</h3>
<p>Pour la RAM, il y a 2 choses à garder en mémoire : 1)Elasticsearch est codé en Java, donc vous allez faire tourner une JVM et 2) Comme toute BDD, plus vous lui fournissez de RAM, meilleures seront les performances.</p>
<h3 id="hautement-distribue">Hautement distribué</h3>
<p>Dernière chose, et qui fait la force d'Elasticsearch, c'est qu'il est totalement distribué. Si vous ne pouvez pas tout faire tenir sur un seul serveur, alors utilisez-en plusieurs, ça ne sera pas plus difficile. C'est même recommandé. Un exemple de configuration <a href="{static}/2017-06-27.ElasticSearch.configuration.md" title="Comment configurer Elasticsearch">vous a été fourni</a>, c'est la configuration que j'utilise sur un petit cluster de production de 12 serveurs.</p>
<h1 id="conclusion">Conclusion</h1>
<p>Avant de plongé plus loin dans la technique et la configuration proprement dite de cette architecture, récapitulons.</p>
<p>Nous voulons centraliser, parser et stocker des logs dans une infrastructure "ELK".</p>
<p>Nous avons tout découpé en étapes unitaires et les avons isolé.</p>
<p>Nous avons regardé les paramètres hardware important pour le dimensionnement de notre infra pour chaque étape.</p>
<p><img alt="Schéma plus complet de notre infra ELK" src="https://blog.etienne-magro.fr/images/Archi_complete_ELK.svg"></p>
<p>Voici le workflow complet :</p>
<ol>
<li>Vos serveurs applicatifs/web/DB/etc... vont envoyés leurs logs à un serveur redis à l'aide de <a href="https://www.elastic.co/products/beats/filebeat" title="Filebeat sur elastic.co">filebeat</a></li>
<li>Le serveur <a href="https://redis.io/" title="Site officiel de redis">redis</a> concentrera les logs en faisant également office de buffer et de fusible</li>
<li>Le/Les serveurs <a href="https://www.elastic.co/products/logstash" title="Page Logstash sur elastic.co">Logstash</a> consommeront les logs et les analyseront avant d'envoyer le resultat du parsing au cluster elasticsearch</li>
<li>Le cluster <a href="https://www.elastic.co/products/elasticsearch" title="Page officielle Elasticsearch sur elastic.co">elasticsearch</a> sera chargé de stocker les logs</li>
<li>Un petit serveur <a href="https://www.elastic.co/products/kibana" title="Page officielle Kibana sur elastic.co">kibana</a> nous aidera à requêter les logs et à faire quelques graphiques et dashboard</li>
</ol>
<p>Prochaine étape : faisons-le pour de vrai avec de vrais morceaux de bash et de configuration en se basant sur une vraie infrastructure ELK en production.</p>Info-Medoc.fr, Présentation2017-07-02T19:00:00+02:002017-07-02T19:00:00+02:00Etienne Magrotag:blog.etienne-magro.fr,2017-07-02:/info-medocfr-presentation.html<p>Présentation de mon nouveau site : info-medoc.fr créé grâce à l'opendata gouvernemental. On commence par l'import des données.</p><p>La semaine dernière, j'ai lancé un nouveau site : <a href="https://www.info-medoc.fr/" title="Vos médicaments sont-ils remboursables par la Sécu ? Sont-ils disponible en automédication ?">info-medoc.fr</a>. C'est parti d'une idée toute bête : je voulais savoir si mes médicaments étaient remboursables par la sécu et si oui, dans quelle proportion. Problème, je ne trouvais l'information nul part.</p>
<h1 id="open-data">Open-Data</h1>
<p>Après moult recherches, je suis tombé sur <a href="http://medicaments.gouv.fr/">http://medicaments.gouv.fr/</a>, qui mettait à disposition la base de donnée des médicaments à la vente en France via <a href="http://base-donnees-publique.medicaments.gouv.fr/">http://base-donnees-publique.medicaments.gouv.fr/</a>.</p>
<p>Avant cela, petit point "<a href="https://fr.wikipedia.org/wiki/Open_data" title="Définition de l'OpenData sur Wikipedia">OpenData</a>" :</p>
<blockquote>
<p>L'open data ou donnée ouverte est une donnée numérique dont l'accès et l'usage sont laissés libres aux usagers. Elle peut être d'origine publique ou privée, produite notamment par une collectivité, un service public (éventuellement délégué) ou une entreprise. Elle est diffusée de manière structurée selon une méthode et une licence ouverte garantissant son libre accès et sa réutilisation par tous, sans restriction technique, juridique ou financière.</p>
</blockquote>
<p>Ni une, ni deux, je vérifie les <a href="http://base-donnees-publique.medicaments.gouv.fr/telechargement.php" title="Conditions de téléchargement de la base de donnée publique des médicaments">conditions</a> qui sont très permissives. Merci l'état Français !<br/>
Le contenu des fichiers est très bien expliqué avec un beau fichier <a href="http://base-donnees-publique.medicaments.gouv.fr/docs/Contenu_et_format_des_fichiers_telechargeables_dans_la_BDM_v1.pdf">PDF</a>. Merci encore une fois aux personnes qui s'en sont chargé, ce n'était pas couru d'avance.</p>
<h1 id="importation-de-la-base-de-donnee">Importation de la base de donnée</h1>
<p>Commençons le code pour importer tout ça dans une base de donnée. Vue la taille des fichiers, une base <a href="https://www.sqlite.org/" title="Site officiel de la base de donnée SQLite">SQLite</a> ira très bien.</p>
<p>Commençons par décrire notre base de donnée. D'après le <a href="http://base-donnees-publique.medicaments.gouv.fr/docs/Contenu_et_format_des_fichiers_telechargeables_dans_la_BDM_v1.pdf">document descriptif</a> nous pouvons créer la première table ainsi :</p>
<div class="highlight"><pre><span></span><code><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">CIS_bdpm</span><span class="w"> </span><span class="p">(</span><span class="n">cis</span><span class="w"> </span><span class="nb">INT</span><span class="p">,</span>
<span class="w"> </span><span class="n">denomination</span><span class="w"> </span><span class="nb">TEXT</span><span class="p">,</span>
<span class="w"> </span><span class="n">forme</span><span class="w"> </span><span class="nb">TEXT</span><span class="p">,</span>
<span class="w"> </span><span class="n">administration</span><span class="w"> </span><span class="nb">TEXT</span><span class="p">,</span>
<span class="w"> </span><span class="n">status_amm</span><span class="w"> </span><span class="nb">TEXT</span><span class="p">,</span>
<span class="w"> </span><span class="n">type_amm</span><span class="w"> </span><span class="nb">TEXT</span><span class="p">,</span>
<span class="w"> </span><span class="n">etat_com</span><span class="w"> </span><span class="nb">TEXT</span><span class="p">,</span>
<span class="w"> </span><span class="n">date_amm</span><span class="w"> </span><span class="nb">DATE</span><span class="p">,</span>
<span class="w"> </span><span class="n">statut_bdm</span><span class="w"> </span><span class="nb">TEXT</span><span class="p">,</span>
<span class="w"> </span><span class="n">autorisation_euro</span><span class="w"> </span><span class="nb">TEXT</span><span class="p">,</span>
<span class="w"> </span><span class="n">titulaire</span><span class="w"> </span><span class="nb">TEXT</span><span class="p">,</span>
<span class="w"> </span><span class="n">surveillance</span><span class="w"> </span><span class="n">BOOL</span>
<span class="w"> </span><span class="p">);</span>
</code></pre></div>
<p>Je ne vais pas détailler les autres tables, tout est disponible sur ce <a href="https://gist.github.com/Mageti/a8df8328ae3d80530461a619199f10c7" title="Modèle de la base de donnée publique des médicaments">gist github</a>.</p>
<p>Mettons tout ça dans du code python :</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">sqlite3</span>
<span class="kn">import</span> <span class="nn">urllib.request</span>
<span class="n">conn</span> <span class="o">=</span> <span class="n">sqlite3</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="s1">'medocs_db.sqlite3'</span><span class="p">)</span>
<span class="n">cur</span> <span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="n">cursor</span><span class="p">()</span>
<span class="c1"># Téléchargement du fichier</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">urllib</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">urlopen</span><span class="p">(</span><span class="n">db</span><span class="p">[</span><span class="s1">'url'</span><span class="p">])</span>
<span class="n">data</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="n">read</span><span class="p">()</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s1">'latin_1'</span><span class="p">)</span>
<span class="c1"># Creation la DB</span>
<span class="n">cur</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="n">db</span><span class="p">[</span><span class="s1">'sql_create'</span><span class="p">])</span>
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">data</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">'</span><span class="se">\n</span><span class="s1">'</span><span class="p">):</span>
<span class="c1"># Insertion ligne par ligne dans la DB</span>
<span class="n">cur</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="n">db</span><span class="p">[</span><span class="s1">'sql_insert'</span><span class="p">],</span> <span class="n">elements</span><span class="p">)</span>
<span class="n">conn</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
<span class="n">conn</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</code></pre></div>
<p>Avec ça, on a la base. Il manque un peu d'enrobage et on est bon.<br/>
Pour le <a href="https://gist.github.com/Mageti/afb64ee43c7ae8d42c33ad9ec04bdf2a" title="Code complet d'importation de la base de donnée publique des médicaments">code complet de l'importation</a>, ça se passe aussi sur github (il faut combiner avec le fichier de <a href="https://gist.github.com/Mageti/a8df8328ae3d80530461a619199f10c7" title="Modèle de la base de donnée publique des médicaments">settings</a>).</p>
<p>Nous disposons maintenant d'un magnifique fichier de base de donnée SQLite avec toutes les informations disponibles en opendata sur <a href="http://base-donnees-publique.medicaments.gouv.fr/">la base de donnée publique des médicaments</a>.</p>
<h1 id="benefices">Bénéfices</h1>
<p>C'est bien, d'avoir cette base de donnée. Mais qu'en faire ?<br/>
On peut en extraire pas mal de données. Celles qui viennent le plus à l'esprit sont de savoir si un médicament est remboursable par la sécu.<br/>
Heureusement ! C'était quand même le point de départ de ce projet. Et aussi si le médicament est disponible en automédication ou uniquement sur prescription.</p>
<p>En gros, un petit site web en a émergé : <a href="https://www.info-medoc.fr/" title="Vos médicaments sont-ils remboursables par la Sécu ? Sont-ils disponible en automédication ?">info-medoc.fr</a>.</p>
<p>On peut aller plus loin en extrayant quelques <a href="https://www.info-medoc.fr/stats" title="Quelques statistiques sur les médicaments vendus en France">stats</a>. En voici quelques exemples :</p>
<ul>
<li>il y a 620 laboratoires qui vendent 14583 médicaments en France.</li>
<li>moins d'un quart des médicaments qui ne nécessitent pas de prescription.</li>
<li>environ la moitié des médicaments ne sont plus remboursables par la sécu.</li>
</ul>Elasticsearch, Configuration2017-06-26T19:00:00+02:002017-06-26T19:00:00+02:00Etienne Magrotag:blog.etienne-magro.fr,2017-06-26:/elasticsearch-configuration.html<p>Elasticsearch, dans sa configuration par défaut fonctionne bien, mais n'est pas utilisable en production. Voyons comment nous pouvons le configurer.</p><p>Les paramètres par défaut d'Elasticsearch ne sont pas mauvais, mais je vais détailler ici la configuration qu'il faut vérifier, et éventuellement modifier pour avoir un cluster près pour la production.</p>
<p><strong>Attention</strong>: <em>La configuration qui sera donnée ici n'est pas une configuration "aux petits oignons" pour une production exigeante.</em> Il s'agit surtout de montrer la configuration minimale à modifier pour avoir un cluster Elasticsearch prêt à fonctionner dans de bonnes conditions, redondant et scalable.</p>
<p>Toute la configuration se fait dans un fichier <code>elasticsearch.yml</code>. Vous trouverez plus de documentation sur la <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/important-settings.html" title="Configuration importante sur elastic.co">page officielle</a>.</p>
<h2 id="paths">Paths</h2>
<p>Il y a 2 paramètres <code>path.*</code> à regarder, dont un particulièrement important :</p>
<div class="highlight"><pre><span></span><code><span class="c1">#################################### Paths ####################################</span>
<span class="c1"># Path to directory where to store index data allocated for this node.</span>
<span class="c1">#</span>
<span class="nt">path.data</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">/mnt/data</span>
<span class="c1"># Path to log files:</span>
<span class="c1">#</span>
<span class="nt">path.logs</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">/var/log/elasticsearch</span>
</code></pre></div>
<p>Je ne m'étendrais que sur <code>path.data</code>. C'est là que vont se retrouver les fichiers utilisés par Elasticsearch pour le stockage de ses donnnées. Il est de bon ton de les mettre sur une partition séparée, la plus grosse possible, en fonction de la quantité de données que vous pensez devoir stocker.</p>
<h2 id="cluster-name">Cluster Name</h2>
<p>Le paramètre <code>cluster.name</code> est important car votre serveur Elasticsearch ne pourra rejoindre qu'un seul cluster, et ce cluster aura le nom configuré avec ce paramètre.</p>
<div class="highlight"><pre><span></span><code><span class="c1">################################### Cluster ###################################</span>
<span class="c1"># Cluster name identifies your cluster for auto-discovery. If you're running</span>
<span class="c1"># multiple clusters on the same network, make sure you're using unique names.</span>
<span class="c1">#</span>
<span class="nt">cluster.name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">elk4logs-int</span>
</code></pre></div>
<p>Assurez-vous que ce nom est unique au sein de votre réseau, sinon vous pourriez vous retrouvez avec un serveur joignant le mauvais cluster.</p>
<h2 id="node-name">Node Name</h2>
<p>Chaque serveur Elasticsearch doit avoir un nom unique qui l'identifie au sein de son cluster. Vous pouvez, soit lui indiquer un nom , celui que vous souhaitez, soit juste mettre le hostname.</p>
<div class="highlight"><pre><span></span><code><span class="c1">#################################### Node #####################################</span>
<span class="nt">node.name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">${HOSTNAME}</span>
</code></pre></div>
<p>Si votre serveur/VM a déjà un nom identifiable, utilisez-le, c'est le plus simple.</p>
<h2 id="memory">Memory</h2>
<p>Elasticsearch est un logiciel écrit en Java, et il est donc important que votre JVM ne swappe pas. Pour cela, mettez à <code>true</code> le <code>memory_lock</code>.</p>
<div class="highlight"><pre><span></span><code><span class="c1">################################### Memory ####################################</span>
<span class="c1"># Elasticsearch performs poorly when JVM starts swapping: you should ensure that</span>
<span class="c1"># it _never_ swaps.</span>
<span class="c1">#</span>
<span class="c1"># Set this property to true to lock the memory:</span>
<span class="c1">#</span>
<span class="nt">bootstrap.memory_lock</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">true</span>
</code></pre></div>
<p><strong>Attention</strong> : Pour que ce paramètre fontionne correctement, il y a quelques <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-configuration-memory.html#mlockall" title="Configuration du système hôte pour profiter du JVM memory lock">manipulations</a> à effectuer sur votre système linux avant.</p>
<h2 id="network">Network</h2>
<p>Par défaut, Elasticsearch bind sur <code>127.0.0.1</code>. Il n'écoutera que les connexions locales. Pour se mettre en cluster, il faut pouvoir écouter sur le réseau.</p>
<div class="highlight"><pre><span></span><code><span class="c1">################################### Network ###################################</span>
<span class="nt">network.host</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">192.168.1.10</span>
</code></pre></div>
<p><strong>Attention</strong> : Ici se place un potentiel problème de sécurité. Soyez sûr que votre serveur Elasticsearch est bien protégé. Typiquement, il ne doit <strong>jamais</strong> écouter sur le WAN (internet). Elasticsearch, par défaut n'embarque pas de mécanisme d'authentification/autorisation d'accès. Il doit être protégé avec des règles pare-feu qui vont bien.</p>
<h2 id="discovery">Discovery</h2>
<p>Il faut indiquer à vos serveurs Elasticsearch au moins un autre serveur qui fera parti du même cluster que lui (dans le cas d'une installation en cluster). Cela se fait avec <code>discovery.zen.ping.unicast.hosts</code>.</p>
<p>Il n'est pas important d'y faire figurer tous les serveurs d'un même cluster. Typiquement, si vous montez un cluster de plusieurs dizaines ou centaines de nœuds, cela rendrait votre configuration horrible et inutile. Un seul suffit, mais si vous avez plus de 3 serveurs, indiquer ici 3 IP/hostname me semble pas mal.</p>
<p>Le second paramètre <code>discovery.zen.minimum_master_nodes</code> indique le nombre minimum de serveurs capable d'agir en tant que master qui doivent se voir pour commencer à former un cluster. Ce point est important pour éviter un <a href="https://fr.wikipedia.org/wiki/Split-brain" title="Split-Brain sur Wikipedia">split-brain</a> du cluster. plus d'informations sur <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-node.html#split-brain" title="Comment éviter le split-brain sur elastic.co">elastic.co</a>.</p>
<div class="highlight"><pre><span></span><code><span class="c1">################################## Discovery ##################################</span>
<span class="c1"># Pass an initial list of hosts to perform discovery when new node is started:</span>
<span class="c1">#</span>
<span class="nt">discovery.zen.ping.unicast.hosts</span><span class="p">:</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">192.168.1.10:9300</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">192.168.1.11</span><span class="w"> </span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">seeds.mydomain.com</span>
<span class="c1"># Prevent the "split brain" by configuring the majority of nodes (total number of nodes / 2 + 1):</span>
<span class="c1">#</span>
<span class="nt">discovery.zen.minimum_master_nodes</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">2</span>
</code></pre></div>
<h2 id="shards-replica">Shards & Replica</h2>
<p>Pour terminer, je vous recommande également de configurer un nombre de shards et de replica par défaut.
Ces valeurs peuvent être modifiées lors de la création d'une nouvelle "database" dans Elasticsearch, mais j'aime en mettre pour les cas où l'appli qui utilisera Elasticsearch ne le ferait pas elle-même.</p>
<p>Idéalement, le nombre de shards devrait être suppérieur ou égal au nombre total de nœuds de stockage prévu au final. Cela peut paraître compliqué/vague, mais je développerai cela dans un prochain blogpost.</p>
<div class="highlight"><pre><span></span><code><span class="c1">################################ Shards & Replica #############################</span>
<span class="nt">index.number_of_shards</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">12</span>
<span class="nt">index.number_of_replicas</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">1</span>
</code></pre></div>
<h2 id="exemple">Exemple</h2>
<p>Voici en example un fichier de configuration <code>elasticsearch.yml</code> complet que j'utilise sur un de mes clusters actuellement en prod (petit cluster de 12 nœuds utilisé pour le stockage de logs).</p>
<p>L'avantage de ce fichier de configuration est qu'il est le même pour tous mes serveurs propres au cluster <code>team_ELK</code>. Le seul paramètre unique à chaque serveur est <code>network.publish_host</code> que je change avec un petit <code>sed</code> à l'installation pour mettre l'IP locale de la machine. (Et cette configuration fonctionne aussi en docker ;-))</p>
<div class="highlight"><pre><span></span><code><span class="c1">#################################### Paths ####################################</span>
<span class="c1"># Path to directory where to store index data allocated for this node.</span>
<span class="c1">#</span>
<span class="nt">path.data</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">/mnt/data</span>
<span class="c1"># Path to temporary files:</span>
<span class="c1">#</span>
<span class="nt">path.work</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">/tmp/ELK-elasticsearch</span>
<span class="c1"># Path to log files:</span>
<span class="c1">#</span>
<span class="nt">path.logs</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">/srv/app/log</span>
<span class="c1"># Path to where plugins are installed:</span>
<span class="c1">#</span>
<span class="nt">path.plugins</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">/srv/app/src/plugins</span>
<span class="c1">################################### Cluster ###################################</span>
<span class="c1"># Cluster name identifies your cluster for auto-discovery. If you're running</span>
<span class="c1"># multiple clusters on the same network, make sure you're using unique names.</span>
<span class="c1">#</span>
<span class="nt">cluster.name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">team_ELK</span>
<span class="c1">################################### Memory ####################################</span>
<span class="c1"># Elasticsearch performs poorly when JVM starts swapping: you should ensure that</span>
<span class="c1"># it _never_ swaps.</span>
<span class="c1">#</span>
<span class="c1"># Set this property to true to lock the memory:</span>
<span class="c1">#</span>
<span class="nt">bootstrap.mlockall</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">true</span>
<span class="c1"># Make sure that the ES_MIN_MEM and ES_MAX_MEM environment variables are set</span>
<span class="c1"># to the same value, and that the machine has enough memory to allocate</span>
<span class="c1"># for Elasticsearch, leaving enough memory for the operating system itself.</span>
<span class="c1">#</span>
<span class="c1"># You should also make sure that the Elasticsearch process is allowed to lock</span>
<span class="c1"># the memory, eg. by using `ulimit -l unlimited`.</span>
<span class="c1">############################## Network And HTTP ###############################</span>
<span class="c1"># Set the bind address specifically (IPv4 or IPv6):</span>
<span class="c1">#</span>
<span class="nt">network.bind_host</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">0.0.0.0</span>
<span class="c1"># Set the address other nodes will use to communicate with this node. If not</span>
<span class="c1"># set, it is automatically derived. It must point to an actual IP address.</span>
<span class="c1">#</span>
<span class="nt">network.publish_host</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">192.168.6.1</span>
<span class="c1"># Set both 'bind_host' and 'publish_host':</span>
<span class="c1">#</span>
<span class="c1">#network.host: 192.168.0.1</span>
<span class="c1"># Set a custom port for the node to node communication (9300 by default):</span>
<span class="c1">#</span>
<span class="nt">transport.tcp.port</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">9300</span>
<span class="c1"># Set a custom port to listen for HTTP traffic:</span>
<span class="c1">#</span>
<span class="nt">http.port</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">8888</span>
<span class="c1"># --------------------------------- Discovery ----------------------------------</span>
<span class="c1">#</span>
<span class="c1"># Pass an initial list of hosts to perform discovery when new node is started:</span>
<span class="c1">#</span>
<span class="nt">discovery.zen.ping.unicast.hosts</span><span class="p">:</span><span class="w"> </span><span class="p p-Indicator">[</span><span class="s">"elkdb01.ma.superboite.net:9300"</span><span class="p p-Indicator">,</span><span class="w"> </span><span class="s">"elkdb02.ma.superboite.net:9300"</span><span class="p p-Indicator">,</span><span class="w"> </span><span class="s">"elkdb03.ma.superboite.net:9300"</span><span class="p p-Indicator">]</span>
<span class="c1">#</span>
<span class="c1"># Prevent the "split brain" by configuring the majority of nodes (total number of nodes / 2 + 1):</span>
<span class="c1">#</span>
<span class="nt">discovery.zen.minimum_master_nodes</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">7</span>
<span class="c1"># ------------------------------ Shards & Replica ------------------------------</span>
<span class="nt">index.number_of_shards</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">12</span>
<span class="nt">index.number_of_replicas</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">1</span>
<span class="nt">cluster.routing.allocation.enable</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">all</span>
</code></pre></div>Elasticsearch, comment l'installer2017-06-13T16:00:00+02:002017-06-27T19:00:00+02:00Etienne Magrotag:blog.etienne-magro.fr,2017-06-13:/elasticsearch-installation.html<p>Après avoir trouvé une raison de l'utiliser, intéressons-nous à l'installation d'Elasticsearch.</p><p>Elasticsearch est un logiciel écrit en java et peut sembler compliqué à installer. Détrompons-nous, c'est on ne peut plus simple.</p>
<p>Pour cela, je vous propose 2 manières d'installer elasticsearch : avec et sans Docker.</p>
<h1 id="installer-elasticsearch-avec-docker">Installer Elasticsearch avec Docker</h1>
<p>Je vais supposer que vous avez déjà <a href="https://fr.wikipedia.org/wiki/Docker_(logiciel)" title="Page Docker sur Wikipedia">Docker</a> d'installé sur votre machine. Autrement, je vous laisse regarder la <a href="https://store.docker.com/search?offering=community&type=edition">documentation officielle</a>.</p>
<p>Pour référence, voici le <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html" title="Guide officiel d'installation d'Elasticsearch en utilisant Docker sur elastic.co">guide officiel d'installation d'Elasticsearch en utilisant Docker</a>.</p>
<h2 id="installation-rapide-pour-tests">Installation rapide, pour tests</h2>
<div class="highlight"><pre><span></span><code>docker<span class="w"> </span>pull<span class="w"> </span>docker.elastic.co/elasticsearch/elasticsearch:5.4.1
docker<span class="w"> </span>run<span class="w"> </span>-p<span class="w"> </span><span class="m">9200</span>:9200<span class="w"> </span>-e<span class="w"> </span><span class="s2">"http.host=0.0.0.0"</span><span class="w"> </span>-e<span class="w"> </span><span class="s2">"transport.host=127.0.0.1"</span><span class="w"> </span>docker.elastic.co/elasticsearch/elasticsearch:5.4.1
</code></pre></div>
<p>Et voilà.
Vous avez maintenant un serveur elasticsearch pour test qui tourne sur votre machine.</p>
<h2 id="installation-propre">Installation propre</h2>
<h3 id="docker-officiel">Docker officiel</h3>
<p>Créez un fichier custom_elasticsearch.yml et montez-le dans le docker. Pareil, ne placez pas les fichier de DB dans le docker, montez plutôt un dossier qui les contiendra.</p>
<div class="highlight"><pre><span></span><code>docker<span class="w"> </span>run<span class="w"> </span>-p<span class="w"> </span><span class="m">9200</span>:9200<span class="w"> </span>-v<span class="w"> </span>full_path_to/custom_elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml<span class="w"> </span>-v<span class="w"> </span>/mnt/elasticsearchdata:/usr/share/elasticsearch/data<span class="w"> </span>docker.elastic.co/elasticsearch/elasticsearch:5.4.1
</code></pre></div>
<h3 id="custom-docker-heritant-du-docker-officiel">Custom docker héritant du docker officiel</h3>
<p>Créez un fichier <code>Dockerfile</code> comme suit :</p>
<div class="highlight"><pre><span></span><code><span class="k">FROM</span><span class="w"> </span><span class="s">docker.elastic.co/elasticsearch/elasticsearch:5.4.1</span>
<span class="k">ADD</span><span class="w"> </span>elasticsearch.yml<span class="w"> </span>/usr/share/elasticsearch/config/
<span class="k">USER</span><span class="w"> </span><span class="s">root</span>
<span class="k">RUN</span><span class="w"> </span>chown<span class="w"> </span>elasticsearch:elasticsearch<span class="w"> </span>config/elasticsearch.yml
<span class="k">USER</span><span class="w"> </span><span class="s">elasticsearch</span>
</code></pre></div>
<p>Puis exécutez-le :</p>
<div class="highlight"><pre><span></span><code>docker<span class="w"> </span>build<span class="w"> </span>--tag<span class="o">=</span>elasticsearch-custom<span class="w"> </span>.
docker<span class="w"> </span>run<span class="w"> </span>-ti<span class="w"> </span>-v<span class="w"> </span>/usr/share/elasticsearch/data<span class="w"> </span>elasticsearch-custom
</code></pre></div>
<p>Vous avez aussi la possibilité de créer vous-même votre <a href="https://docs.docker.com/engine/reference/builder/" title="Documentation de référence Dockerfile sur le site de Docker">Dockerfile</a>, en partant de <code>FROM ubuntu</code>, par exemple, mais je vous laisse le faire. Le moins compliqué sera de s'inspirer des l'installation sans docker et de mettre ça dans un <code>Dockerfile</code>.</p>
<h1 id="installer-elasticsearch-sans-docker">Installer Elasticsearch sans Docker</h1>
<p>Je vais supposer que la dernière version de Java est déjà installé sur votre serveur. Pour rappel, Elasticsearch recommande Java 8.</p>
<p>Pour référence, voici le <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/_installation.html" title="Guide officiel d'installation d'Elasticsearch sur elastic.co">guide officiel d'installation d'Elasticsearch</a>.</p>
<h2 id="installation-rapide-pour-tests_1">Installation rapide pour tests</h2>
<ol>
<li>
<p>Téléchargez la dernière version d'elasticsearch</p>
<div class="highlight"><pre><span></span><code>curl<span class="w"> </span>-L<span class="w"> </span>-O<span class="w"> </span>https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.4.1.tar.gz
</code></pre></div>
</li>
<li>
<p>Extrayez-le</p>
<div class="highlight"><pre><span></span><code>tar<span class="w"> </span>-xvf<span class="w"> </span>elasticsearch-5.4.1.tar.gz
</code></pre></div>
</li>
<li>
<p>Lancez le serveur Elasticsearch</p>
<div class="highlight"><pre><span></span><code><span class="nb">cd</span><span class="w"> </span>elasticsearch-5.4.1/bin<span class="w"> </span><span class="o">&&</span><span class="w"> </span>./elasticsearch
</code></pre></div>
</li>
</ol>
<h2 id="installation-propre_1">Installation propre</h2>
<p>Je ne couvrirais pas l'installation en utilisant les packages système (<code>.deb</code> et <code>.rpm</code>), uniquement l'installation "à la main" (qui pourra et devrait être automatisée) car elle permet de garder facilement la main sur les numéros de version du soft (ce qui peut être utile en prod).</p>
<p>Pour une installation prod-ready, nous verrons cela dans un autre blogpost, nous y verrons notamment les paramètres elasticsearch et système à vérifier.</p>
<p>En résumé :</p>
<div class="highlight"><pre><span></span><code><span class="nb">cd</span><span class="w"> </span>/opt
wget<span class="w"> </span>https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.4.1.tar.gz
curl<span class="w"> </span>https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.4.1.tar.gz.sha1
sha1sum<span class="w"> </span>elasticsearch-5.4.1.tar.gz<span class="w"> </span><span class="c1"># devrait renvoyer exactement la même chose que la ligne précédente</span>
tar<span class="w"> </span>-xzf<span class="w"> </span>elasticsearch-5.4.1.tar.gz
ln<span class="w"> </span>-s<span class="w"> </span>/opt/elasticsearch<span class="w"> </span>/opt/elasticsearch-5.4.1
<span class="nb">cd</span><span class="w"> </span>elasticsearch
</code></pre></div>
<p>Créez votre propre elasticsearch.yml ou modifiez celui présent dans <code>/opt/elasticsearch/config/elasticsearch.yml</code>. (Pour plus d'information sur ce fichier de configuration reportez-vous au post <a href="https://blog.etienne-magro.fr/elasticsearch-configuration.html" title="Comment configurer de manière basique, mais utilisable votre cluster Elasticsearch">elasticsearch_conf</a>).</p>
<p>Puis, récupérez le fichier de service systemd ici : <a href="https://github.com/elastic/elasticsearch/blob/master/distribution/src/main/packaging/systemd/elasticsearch.service">https://github.com/elastic/elasticsearch/blob/master/distribution/src/main/packaging/systemd/elasticsearch.service</a> et installez-le :</p>
<div class="highlight"><pre><span></span><code>wget<span class="w"> </span>-O<span class="w"> </span>/etc/systemd/system/elasticsearch.service<span class="w"> </span>https://github.com/elastic/elasticsearch/raw/master/distribution/src/main/packaging/systemd/elasticsearch.service
</code></pre></div>
<p>Vous pouvez maintenant démarrer/arrêter Elasticsearch comme un service système.</p>Elasticsearch, Pourquoi l'utiliser2017-06-10T12:00:00+02:002017-06-10T12:00:00+02:00Etienne Magrotag:blog.etienne-magro.fr,2017-06-10:/elasticsearch-usage.html<p>Elasticsearch est une base de donnée NoSQL, OK, mais dans quel cas l'utilise-t-on ?</p><p>Nous avons vu précédemment qu'<a href="https://fr.wikipedia.org/wiki/Elasticsearch" title="Elasticsearch sur Wikipedia">Elasticsearch</a> était un serveur de base de donnée NoSQL et en quoi cela le distinguait des serveur de BDD <a href="https://fr.wikipedia.org/wiki/Structured_Query_Language" title="SQL on Wikipedia">SQL</a> habituels.</p>
<p>Mais maintenant, quand faut-il l'utiliser, dans quels cas faut-il l'utiliser ?</p>
<p>On distingue 2 grandes usages principaux à Elasticsearch : le stockage spécialisé de logs et l'usage en base de donnée "normale".</p>
<h1 id="le-stockage-de-logs">Le stockage de logs</h1>
<h2 id="le-reve-des-administrateurs-systeme-et-devops">Le rêve des administrateurs système et DevOps</h2>
<p>Je ne vous ferais pas l'affront de vous expliquer ce que sont des <a href="https://fr.wikipedia.org/wiki/Historique_(informatique)" title="Logs sur Wikipedia">logs</a>. Par contre, quand vous avez quelques dizaines/centaines/milliers de serveurs dont vous vous occupez et que vous devez investiguer pourquoi tel grappe de serveur est partie en caraffe, c'est vite fastidieux de parcourir tous les logs systèmes de toutes les machines.</p>
<p>La seule solution vraiment intéressante, c'est de centraliser. Tous vos logs.</p>
<p>Et si en plus, on pouvait chercher facilement dedans et faire de la corrélation, ça serait le top.</p>
<p>Bienvenue dans le monde merveilleux d'elasticsearch et de ses amis filebeat, <a href="https://www.elastic.co/products/logstash" title="Page officielle du produit logstash">logstash</a> et kibana.</p>
<h2 id="le-but-vers-lequel-tendre-pour-les-dev">Le but vers lequel tendre pour les dev</h2>
<p>Un bon soft est toujours accompagné de bons logs applicatifs. Si votre super appli web ultra-responsive écrite en <a href="https://golang.org/">go</a>+<a href="https://facebook.github.io/react/">react</a> ne produit pas de logs, correctement formatés, significatifs, etc... ce n'est pas un bon soft.</p>
<p>Ok, les logs, c'est pas sexy, mais pourtant, ça sauve la vie quand tout plante.</p>
<p>Vous pouvez faire simple : tout envoyer dans un fichier, sans formatage, et c'est l'adminsys qui s'occupera du reste. Mais vous pouvez (et c'est très fortement conseillé) convenir d'un format de log facilement parsable qui sera plus facilement utilisable. Le top, étant de les envoyer directement dans un elasticsearch.</p>
<p>Mais qu'est-ce que les dev en retire ? De <a href="https://www.google.fr/search?q=kibana&tbm=isch" title="Recherche Kibana sur Google Image">belles métriques</a> à présenter aux managers/commerciaux, des débogages (post-mortem ou pas) à coups de requêtes simili-SQL. Le tout dans une super <a href="https://www.elastic.co/products/kibana" title="Kibana sur elastic.co">interface</a> ou une <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-get.html">API simple</a> (<a href="https://fr.wikipedia.org/wiki/Representational_state_transfer" title="REST sur Wikipedia">REST</a>).</p>
<p><img alt="Interface de kibana" src="https://static-www.elastic.co/assets/blt375759845321ddbb/kibana-screenshot-aspot-v2.jpg?q=346">
<img alt="Interface de kibana" src="https://static-www.elastic.co/assets/bltcc54f0658d68183a/reporting_no_zoom-optimized.gif?q=346"></p>
<h1 id="la-base-de-donnee-normale">La base de donnée "normale"</h1>
<p>Sur ce point, je vais vous faire pointer vers le graph fait par <a href="https://thehftguy.com/2017/03/27/sql-vs-nosql-whats-the-best-database-and-when/" title="Blogpost sur le choix d'un système de base de donnée">The HFT guy</a> pour un résumé très visuel.</p>
<h2 id="contrainte-forte-sur-le-temps-de-reponse">Contrainte forte sur le temps de réponse</h2>
<p>Elasticsearch a été construit pour répondre à n'importe quelle requête très vite. Quelque soit la requête. Par défaut, tout est analysé et indexé pour permettre des temps de réponse très bas. Par exemple, sur un de mes systèmes en production actuellement, quelque soit la requête, elle est répondu en un temps moyen de 20ms, et un maximum de 200ms (et un temps median à 10ms).</p>
<p><a href="https://www.elastic.co/products/elasticsearch" title="Page officielle du produit Elasticsearch">Elasticsearch</a> a été codé pour la vitesse. Et il va vraiment vite.</p>
<h2 id="scalabilite">Scalabilité</h2>
<p>Ce système de base de donnée peut tourner en test sur votre ordinateur portable, en intégration sur un petit cluster de 2 noeuds et en production sur un cluster de dizaines ou centaines de serveurs.</p>
<p>Il peut stocker plusieurs peta-octets de données, tout en garantissant les temps de réponse aux requêtes, la facilité d'accès à vos données.</p>
<p>Et ce qu'il y a de beau dans tout cela, c'est la facilité que vous aurez à augmenté la capacité de stockage de votre base de donnée : installez Elasticsearch sur un nouveau serveur, modifiez un seul paramètre dans son fichier de configuration, et voilà. Votre capacité a été augmentée.</p>
<p>Cela vous permettra de gagner en capacité aisément, même si vous ne l'aviez pas planifié au départ.</p>
<h2 id="bonus">Bonus</h2>
<p>En complément, viennent nativement avec Elasticsearch :</p>
<ul>
<li>Full-text search</li>
<li>Facilité d'installation et de maintenance</li>
</ul>Elasticsearch, qu'est-ce que c'est2017-06-08T19:00:00+02:002017-06-08T19:00:00+02:00Etienne Magrotag:blog.etienne-magro.fr,2017-06-08:/elasticsearch-whatisit.html<p>Elasticsearch est une technologie à la mode, avec de belles promesses, mais concrètement, c'est quoi ?</p><p>Elasticsearch est une technologie à la mode, avec de belles promesses, mais concrètement, c'est quoi ?</p>
<p>En résumé, <a href="https://fr.wikipedia.org/wiki/Elasticsearch" title="Elasticsearch sur Wikipedia">Elasticsearch</a> est un système de base de donnée NoSQL hautement distribué et scallable.</p>
<h1 id="nosql">NoSQL</h1>
<h2 id="serveurs-sql-traditionnels">Serveurs SQL traditionnels</h2>
<p>Lorsqu'une application interroge un serveur de base de donnée, généralement, il s'agit de requêtes SQL envoyées à des serveurs Oracle, MySQL/MariaDB ou PostgreSQL pour les plus connus.
Cela impose quelques contraintes : </p>
<ul>
<li>Utilisation d'un language additionnel, le <a href="https://fr.wikipedia.org/wiki/Structured_Query_Language" title="SQL on Wikipedia">SQL</a></li>
<li>Définition d'un schéma figé <code>CREATE TABLE ma_table(id INT, colonne1 TEXT, colonne2 INT);</code>, aux changements compliqués</li>
<li>Installation/Import des drivers propres au serveur de BDD</li>
<li>Utilisation d'un unique serveur non-scalable et non-distribué (même si cela devient tout doucement possible, ça reste réellement compliqué)</li>
</ul>
<h2 id="elasticsearch">Elasticsearch</h2>
<p>De son côté, <a href="https://www.elastic.co/products/elasticsearch" title="Elasticsearch on elastic.co">Elasticsearch</a> permet d'enlever certaines de ces contraintes :</p>
<ul>
<li>Utilisation de requêtes <a href="https://fr.wikipedia.org/wiki/Representational_state_transfer" title="REST sur Wikipedia">REST</a> (Json over HTTP)</li>
<li>Le schéma n'est pas figé, et l'ajout d'un champ se fait à la volée</li>
<li>Pas besoin de drivers puisque que le protocol est vraiment standard (même votre navigateur internet peut le faire)</li>
<li>Elasticsearch a été pensé dès le début pour être distribué et scallabe</li>
</ul>
<h2 id="inconvenients">Inconvénients</h2>
<p>D'un autre côté, tout n'est pas rose pour elasticsearch, loin de là. Chaque système a ses avantages et ses inconvénients. Typiquement, Elasticsearch est un gouffre à ressource (disqur dur/RAM) et ne pas avoir de schéma figé ne veut pas dire qu'il ne faut pas avoir de schéma tout court. Au contraire. Elasticsearch sera d'autant plus utilisable et performant que votre base de donnée aura un schéma.</p>
<h1 id="hautement-distribue-et-scallable">Hautement distribué et scallable</h1>
<h2 id="linteret-davoir-sa-bdd-eclatee-sur-plusieurs-serveurs">L'intérêt d'avoir sa BDD éclatée sur plusieurs serveurs</h2>
<p>Un système est dit "distribué" quand il ne réside pas sur un seul serveur, mais plusieurs. </p>
<p>Par exemple, MySQL est un système qui ne peut être installé que sur un seul serveur. Si vous installé un 2e serveur MySQL, il vous sera impossible d'accéder aux données présentes sur le 1e serveur en l'interrogeant. (En vrai, c'est possible, mais après moults efforts fait par un/des experts).</p>
<p>Dans le cas d'Elasticsearch, installez-en un, puis un deuxième, puis un centième si vous le voulez, ils partageront leurs données que vous pourrez récupérer en interrogeant le serveur de votre choix.</p>
<h2 id="comment-vont-grossir-vos-donnees">Comment vont grossir vos données</h2>
<p>Admettons que d'ici quelques semaines ou mois vos données auront doublé de volume. Et que la semaine prochaine vous aurez atteind les limites de capacité de votre serveur.</p>
<p>Comment cela se passe pour un serveur traditionnel SQL ? Facile, vous achetez un autre serveur plus gros (ou vous demander un nouvelle VM plus grosse), et vous déplacez les données dessus. Cela peut prendre de quelques heures à quelques semaines pour mener à bien cette opération, en fonction de la taille de vos données, des applications les utilisant, des administrateurs, etc...</p>
<p>Et pour Elasticsearch, me direz-vous ? Demandez un nouveau serveur ou une nouvelle VM, mais au lieu de migrer les données dessus, vous l'ajoutez au groupe de serveur. Les serveurs vont alors se répartir les données équitablement, comme si vous aviez ajouté à chaud du disque, de la RAM et du CPU sur un serveur, et vous voilà reparti pour un tour. Cela prend entre 5 minutes et une demi-journée. Et sans downtime. Aucun.</p>
<h1 id="usage">Usage</h1>
<p>Attention. Elasticsearch, que nous allons étudier dans cette série de post, a beau être une technologie merveilleuse, il n'en faut pas pour autant remiser vos serveurs SQL.</p>
<p>Ce sont 2 technologies différentes qui n'adressent pas les même usages.</p>
<h2 id="relationnelle">Relationnelle</h2>
<p>Les jointures sont propres au système SQL et n'existent pas dans le monde NoSQL. Ou sont bien moins performante.</p>
<p>Typiquement, les relations qu'il peut y avoir entre enregistrements (on parle souvent de base de donnée relationnelle) n'existent qu'en SQL. Si cela est important pour vous ou votre application, passez votre chemin. Ou changez votre manière de penser et votre application de fond en comble.</p>
<h2 id="grossissement-dans-le-temps">Grossissement dans le temps</h2>
<p>Si vous savez que vous n'allez pas avoir plus de données dans le futur, ou que le grossissement se fera à un taux dérissoir, prenez une base de donnée SQL. Dans le cas contraire, regardez les système NoSQL de près, cela pourrait vous sauvez la vie dans un futur pas si lointain.</p>
<p>Au final, je vous renvoie vers cet excellent post <a href="https://thehftguy.com/2017/03/27/sql-vs-nosql-whats-the-best-database-and-when/" title="Blogpost sur le choix d'un système de base de donnée">thehftguyblogpost</a>, en particulier le graphique de décision qui y est attaché.
<img alt="Choix SQL ou NoSQL" src="https://thehftguy.files.wordpress.com/2017/03/databases-flowchart.png?w=809" title="Database flowchart from thehftguy.com">
(<a href="https://thehftguy.files.wordpress.com/2017/03/databases-flowchart.png" title="Database flowchart from thehftguy.com">voir en plus grand</a>)</p>Analysez l'information !2016-01-28T19:00:00+01:002016-01-28T19:00:00+01:00Etienne Magrotag:blog.etienne-magro.fr,2016-01-28:/analysez-linformation.html<p>Et si au lieu d'être passif face à l'information de l'actualité, vous preniez le temps de regarder le contexte, d'avoir du recul et comprendre ce qu'il arrive ? C'est maintenant faisable grâce à l'apparition d'une nouvelle rubrique : "Analyse de l'actualité", et du premier blog d'analyse dedans : Crashdebug.fr.</p><p><em>Suite à la disparition du blog de What.IsUp, les blogposts ont été déplacés ici pour ne pas les perdre.</em></p>
<p>What.IsUp est heureux de vous annoncer une nouvelle rubrique sur notre site : "<a href="https://what.isup.ws/fr/articles/analyse">Analyse de l'actualité</a>", et avec elle arrive un premier blog dont c'est la thématique : <a href="https://what.isup.ws/fr/journal/Crashdebug.fr" title="Crashdebug sur What.IsUp">Crashdebug.fr</a>.</p>
<h1 id="reflechissons">Réfléchissons</h1>
<h2 id="passivite-de-linformation">Passivité de l'information</h2>
<p>Quand on regarde le journal télévisé, on avale de l'information pré-machée. "Avalez, voici la vérité, il n'y a rien à penser d'autre". Le format même de la télévision nous empêche de prendre du recul, d'analyser ce qui est dit, de croiser les sources et de se faire une opinion propre.</p>
<h2 id="croiser-les-effluves">Croiser les effluves</h2>
<p><a href="https://what.isup.ws/">What.isup</a> a été créé dans l'idée de croiser les informations, de ne plus ingurgiter l'actualité telle quelle, mais de naviguer entre différents thèmes, différentes sources pour justement reprendre le pouvoir sur notre opinion, sur notre manière de consommer l'information</p>
<h1 id="blog-danalyse">Blog d'analyse</h1>
<h2 id="nouvelle-rubrique">Nouvelle rubrique</h2>
<p>Dans cet optique, nous avons le plaisir d'ouvrir une nouvelle page sur What.isup qui regroupera divers blogs et sites d'actualité décodée : "<a href="https://what.isup.ws/fr/articles/analyse">Analyse de l'actualité</a>"</p>
<h2 id="premier-blog">Premier blog</h2>
<p>Et c'est donc avec une grande joie que nous accueillons le premier blog de cette catégorie : <a href="https://what.isup.ws/fr/journal/Crashdebug.fr" title="Crashdebug sur What.IsUp">Crashdebug.fr</a>. Avec un nouvel article par jour, il nous permet de décrypter l'actualité et de mieux comprendre le monde dans lequel nous vivons.</p>
<h1 id="la-suite">La suite</h1>
<p>Il a été vite rejoint par d'autres media : <a href="https://what.isup.ws/fr/journal/AgoraVox" title="AgoraVox sur What.IsUp">AgoraVox</a>, <a href="https://what.isup.ws/fr/journal/Reflets" title="Reflets.info sur What.IsUp">Reflets.info</a>, et bien d'autres.</p>Penser l'information autrement2016-01-04T19:00:00+01:002016-01-04T19:00:00+01:00Etienne Magrotag:blog.etienne-magro.fr,2016-01-04:/penser-linformation-autrement.html<p>Et si cette année était pour vous l'occasion de vous informer comme VOUS l'entendez ?</p><p><em>Suite à la disparition du blog de What.IsUp, les blogposts ont été déplacés ici pour ne pas les perdre.</em></p>
<h1 id="les-infos">"Les infos"</h1>
<p>Quand on pense informations, actualités, On pense au journal télévisé que l'on regarde le soir chez soi. Là, on a le choix entre le journal de 20h sur la chaîne 1 ou la 2, ou celui de la 3 ou la 4, etc... Mais pas tous en même temps. Comment était-ce du temps des journaux papiers ?</p>
<p>Facile :
<img alt="Kiosque à journaux" src="http://www.humanite.fr/sites/default/files/styles/abonnez_vous/public/images/presse_0.jpg"></p>
<p>Vous avez le choix entre une multitude de journaux. Ils parlent en partie de la même chose, avec parfois des points de vue différents, ou sont des copier-coller les uns des autres, et dans une autre mesure, ils ont des sujets qu'ils ne partagent pas, mais qui peuvent se trouver être moins intéressants.</p>
<h2 id="en-un-coup-doeil">En un coup d'oeil</h2>
<p>Si vous voulez faire un rapide tour de toute l'actualité, d'un coup d'oeil, qu'avez-vous ? Google News ou MSN actu ou Yahoo actualité. Ou alors une myriade de flux RSS que vous avez collecté en suant eau et sang sur chaque site internet qui vous intéresse.</p>
<h2 id="pour-creuser-un-sujet">Pour creuser un sujet</h2>
<p>Si vous voulez approfondir un sujet d'actualité qui vous tient à coeur ou croiser les sources, comment faites-vous ? Vous avez le choix : vous ne le faites pas parce que vous regarder le journal de 20h, ou vous faites une recherche sur votre moteur de recherche préféré ou alors vous allez voir un par un chaque site internet que vous avez répertorié et cherchez dessus.</p>
<h1 id="whatisup">What.isup</h1>
<p>Et si je vous proposais un outil qui vous facilite tout ça. Oui, tout ça. C'est à dire un endroit où l'information est disponible d'un coup d'oeil, mais où vous avez la possibilité de comparer différentes sources.</p>
<h2 id="lactu-en-10-secondes">L'actu en 10 secondes</h2>
<p>Être au courant de l'actualité entre deux rendez-vous est possible si l'information est triée, condensée et correctement présentée. 10 secondes est le temps qu'il vous faudra pour voir l'ensemble des sujets d'actualité les plus importants.</p>
<h2 id="lactu-en-1-heure">L'actu en 1 heure</h2>
<p>La possibilité de croiser les sources, de choisir ce qui vous intéresse vraiment et d'aller plus loin ne vous est cependant pas enlevée. Je dirais même qu'elle est encouragée. Vous vous ferez une bien meilleure idée d'un sujet si la lecture de plusieurs sources est facilitée.</p>
<h1 id="loutil">L'outil</h1>
<p>Après quelques semaines de développement, après quelques semaines de test et de nourrissage de la DB, je suis fier de vous présenter <a href="https://what.isup.ws/" title="Retrouvez toute l'actualité dans un endroit unique et convivial">What.IsUp</a>.</p>
<p>Aujourd'hui, 99 sources d'information différentes, 6000 nouveaux articles d'actualité enregistrés par jour, plus de 7000 articles analysés toutes les heures. Pour vous présenter l'actualité importante en première page et ouvrir le champ des possibilités à portée de clic. Et demain, toujours plus. </p>
<p>Il répond au cahier des charges et ne croule pas sous les pub. Pourquoi ne l'utilisez-vous pas encore ? ;-)</p>