Le blog tech de Nicolas Steinmetz (Time Series, IoT, Web, Ops, Data)
Quelques liens récupérés des différentes conférences auxquelles j’ai pu assister.
Et pour finir, mes propres slides sur la plateforme TICK & Grafana pour collecter et exploiter vos données temporelles. J’ai également eu l’opportunité de refaire cette conférence chez Les Furets (où je suis en mission actuellement). Je vais aussi la faire le 3 Mai prochain au NantesJUG.
Bonus : la vidéo est disponible
Docker, lorsqu’il exécute un container, injecte normalement les serveurs DNS de la machine hôte. Sauf que quand il voit que c’est une IP locale (127.0.0.*), alors il met les DNS de Google (8.8.8.8
et 8.8.4.4
) comme DNS. Typiqyement, Ubuntu utilise un résolveur DNS local via systemd-resolved pour la résolution DNS. Du coup, la configuration de /etc/resolv.conf
pointe sur 127.0.0.*
. Or sur un réseau local, les accès à un DNS public peuvent être filtrés. Dès lors, votre conteneur est incapable de faire une quelconque résolution DNS (interne ou externe à votre réseau).
La solution consiste à mettre en place un “drop-in” systemd
Source :
Nous voulons surcharger le fichier de type unit docker.service
et surcharger plus précisément la valeur de ExectStart
.
Etape 1 : Créer un répertoire /etc/systemd/system/docker.service.d
sudo mkdir -p /etc/systemd/system/docker.service.d
Etape 2 : Créer un fichier de configuration contenant la surcharge :
sudo $EDITOR /etc/systemd/system/docker.service.d/10-dns.conf
Contenu du fichier :
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H fd:// --dns 192.168.2.1 --dns 192.168.2.2
Le “doublon” sur ExecStart
est normal, cela permet de réinitialiser la commande pour en définir un autre ensuite.
Il ne reste plus qu’à rechercher la configuration de systemd et relancer le service docker :
sudo systemctl daemon-reload
sudo systemctl restart docker
Docker utilisera donc ces DNS lors du lancement d’un conteneur et cela évitera qu’il fasse un fallback sur les DNS de Google quand il voit un resolver local…
Par contre, si vous utilisez docker dans un autre contexte que celui du réseau de votre entreprise, cela peut ne plus fonctionner (ou alors il faut enrichir la configuration DNS pour rajouter d’autres DNS)
Une autre solution peut être de renseigner les DNS dans /etc/docker/daemon.json
(documentation) avec les mêmes inconvénients.
Au-delà de cet exemple appliqué à Docker, l’utilisation des “drop-ins” systemd est une solution intéressante pour surcharger une configuration sans impacter les fichiers sources et évite tout conflit lors d’une mise à jour du fichier.
*.domaine.fr
). Si je m’étais réjoui de l’idée au début, je ne vois finalement pas ou peu l’intérêt du fait de la méthode de validation (enregistrement DNS avec le temps de propagation associé). En dehors du cas où l’on dépassait les limites d’enregistrement de Let’s Encrypt en terme de nombre de certificats, la génération dynmique et unitaire via une méthode HTTP me semble plus simple. Surtout quand on utilise Traefik ;-)J’utilise Ansible dans une logique d’IAC et pour automatiser un maximum des actions pour la gestion de l’infrastructure et des applications de mes clients. Toutefois, chaque client avait son arborescence et la réutilisation d’un composant d’un client à l’autre était fastidieuse (copier/coller).
Un premier travail a consisté à extraire les rôles commun dans un dépôt git tiers et faire des liens symboliques mais cela n’était pas très pratique. Suite à un travail chez un autre client, j’ai revu mon approche et je pars pour le moment sur la solution suivante :
<composant>.<client>
ou <client>.<composant>
(le choix n’est pas encore arrêté). Le second répertoire contenant les éléments spécifiques au client.Exemple avec un rôle MariaDB :
mariadb/
├── README.md
├── defaults
│ └── main.yml
├── files
├── handlers
│ └── main.yml
├── meta
├── tasks
│ └── main.yml
├── templates
│ └── my.cnf.j2
└── vars
└── main.yml
co.mariadb/
├── README.md
├── handlers
│ └── main.yml
├── tasks
│ └── main.yml
├── templates
│ ├── my-primary.cnf.j2
│ └── my-replica.cnf.j2
Ainsi, la partie générique et la partie spécifique à mon client sont isolées. Il faut juste envisager le séquencement entre les deux rôles pour que cela se passe bien. Pour le moment, le seul code dupliqué se retrouve au niveau des handlers.
Si je devais donner accès à mes clients à leurs playbooks respectifs, il faudrait que je revois l’organisation pour ne leur donner accès qu’à leurs données. Il faudrait alors recréeer n dépots mais avec cette méthode, il est aussi facile de reconstruire a posteriori des dépots par clients depuis un dépot global. L’intérêt étant pour moi de conserver ma flexibilité et d’améliorer la réutilisabilité de mes composants.
git --force
est déconseillée si ce n’est proscrite, sa variante git --force-with-lease
est plus intéressante et permet d’éviter d’écraser le travail de vos camarades alors que vous pensiez juste faire un push en force sur une branche distante suite à un rebase local.Lorsque l’on déploie une même application dans plusieurs contextes via docker-compose
, il est intéressant d’utiliser le COMPOSE_PROJECT_NAME qui permet de donner un préfixe à vos réseaux et containers docker a minima.
L’inconvénient est qu’il faut ajouter à vos commandes un -p <project_name>
:
docker-compose -p instancea build --pull
docker-compose -p instancea up -d
docker-compose -p instancea logs -f
docker-compose -p instancea stop <service>
docker-compose -p instancea down
...
Ainsi, vos conteneurs seront nommés instancea_<service name>_<occurence>
et votre réseau instancea_<network name>
.
Mais il est possible d’aller plus loin avec les fichiers d’environnement .env
.
Dans votre fichier .env
à la racine de votre dossier où se trouve votre fichier docker-compose.yml
, définissez la/les variable(s) dont vous avez besoin. Ici, nous allons nous limiter à COMPOSE_PROJET_NAME
mais ne vous privez pas.
COMPOSE_PROJECT_NAME=instancea
A partir de ce moment-là, plus besoin de précier l’argument -p <project name>
, vos commandes redeviennent :
docker-compose build --pull
docker-compose up -d
docker-compose logs -f
docker-compose stop <service>
docker-compose down
...
… et pour autant, vos réseaux et containers ont le bon préfix car le fichier .env
est lu à l’exécution de la commande docker-compose
avant de parser docker-compose.yml
.
On peut aller encore plus loin en utilisant ce COMPOSE_PROJECT_NAME
dans le taggage des images d’un container par ex ou
version: '3'
services:
nginx:
build:
context: ./nginx/
image: "registry.mycompany.com/nginx:${COMPOSE_PROJECT_NAME}"
Lors de la phase de build, l’image sera tagguée avec le nom passé au projet compose. Ensuite, vous pouvez poussez sur la registry de votre entreprise puis déployer cette version sur votre cluster Swarm par ex.
A noter justement une limitation actuelle de docker stack deploy <stack name> -c docker-compose.yml
qui ne lit pas le fichier .env
en amont et donc COMPOSE_PROJECT_NAME
reste vide lors de la lecture du fichier docker-compose.yml
.
Une solution possible est par ex dans le script (simplifié) de déploiement :
cd $BUILDDIR/compose/
source .env
# Remplace la variable COMPOSE_PROJECT_NAME par sa valeur
sed -i -e "s/\${COMPOSE_PROJECT_NAME}/${COMPOSE_PROJECT_NAME}/g" docker-compose.yml
docker stack deploy ${COMPOSE_PROJECT_NAME} -c docker-compose.yml
Et voilà !
mysql_secure_installation
, voici enfin la réponse. Sur les nouvelles installations, l’accès au compte root de mariadb est protégé. Il faut passer par sudo mysql -u root -p ...
par ex pour pouvoir se connecter en root.TLS-SNI
. Avec le partage d’une même IP, il serait possible pour une personne malveillante de récupérer un certificat pour votre domaine à votre insu. Let’s Encrypt a finalement décidé de désactiver complètement les méthodes TLS-SNI-001
et TLS-SNI-002
et de travailler sur une nouvelle méthode TLS-SNI-003
qui corrigerait ce problème. En attendant, il faut utiliser les méthodes HTTP-01
ou DNS-01
.La commande docker history
(ou docker image history
) permet de voir le nombre de layers d’une image, la taille de ces layers et leur âge. Exemple avec l’image nginx:stable
:
docker image history nginx:stable
IMAGE CREATED CREATED BY SIZE COMMENT
dfe062ee1dc8 6 weeks ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon… 0B
<missing> 6 weeks ago /bin/sh -c #(nop) STOPSIGNAL [SIGTERM] 0B
<missing> 6 weeks ago /bin/sh -c #(nop) EXPOSE 80/tcp 0B
<missing> 6 weeks ago /bin/sh -c ln -sf /dev/stdout /var/log/nginx… 22B
<missing> 6 weeks ago /bin/sh -c set -x && apt-get update && apt… 53.1MB
<missing> 6 weeks ago /bin/sh -c #(nop) ENV NJS_VERSION=1.12.2.0.… 0B
<missing> 6 weeks ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.12.2-… 0B
<missing> 6 weeks ago /bin/sh -c #(nop) LABEL maintainer=NGINX Do… 0B
<missing> 6 weeks ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 6 weeks ago /bin/sh -c #(nop) ADD file:f30a8b5b7cdc9ba33… 55.3MB
Pour avoir l’intégralité du contenu de la ligne “CREATED BY”, il suffit de rajouter un --no-trunc
.
Source : Docker - history
C’est assez pratique lorsque l’on cherche notamment à comprendre et optimiser la taille de ses images Docker.
Il fut un temps où pour programmer le lancement des actions, notre meilleur ami était “cron” et la “crontab”. Outre la syntaxe et l’organisation de la crontab peu mémorisable, les tâches cron restaient complexes à déployer et à monitorer dans leur exécution. Si le fait d’avoir des répertoires plus génériques comme /etc/cron.{d,hourly,daily,weekly,monthly}
ont un peu amélioré la partie exploitabilité/déployabilité, ils offraient assez peu de souplesses pour une plannification fine des tâches. De nombreux serveurs ont donc eu un mix de crontab classique et de ce format pendant des années.
Debian 9.0 (mais d’autres aussi comme Archlinux par ex) a fait le choix dans le cadre de son adoption de systemd de ne plus proposer un logiciel de type cron mais de s’appuyer sur l’implémentation interne à systemd, à savoir les “timers”. C’est une unité (unit) spéciale de systemd qui est controllée et supervisée par systemd et qui s’exécute selon une configuration temporelle donnée.
Voyons comment passer de cette crontab :
0 4 * * * /path/to/scripts/backup.sh >> /var/log/backup.log 2>&1
… ou de /etc/cron.daily/mybackup.sh
appelant le même script à son équivalent sous un timer systemd.
Si vous gérez déjà vos services via systemd, vous avez déjà utilisé des “unit” systemd de type “service”. Ces “unit” permettent de définir un process et son mode d’éxécution.
Pour implémenter un “timer” sous systemd, il va nous falloir un fichier “service”.
Pour notre tâche à planifier, nous allons avoir au final 3 fichiers :
/path/to/scripts/backup.sh
; il est inchangé par rapport à précédemment.A noter que par convention, les fichiers service et timer doivent avoir le même nom (foo.service
, foo.timer
).
Pour le fichier service, une base simple est la suivante :
/etc/systemd/system/mybackup.service
:
[Unit]
Description=My Daily Backup
[Service]
Type=simple
ExecStart=/path/to/scripts/backup.sh
StandardError=journal
Je fournis une description à mon service, indique que c’est un process de type simple, le chemin vers mon script et je rajoute que le flux d’erreur est envoyé dans le journal. Pour plus d’information, se reporter à la doc des services et des exécutions.
Attention néanmoins, il ne faut pas de section [Install]
car le script va être piloté par le fichier timer.
Ensuite, il nous faut un fichier “timer” :
/etc/systemd/system/mybackup.timer
:
[Unit]
Description=My Daily Backup
[Timer]
# Define a calendar event (see `man systemd.time`)
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
Au-delà de la description, les lignes importantes sont la section [Timer]
et [Install]
:
OnCalendar
permet d’indiquer l’occurrence et la fréquence d’exécution du script. Il y a les abréviations classiques (daily, weekly, etc) mais vous pouvez avoir des choses plus complexes comme "Mon,Tue *-*-01..04 12:00:00"
- voir systemd.timePersistent
va forcer l’exécution du script si la dernière exécution a été manquée suite à un reboot de serveur ou autre événement.Il ne reste plus qu’à activer le timer et le démarrer :
systemctl enable mybackup.timer
systemctl start mybackup.timer
Pour voir la liste des “timers” actifs sur votre serveur, la date de leur dernière et prochaine exécution :
systemctl list-timers
NEXT LEFT LAST PASSED UNIT ACTIVATES
Mon 2018-01-29 17:25:59 CET 2h 27min left Sun 2018-01-28 22:20:35 CET 16h ago apt-daily.timer apt-daily.service
Tue 2018-01-30 06:36:33 CET 15h left Mon 2018-01-29 06:37:35 CET 8h ago apt-daily-upgrade.timer apt-daily-upgrade.service
Tue 2018-01-30 09:25:48 CET 18h left Mon 2018-01-29 09:25:48 CET 5h 33min ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
et accéder aux logs de vos “timers” :
journalctl -f -u apt-daily
-- Logs begin at Tue 2018-01-23 13:50:12 CET. --
Jan 28 22:20:35 codb2d9 systemd[1]: Starting Daily apt download activities...
Jan 28 22:20:35 codb2d9 systemd[1]: Started Daily apt download activities.
En cas de modification du .timer
ou du .service
, ne pas oublier de faire un system daemon-reload
pour que la version actualisée de vos fichiers soit prise en compte par systemd.
Passé la petite prise en main de ce nouveau cadre d’exécution, elle me semble plus simple et surtout plus facile à intégrer dans des chaines de déploiements pilotées par Ansible ou autre.