Toute l'actualité devOps dans le média Bruno Levasseur
Uptime Kuma
Uptime Kuma est un outil de monitoring léger pour vos services hébergés. Couplé à un service de messages comme Gotify ou Ntfy, il permet aussi de recevoir des notifications push sur smartphone en cas de défaillance. Il fournit également des fonctionnalités de page de status et le taux de disponibilité des services. Installation Cette installation repose sur une infra Docker / Traefik existante: voir cet exemple de déploiement Créer un utilisateur système dédié pour l’exécution du processus dans le conteneur Docker 1sudo adduser --system --shell /usr/sbin/nologin --group --disabled-password --home /home/kuma kuma 2 3Adding system user `kuma' (UID 112) ... 4Adding new group `kuma' (GID 120) ... 5Adding new user `kuma' (UID 112) with group `kuma' ... 6Creating home directory `/home/kuma' ... Créer une arborescence dédiée 1sudo mkdir /opt/uptime-kuma && cd $_ 2sudo touch docker-compose.yml Adapter le fichier docker-compose/yml (uid/gid, domaine, version de l’image) 1version: '3.3' 2services: 3 uptime-kuma: 4 image: louislam/uptime-kuma:1.20.2 5 container_name: uptime-kuma 6 networks: [traefik_lan] 7 expose: [3001] 8 volumes: 9 - data:/app/data 10 - /var/run/docker.sock:/var/run/docker.sock:ro //monitor Docker containers 11 restart: unless-stopped 12 environment: 13 - PUID=112 14 - GUID=120 15 labels: 16 - "traefik.enable=true" 17 - "traefik.docker.network=traefik_lan" 18 - "traefik.http.routers.kuma.rule=Host(`status.example.tld`)" 19 - "traefik.http.routers.kuma.entrypoints=websecure" 20 - "traefik.http.routers.kuma.tls=true" 21 - "traefik.http.routers.kuma.tls.certresolver=letsencrypt" 22volumes: 23 data: {} 24networks: 25 traefik_lan: 26 external: true Lancer le conteneur 1docker-compose -f /opt/uptime-kuma/docker-compose.yml up -d Configuration La première connexion sur l’interface web permet de créer le compte administrateur de l’application. Il suffit ensuite d’ajouter les différentes sondes (HTTP(S), Ping, TCP, DNS) pour monitorer vos services. Dans cet exemple: sonde HTTPS contrôle toutes les 5 minutes (service non critique) 2 essais avant de lever une alarme internal d’une minute entre les 2 essais La ligne /var/run/docker.sock:/var/run/docker.sock:ro du fichier docker-compose.yml permet de surveiller le status des conteneurs qui n’exposent pas directement leurs services sur l’hôte (exemple: les bases de données) Dans la configuration de la sonde, il est préférable de choisir le nom du conteneur plutôt que son ID qui va changer à la prochaine mise à jour. Gotify: ajouter un service d’alerte on profite le l’infra Docker / Traefik existante… Créer une arborescence dédiée 1sudo mkdir /opt/gotify && cd $_ 2sudo touch docker-compose.yml Adapter le fichier docker-compose/yml (domaine, version de l’image) 1version: "3.3" 2services: 3 gotify: 4 image: gotify/server:2.2.4 5 container_name: gotify 6 networks: [traefik_lan] 7 restart: unless-stopped 8 expose: [80] 9 environment: 10 - TZ=Europe/paris 11 volumes: 12 - data:/app/data" 13 labels: 14 - "traefik.enable=true" 15 - "traefik.docker.network=traefik_lan" 16 - "traefik.http.routers.gotify.rule=Host(`push.exemple.tld`)" 17 - "traefik.http.routers.gotify.entrypoints=websecure" 18 - "traefik.http.routers.gotify.tls=true" 19 - "traefik.http.routers.gotify.tls.certresolver=letsencrypt" 20volumes: 21 data: {} 22networks: 23 traefik_lan: 24 external: true Lancer le conteneur 1docker-compose -f /opt/gotify/docker-compose.yml up -d Se connecter à l’interface d’administration avec le compte par défaut: admin / admin (à changer rapidement donc) Configuration onglet USERS: CREATE USER -> saisir un login (ex: prenom-gsm) et un mot de passe se re-connecter avec ce nouvel utilisateur onglet APPS: CREATE APPLICATION -> saisir le nom de l’application (ex: uptime-kuma) remplacer l’icône gopher (utilisé dans les notifications) par celle d’Uptime-Kuma noter le Token (utilisé pour configurer Uptime Kuma) Configurer les notifications Dans l’interface d’Uptime Kuma: Cliquer sur l’icône de votre compte en haut à droite -> Paramètres -> notifications -> Créer une notification Type de notification: Gotify Jeton d’application: mettre le Token créé précédemment dans Gotify URL du serveur: mettre l’URL de votre service gotify Modifier vos sondes pour activer la notification Configurer smartphone Installer le client Gotify depuis votre store préféré (Android et IOS) Configurer l’adresse de votre serveur Gotify et saisir les identifiants créés précédemment Si tout est bien configuré, vous devez recevoir directement les notifications sur votre smartphone Références site web Uptime Kuma documentation Uptime Kuma dépôt GIT Uptime Kuma site web de Gotify documentation de Gotify Photo by Shaun Darwood on Unsplash

Uptime Kuma
Uptime Kuma est un outil de monitoring léger pour vos services hébergés.
Couplé...
Source: Bruno Levasseur
Gitea
Gitea est une forge logicielle GIT écrite en Go dérivée de Gogs. Comme souvent, au moment de choisir une solution logicielle, je regarde : la couverture fonctionnelle la vitalité du projet les ressources nécessaires (mon VPS est un peu limité) la qualité de la documentation Gitea coche toutes ces cases avec une version Docker pour la facilité de maintenance, SQLite pour limiter les ressources (un seul conteneur) et des fonctionnalités à mi-chemin entre un Gitolite un peu trop “roots” et l’usine à gaz GitLab. Le développement communautaire semble dynamique et la documentation est plutôt bien faite. Gitea propose un comparatif des fonctionnalités avec les principales alternatives. Installation Une fois de plus, je vais partir de ma plateforme déjà en place avec Traefik pour gérer les accés HTTPS (reverse-proxy) et SSH (routeur TCP) et Docker pour l’exécution des conteneurs. Je commence par modifier la configuration de Traefik pour ajouter un point d’entrée pour les connexions TCP vers le service SSH du conteneur Gitea: --entrypoints.ssh.address=:2222 : pour créer un point d’entrée ssh dans Traefik 2222:2222/tcp : pour ouvrir le port 2222 (le port 22 standard est déjà utilisé sur l’hôte) 1version: '3' 2services: 3 traefik: 4 container_name: traefik 5 image: traefik:v2.9.6 6 restart: unless-stopped 7 command: 8 - "--providers.docker=true" 9 - "--providers.docker.exposedbydefault=false" 10 - "--providers.file.directory=/etc/traefik/dynamic-conf" 11 - "--providers.file.watch=true" 12 - "--api.dashboard=true" 13 - "--entrypoints.web.address=:80" 14 - "--entrypoints.websecure.address=:443" 15 - "--entrypoints.ssh.address=:2222" 16 - "--entrypoints.web.http.redirections.entrypoint.to=websecure" 17 - "--entrypoints.web.http.redirections.entrypoint.scheme=https" 18 - "--certificatesResolvers.letsencrypt.acme.email=admin@domaine.tld" 19 - "--certificatesResolvers.letsencrypt.acme.storage=acme.json" 20 - "--certificatesResolvers.letsencrypt.acme.tlsChallenge=true" 21 - "--log=true" 22 - "--log.level=INFO" 23 - "--log.filepath=/var/log/traefik.log" 24 - "--accesslog.filepath=/var/log/traefix-access.log" 25 labels: 26 - "traefik.enable=true" 27 - "traefik.http.routers.dashboard.rule=Host(`traefik.domaine.tld`)" 28 - "traefik.http.routers.dashboard.service=api@internal" 29 - "traefik.http.routers.dashboard.entrypoints=websecure" 30 - "traefik.http.routers.dashboard.middlewares=auth-dashboard" 31 - "traefik.http.middlewares.auth-dashboard.basicauth.users=admin:#########################" 32 - "traefik.http.routers.dashboard.tls=true" 33 - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt" 34 networks: 35 - traefik_lan 36 ports: 37 - 80:80 38 - 443:443 39 - 2222:2222/tcp 40 volumes: 41 - /var/run/docker.sock:/var/run/docker.sock:ro 42 - ./config/acme.json:/acme.json 43 - ./config:/etc/traefik:ro 44 - logs:/var/log/ 45 46networks: 47 traefik_lan: 48 external: true 49 50volumes: 51 logs: Pour les autres paramètres, je vous laisse jeter un coup d’œil à mon précédent article.. Je crée un compte local git qui sera utilisé pour l’exécution des processus du conteneur Gitea : 1$ sudo adduser --system --shell /usr/sbin/nologin --group --disabled-password --home /home/git git 2$ id git 3uid=110(git) gid=118(git) groups=118(git) l’IUD: 110 et le GID: 118 seront utilisés dans le docker-compose de Gitea J’ajoute un dossier pour contenir le fichier docker-compose de Gitea: 1$ sudo mkdir /opt/gitea && cd "$_" 2$ touch docker-compose.yaml J’édite le fichier docker-compose.yaml 1version: '3' 2services: 3 gitea: 4 container_name: gitea 5 image: gitea/gitea:1.18.0 6 restart: unless-stopped 7 environment: 8 - USER_UID=110 9 - USER_GID=118 10 - RUN_MODE=prod 11 - APP_NAME="My forge!" 12 - GITEA__server__SSH_PORT=2222 13 - GITEA__server__SSH_LISTEN_PORT=22 14 - GITEA__server__HTTP_PORT=3000 15 - GITEA__server__ROOT_URL=https://git.domain.tld 16 - GITEA__database__DB_TYPE=sqlite3 17 - GITEA__service__DISABLE_REGISTRATION=true 18 - GITEA__service__REQUIRE_SIGNIN_VIEW=true 19 - GITEA__service__REGISTER_EMAIL_CONFIRM=true 20 - GITEA__mailer__ENABLED=true 21 - GITEA__mailer__SMTP_ADDR=smtp.domain.tld 22 - GITEA__mailer__SMTP_PORT=587 23 - GITEA__mailer__PROTOCOL=smtp+starttls 24 - GITEA__mailer__USER=admin@domain.tld 25 - GITEA__mailer__PASSWD=V3ryS3cur3 26 - GITEA__mailer__FROM=noreply-gitea@domain.tld 27 expose: 28 - "3000" 29 - "22" 30 networks: 31 - traefik_lan 32 volumes: 33 - data:/data 34 - /etc/timezone:/etc/timezone:ro 35 - /etc/localtime:/etc/localtime:ro 36 labels: 37 - "traefik.enable=true" 38 - "traefik.http.services.gitea.loadbalancer.server.port=3000" 39 - "traefik.docker.network=traefik_lan" 40 - "traefik.http.routers.gitea.rule=Host(`git.domain.tld`)" 41 - "traefik.http.routers.gitea.entrypoints=websecure" 42 - "traefik.http.routers.gitea.middlewares=secured@file" 43 - "traefik.http.routers.gitea.tls=true" 44 - "traefik.http.routers.gitea.tls.certresolver=letsencrypt" 45 - "traefik.tcp.routers.gitea-ssh.rule=HostSNI(`*`)" 46 - "traefik.tcp.routers.gitea-ssh.entrypoints=ssh" 47 - "traefik.tcp.routers.gitea-ssh.service=gitea-ssh-svc" 48 - "traefik.tcp.services.gitea-ssh-svc.loadbalancer.server.port=22" 49networks: 50 traefik_lan: 51 external: true 52volumes: 53 data: Dans cette configuration: les inscriptions sont désactivées (l’administrateur doit créer les comptes) l’accès est soumis à une authentification systématique (pas d’accès libre à un dépôt public) le courriel d’enregistrement doit être validé pour accéder à la forge les données sont persistées dans un volume nommé Docker (/var/lib/docker/volume/gitea_data/) la redirection HTTP vers HTTPS est gérée en amont directement dans Traefik les connexions TCP sont redirigées du point d’entrée ssh de Traefik vers le port TCP 22 du conteneur Gitea propose de nombreux paramètres de configurations. Vous devriez trouver votre bonheur dans la documentation en respectant la convention de nommage des variables d’environnement : GITEA__SECTION__KEY-NAME Il ne reste plus qu’à démarrer le conteneur : 1$ docker-compose -f /opt/gitea/docker-compose.yaml up -d et à finaliser l’installation dans l’interface web : Il faut impérativement créer le compte administrateur puisque les inscriptions sont fermées dans cette configuration. A ce stade, Gitea est opérationnel. Vous pouvez maintenant: créer un compte utilisateur ajouter une clé publique SSH créer un premier dépôt et tester les connexions SSH : 1$ mkdir ~/projet && cd "$_" 2$ touch README.md 3$ git init 4$ git checkout -b main 5$ git add README.md 6$ git commit -m "first commit" 7$ git remote add origin ssh://git@git.domain.tld:2222/johndoe/demo.git 8$ git push -u origin main 9$ git push origin main 10Énumération des objets: 3, fait. 11Décompte des objets: 100% (3/3), fait. 12Écriture des objets: 100% (3/3), 219 octets | 219.00 Kio/s, fait. 13Total 3 (delta 0), réutilisés 0 (delta 0), réutilisés du pack 0 14remote: . Processing 1 references 15remote: Processed 1 references in total 16To ssh://git.domain.tld:2222/johndoe/demo.git 17 * [new branch] main -> main Conclusion Gitea offre une solution clé en main élégante et facile à mettre en oeuvre. Peu gourmande en ressources (moins de 200Mo de RAM et une image à 257Mo), elle est particulièrement bien adaptée aux contraites de l’auto-hébergement. Ressources documentation Gitea config Cheat Sheet (en) forum Traefik tutorial DigitalOcean (en) blog de Richard Dern blog de Romain Boulanger Photo by Malcolm Lightbody on Unsplash

Gitea
Gitea est une forge logicielle GIT écrite en Go dérivée de Gogs.
Comme souvent,...
Source: Bruno Levasseur
Serveur Joplin
Joplin est une application libre bien connue de prise de notes qui permet d’organiser vos notes et de les synchroniser avec tous vos appareils. Dans cet article, je vais détailler l’installation d’un serveur Joplin sous Docker. Application L’application Joplin propose toutes les fonctionnalités qu’on est en droit d’attendre d’une solution de prise de notes moderne : client multi-plateformes deskop et mobile (Windows, OSX, Linux, Android et iOS) prise de notes, listes todo éditeur markdown gestion des versions support multimédia (images, vidéos, audio, PDF) publication web (clic droit sur une note -> Publier la note) web clipper (extension navigateur pour importer des pages web) partage de notes entre amis, famille ou collègues chiffrement bout en bout (E2EE) import Evernote plugin VScode Joplin Server Joplin est compatible avec de nombreux services de nuage (Nextcloud, OneDrive, Dropbox) mais aussi des solutions plus simple comme WebDAV et surtout la solution maison : Joplin Server. Notez que l’éditeur propose aussi une solution hébergée avec Joplin Cloud. Installation Avant de commencer, je précise que le serveur Joplin est toujours en version bêta (2.9.7-beta) au moment où j’écris cet article. Si vous démarrez avec Docker, je vous propose de jeter un coup d’œil à mon précédent article.. Pour les autres, on va directement entrer dans le vif du sujet en déclarant le frontal applicatif et la base de données (PostgreSQL) dans un traditionnel docker-compose.yaml: 1$ sudo mkdir /opt/joplin 2$ sudo touch /opt/joplin/docker-compose.yaml Avec le contenu suivant en veillant à : personnaliser les mots de passe, la configuration mail et les URL utiliser la dernière version du server joplin 1version: '3' 2services: 3 db: 4 image: postgres:15 5 container_name: joplin_db 6 restart: unless-stopped 7 volumes: 8 - db:/var/lib/postgresql/data 9 expose: 10 - 5432 11 networks: 12 - lan 13 restart: unless-stopped 14 environment: 15 - POSTGRES_PASSWORD=V3ryS3cr3t 16 - POSTGRES_USER=joplin 17 - POSTGRES_DB=joplin 18 app: 19 image: joplin/server:2.9.7-beta 20 container_name: joplin_app 21 depends_on: 22 - db 23 expose: 24 - 22300 25 restart: unless-stopped 26 environment: 27 - APP_PORT=22300 28 - APP_BASE_URL=https://joplin.domain.tld 29 - DB_CLIENT=pg 30 - POSTGRES_PASSWORD=V3ryS3cr3t 31 - POSTGRES_DATABASE=joplin 32 - POSTGRES_USER=joplin 33 - POSTGRES_PORT=5432 34 - POSTGRES_HOST=db 35 - MAILER_ENABLED=1 36 - MAILER_HOST=smtp.domain.tld 37 - MAILER_PORT=25 38 - MAILER_SECURITY=none 39 - MAILER_NOREPLY_NAME=joplin-server 40 - MAILER_NOREPLY_EMAIL=joplin-noreply@domain.tld 41 networks: 42 - traefik_lan 43 - lan 44 labels: 45 - "traefik.enable=true" 46 - "traefik.docker.network=traefik_lan" 47 - "traefik.http.routers.joplin.rule=Host(`joplin.domain.tld`)" 48 - "traefik.http.routers.joplin.entrypoints=websecure" 49 - "traefik.http.routers.joplin.tls=true" 50networks: 51 traefik_lan: 52 external: true 53 lan: 54volumes: 55 db: 56 driver: local Dans ce déploiement, j’utilise toujours Traefik en reverse proxy et pour la terminaison TLS. La persistance des données est assurée avec un volume nommé pour la base de données (pensez à mettre en place une sauvegarde). Il ne reste plus qu’à lancer l’application: 1$ docker-compose -f /opt/joplin/docker-compose.yaml up -d et consulter les logs pour s’assurer que tout est correct : 1$ docker-compose -f /opt/joplin/docker-compose.yaml logs app Si tout va bien vous devez pouvoir utiliser l’interface web de l’application à l’adresse: https://[fqdn] (en fonction du nom de domaine que vous avez déclaré dans votre docker-compose). les identifiants par défaut sont : email : admin@localhost password: admin Après avoir changé le mot de passe du compte admin et validé le compte avec le lien fourni dans le courriel reçu, il faut créer les comptes pour les utilisateurs qui souhaitent utiliser votre serveur (il existe une variable pour permettre aux utilisateurs de s’enregistrer: SIGNUP_ENABLED=1) Le quota maximum et la taille max des fichiers téléchargés sont exprimés en octets (ici respectivement 5GB et 2MB). Configuration du client Je vous recommande de commencer par activer le chiffrement E2EE (Préférences -> Chiffrement) et ensuite de configurer la synchronisation : Partager un carnet Le partage se situe au niveau carnet (notebook): clic droit sur le carnet -> partager le carnet Conclusion Joplin Server est une alternative crédible à Evernote tout en gardant la maitrise de ses données. Elle est particulièrement légère (empreinte mémoire de 350MB) et facile à prendre en main. Dernier point, L’application gère maintenant des profils avec des réglages indépendants pratique pour séparer des univers personnel et professionnel.

Serveur Joplin
Joplin est une application libre bien connue de prise de notes qui permet d’organiser...
Source: Bruno Levasseur
Proxmox Backup Server
Proxmox Backup Server est une solution de sauvegarde de classe entreprise particulièrement adaptée (mais pas que) à la sauvegarde des machines virtuelles. Une sorte de Veeam Backup libre… Cet outil relativement jeune (1er stable en 2020) est basé sur Debian 11 (PBS 2.x) et écrit principalement en Rust pour garantir de bonnes performances. Il est proposé en version communautaire ou avec du support éditeur via des souscriptions. forum de support communautaire bugtracker support entreprise avec souscription Dans ce rapide guide, je vais privilégier l’intégration avec Proxmox Virtual Environment (PVE) et l’utilisation de la ligne de commande, mais sachez que tout est réalisable depuis l’interface Web. Je ferai également un focus sur l’utilisation des namespaces introduits avec la version 2.x. Fonctionnalités principales sauvegarde incrémentale pour machines virtuelles, conteneurs et serveurs physiques. intégration étroite avec Proxmox Virtual Environment (PVE) chiffrement (AES-256) et compression (Zstandard) à la source déduplication côté serveur (par VM et entre les VMs d’un même datastore) synchronisation sur d’autres serveurs distants (pour respecter la règle des 3.2.1) restauration au niveau fichier sans devoir restaurer toute la VM somme de contrôle pour vérifier l’intégrité des sauvegardes (SHA-256) logiciel libre (GNU AGPL,v3) avec support entreprise possible (souscription) architecture client-serveur avec les communications chiffrées en TLS interface Web d’administration support de QEMU dirty bitmaps (ressemble à VMware CBT) API RESTfull contrôle d’accès basé sur des rôles (RBAC) OpenID, 2FA Fonctionnement Dans PBS, les données sont découpées en blocs (chunks) identifiés par un condensat (SHA-256) dans un index. La déduplication est basée sur la réutilisation des blocs (plusieurs index peuvent pointer vers un même bloc pour réduire le stockage nécessaire). A chaque nouvelle sauvegarde, le client télécharge la liste des blocs déjà présents sur le serveur et ne transfère que les blocs modifiés pour reduire la bande passante réseau et la fenêtre de sauvegarde (voir fonctionnement détaillé). Installation Je ne m’attarde pas sur l’installation avec l’ISO fournit par Proxmox qui ne présente pas de difficulté particulière. Vous pouvez choisir d’installer PBS en machine virtuelle (si vous aimez le coté “Inception”) avec du stockage distant en NFS par exemple ou plus logiquement directement sur un serveur physique. Dans les 2 cas, il faudra provisionner la volumétrie nécessaire pour sauvegarder toutes vos VMs avec la rétention souhaitée et en tenant compte de la déduplication (ratio 2:1 minimum). À l’issue de l’installation, vous devriez pouvoir vous connecter à l’interface web à l’adresse: https://<fqdn ou IP>:8007 Par défaut, le gestionnaire de paquets est configuré pour pointer vers la version enterprise. Si vous souhaitez passer sur la version communautaire, il faut modifier le fichier pbs-enterprise.list: 1root@pbs-server:~# cat /etc/apt/sources.list.d/pbs-enterprise.list 2 3#deb https://enterprise.proxmox.com/debian/pbs bullseye pbs-enterprise 4# PBS pbs-no-subscription repository provided by proxmox.com, 5# NOT recommended for production use 6deb http://download.proxmox.com/debian/pbs bullseye pbs-no-subscription 7 8root@pbs-server:~# apt update && apt update Si vous n’utilisez pas NFS, vous pouvez supprimer le service rpcbind qui est lancé par défaut : 1root@pbs-server:~# apt-get purge rpcbind Le fonctionnement repose sur 2 services : 1root@pbs-server:~# systemctl list-unit-files | grep proxmox 2proxmox-backup-proxy.service enabled enabled 3proxmox-backup.service enabled enabled 4 5root@pbs-server:~# lsof -i -P -n 6COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME 7proxmox-b 14519 backup 21u IPv6 20682 0t0 TCP *:8007 (LISTEN) 8proxmox-b 27152 root 17u IPv4 16907 0t0 TCP 127.0.0.1:82 (LISTEN) 9proxmox-b 27152 root 18u IPv4 16907 0t0 TCP 127.0.0.1:82 (LISTEN) le service proxmox-backup-proxy expose l’API PBS en HTTPS sur le port 8007 (tcp). Il tourne avec des permissions restreintes (utilisateur backup) et transmet les opérations qui nécéssitent des permissions étendues au service proxmox-backup le service proxmox-backup expose l’API de management sur le port 82 uniquement en localhost. Ajout d’un Datastore en ZFS Un datastore est l’emplacement où sont stockées les sauvegardes. Un serveur PBS doit avoir au moins un datastore configuré. Il peut être constitué d’une grappe RAID formatée en XFS ou EXT4, un point de montage NFS ou mieux encore : un pool ZFS ! Si vous n’êtes pas familiarisé avec ZFS, pas de panique: PBS permet de gérer la création du pool directement depuis l’interface WEB. Vous pouvez aussi vous référer à mes précédents articles. Pour cette prise en main, je vais juste créer un pool ZFS avec deux disques de 1TB en miroir (RAID-1). En production, Proxmox recommande l’ajout du SSD en special device ZFS pour améliorer les performances. Afficher la liste des disques disponibles 1root@pbs-server:~# proxmox-backup-manager disk list 2┌──────┬────────┬─────┬───────────┬───────────────┬───────────────┬─────────┬────────┐ 3│ name │ used │ gpt │ disk-type │ size │ model │ wearout │ status │ 4╞══════╪════════╪═════╪═══════════╪═══════════════╪═══════════════╪═════════╪════════╡ 5│ sda │ lvm │ 1 │ hdd │ 34359738368 │ QEMU_HARDDISK │ - │ passed │ 6├──────┼────────┼─────┼───────────┼───────────────┼───────────────┼─────────┼────────┤ 7│ sdb │ unused │ 0 │ hdd │ 1099511627776 │ QEMU_HARDDISK │ - │ passed │ 8├──────┼────────┼─────┼───────────┼───────────────┼───────────────┼─────────┼────────┤ 9│ sdc │ unused │ 0 │ hdd │ 1099511627776 │ QEMU_HARDDISK │ - │ passed │ 10└──────┴────────┴─────┴───────────┴───────────────┴───────────────┴─────────┴────────┘ Création du pool ZFS. L’argument local-zfs est à la fois le nom du pool ZFS et celui du datastore PBS. 1root@pbs-server:~# proxmox-backup-manager disk zpool create local-zfs --devices sdb,sdc --raidlevel mirror --add-datastore 2create Mirror zpool 'local-zfs' on devices 'sdb,sdc' 3# "zpool" "create" "-o" "ashift=12" "-m" "/mnt/datastore/local-zfs" "local-zfs" "mirror" "sdb" "sdc" 4# "zfs" "set" "relatime=on" "local-zfs" 5 6Chunkstore create: 1% 7Chunkstore create: 2% 8---8<-- 9Chunkstore create: 98% 10Chunkstore create: 99% 11TASK OK Vérification du pool ZFS et du datastore PBS 1root@pbs-server:~# zpool status 2 pool: local-zfs 3 state: ONLINE 4config: 5 NAME STATE READ WRITE CKSUM 6 local-zfs ONLINE 0 0 0 7 mirror-0 ONLINE 0 0 0 8 sdb ONLINE 0 0 0 9 sdc ONLINE 0 0 0 10 11root@pbs-server:~# proxmox-backup-manager datastore list 12┌───────────┬──────────────────────────┬─────────┐ 13│ name │ path │ comment │ 14╞═══════════╪══════════════════════════╪═════════╡ 15│ local-zfs │ /mnt/datastore/local-zfs │ │ 16└───────────┴──────────────────────────┴─────────┘ A ce stade, vous devriez obtenir quelque chose comme ça : Ajout des namespaces: Les namespaces ont été introduits avec PBS 2.x et permettent de structurer un datastore en plusieurs éléments sur lesquels vous pouvez positionner des politiques de rétentions et des permissions d’accès différentes. Cela permet de prendre en charge 2 cas de figure particuliers : la sauvegarde de plusieurs serveurs Proxmox isolés ou plusieurs clusters en évitant les conflits (vmid). la délégation de l’espace d’un datastore à d’autres PBS pour faire de la synchronisation (2 PBS d’extrémités qui se synchronisent sur un PBS central avec 2 namespaces). L’intérêt des namespaces est de conserver la déduplication entre les VMs d’un même datastore (sauf si vous activez le chiffrement à la source : dans ce cas, les sommes de contrôles d’un même bloc sont différentes). Pour la création des namespaces, il va falloir passer par l’interface Web puisque pour le moment, il n’existe pas d’outil en ligne de commande (j’ai cherché un moment : voir flèche rouge) : Ici, j’ai créé un namespace pour chacun des serveurs Proxmox VE (pve-01 à pve-03) à sauvegarder. Configurer la rétention (prune) La rétention (délais de conservation des sauvegardes) peut être définie globalement niveau datastore ou plus finement au niveau namespace pour avoir des politiques différentes en fonction des clients de sauvegardes. Pour planifier au mieux vos rétentions, le plus simple est d’utiliser le simulateur fourni. Dans cet exemple, PBS conserve les sauvegardes sur 1 an avec: planification quotidienne décalée des purges (20:00 pour pve-01 à 00:00 pour pve-03) 2 semaines de sauvegardes quotidiennes 2 mois de sauvegardes hebdomadaires 1 an de sauvegardes mensuelles. Purger les données (garbage collector) Dans PBS, l’opération de prune traite uniquement les index et pas les blocs de données du datastore. Cette tâche revient au garbage collector qu’il faut planifier régulièrement. Voir l’exemple dans la capture ci-dessus avec une planification chaque samedi à 18h15 (l’opération peut être longue sur un gros datastore). Créer des utilisateurs dédiés L’utilisation du compte root@pam du serveur PBS pour la connexion des clients de sauvegarde est évidemment à proscrire. Je vais créer un utilisateur distinct pour chaque serveur à sauvegarder et l’associer son namespace (pve-01 dans l'exemple). 1root@pbs-server:~# proxmox-backup-manager user create user-pve-01@pbs --password VerySecret1 --comment "backup user for pve-01" 2 3root@pbs-server:~# proxmox-backup-manager user list 4┌─────────────────┬────────┬────────┬───────────┬──────────┬───────────────────┬────────────────────────┐ 5│ userid │ enable │ expire │ firstname │ lastname │ email │ comment │ 6╞═════════════════╪════════╪════════╪═══════════╪══════════╪═══════════════════╪════════════════════════╡ 7│ root@pam │ 1 │ never │ │ │ admin@domain.tld │ │ 8├─────────────────┼────────┼────────┼───────────┼──────────┼───────────────────┼────────────────────────┤ 9│ user-pve-01@pbs │ 1 │ never │ │ │ │ backup user for pve-01 │ 10├─────────────────┼────────┼────────┼───────────┼──────────┼───────────────────┼────────────────────────┤ 11│ user-pve-02@pbs │ 1 │ never │ │ │ │ backup user for pve-02 │ 12├─────────────────┼────────┼────────┼───────────┼──────────┼───────────────────┼────────────────────────┤ 13│ user-pve-03@pbs │ 1 │ never │ │ │ │ backup user for pve-03 │ 14└─────────────────┴────────┴────────┴───────────┴──────────┴───────────────────┴────────────────────────┘ Et accorder le rôle DatastoreBackup sur leurs namespaces respectifs (pve-01 dans l'exemple): 1root@pbs-server:~# proxmox-backup-manager acl update /datastore/local-zfs/pve-01 DatastoreBackup --auth-id user-pve-01@pbs 2 3root@pbs-server:~# proxmox-backup-manager acl list 4┌─────────────────┬─────────────────────────────┬───────────┬─────────────────┐ 5│ ugid │ path │ propagate │ roleid │ 6╞═════════════════╪═════════════════════════════╪═══════════╪═════════════════╡ 7│ user-pve-01@pbs │ /datastore/local-zfs/pve-01 │ 1 │ DatastoreBackup │ 8├─────────────────┼─────────────────────────────┼───────────┼─────────────────┤ 9│ user-pve-02@pbs │ /datastore/local-zfs/pve-02 │ 1 │ DatastoreBackup │ 10├─────────────────┼─────────────────────────────┼───────────┼─────────────────┤ 11│ user-pve-03@pbs │ /datastore/local-zfs/pve-03 │ 1 │ DatastoreBackup │ 12└─────────────────┴─────────────────────────────┴───────────┴─────────────────┘ Configurer Proxmox VE avec PBS Pour commencer, il faut récupérer l’empreinte de la clé publique du certificat TLS du serveur PBS (attention, si vous décidez d’utiliser autre chose que les certificats auto-signés générés à l’installation) 1root@pbs-server:~# proxmox-backup-manager cert info | grep Fingerprint 2Fingerprint (sha256): 1c:3b:0e:80:9a:da:dc:26:31:92:2e:76:f6:2d:0b:52:85:0e:e0:6f:b7:cd:af:e7:25:55:ef:c2:5c:d4:e9:f9 et ajouter le serveur PBS sur les serveurs Proxmox VE (pve-01 dans l’exemple): 1root@pve-01:~# pvesm add pbs pbs-server --datastore local-zfs --server <IP server PBS> --fingerprint 1c:3b:0e:80:9a:da:dc:26:31:92:2e:76:f6:2d:0b:52:85:0e:e0:6f:b7:cd:af:e7:25:55:ef:c2:5c:d4:e9:f9 --username user-pve-01@pbs --password --namespace pve-01 2Enter Password: ********** Dans l’interface Web de votre serveur Proxmox VE : Planifier une sauvegarde La planification des sauvegardes se configure directement depuis l’interface Web de Proxmox VE: Dans cette exemple, le serveur Proxmox VE va faire une sauvegarde de toutes les VMs tous les jours à partir de 21h00 sur le serveur PBS en mode snapshot (sans interruption de service). Restauration La restauration est toute aussi simple, avec la possibilité de restaurer l’ensemble de la VM / conteneur ou simplement un fichier en parcourant l’arborescence. Conclusion PBS est une solution incontournable pour la sauvegarde des environnements Proxmox VE. Les planifications proposées dans cet article doivent être adaptées à vos besoins. Cet article ne couvre pas la gestion des vérifications, les sauvegardes de serveurs en mode fichier et la synchronisation sur un second serveur distant…bref encore un peu de grain à moudre à l’occasion de la mise en service de l’infrastructure de production. Ressources: PBS Documentation (EN) Blog Notamax (FR) Simulateur (EN) Wiki Ordinoscope (FR) Photo by Jason Pofahl on Unsplash

Proxmox Backup Server
Proxmox Backup Server est une solution de sauvegarde de classe entreprise particulièrement...
Source: Bruno Levasseur
Crowdsec avec Traefik
Crowdsec est un IPS (Intrusion Prevention System) moderne et collaboratif, associé à un réseau mondial de réputation IP. Une sorte de Fail2ban 2.0 massivement multijoueur… A moins de sortir d’une grotte, il est difficile en 2022 de passer à côté de cette solution française qui bénéficie d’une large communauté d’utilisateurs. Dans cet article, je vais détailler sa mise en œuvre dans un contexte Docker avec Traefik. Si besoin, vous pouvez vous référer à mes précédents articles sur l’infrastructure déjà en place : mise en oeuvre Traefik nouvelle config Traefik Fonctionnement de Crowdsec Crowdsec utilise un agent qui va analyser des logs (Parser) à la recherche d’événements pouvant correspondre à des scénarios d’attaques connus. Crowdsec en fournit un grand nombre par l’intermédiaire de collections disponibles sur le hub de Crowdsec Lorsqu’un scénario est détecté, l’agent génère une alerte qui est envoyée à l’API locale (LAPI) de Crowdsec qui va prendre une décision associée: l’alerte est principalement une information de traçabilité et restera même après l’expiration de la décision. la décision, quant à elle, est de courte durée (4h) et indique quelle action doit être entreprise contre l’IP ou la plage IP incriminée. Ces informations sont ensuite stockées dans une base de données. Crowdsec va également prévenir l’API centrale (CAPI) pour en faire bénéficier l’ensemble des participants du réseau Crowdsec (après un mécanisme de modération). Les bouncers sont ensuite chargés d’appliquer les décisions prisent par Crowdsec: bloquer une IP, présenter un captcha, appliquer un MFA à un utilisateur donné, une erreur 403, etc. Ce diagramme résume le fonctionnement : Fonctionnement avec traefik Pour Traefik, il existe un bouncer spécifique qui utilise le middleware ForwardAuth pour valider l’accès aux applications en fonction des décisions prisent par Crowdsec. Plutôt qu’une longue description, je vous ai fait un schéma du fonctionnement: les logs générés par Traefik sont parsés par le conteneur Crowdsec qui contient l’API locale le conteneur bouncer consulte l’API Crowdsec pour connaitre les IP à bannir le middleware ForwardAuth de Traefik sollicite le conteneur bouncer pour savoir si il doit permettre l’accès à l’application Configuration de Traefik L’intégration de Crowdsec nécessite quelques modifications dans le fichier docker-compose de Traefik: activer la gestion des logs et définir un volume docker pour les stocker configurer le middleware ForwardAuth avec l’adresse du bouncer Crowdsec intercaler le middleware ForwardAuth directement après le point d’entrée en HTTPS (je n’expose pas de service en HTTP) 1version: '3' 2services: 3 traefik: 4 container_name: traefik 5 image: traefik:v2.8.0 6 restart: unless-stopped 7 command: 8 - "--providers.docker=true" 9 - "--providers.docker.exposedbydefault=false" 10 - "--providers.file.directory=/etc/traefik/dynamic-conf" 11 - "--providers.file.watch=true" 12 - "--api.dashboard=true" 13 - "--entrypoints.web.address=:80" 14 - "--entrypoints.websecure.address=:443" 15 - "--entrypoints.web.http.redirections.entrypoint.to=websecure" 16 - "--entrypoints.web.http.redirections.entrypoint.scheme=https" 17 - "--certificatesResolvers.letsencrypt.acme.email=john.doe@domain.tld" # à modifier 18 - "--certificatesResolvers.letsencrypt.acme.storage=acme.json" 19 - "--certificatesResolvers.letsencrypt.acme.tlsChallenge=true" 20 - "--entrypoints.websecure.http.middlewares=crowdsec-bouncer@docker" 21 - "--log=true" 22 - "--log.level=INFO" 23 - "--log.filepath=/var/log/traefik.log" 24 - "--accesslog.filepath=/var/log/traefix-access.log" 25 labels: 26 - "traefik.enable=true" 27 - "traefik.http.routers.dashboard.rule=Host(`traefik.domain.tld`)" 28 - "traefik.http.routers.dashboard.service=api@internal" 29 - "traefik.http.routers.dashboard.entrypoints=websecure" 30 - "traefik.http.routers.dashboard.middlewares=auth-dashboard" 31 - "traefik.http.middlewares.auth-dashboard.basicauth.users=admin:$$xxxxxxxxx$$xxxxxxx/" # à modifier 32 - "traefik.http.routers.dashboard.tls=true" 33 - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt" 34 - "traefik.http.middlewares.crowdsec-bouncer.forwardauth.address=http://crowdsec-bouncer:8080/api/v1/forwardAuth" 35 - "traefik.http.middlewares.crowdsec-bouncer.forwardauth.trustForwardHeader=true" 36 networks: 37 - traefik_lan 38 ports: 39 - 80:80 40 - 443:443 41 volumes: 42 - /var/run/docker.sock:/var/run/docker.sock:ro 43 - ./config/acme.json:/acme.json 44 - ./config:/etc/traefik:ro 45 - logs:/var/log/ 46 47networks: 48 traefik_lan: 49 external: true 50 51volumes: 52 logs: Installation de Crowdsec Comme d’habitude, je commence par créer l’arborescence dans /opt: 1sudo mkdir /opt/crowdsec && cd $_ 2sudo touch acquis.yaml docker-compose.yaml Le fichier acquis.yaml va permettre d’indiquer à l’agent Crowdsec l’emplacement des logs à analyser 1filenames: 2 - /var/log/traefik/* 3labels: 4 type: traefik Le fichier docker-compose.yaml ne présente pas de problème particulier. Il faut juste être vigilant sur la cohérence des chemins vers les fichiers de logs générés par Traefik. 1version: '3' 2services: 3 crowdsec: 4 image: docker.io/crowdsecurity/crowdsec:latest 5 container_name: crowdsec 6 restart: unless-stopped 7 environment: 8 - COLLECTIONS=crowdsecurity/traefik crowdsecurity/http-cve 9 - CUSTOM_HOSTNAME=crowdsec 10 volumes: 11 - config:/etc/crowdsec 12 - db:/var/lib/crowdsec/data/ 13 - /var/lib/docker/volumes/traefik_logs/_data/:/var/log/traefik/:ro 14 - ./acquis.yaml:/etc/crowdsec/acquis.yaml:ro 15 networks: 16 - traefik_lan 17 restart: unless-stopped 18 19 crowdsec-bouncer: 20 image: fbonalair/traefik-crowdsec-bouncer:latest 21 container_name: crowdsec-bouncer 22 restart: unless-stopped 23 environment: 24 CROWDSEC_BOUNCER_API_KEY: xxxxxxxxxxxxxxxxxxxxxx # à modifier 25 CROWDSEC_AGENT_HOST: crowdsec:8080 26 expose: 27 - 8080 28 networks: 29 - traefik_lan 30 31networks: 32 traefik_lan: 33 external: true 34 35volumes: 36 config: 37 db: Il suffit maintenant de lancer le docker-compose: 1docker-compose -f /opt/crowdsec/docker-compose.yaml up -d A ce stade, il nous manque toujours la clé d’API permettant au bouncer de contacter l’API locale de Crowdsec. Pour cela, il faut utiliser la ligne de commande Crowdsec (cscli) et le plus simple est d’ajouter un alias à votre shell pour éviter de préfixer vos commandes avec un docker exec 1alias cscli='docker exec -t crowdsec cscli' Vous pouvez alors ajouter le bouncer Traefix et obtenir en retour la clé d’API (attention, il est impossible de l’obtenir ultérieurement) 1cscli bouncers add traefik-bouncer Après avoir renseigné la clé d’API obtenue dans le docker-compose, il vous suffit de relancer les services. Vérifier le fonctionnement Quelques commandes cscli permettent de rapidement vérifier le bon fonctionnement: cscli bouncer list cscli collections list cscli metrics permet de vérifier la bonne prise en charge des logs cscli decisions list ne retourne rien puisqu’il est peu probable que vous ayez déjà subi une attaque 1$ cscli bouncer list 2----------------------------------------------------------------------------------------------- 3 NAME IP ADDRESS VALID LAST API PULL TYPE VERSION AUTH TYPE 4----------------------------------------------------------------------------------------------- 5 traefik-bouncer 172.19.0.10 ✔️ 2022-07-30T16:19:59Z Go-http-client 1.1 api-key 6----------------------------------------------------------------------------------------------- 7 8$ cscli collections list 9COLLECTIONS 10------------------------------------------------------------------------------------------------------------ 11 NAME 📦 STATUS VERSION LOCAL PATH 12------------------------------------------------------------------------------------------------------------ 13 crowdsecurity/base-http-scenarios ✔️ enabled 0.6 /etc/crowdsec/collections/base-http-scenarios.yaml 14 crowdsecurity/http-cve ✔️ enabled 1.0 /etc/crowdsec/collections/http-cve.yaml 15 crowdsecurity/linux ✔️ enabled 0.2 /etc/crowdsec/collections/linux.yaml 16 crowdsecurity/sshd ✔️ enabled 0.2 /etc/crowdsec/collections/sshd.yaml 17 crowdsecurity/traefik ✔️ enabled 0.1 /etc/crowdsec/collections/traefik.yaml 18------------------------------------------------------------------------------------------------------------ 19 20$ cscli metrics 21INFO[30-07-2022 04:21:35 PM] Buckets Metrics: 22+-------------------------------------------+---------------+-----------+--------------+--------+---------+ 23| BUCKET | CURRENT COUNT | OVERFLOWS | INSTANTIATED | POURED | EXPIRED | 24+-------------------------------------------+---------------+-----------+--------------+--------+---------+ 25| crowdsecurity/http-crawl-non_statics | 2 | 43 | 838 | 2.69k | 793 | 26| crowdsecurity/http-cve-2021-41773 | - | 1 | 1 | - | - | 27| crowdsecurity/http-path-traversal-probing | - | - | 1 | 1 | 1 | 28| crowdsecurity/http-probing | 2 | 171 | 277 | 1.99k | 104 | 29| crowdsecurity/http-sensitive-files | - | 16 | 17 | 84 | 1 | 30| crowdsecurity/thinkphp-cve-2018-20062 | - | 1 | 1 | - | - | 31+-------------------------------------------+---------------+-----------+--------------+--------+---------+ 32INFO[30-07-2022 04:21:35 PM] Acquisition Metrics: 33+------------------------------------------+------------+--------------+----------------+------------------------+ 34| SOURCE | LINES READ | LINES PARSED | LINES UNPARSED | LINES POURED TO BUCKET | 35+------------------------------------------+------------+--------------+----------------+------------------------+ 36| file:/var/log/traefik/traefik.log | 6.62k | - | 6.62k | - | 37| file:/var/log/traefik/traefix-access.log | 18.37k | 3.41k | 14.96k | 4.77k | 38+------------------------------------------+------------+--------------+----------------+------------------------+ 39 40$ cscli decisions list 41No active decisions Ca fait le Job ? Bannir manuellement une adresse IP: Depuis mon poste client, je teste l’accès à l’application avant de bannir l’adresse IP 1curl -I https://app.domain.tld 2 3HTTP/2 200 4accept-ranges: bytes 5content-type: text/html 6date: Sat, 30 Jul 2022 16:33:19 GMT 7etag: "62865c91-b67e" 8last-modified: Thu, 19 May 2022 15:04:49 GMT 9server: nginx/1.19.2 J’obtiens un code 200: l’application est accessible Sur le serveur, j’ajoute l’IP publique de mon poste client dans Crowdsec 1$ cscli decisions add --ip xxx.xxx.xxx.xxx 2INFO[30-07-2022 04:38:32 PM] Decision successfully added 3 4$ cscli decision list 5+--------+--------+-------------------+------------------------------+--------+---------+----+--------+--------------------+----------+ 6| ID | SOURCE | SCOPE:VALUE | REASON | ACTION | COUNTRY | AS | EVENTS | EXPIRATION | ALERT ID | 7+--------+--------+-------------------+------------------------------+--------+---------+----+--------+--------------------+----------+ 8| 154892 | cscli | Ip:xx.xx.xx.xx | manual 'ban' from 'crowdsec' | ban | | | 1 | 3h59m32.102173374s | 18 | 9+--------+--------+-------------------+------------------------------+--------+---------+----+--------+--------------------+----------+ Sur mon poste client, je retente l’accès à l’application 1curl -I https://app.domain.tld 2HTTP/2 403 3content-type: text/plain; charset=utf-8 4date: Sat, 30 Jul 2022 16:41:07 GMT 5content-length: 9 Cette fois, j’obtiens un code 403: l’accès est bien réfusé ! Pour lever le banissement, il suffit de faire: 1cscli decision delete -i xxx.xxx.xxx.xxx 2INFO[30-07-2022 04:44:45 PM] 1 decision(s) deleted Simuler une attaque Sur mon poste client, je vais utiliser un outil de pentest courant: dirsearch 1dirsearch -u https://app.domain.tld Sur le serveur, la réponse de Crowdsec ne se fait pas attendre: 1cscli decision list 2+--------+----------+------------------+------------------------------------+--------+---------+--------------+--------+--------------------+----------+

Crowdsec avec Traefik
Crowdsec est un IPS (Intrusion Prevention System) moderne et collaboratif, associé...
Source: Bruno Levasseur
Terraform et Proxmox
Cet article est un peu le préquel de celui sur Ansible. Cette fois, on va s’attarder sur l’étape précédente avec le déploiement d’une dizaine de VM sous Promox avec Terraform. Terraform est un outil libre d’orchestration développé par HashiCorp à qui on doit déjà les excellents Vault, Packer et Consul. Il est utilisé pour provisionner des infrastructures complètes (serveurs, équipements réseaux, DNS, Firewall…) en utilisant un langage déclaratif (HCL Hashicop Configuration Language). Terraform est aujourd’hui un standard de fait de l’IaC (Infrastructure as Code). Configurer Proxmox Création d’un jeton d’API Je commence par déclarer un rôle dédié avec les permissions nécéssaires pour créer des VMs. En SSH sur le serveur Proxmox : créer un rôle dédié Terraform 1pveum role add Terraform -privs "VM.Allocate VM.Clone VM.Config.CDROM VM.Config.CPU VM.Config.Cloudinit VM.Config.Disk VM.Config.HWType VM.Config.Memory VM.Config.Network VM.Config.Options VM.Monitor VM.Audit VM.PowerMgmt Datastore.AllocateSpace Datastore.Audit" ajouter un utilisateur dédié terraform dans le royaume pve (voir gestion des utilisateurs) 1pveum user add terraform@pve -password <password> -comment "Terraform account" affecter le rôle Terraform à l’utilisateur terraform@pve 1pveum aclmod / -user terraform@pve -role Terraform créer le jeton pour accéder à l’API (à conserver précieusement) 1pveum user token add terraform@pve terraform -expire 0 -privsep 0 -comment "Terraform token" 2┌──────────────┬──────────────────────────────────────────────────────────┐ 3│ key │ value │ 4╞══════════════╪══════════════════════════════════════════════════════════╡ 5│ full-tokenid │ terraform@pve!terraform │ 6├──────────────┼──────────────────────────────────────────────────────────┤ 7│ info │ {"comment":"Terraform token","expire":"0","privsep":"0"} │ 8├──────────────┼──────────────────────────────────────────────────────────┤ 9│ value │ c9995d9f-72d9-4adc-8ddb-80a0ccf470cc │ 10└──────────────┴──────────────────────────────────────────────────────────┘ L’option -privsep 0 permet l’héritage des permissions de l’utilisateur. Je peux maintenant interroger directement l’API Proxmox en utilisant ce jeton (la liste des noeuds dans cet exemple): Depuis un poste client: 1curl -X GET 'https://pve-01.exemple.tld:8006/api2/json/nodes' -H 'Authorization: PVEAPIToken=terraform@pve!terraform=c9995d9f-72d9-4adc-8ddb-80a0ccf470cc' 2 3{"data":[{"status":"online","id":"node/pve-01","node":"pve-01","ssl_fingerprint":"C9:3B:55:FD:15:4F:35:46:75:85:B8:D7:CD:8E:F9:0C:75:39:3D:99:29:EF:56:F3:6C:F2:7C:21:70:08:95:1A","level":"","type":"node"}]}% Création d’un modèle de VM Terraform ne réalise pas de miracle, il se contente de cloner un modèle de VM et plus précisément une image “cloud” pour bénéficier de la personnalisation apportée par cloud-init. La plupart des distributions fournissent des images au format qcow2 pour KVM: Debian Ubuntu Rocky Linux Exemple avec Debian 11 (toujours en SSH sur le serveur Proxmox): télécharger l’image debian 1cd /tmp 2wget https://cdimage.debian.org/cdimage/cloud/bullseye/latest/debian-11-genericcloud-amd64.qcow2 créer une VM avec un VMID libre (1000 dans l’exemple), adapter le nom du bridge à votre configuration réseau (vmbr1 dans mon cas pour le traffic réseau des VMs) 1qm create 1000 --memory 1024 --core 1 --name debian11-temp --net0 virtio,bridge=vmbr1 --description "Debian 11 cloud template" importer l’image Debian dans l’espace de stockage des VMs (“local-lvm” dans l’exemple) 1qm importdisk 1000 /tmp/debian-11-genericcloud-amd64.qcow2 local-lvm 2importing disk '/tmp/debian-11-genericcloud-amd64.qcow2' to VM 1000 ... 3 Logical volume "vm-1000-disk-0" created. 4Successfully imported disk as 'unused0:local-lvm:vm-1000-disk-0' attacher le disque au contrôleur de la VM 1qm set 1000 --scsihw virtio-scsi-pci --scsi0 local-lvm:vm-1000-disk-0 2update VM 1000: -scsi0 local-lvm:vm-1000-disk-0 -scsihw virtio-scsi-pci définir le disque comme périphérique de boot par défaut 1qm set 1000 --boot c --bootdisk scsi0 ajouter le CDrom pour cloud-init 1qm set 1000 --ide2 local-lvm:cloudinit ajouter un port serie et l’utiliser pour afficher la console 1qm set 1000 --serial0 socket --vga serial0 convertir la VM en modèle (template) 1qm template 1000 supprimer l’image Debian téléchargée 1rm -f /tmp/debian-11-genericcloud-amd64.qcow2 Si tout c’est bien passé, vous devriez obtenir ce résultat: Installer Terraform Terraform est un binaire écrit en Go. Hashicop fournit un dépôt pour la majorité des distributions (https://www.terraform.io/downloads) mais le plus simple reste encore de télécharger directement le binaire compilé pour votre plateforme. exemple pour Mac OSX Intel (sur un poste client): 1wget https://releases.hashicorp.com/terraform/1.1.7/terraform_1.1.7_darwin_amd64.zip 2unzip terraform_1.1.7_darwin_amd64.zip 3sudo mv terraform /usr/local/bin/ 4sudo chmod +x /usr/local/bin/terraform 5 6terraform -version 7Terraform v1.1.7 8on darwin_amd64 Utiliser Terraform Le cycle de vie d’un projet Terraform est décomposé en 4 phases: Init : initialise le répertoire de travail et installe les fournisseurs (providers) nécessaires Plan : calcule les opérations à effectuer pour obtenir l’état souhaité Apply : applique les opérations pour atteindre l’état souhaité Destroy : supprime les ressources Terraform fournit un registre qui référence les provider pour lui permettent de communiquer avec une multitude de plateformes et d’équipements. Pour Proxmox, le plus populaire est celui proposé par Telmate. Sur un poste client, je vais créer un dossier pour mon projet et 3 fichiers de configuration: 1mkdir Proxmox-Terraform && cd Proxmox-Terraform 2touch provider.tf main.tf variables.tfvars 3tree 4. 5├── main.tf 6├── provider.tf 7└── variables.tfvars provider.tf : pour installer et configurer le provider Proxmox (mettre la dernière version disponible) 1terraform { 2 required_providers { 3 proxmox = { 4 source = "Telmate/proxmox" 5 version = "2.9.6" 6 } 7 } 8} 9 10provider "proxmox" { 11 pm_api_url = var.pm_api_url 12 pm_api_token_id= var.pm_api_token_id 13 pm_api_token_secret= var.pm_api_token_secret 14 #pm_tls_insecure = true //for Proxmox self-signed certificate WUI 15} variables.tfvars : pour contenir les variables nécéssaires Je reprends ici le jeton API et le nom du modèle de VM créés précédemment ainsi que le nom du noeud Proxmox qui va exécuter les VMs. 1variable "pm_api_url" { 2 default = "https://pve-01.exemple.tld:8006/api2/json" 3} 4variable "pm_api_token_id" { 5 default = "terraform@pve!terraform" 6} 7variable "pm_api_token_secret" { 8 default = "c9995d9f-72d9-4adc-8ddb-80a0ccf470cc" 9} 10variable "target_node" { 11 default = "pve-01" 12} 13variable "clone" { 14 default = "debian11-temp" 15} 16variable "ssh_key" { 17 default = "ssh-rsa AAAAB3NzaC1y............c2EAAAABIwAAA== user@destop.exemple.tld" 18} main.tf : pour décrire l’état souhaité (ajout de 10 VMs) Dans cette configuration, j’utilise une itération sur la valeur “count” pour construire le nom de la VM et son adresse IP (test-vm-1 à test-vm-10). 1resource "proxmox_vm_qemu" "tp_servers" { 2 desc = "Deploiement 10 VM Debian sur Proxmox" 3 count = 10 4 name = "test-vm${count.index + 1}" 5 target_node = var.target_node 6 clone = var.clone 7 8 os_type = "cloud-init" 9 cores = 2 10 sockets = 1 11 cpu = "host" 12 memory = 2048 13 scsihw = "virtio-scsi-pci" 14 bootdisk = "scsi0" 15 16 disk { 17 slot = 0 18 size = "2G" 19 type = "scsi" 20 storage = "local-lvm" 21 iothread = 1 22 } 23 24 network { 25 model = "virtio" 26 bridge = "vmbr1" 27 } 28 29 ipconfig0 = "ip=192.168.1.${count.index + 1}/24,gw=192.168.1.1" 30 nameserver = 192.168.1.254 31 searchdomain = exemple.tld 32} terraform init La première commande à lancer est un terraform init pour initialiser le répertoire et travail mais aussi installer le provider Proxmox. 1terraform init 2Initializing the backend... 3Initializing provider plugins... 4- Finding telmate/proxmox versions matching "2.9.6"... 5- Installing telmate/proxmox v2.9.6... 6- Installed telmate/proxmox v2.9.6 (self-signed, key ID A9EBBE091B35AFCE) 7 8Terraform has been successfully initialized! terraform plan Avec la commande terraform plan, Terraform va determiner les actions à accomplir pour atteindre l’objectif que j’ai défini dans le main.tf. Le résultat de la commande est relativement verbeuse (tronqué dans l’exemple) et permet de connaitre précisément les opérations qui vont être réalisées. 1terraform plan 2 + create 3 4Terraform will perform the following actions: 5 6 # proxmox_vm_qemu.test_server[0] will be created 7 + resource "proxmox_vm_qemu" "test_server" { 8 + clone = "debian11-temp" 9 + cores = 2 10 + cpu = "host" 11 + full_clone = true 12 + ipconfig0 = "ip=192.168.1.1/24,gw=192.168.1.1" 13 + memory = 2048 14 + name = "test-vm-1" 15 + nameserver = "192.168.1.254" 16 + os_type = "cloud-init" 17 + scsihw = "virtio-scsi-pci" 18 + searchdomain = "exemple.tld" 19 + sockets = 1 20 + tablet = true 21 + target_node = "pve-01" 22 23 + disk { 24 + size = "2G" 25 + storage = "local-lvm" 26 + type = "scsi" 27 } 28 29 + network { 30 + bridge = "vmbr1" 31 + macaddr = (known after apply) 32 + model = "virtio" 33 + queues = (known after apply) 34 + rate = (known after apply) 35 + tag = -1 36 } 37 } terraform apply C’est maintenant que la magie s’opère avec la commande terraform apply --auto-approve (l’option --auto-approve permet d’éviter le prompt de confirmation) qui va provisionner les VMs sur le serveur Proxmox. 1terraform apply --auto-approve 2proxmox_vm_qemu.test_server[8]: Creation complete after 1m59s [id=pve-01/qemu/107] 3proxmox_vm_qemu.test_server[3]: Still creating... [2m0s elapsed] 4proxmox_vm_qemu.test_server[9]: Still creating... [2m10s elapsed] 5proxmox_vm_qemu.test_server[9]: Creation complete after 2m11s [id=pve-01/qemu/108] 6proxmox_vm_qemu.test_server[7]: Creation complete after 2m12s [id=pve-01/qemu/109] 7proxmox_vm_qemu.test_server[3]: Still creating... [2m20s elapsed] 8proxmox_vm_qemu.test_server[3]: Still creating... [2m30s elapsed] 9proxmox_vm_qemu.test_server[3]: Creation complete after 2m34s [id=pve-01/qemu/110] 10proxmox_vm_qemu.test_server[6]: Still creating... [2m40s elapsed] 11proxmox_vm_qemu.test_server[6]: Creation complete after 2m47s [id=pve-01/qemu/111] 12 13Apply complete! Resources: 10 added, 0 changed, 0 destroyed. Et voilà: 2m47s pour créer 10 VMs prêtes à l’emploi ! 1qm list 2 VMID NAME STATUS MEM(MB) BOOTDISK(GB) PID 3 102 test-vm-2 running 2048 2.00 536444 4 103 test-vm-1 running 2048 2.00 536524 5 104 test-vm-3 running 2048 2.00 536638 6 105 test-vm-6 running 2048 2.00 536795 7 106 test-vm-5 running 2048 2.00 537133

Terraform et Proxmox
Cet article est un peu le préquel de celui sur Ansible. Cette fois, on va s’attarder...
Source: Bruno Levasseur
ZFS partie 2
Photo by Gary Meulemans on Unsplash Maintenant que ZFS n’a plus de secret pour vous, on va pouvoir mettre les mains dans le cambouis ! Cet article s’inscrit dans la continuité du précédent billet sur la terminologie ZFS. N’hésitez pas à vous y référer pour avoir plus de détails sur les notions abordées. Installation ZFS Je ne m’étale pas sur l’installation classique de l’Ubuntu 20.04 LTS à grands coups de PXE, Preseed et d’Ansible pour me focaliser sur la partie ZFS. La commande lsmod | grep zfs permet de savoir si le module kernel ZFS est déjà installé. Dans le cas contraire, il suffit de faire: 1$ sudo apt install zfsutils-linux 2$ lsmod | grep zfs 3zfs 4034560 6 4zunicode 331776 1 zfs 5zlua 147456 1 zfs 6zavl 16384 1 zfs 7icp 303104 1 zfs 8zcommon 90112 2 zfs,icp 9znvpair 81920 2 zfs,zcommon 10spl 126976 5 zfs,icp,znvpair,zcommon,zavl Je vais commencer par une étape facultative mais qui facilite grandement la vie au quotidien. Elle consiste à utiliser des alias pour désigner les disques en fonction du numéro de logement indiqué sur le serveur. En cas de panne, cela permet d’identifier plus facilement le disque à remplacer. Sur le Dell PowerEdge R740XD, les disques sont répartis selon le diagramme suivant: Ces numéros de logements correspondent aux identifiants de disques (target) sur la chaine SCSI ([Host:Bus:Target:Lun]) 1$ lsscsi 2lsscsi 3[6:0:0:0] disk TOSHIBA MG08SCA16TEY EJ07 /dev/sda 4[6:0:1:0] disk TOSHIBA MG08SCA16TEY EJ07 /dev/sdb 5[6:0:2:0] disk TOSHIBA MG08SCA16TEY EJ07 /dev/sdc 6[6:0:3:0] disk TOSHIBA MG08SCA16TEY EJ07 /dev/sdd 7[6:0:4:0] disk TOSHIBA MG08SCA16TEY EJ07 /dev/sde 8[6:0:5:0] disk TOSHIBA MG08SCA16TEY EJ07 /dev/sdf 9[6:0:6:0] disk TOSHIBA MG08SCA16TEY EJ07 /dev/sdg 10[6:0:7:0] disk TOSHIBA MG08SCA16TEY EJ07 /dev/sdh 11[6:0:8:0] disk TOSHIBA MG08SCA16TEY EJ07 /dev/sdi 12[6:0:9:0] disk TOSHIBA MG08SCA16TEY EJ07 /dev/sdj 13[6:0:10:0] disk TOSHIBA MG08SCA16TEY EJ07 /dev/sdk 14[6:0:11:0] disk TOSHIBA MG08SCA16TEY EJ07 /dev/sdl 15[6:0:12:0] disk TOSHIBA KPM5XVUG480G B028 /dev/sdm 16[6:0:13:0] disk TOSHIBA KPM5XVUG480G B028 /dev/sdn 17[6:0:14:0] disk TOSHIBA KPM5XVUG480G B028 /dev/sdo 18[6:0:16:0] disk TOSHIBA MG08SCA16TEY EJ07 /dev/sdp 19[6:0:17:0] disk TOSHIBA MG08SCA16TEY EJ07 /dev/sdq 20[6:0:18:0] disk TOSHIBA MG08SCA16TEY EJ07 /dev/sds 21[6:0:19:0] disk TOSHIBA MG08SCA16TEY EJ07 /dev/sdr Je vais donc créer un fichier vdev_id.conf pour indiquer la correspondance entre un alias plus parlant (diskxx) et le disque sur la chaine SCSI (et donc son numéro de logement sur le serveur et dans la carte d’administration à distance DELL iDRAC). 1$ cat /etc/zfs/vdev_id.conf 2 3# Front side - SAS-NL 16TB 4alias disk00 /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:0:0 5alias disk01 /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:1:0 6alias disk02 /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:2:0 7alias disk03 /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:3:0 8alias disk04 /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:4:0 9alias disk05 /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:5:0 10alias disk06 /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:6:0 11alias disk07 /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:7:0 12alias disk08 /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:8:0 13alias disk09 /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:9:0 14alias disk10 /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:10:0 15alias disk11 /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:11:0 16 17# Rear side - SSD 447GB MLC 18alias disk12 /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:12:0 19alias disk13 /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:13:0 20alias disk14 /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:14:0 21 22# Inside - SAS-NL 16TB 23alias disk16 /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:16:0 24alias disk17 /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:17:0 25alias disk18 /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:18:0 26alias disk19 /dev/disk/by-path/pci-0000:65:00.0-scsi-0:0:19:0 Il suffit maintenant de générer les liens avec la commande: udevadm trigger 1$ sudo udevadm trigger 2$ ls -l /dev/disk/by-vdev/ 3total 0 4lrwxrwxrwx 1 root root 9 déc. 6 19:12 disk00 -> ../../sda 5lrwxrwxrwx 1 root root 9 déc. 6 19:12 disk01 -> ../../sdb 6lrwxrwxrwx 1 root root 9 déc. 6 19:12 disk02 -> ../../sdc 7lrwxrwxrwx 1 root root 9 déc. 6 19:12 disk03 -> ../../sdd 8lrwxrwxrwx 1 root root 9 déc. 6 19:12 disk04 -> ../../sde 9lrwxrwxrwx 1 root root 9 déc. 6 19:12 disk05 -> ../../sdf 10lrwxrwxrwx 1 root root 9 déc. 6 19:12 disk06 -> ../../sdg 11lrwxrwxrwx 1 root root 9 déc. 6 19:12 disk07 -> ../../sdh 12lrwxrwxrwx 1 root root 9 déc. 6 19:12 disk08 -> ../../sdi 13lrwxrwxrwx 1 root root 9 déc. 6 19:12 disk09 -> ../../sdj 14lrwxrwxrwx 1 root root 9 déc. 6 19:12 disk10 -> ../../sdk 15lrwxrwxrwx 1 root root 9 déc. 6 19:12 disk11 -> ../../sdl 16lrwxrwxrwx 1 root root 9 déc. 6 19:12 disk12 -> ../../sdm 17lrwxrwxrwx 1 root root 9 déc. 6 19:12 disk13 -> ../../sdn 18lrwxrwxrwx 1 root root 9 déc. 6 19:12 disk14 -> ../../sdo 19lrwxrwxrwx 1 root root 9 déc. 6 19:12 disk16 -> ../../sdp 20lrwxrwxrwx 1 root root 9 déc. 6 19:12 disk17 -> ../../sdq 21lrwxrwxrwx 1 root root 9 déc. 6 19:12 disk18 -> ../../sds 22lrwxrwxrwx 1 root root 9 déc. 6 19:12 disk19 -> ../../sdr Création du ZPOOL Si vous avez lu attentivement mon précédent article, vous avez probablement déjà une idée de l’organisation ZFS retenue pour les 16 disques mécaniques et les 3 disques SSD: 2 VDEVs de 8 disques en RAIDz2 2 SSDs en miroir pour le SLOG 1 SSD pour le L2ARC Cette configuration offre une tolérance à la panne de 2+2 disques (2 par VDEVs) qui statistiquement devrait être acceptable même avec cette taille de disque (temps de reconstruction très long). Par contre comme tous les disques sont identiques, il faut croiser les doigts pour ne pas avoir un défaut de série (certains constructeurs panachent les fabricants de disques pour éviter ce type de problème). Pour les besoins de cet article, je vais créer le ZPOOL en 3 étapes mais il est bien entendu possible de le faire en une seule commande. Création du ZPOOL avec 2 VDEVs en RAIDz2: 1er VDEV avec les disques: disk00 à disk07 2nd VDEV avec les disques: disk08 à disk11 et disk16 à disk19 1$ sudo zpool create -f -o ashift=12 -O xattr=sa -O atime=off -O com.sun:auto-snapshot=false -O compression=on -O mountpoint=none tank raidz2 disk0{0..7} raidz2 disk0{8..9} disk1{0..1} disk1{6..9} 2 3$ zpool status 4 pool: tank 5 state: ONLINE 6 scan: none requested 7config: 8 9 NAME STATE READ WRITE CKSUM 10 tank ONLINE 0 0 0 11 raidz2-0 ONLINE 0 0 0 12 disk00 ONLINE 0 0 0 13 disk01 ONLINE 0 0 0 14 disk02 ONLINE 0 0 0 15 disk03 ONLINE 0 0 0 16 disk04 ONLINE 0 0 0 17 disk05 ONLINE 0 0 0 18 disk06 ONLINE 0 0 0 19 disk07 ONLINE 0 0 0 20 raidz2-1 ONLINE 0 0 0 21 disk08 ONLINE 0 0 0 22 disk09 ONLINE 0 0 0 23 disk10 ONLINE 0 0 0 24 disk11 ONLINE 0 0 0 25 disk16 ONLINE 0 0 0 26 disk17 ONLINE 0 0 0 27 disk18 ONLINE 0 0 0 28 disk19 ONLINE 0 0 0 29 30errors: No known data errors Détails des options: tank : nom usuel des zpool dans ZFS atime=off: améliore les performances en désactivant l’enregistrement des dates d’accès compression=on : par défaut en LZ4 mais il est possible de changer l’algorithme (gzip…) ashift=12 : force l’utilisation des secteurs de 4K sur les disques xattr=sa : active le support des attributs étendus (compatible avec les ACL Posix) La compression: Inutile de se poser la question, il faut l’activer par défaut. En plus d’économiser de l’espace disque, elle permet d’augmenter globalement les performances (débit et IOPS) du stockage. Elle peut être définie au niveau ZPOOL et DATASET (par héritage du zpool ou en surchargeant la valeur). La directive compression=on active la compression avec l’algorithme LZ4 par défaut. Il est possible d’utiliser des algorithmes plus performants (GZIP) en terme de taux de compression mais forcement avec un coût CPU plus élevé. Pour auditer une configuration existante (tank = nom du zpool): 1$ sudo zfs get compression tank 2NAME PROPERTY VALUE SOURCE 3tank compression on local 4 5$ sudo zpool get feature@lz4_compress tank 6NAME PROPERTY VALUE SOURCE 7tank feature@lz4_compress active local ashift: Cette option permet d’indiquer à ZFS la taille réelle des secteurs des disques. Elle est exprimée en puissance de 2: ashift taille secteur usage 9 512B ancien disque, certain SSD 12 4KB disque récent 13 8K certain SSD Les disques modernes continuent d’émuler les secteurs de 512B pour maintenir la compatibilité avec les anciens systèmes de fichiers (voir: Advanced Format). En général ZFS se débrouille bien pour détecter la bonne taille mais il est possible de le définir au moment de la création des VDEVs (non modifiable après). Sur une configuration existante, vous pouvez verifier que le “sector size physical” (4KB dans l’exemple) des disques correspond bien à la valeur ashift utilisée (ashift=12): 1$ sudo sfdisk /dev/sda // disque mécanique 2Disk /dev/sda: 14,57 TiB, 16000900661248 bytes, 31251759104 sectors 3Disk model: MG08SCA16TEY 4Units: sectors of 1 * 512 = 512 bytes 5Sector size (logical/physical): 512 bytes / 4096 bytes 6I/O size (minimum/optimal): 4096 bytes / 4096 bytes 7 8$ sudo sfdisk /dev/sdm //disque SSD 9Disk /dev/sdm: 447,13 GiB, 480103981056 bytes, 937703088 sectors 10Disk model: KPM5XVUG480G 11Units: sectors of 1 * 512 = 512 bytes 12Sector size (logical/physical): 512 bytes / 4096 bytes 13I/O size (minimum/optimal): 4096 bytes / 4096 bytes 14 15$ sudo zdb | grep -e ashift 16 ashift: 12 Une erreur d’alignement des blocks ZFS avec la taille des secteurs des disques entraine une dégradation importante des performances. Ajout du SLOG: Nous avons vu précédemment que pour augmenter les performances de ZFS sur les écritures synchrones (avec NFS par exemple), il peut être intéressant d’ajouter un périphérique rapide de type SSD ou PMEM. Pour des raisons de sécurité, le SLOG est monté en miroir sur les disques SSD arrière 12 et 13: 1$ sudo zpool add -f tank log mirror disk1{2..3} 2 3$ sudo zpool status | grep -A 4 "logs" 4logs 5 mirror-2 ONLINE 0 0 0 6 disk12 ONLINE 0 0 0 7 disk13 ONLINE 0 0 0 Ajout du L2ARC: Je suis pratiquement convaincu que ce cache en lecture n’est pas nécéssaire dans mon cas d’usage. Il est même potentiellement contre-productif en consommant de la RAM qui pourrait être bénéfique pour l’ARC. Je vais mettre en place un tableau de bord Graphana pour mesure les hits sur le cache L2ARC et aviser après 1 mois d’utilisation. Le L2ARC n’est pas critique pour le fonctionnement de ZFS, je vais me contenter du dernier SSD (disk14) en face arrière: 1$ sudo zpool add -f tank cache disk14 2$ sudo zpool status | grep -A 2 "cache" 3 cache 4 disk14 ONLINE 0 0 0 Création du DATASET: ZFS stocke les données dans des blocks logiques appelés records avant de les écrire dans un ou plusieurs blocks matériels (voir ashift plus haut). Dans l’ideal, il faut tenter de faire correspondre la taille des records (RECORDSIZE) avec le type des données qui vont être stockées dans le DATASET Quelques exemples: MariaDB utilise des pages de 16KB pour son moteur innodb PostgreSQL utilise des pages de 8KB KVM avec le format qcow2 utilise un cluster_size de 64KB En dehors de ces cas d’usages particuliers (il en existe probablement d’autres), la recommandation consiste à utiliser la plus grande taille possible (128KB par défaut, configurable de 512B à 16MB et exprimée en puissance de 2). Cette valeur est modifiable à chaud mais avec effet uniquement sur les nouveaux fichiers. De ce que je comprends, le moteur de compression de ZFS intervient entre les records et l’écriture sur les secteurs des disques. En toute logique, plus le RECORDSIZE est élevé et plus le ratio de compression peut être efficace. En contre-partie, la ré-écriture partielle d’un gros record va nécessiter une relecture complète des données depuis le cache ARC ou pire depuis les disques. Un RECORSIZE trop grand risque alors de pénaliser les performances. Dans le doute, je vais partir sur un RECORDSIZE de 1MB qui est la valeur recommandée dans la documentation OpenZFS pour un usage en serveur de fichiers. 1$ sudo zfs create tank/nfs 2$ sudo zfs set recordsize=1M tank/nfs 3$ zfs set quota=128T tank/nfs 4 5$ zfs list 6NAME USED AVAIL REFER MOUNTPOINT 7tank 1,45M 160T 205K none 8tank/nfs 205K 128T 205K none 9 10$ sudo zfs set mountpoint=/srv/nfs tank/nfs 11 12$ $ df -h 13Filesystem Size Used Avail Use% Mounted on 14tank/nfs 128T 1,0M 128T 1% /srv/nfs 15 16$ sudo zfs set com.sun:auto-snapshot=true tank/nfs Par défaut, le DATASET peut consommer l’intégralité du ZPOOL. Je fixe un quota de 128TB (80%) par mesure de précaution. Les 2 dernières commandes ZFS permettent respectivement de monter le dataset (ZFS n’utilise pas le traditionnel fichier /etc/fstab) sur le point de montage /srv/nfs et à activer une propriété pour la gestion automatique des snapshots Gestion des snapshots: Sans dispenser de mettre en place une vrai solution de sauvegarde (un snapshot n’est pas une sauvegarde on est d’accord hein !), les snasphots ZFS peuvent proposer un 1er rempart aux CryptoLockers sur un partage réseau. L’utilisateur dispose également d’une solution rapide pour récupérer des données effacées par mégarde. En effet, ZFS va proposer un dossier caché (.zfs/) à la racine du point de montage qui va contenir l’état du système de fichiers au moment des différents snapshots. Pour faciliter la gestion de la rétention, je vais utiliser le projet zfs-auto-snapshot même si il ne semble plus très actif. 1$ cd /tmp 2$ wget https://github.com/zfsonlinux/zfs-auto-snapshot/archive/master.zip 3$ unzip master.zip 4$ cd zfs-auto-snapshot-master 5$ sudo make install Cette configuration propose : 1 snapshot par heure avec une rétention de 24 heures 1 snapshot par jour avec une rétention de 31 jours 1 snapshot par semaine avec une rétention sur 8 semaines 1 snapshot par mois avec une rétention sur 12 mois Conclusion: Je vais m’arrêter là pour conserver un article pas trop indigeste (du moins je l’espère). Je vous propose d’aborder la configuration NFS (Ganesha), le monitoring (Graphana), le tuning et les benchmarks dans le prochain épisode… Références: blog JRS Systems Arstechnica - understanding zfs performance Pthree - install on debian OpenZFS - module parameters OpenZFS - workload tuning klarasystems - transparent compression OpenZFS - Advanced Format Disks blog Leo Leung wiki ddebian ZFS documentation OpenZFS

ZFS partie 2
Photo by Gary Meulemans on Unsplash
Maintenant que ZFS n’a plus de secret...
Source: Bruno Levasseur
ZFS partie 1
Photo by Alexander Sinn on Unsplash Si vous passez de temps en temps sur ce site, vous avez probablement remarqué un certain penchant pour le stockage. Aujourd’hui on va parler du petit surdoué: ZFS ! Cet article me trotte dans la tête depuis un moment mais j’attendais l’occasion idéale et justement j’ai une grosse bête de 256TB brut qui vient d’arriver sur mon bureau… Dans cette première partie (la mise en oeuvre arrive bientôt..), on va s’attarder sur la terminologie propre à ZFS. Je ne vais pas refaire la genèse complète de ZFS mais simplement retenir que ce système de fichiers a été développé à l’origine par Sun Microsystems avant de tomber dans l’escarcelle d’Oracle après le rachat de 2009. Pour d’obscures raisons de licence, il n’a jamais été intégré au noyau GNU-Linux mais bénéficie d’un support mature grâce au projet ZFS On Linux. Pourquoi ZFS ? ZFS est bien plus qu’un système de fichiers, c’est à la fois un gestionnaire de volume (comme LVM) et un gestionnaire de RAID logiciel (comme mdadm). Il possède des capacités de stockage quasi-illimitées (128 bits - 16 exbioctets) et une liste de fonctionnalités à faire pâlir la concurrence: snapshots compression à la volée déduplication à la volée réplication asynchrone (transfert incrémental de snapshot en ssh) mécanismes de cache (en ram ou sur disque) mécanismes d’auto-correction en ligne (crc / scrubbing) Il est surtout extrêmement robuste grâce à un fonctionnement transactionnel en copy-on-write (CoW: les blocs à modifier ne sont pas directement écrasés mais copiés à un autre emplacement, les métadonnées sont ensuite modifiées pour pointer vers ce nouvel emplacement). Comment ça marche ? La gestion des disques: ZFS est conçu pour utiliser directement les disques en mode JBOD. L’espace de stockage appelé ZPOOL est constitué d’un ou plusieurs VDEV (Virtual Device) pouvant contenir à leur tour un ou plusieurs disques physiques (ou partitions). Les VDEVs supportent différents types d’agrégations des disques pour obtenir de la performance ou inversement de la tolérance à la panne: type nb mini (disque) tolérance de panne (disque) équivalence RAID perte d’espace stripe 1 aucune RAID-O aucune mirror 2 (N-1) disque RAID-1 (N-1)/N disque RAIDz-1 3 1 RAID-5 1 disque RAIDz-2 4 2 RAID-6 2 disques RAIDz-3 5 3 triple parité 3 disques Voici 3 exemples de configurations possibles (parmis d’autres) avec 12 disques physiques (disques de parité en rouge): 3 VDEVs en RAIDz-1 offre la meilleure performance mais avec fiabilité la plus faible. En cas de panne simultanée sur 2 disques d’un même VDEV, c’est tout le zpool qui est perdu ! 2 VDEVs en RAIDz-2 offre le meilleur ratio performance / sécurité mais avec la volumétrie utile la plus faible. 1 VDEV en RAIDz-3 est intéressante pour favoriser la volumétrie typiquement pour un serveur de sauvegarde par exemple sans sacrifier la fiabilité. Je n’ai pas détaillé la version composée de 6 VDEVs de 2 disques en mirroir. Elle permet d’offrir un maximum de performances pour héberger par exemple des machines virtuelles ou des bases de données. On trouve encore beaucoup de ressources sur internet à propos de l’optimisation du nombre de disques dans un RAIDz-x. Il faut savoir que le recours systématique à la compression qui augmente à la fois les débits disques et la volumétrie rend ces considérations un peu obsolètes. Dataset: L’espace de stockage du zpool est consommé par des DATASET qui peuvent être soit: des systèmes de fichiers (plusieurs points de montage par exemple) des snapshots des clones des volumes ZVOL (des blocks pour faire de l’iSCSI ou pour formater avec un autre système de fichiers) La commande zfs list -o space -r <zpool_name> permet de visualiser les datasets avec la consommation d’espace des snapshots. La commande simple zfs list permet de voir les points de montages des datasets. En l’absence de quota défini, les datasets peuvent consommer l’intégralité de l’espace de stockage du pool (zfs set quota=10G <zpool_name>/<dataset_name>). 1$ zfs list -o space -r tank 2NAME AVAIL USED USEDSNAP USEDDS USEDREFRESERV USEDCHILD 3tank 53.7T 2.45T 0B 192K 0B 2.45T 4tank/mariadb 53.7T 3.26G 2.89G 375M 0B 0B 5tank/home 53.7T 2.42T 472G 1.95T 0B 0B 6tank/opt 53.7T 20.6G 189M 20.5G 0B 0B 7 8$ zfs list 9NAME USED AVAIL REFER MOUNTPOINT 10tank 2.45T 53.7T 192K none 11tank/mariadb 3.25G 53.7T 375M /var/lib/mysql 12tank/home 2.42T 53.7T 1.95T /home 13tank/opt 20.6G 53.7T 20.5G /opt Adaptive Replacement Cache: L’ARC (Adaptive Replacement Cache) est un mécanisme de cache utilisant la RAM de la machine. Il stocke à la fois des données et des métadonnées du système de fichiers. Il permet d’améliorer les performances en limitant les accès disques. Par défaut, il peut consommer jusqu’à 50% de la RAM disponible sur le serveur. Quelques commandes pour contrôler le fonctionnement de l’ARC (ici un peu tronquées…) 1$ cat /proc/spl/kstat/zfs/arcstats |grep c_ //serveur de 64GB de RAM 2c_min 4 2095271808 3c_max 4 33524348928 4 5 6$ arc_summary 7------------------------------------------------------------------------ 8ZFS Subsystem Report Fri Nov 19 13:07:57 2021 9Linux 5.4.0-89-generic 0.8.3-1ubuntu12.12 10Machine: server_name 0.8.3-1ubuntu12.12 11 12ARC status: HEALTHY 13 Memory throttle count: 0 14 15 16$ arcstat 17 time read miss miss% dmis dm% pmis pm% mmis mm% arcsz c 1813:20:38 0 0 0 0 0 0 0 0 0 31G 31G Layer 2 ARC: Le L2ARC (Layer 2 ARC) est un cache facultatif sur disque rapide (SSD) alimenté en continu par les données qui vont être prochainement éjectées du cache ARC. Attention, le L2ARC a besoin de RAM pour fonctionner et peut entrainer une perte de performance si la mémoire pour l’ARC n’est pas suffisante. En règle générale on considère qu’un L2ARC ne devrait pas être ajouté à un système avec moins de 64 Go de RAM, et la taille d’un L2ARC ne devrait pas dépasser cinq fois la quantité de RAM. La défaillance d’un disque L2ARC n’est pas critique pour ZFS. On peut se contenter de mettre un seul disque ou à la rigueur plusieurs SSD en striping pour augmenter les performances. 1## analyse des performances du cache L2ARC 2$ cat /proc/spl/kstat/zfs/arcstats | egrep 'l2_(hits|misses)' 3l2_hits 4 6535623 4l2_misses 4 1098440663 ZIL / SLOG / TXG: Mon objectif est de mettre en place un gros NAS avec un partage NFS pour des serveurs de calculs. NFS fonctionne avec des écritures synchrones et va donc solliciter le journal d’intention de ZFS (ZIL: ZFS Intent Log). Son rôle est de garantir la consistance des écritures en cas de panne. Il est souvent assimilé à tort à un cache en écriture. Par défaut, le ZIL est localisé sur les mêmes disques que les données. L’écriture des données synchrones entraine donc une double pénalité (dans le ZIL ET dans le pool de données). Avec des disques mécaniques, les performances en écriture peuvent être fortement pénalisées. La solution consiste à déporter le ZIL sur des périphériques adaptés (SSD ou PMEM), on parle alors de SLOG (Separate ZFS Intent Log) En cas de panne électrique par exemple, l’intégrité du volume de stockage ZFS est garantie par le rejeu des données présentes dans le SLOG. Les groupes de transaction (TXG: Transaction Groups) sont une autre particularité de ZFS. Ils permettent d’organiser les données en mémoire avant de les déverser toutes les 5 secondes (par défaut) dans le pool de stockage pour optimiser les accès disques. ZFS garantit la consistance des données pour chaque groupe de transaction avec un identifiant sur 64 bits . La taille du SLOG est directement liée aux groupes de transaction. Dans ZFS on Linux, les groupes de transactions font 10% de la RAM dans une limite de 4GB. Comme ZFS peut avoir jusqu’à 3 TXG actifs en simultanés, on peut estimer la taille du SLOG à 12GB maximum. Avec 2 SSD (il est recommandé d’utiliser un mirroir) de 32GB en MLC (endurance) on couvre largement le besoin pour un coût dérisoire. La commande zpool status permet de visualiser la configuration complète d’un pool ZFS. une section tank: avec les disques de stockages en RAIDz2 une section Logs: pour le SLOG avec les 2 SSD en mirroir une section cache: pour le SSD L2ARC. 1 NAME STATE READ WRITE CKSUM 2 tank ONLINE 0 0 0 3 raidz2-0 ONLINE 0 0 0 4 disk00 ONLINE 0 0 0 5 disk01 ONLINE 0 0 0 6 disk02 ONLINE 0 0 0 7 disk03 ONLINE 0 0 0 8 disk04 ONLINE 0 0 0 9 disk05 ONLINE 0 0 0 10 raidz2-1 ONLINE 0 0 0 11 disk06 ONLINE 0 0 0 12 disk07 ONLINE 0 0 0 13 disk14 ONLINE 0 0 0 14 disk15 ONLINE 0 0 0 15 disk16 ONLINE 0 0 0 16 disk17 ONLINE 0 0 0 17 logs 18 mirror-2 ONLINE 0 0 0 19 ssd09-part1 ONLINE 0 0 0 20 ssd10-part1 ONLINE 0 0 0 21 cache 22 ssd11-part1 ONLINE 0 0 0 Le schéma ci-dessous les recommandations d’organisations des disques dans ZFS: Conclusion Maintenant que la terminologie ZFS n’a plus de secret pour vous, je vous propose d’aborder concrétement la configuration du serveur dans le prochain article… Références ZIL / SLOG ZIL / SLOG caches ZFS groupes de transactions ARC / L2ARC ARC / L2ARC calculateur RAIDZ dRAID ZFS pool ZFS ZFS 101 ZFS Performance ZFS raid speed capacity stop worrying raidz

ZFS partie 1
Photo by Alexander Sinn on Unsplash
Si vous passez de temps en temps sur ce site,...
Source: Bruno Levasseur
Proxmox HCI
Ce billet propose une approche pragmatique du dimensionnement d’un cluster Proxmox pour faire de l’hyperconvergence. Photo by Marc PEZIN on Unsplash Dans le domaine de la virtualisation, l’hyperconvergence ou HCI (Hyper-Converged Infrastructure) permet de combiner les ressources de calculs, de stockages et de réseaux au sein d’une même solution logicielle. Contrairement aux solutions dites “convergées” comme VMware vSphere, le HCI vous dispense d’une solution de stockage de type SAN (FC, iSCSI) ou NAS (NFS) souvent très onéreuses (sans parler des problèmes bien connus de vendor lock-in). Dans le monde propriétaire, les cadors du marché comme Dell/EMC (VXrail), VMware (vSAN) ou encore Nutanix (AOS) se taillent la part du lion. Il existe pourtant une alternative libre tout à fait crédible avec Proxmox VE (Virtual Environment) et Ceph. Proxmox VE Proxmox VE est une solution bien connue de virtualisation basée sur Debian. Elle permet de créer facilement des clusters pour lancer des machines virtuelles KVM ou des conteneurs LXC. Proxmox VE peut piloter du stockage Ceph depuis la même interface web et proposer ainsi une solution HCI complète avec de nombreux avantages: migration à chaud des VMs (pratique pour faire des maintenances sur les noeuds) haute disponibilité (relance des VMs en cas de crash d’un hôte sur les noeuds resrtants) architecture de type scale-out (il suffit d’ajouter des noeuds pour augmenter les capacités de traitements et de stockages) Proxmox propose également une sauvegarde des VMs parfaitement intégrée: Proxmox Backup Server Ceph Ceph est une solution SDS (Software-Defined Storage) libre de stockage distribuée qui propose nativement des accès en mode bloc (RBD: Rados Block Device), en mode fichier (CephFS: Ceph File System) et objet compatible S3 (RGW: Rados Gateway). Dans Ceph, chaque disque est piloté individuellement par un daemon appelé OSD (Objet Storage Daemon). Ainsi, un noeud avec 12 disques fera tourner 12 daemons OSD). Pour gérer le fonctionnement du cluster, Ceph utilise deux autres daemons appelés MON (monitor) et MGR (manager). Le détail du fonctionnement de Ceph dépasse un peu le cadre de ce billet, je vous renvoie à l’excellente présentation du fondateur si vous souhaitez approfondir cette partie: Dimensionnement Même si l’intégration de CEPH dans Proxmox permet de masquer un peu sa complexité, il est important de bien comprendre certains concepts pour éviter les erreurs de designs. Pour chaque conteneur LXC ou machine virtuelle KVM, Proxmox va créer dans Ceph un périphérique de bloc appelé RBD (Rados Block Device). Sur l’hôte Proxmox qui exécute la VM ou le conteneur, le module Kernel RBD va “découper” ce disque en objets et les répartir sur différents OSD (les disques) en utilisant un algorithme déterministe nommé CRUSH (Controlled Replication Under Scalable Hashing). La protection des données est assurée par une réplication à 3 des objets dans le cluster (CEPH supporte également du code à effacement mais il n’est pas recommandé et proposé par Proxmox). Cette description est volontairement un peu sommaire, dans les faits, le client Ceph va déterminer un PG (Placement Group) lui-même associé à un OSD. Le daemon monitor de Ceph se charge de maintenir la carte de correspondance entre le PG et l’OSD (Sage Weil l’explique plus en détail dans la vidéo proposée). Je vais revenir un peu cette histoire de réplication pour bien comprendre les enjeux. Quand le module RBD cherche à écrire un objet, il va déterminer un OSD primaire et ouvrir une connexion TCP vers l’adresse IP du noeud qui contient l’OSD en question et le port TCP qu’il utile. Ensuite, l’OSD primaire va déterminer les OSD secondaires pour faire les 2 autres copies (réplication à 3) et ouvrir à son tour des connexions vers ces noeuds. Le module RBD obtient l’acquittement de l’écriture seulement lorsque les 2 OSDs secondaires ont confirmé les écritures à l’OSD primaire. Pourquoi je vous détaille ce fonctionnement ? Tout simplement pour introduire un élément fondamental d’une architecture CEPH: Il faut une infra réseau performante (10GbE minimum recommandé), résiliente (prévoir des agrégats) et à faible latence. L’autre conséquence de la réplication à 3, c’est la très faible efficacité du stockage avec ratio utile / brut d’environ 33%. A titre de comparaison un volume en RAID-6 propose une efficacité de 66,6%. En clair, un cluster de 3 noeuds avec chacun 5 disques de 1TB proposera moins de 5TB de stockage utile pour les VMs. Ce chiffre de 3 noeuds n’est pas choisi par hasard puisqu’il s’agit d’un minimum pour déployer un cluster HCI Proxmox en production: un nombre impair facilite l’obtention d’un quorum dans Proxmox, le failure domain de Ceph est défini au niveau noeud. Pour obtenir un FTT (Failure To Tolerate) d’un noeud, il faut en faut au minimum 3 (min_size = 2) ceph recommande au moins 3 monitors dans un cluster (daemon qui maintient la cohérence du cluster) Il s’agit bien ici d’un minimum, Ceph étant un stockage de type scale-out, les performances augmentent avec le nombre de noeuds et d’OSDs (disques). Cas d’étude Pour illustrer un peu la démarche et les differentes ressources nécessaires, je vous propose un cas d’étude classique pour un petit cluster: nombre de noeuds: 3 nombre de VMs: 100 volumétrie utile cible: (40GB par VM) x 100 VMs = 4TB 2 vCPU et 2 GB de vRAM par machine virtuelle usages: WEB, DB, SMTP, DNS, LDAP… possibilité de perdre 1 noeud Le stockage Vous l’avez compris, le dimensionnement des noeuds du cluster Proxmox découle directement de la volumétrie de stockage dont vous souhaitez disposer (et accessoirement un peu de votre budget). Là encore, il faut plonger dans les entrailles de Ceph pour bien comprendre les différentes alternatives. Pour accéder au disque, l’OSD ne va pas utiliser un système de fichiers standard (EXT4, XFS, ZFS…) mais un procédé nommé BlueStore qui consiste à attaquer directement un periphérique de bloc brut (raw device). L’équivalent de la table d’inodes va être géré avec une base de données de type clé-valeur RockDB. Concrètement, chaque disque dur va être découpé avec LVM en 2 volumes logiques. Un premier volume de faible dimension formaté en XFS va contenir la RockDB et un journal d’écriture de type WAL (Write-Ahead Log). Le reste du disque est utilisé directement sans formatage pour stocker les objets. BlueStore est très efficace avec des SSD mais il peut être pénalisé par les temps d’accès plus long et les faibles capacités en IOPS des disques mécaniques. Heureusement, il existe une solution pour améliorer les performances qui consiste à délocaliser la rockdb et le journal sur des disques SSD plus rapides. La documentation Suse SES 7 recommande jusqu’à 6 OSD géré par un SSD et 12 OSD pour un disque NVMe. Là, il faut bien comprendre qu’en cas de défaillance du SSD, il faut reconstruire les données de tous les OSD qu’il gère. Ce n’est pas un problème en soit puisque Ceph est très robuste et il est possible de reconstruire facilement les disques perdus à partir des réplicats (merci le réseau 10GbE) mais l’impact sur les performances risque de rendre votre cluster beaucoup moins réactif. Il faut donc faire preuve de prudence si vous avez un faible nombre d’OSDs et de noeuds. Le dimensionnement standard de ces SSD consiste à réserver au moins 2% (jusqu’à 4% pour faire du stockage S3) de la taille du disque mécanique par OSD à gérer. Exemple: mon noeud contient 4 disques mécaniques de 2TB. Je vais provisionner un SSD de: Nb disques OSD x taille disque x 0,02 4 x 2TB x 0,02 = 0,16TB (~160GB) Parmi les autres points à surveiller au niveau matériel, il faut s’assurer que Ceph peut accéder directement à chaque disque en mode JBOD au niveau du contrôleur disques. Dans le cas contraire, il est parfois possible de faire des configurations RAID-0 individuelles. Il est temps de mettre en pratique toutes ces informations avec notre cas d’étude. Voici les questions auxquelles il va falloir répondre: quel nombre de disques: en fonction du nombre d’emplacements disponibles sur le serveur quelle technologie de disques: HDD, SSD en fonction du budget et des performances attendues (IOPS) quelle capacité individuelle: en fonction de la volumétrie à atteindre (rappel : 4TB) A partir de là, il n’y a pas de recette miracle pour résoudre cette équation et je pars du principe qu’il faut privilégier du stockage SSD. Je consulte un peu les tarifs et trouve un bon compromis prix / volumétrie avec des SSD de 1TB NVMe (par exemple: Samsung 980 PRO NVMe M.2 PCIe 4.0 1TB). Ca tombe bien, 1TB c’est également la taille minimale recommandée dans la documentation Ceph. Autre argument en faveur de ce choix, ce disque est de catégorie MLC avec une endurance adaptée pour ce type d’usage (on ne va pas miner du Chia hein !) J’entre 1TB par disque et une volumétrie cible de 4TB dans le calculateur Ceph et je fais varier le nombre de disque pour atteindre 4TB à 85% de taux d’utilisation brut du cluster (à 95% il passe en lecture seule). Sur 3 noeuds, j’obtiens un total de 5 disques par noeud pour tout juste 4TB. Par sécurité, il faudrait donc plutôt prévoir 6 disques de 1TB par noeud. Avec une volumétrie plus conséquente, il faudrait envisager des disques mécaniques. Dans ce cas, il faudrait prévoir si possibles des disques SAS 10K tr/mn et les disques SSD pour BlueStore. Voici une petite simulation pour le cas d’étude sur la base de disques de 1,2TB: avec le calculateur, j’obtiens 5 disques par noeuds pour 5TB utiles. Auxquels j’ajoute un SSD de 250GB NVMe en MLC largement suffisant (5 x 1,2TB x 0,02) Dans les 2 cas (SSD ou mécanique), il ne faut pas oublier les disques pour installer Proxmox et Ceph. Deux SSD de 250GB en RAID-1 font parfaitement l’affaire (exemple: Samsung 980 M.2 250GB). Les processeurs En plus de la charge de travail propre aux VMs, il faut considérer celle induite par le fonctionnement du stockage CEPH. Voici les recommandations fournies par SUSE pour SES-7: 1x thread par disque rotatif 2x threads par disque SSD 4x threads par disque NVMe Les 4 threads pour les disques NVMe paraissent un peu excessifs. Je vais retenir 12 threads par noeud dans notre cas d’étude (2 threads x 6 disques) auxquels il faut ajouter les ressources pour les services monitors et managers de CEPH (4 threads toujours selon SUSE). Dans notre exemple, chaque noeud doit pouvoir supporter la charge de 50 VMs (100 VMs / 2 en cas de panne d’un noeud). J’utilise une méthode de calcul un peu empirique qui consiste à compter au moins un thread par VM. Bien sûr ça n’engage que moi mais la surallocation CPU est plutôt efficace et ce sont des valeurs confortables que je constate régulièrement sur d’autres clusters. Si j’applique cette méthode, j’obtiens : 50 threads VMs + 16 threads CEPH = 66 threads. Dans la gamme Intel, il faudrait donc partir sur une configuration minimale avec deux processeurs Xeon Silver 4316 (2 x 20 coeurs HT @ 2,3Ghz). La mémoire La surallocation mémoire (memory overcommitment) est toujours moins efficace que pour les CPUs. J’ai tendance à ne pas jouer la contention dans ce domaine. La mémoire consommée par CEPH est couramment évaluée (documentation REDHAT RHCS 4 et SUSE SES 7) à au moins 4GB par OSD. Là encore, il faut aussi prendre en compte les monitors et les managers (+32GB dans la doc Ceph) Toujours dans notre cas d’étude, j’obtiens pour chaque noeud : (50 VMs * 2GB RAM) + (5 OSD x 4GB + 32GB) = 152 GB de RAM. La configuration la plus proche serait donc 12 barrettes de 16GB (192 GB de RAM) soit 6 barrettes par socket CPU. Bien que fonctionnelle et économique, cette configuration ne permet pas d’exploiter toute la bande passante des processeurs modernes qui possèdent souvent 8 canaux mémoires. Voici un petit récapitulatif sur les processeurs récents: Intel Xeon de 1er et 2nd génération (Skylake et Cascade Lake): 6 canaux Intel Xeon de 3ème génération (Ice Lake): 8 canaux AMD EPYC de 1er à 4ème génération: 8 canaux Si votre budget le permet, il est recommandé d’utiliser une configuration mémoire équilibrée (balanced memory configuration) Ce graphique extrait de la documentation Dell met en évidence le gain de bande passante en fonction des banques mémoires occupées. Le réseau Comme je l’ai déjà évoqué un peu plus haut, Ceph sollicite beaucoup le réseau pour son fonctionnement normal et davantage encore dans les phases de reconstructions. En production, il ne faut pas hésiter à multiplier les interfaces pour bien segmenter les usages. Ainsi, on peut distinguer 4 types de réseaux: les machines virtuelles le stockage Ceph l’administration (l’interface Web de Proxmox) le réseau cluster (pour Corosync) Je vous propose le découpage suivant en partant du principe que les serveurs seront dans une armoire avec 2 commutateurs top-of-rack: Les machines virtuelles Un agrégat LACP de 2 interfaces 1GbE (minimum) avec les VLANs nécessaires aux VMs dans un trunk. Le stockage Ceph Un agrégat LACP de 2 interfaces 10GbE (minimum) dans un VLAN et un sous-réseau non routé dédié. N’hésitez pas à activer les jumbo-frames qui apportent un gain de performance significatif. Si vous ne pouvez pas disposer d’une connectivité 10GbE, Proxmox propose une topologie full mesh qui permet de relier directement les noeuds avec des DAC 10GbE sans équipements actifs. L’administration et le reseau cluster Ces 2 réseaux produisent peu de trafic et il peut être intéressant de les mutualiser dans un agrégat de 2 interfaces 1GbE en LACP pour avoir de la redondance. Pour autant, il reste important d’isoler les 2 flux dans des VLANs respectifs. Le réseau cluster est un élément-clé du fonctionnement de Proxmox et il mérite un sous-réseau dédié non routé pour correctement l’isoler. Attention, il ne doit jamais être mutualisé avec les interfaces de stockage Ceph. Si vous devez faire des concessions, il vaut mieux choisir les interfaces dédiées aux machines virtuelles. Divers Ceph est particulièrement sensible à la dérive temporelle et nécessite obligatoirement une synchronisation NTP de tous les noeuds. Enfin, comme toute infrastructure sensible, il est important de bien superviser le cluster. Si vous n’avez pas encore d’outil pour ça, je vous recommande Zabbix qui possède des extensions pour Proxmox et Ceph. Conclusion Ce billet est une synthèse des nombreuses documentations et autres blogs sur ce sujet. Si vous repérez des incohérences ou des approximations, n’hésitez pas à me les signaler pour que je puisse l’améliorer. Il ne reste plus qu’à mettre en pratique tout cela dans une preuve de concept qui fera l’objet du prochain article. Sources documentation Proxmox HCI documenatation Ceph documentation Proxmox documentation Suse SES-7 documentation RedHat RHCS4 balanced memory offres hardwares

Proxmox HCI
Ce billet propose une approche pragmatique du dimensionnement d’un cluster...
Source: Bruno Levasseur
Restic avec MinIO
Inutile en 2021 de rappeler l’importance de faire des sauvegardes. Dans ce billet on va parler de Restic et stockage objet avec MinIO. Photo by Jason Dent on Unsplash Après un premier article consacré à Borg, je vais vous parler maintenant de Restic. Un outil dont la philosophie est similaire mais qui propose deux atouts majeurs: il est compatible Windows et supporte de nombreuses solutions de stockage cloud. Voici une rapide comparaison des deux solutions: BorgBackup Restic language Python Go backend ssh ssh, Ceph, S3, MinIO… déduplication oui oui compression oui non chiffrement facultatif obligatoire support Windows non oui Restic utilise un mécanisme de déduplication qui découpe les fichiers à sauvegarder en blobs de tailles variables ( entre 512KiB et 8MiB avec une taille moyenne à 1MiB). Entre deux sauvegardes, seuls les blobs modifiés sont envoyés sur le dépôt. Cela fonctionne même si des octets sont insérés ou supprimés à des positions arbitraires dans le fichier. Serveur de sauvegarde Puisque Restic supporte nativement le stockage objet, je vais en profiter pour mettre en place un serveur MinIO. Cette solution open source est simple à mettre en oeuvre et permet de déployer des architectures distribuées à hautes performances compatibles avec l’API S3 d’Amazon. Sur ce petit projet, je vais me contenter d’utiliser MinIO sous la forme d’un simple conteneur Docker présenté par le traditionnel couple Traefik - Let’s Encrypt (voir mon précédent article pour l’installation de la plate-forme). J’enfonce probablement des portes ouvertes mais assurez-vous que vos sauvegardes se trouvent dans un autre datacenter que vos données primaires. Si vous cherchez un VPS capacitif abordable, vous pouvez jeter un coup d’oeil à l’offre de cet hébergeur. Docker-Compose Comme d’habitude, l’installation va se limiter à créer un docker-compose : 1sudo mkdir /opt/minio 2sudo touch /opt/minio/docker-compose.yml et à effectuer quelques modifications dans l’exemple fournit: remplacer le mot de passe admin de MinIO modifier le champ host du service ( backup.exemple.tld dans l’exemple ) 1version: '3' 2services: 3 minio: 4 image: minio/minio:latest 5 container_name: minio 6 volumes: 7 - data:/data 8 expose: 9 - "9000" 10 environment: 11 MINIO_ROOT_USER: admin 12 MINIO_ROOT_PASSWORD: V3rYD1ff1culT! 13 command: server /data 14 healthcheck: 15 test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] 16 interval: 30s 17 timeout: 20s 18 retries: 3 19 networks: 20 - "traefik_lan" 21 labels: 22 - "traefik.enable=true" 23 - "traefik.docker.network=traefik_lan" 24 - "traefik.http.routers.minio.rule=Host(`backup.exemple.tld`)" 25 - "traefik.http.routers.minio.entrypoints=websecure" 26 - "traefik.http.routers.minio.tls=true" 27 - "traefik.http.routers.minio.tls.certresolver=letsencrypt" 28 29networks: 30 traefik_lan: 31 external: true 32 33volumes: 34 data: Cette configuration utilise un named volume data pour stocker les backups. Il est localisé par défaut dans /var/lib/docker/volumes. Si vous utilisez une partition /var séparée, il faudra la dimensionner en conséquence (merci LVM…) ! Après avoir lancer le service avec la commande : docker-compose -f /opt/minio/docker-compose.yml up -d, je peux accéder à l’interface web de MinIO avec les identifiants définis dans le docker-compose. Par défaut, MinIO ne fournit pas d’interface d’administration. Pour cela vous pouvez ajouter la brique console MinIO. Dans mon cas d’usage, je vais me contenter du client MinIO en CLI Minio Client: Sur mon poste, je commence par installer le client MinIO: 1$ sudo curl -L https://dl.min.io/client/mc/release/linux-amd64/mc -o /usr/local/bin/mc 2$ sudo chmod +x /usr/local/bin/mc Je crée un alias nommé backup pour simplifier l’accès au serveur MinIO avec les identifiants définis dans le fichier docker-compose.yml 1mc alias set <ALIAS> [SERVER_URL] [ACCESSKEY] [SECRETKEY] 2 3mc alias set backup https://backup.exemple.tld admin V3rYD1ff1culT! 4 5# pour vérifier le bon fonctionnement: 6mc admin info <ALIAS> 7 8mc admin info backup 9● backup.exemple.tld 10 Uptime: 2 weeks 11 Version: 2021-05-18T00:53:28Z 12 Network: 1/1 OK 13 14401 KiB Used, 2 Buckets, 1 Object Attention: la création d’un alias ajoute les identifiants en clair dans le fichier de configuration .mc/config.json. Tout fonctionne correctement. Pour autant, il n’est pas question d’utiliser le compte admin du serveur MinIO pour sauvegarder les différents serveurs. Pour chacun d’eux, je vais créer: un bucket S3 dédié: <HOSTNAME>-bucket un compte d’accès: <HOSTNAME>-user des droits d’accès au bucket pour ce compte: <HOSTNAME>-policy Création du bucket: 1mc mb <ALIAS>/<BUCKET-NAME> 2 3mc mb backup/myserver-bucket 4Bucket created successfully `backup/myserver-bucket`. 5 6mc ls backup 7[2021-06-02 18:40:29 CEST] 0B myserver-bucket/ Création du compte d’accès: 1mc admin user add <ALIAS> [ACCESSKEY] [SECRETKEY] 2 3mc admin user add backup myserver-user V3rYs3cr3tkeY! 4Added user `myserver-user` successfully. 5 6# pour lister les utilisateurs 7mc admin user list <ALIAS> 8mc admin user list backup 9enabled myserver-user Création des droits d’accès: Pour cela il faut créer un fichier myserver-policy.json et le charger sur le serveur MinIO. Dans cet exemple, j’autorise à lister le bucket myserver-bucket et toutes les opérations possibles sur les objets enfants myserver-bucket/* 1{ 2 "Version": "2012-10-17", 3 "Statement": [ 4 { 5 "Sid": "ListObjectsInBucket", 6 "Effect": "Allow", 7 "Action": ["s3:ListBucket"], 8 "Resource": ["arn:aws:s3:::myserver-bucket"] 9 }, 10 { 11 "Sid": "AllObjectActions", 12 "Effect": "Allow", 13 "Action": "s3:*Object", 14 "Resource": ["arn:aws:s3:::myserver-bucket/*"] 15 } 16 ] 17} il faut maintenant charger cette policy sur le serveur MinIO: 1mc admin policy add <ALIAS> <POLICY-NAME> </PATH/TO/JSON-FILE> 2 3mc admin policy add backup myserver-policy myserver-policy.json 4Added policy `myserver-policy` successfully. 5 6# pour lister les policies disponibles 7mc admin policy list backup 8diagnostics 9readonly 10readwrite 11myserver-policy 12writeonly 13 14# pour obtenir le détail d'une policy 15mc admin policy info backup myserver-policy 16{ 17 "Version": "2012-10-17", 18 "Statement": [ 19 { 20 "Sid": "ListObjectsInBucket", 21 "Effect": "Allow", 22 "Action": [ 23 "s3:ListBucket" 24 ], 25 "Resource": [ 26 "arn:aws:s3:::myserver-bucket" 27 ] 28 }, 29 { 30 "Sid": "AllObjectActions", 31 "Effect": "Allow", 32 "Action": [ 33 "s3:*Object" 34 ], 35 "Resource": [ 36 "arn:aws:s3:::myserver-bucket/*" 37 ] 38 } 39 ] 40} et l’associer à l’utilisateur myserver-user 1mc admin policy set <ALIAS> <POLICY-NAME> user=<USER-NAME> 2 3mc admin policy set backup myserver-policy user=myserver-user 4Policy `myserver-policy` is set on user `myserver-user` 5 6# pour vérifier l'association 7mc admin user list backup 8enabled myserver-user myserver-policy La configuration est terminée. Je peux créer un nouvel alias (myserver) pour tester l’accès au serveur MinIO avec ce nouveau compte 1mc alias set myserver https://backup.exemple.tld myserver-user V3rYs3cr3tkeY! 2 3# pour lister les accès: 4mc ls myserver 5[2021-06-02 11:04:46 CEST] 0B myserver-bucket/ 6 7# pour copier un fichier dans le bucket: 8mc cp <FILE> <ALIAS>/<BUCKET-NAME> 9mc cp demo.pdf myserver/myserver-bucket 10demo.pdf: 24.64 KiB / 24.64 KiB ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 257.50 KiB/s 0s et en version web: Le serveur MinIO est opérationnel et prêt à recevoir les sauvegardes Restic… Serveur à sauvegarder Installation de Restic Comme je le disais en intro, Restic est écrit en Go. On peut directement télécharger la dernier version disponible sur le dépôt Github ou faire confiance à son gestionnaire de paquets habituel (souvent avec une version plus ancienne) 1wget https://github.com/restic/restic/releases/download/v0.12.0/restic_0.12.0_linux_amd64.bz2 2bunzip2 restic_0.12.0_linux_amd64.bz2 3mv restic_0.12.0_linux_amd64 /usr/local/sbin/restic 4chmod +x /usr/local/sbin/restic 5restic version 6restic 0.12.0 compiled with go1.15.8 on linux/amd64 Automatisation Restic ne fournit pas d’outil pour planifier les sauvegardes. On peut utiliser un simple script shell dans un cron mais j’ai préféré opter pour runrestic qui propose un wrapper en python. 1sudo pip3 install --upgrade runrestic 2runrestic -v 3runrestic 0.5.23 Auquel il faut ajouter un fichier de configuration qui va contenir: l’adresse du dépôt S3 ( https://URL/bucket_name ) les identifiants d’accès S3 (myserver-user) la clé de chiffrement des données (RESTIC_PASSWORD) la liste des arborescences à sauvegarder (/opt) les périodes de rétentions (3 mois) 1# /etc/runrestic.toml 2repositories = [ 3 "s3:https://backup.exemple.tld/myserver-bucket" 4 ] 5 6[environment] 7RESTIC_PASSWORD = "VeRySecuredPassPhrase" 8AWS_ACCESS_KEY_ID= "myserver-user" 9AWS_SECRET_ACCESS_KEY= "V3rYs3cr3tkeY!" 10 11[backup] 12sources = [ 13 "/opt" 14 ] 15 16[prune] 17keep-daily = 7 18keep-weekly = 4 19keep-monthly = 3 Je peux maintenant initialiser le dépôt 1runrestic init 2[(0, 'created restic repository a9d0eea4dc at s3:https://backup.exemple.tld/myserver-bucketnnPlease note that knowledge of your password is required to accessnthe repository. Losing your password means that your data isnirrecoverably lost.n')] ajouter un service dans systemd 1# /etc/systemd/system/runrestic.service 2[Unit] 3Description=runrestic backup 4 5[Service] 6Type=oneshot 7ExecStart=/usr/local/bin/runrestic avec un timer unit pour une exécution quotidienne 1# /etc/systemd/system/runrestic.timer 2[Unit] 3Description=Run runrestic backup 4 5[Timer] 6OnCalendar=daily 7Persistent=true 8 9[Install] 10WantedBy=timers.target et enfin activer le service 1sudo systemctl enable runrestic.timer 2sudo systemctl start runrestic.timer L’option shell de runresctic permet de sourcer les variables d’environnements nécessaires pour utiliser directement les commandes de Restic. 1runrestic shell 2# obtenir la liste des snapshots 3restic snapshots 4repository a9d0eea4dc opened successfully, password is correct 5ID Time Host Tags Paths 6----------------------------------------------------------------------- 74603f061 2021-06-20 00:00:02 myserver.exemple.tld /opt 8bd220334 2021-06-27 00:00:02 myserver.exemple.tld /opt 9ff32d4a9 2021-06-29 00:00:05 myserver.exemple.tld /opt 1009d57259 2021-06-30 00:00:06 myserver.exemple.tld /opt 11bb136478 2021-07-01 00:00:07 myserver.exemple.tld /opt 12e568c376 2021-07-02 00:00:07 myserver.exemple.tld /opt

Restic avec MinIO
Inutile en 2021 de rappeler l’importance de faire des sauvegardes. Dans ce...
Source: Bruno Levasseur
Nouvelle config Traefik
Déjà 10 mois que mon infra Docker / Traefik ronronne tranquillement…il est temps de tout casser ! Photo by Denys Nevozhai on Unsplash J’ai commencé mon infra auto-hébergée en août dernier pour me faire un peu la main sur Docker et Traefik. Depuis beaucoup d’eau a coulé sous les ponts (oui je suis normand…) et il est temps de revoir un peu la configuration. Pourquoi changer un truc qui marche ? simplifier la configuration: avec une configuration globale incluse dans le docker-compose.yml au lieu du traditionnel “traefik.toml” ajouter un peu de sécurité: avec des passwords gérés par des Docker secrets gérer les redirections HTTPS au niveau global au lieu de par service passer la config du dashboard Traefik dans le docker-compose.yml Configuration Je pars du principe que vous avez un serveur clean avec Docker, docker-compose installés (sinon voir la doc officielle) et une petite idée de comment ça marche… Arborescence Comme dans la précédente config, l’ensemble de la configuration des conteneurs est stocké dans /opt 1/opt 2├── traefik 3│ ├── config 4│ │ ├── acme.json 5│ │ ├── dynamic-conf 6│ │ │ ├── tls.toml 7│ └── docker-compose.yml J’ai conservé le fonctionnement dynamique de Traefik avec le dossier dynamic-conf pour prendre en charge des modifications sans avoir à relancer le conteneur. Voici les commandes pour créer l’arborescence, le fichier docker-compose.yml et le fichier acme.json qui va contenir les certificats TLS et les clés privées obtenus grâce à Let’s Encrypt: 1sudo mkdir -p /opt/traefik/config/dynamic-conf 2sudo touch /opt/traefik/config/acme.json /opt/traefik/docker-compose.yml 3sudo chmod 600 /opt/traefik/config/acme.json Docker-Compose J’utilise un bridge Docker dédié pour l’interconnexion entre les conteneurs et Traefik. 1 sudo docker network create --driver=bridge --subnet=172.19.0.0/24 traefik_lan Sans cette astuce et si vous laissez docker-compose créer le reseau pour vous, vous ne pourrez pas stopper le conteneur traefik sans couper tous les autres conteneurs qui utiliseraient ce même réseau. Il ne reste plus qu’à éditer le fichier docker-compose.yml et modifier les lignes: email@exemple.tld: mettre votre adresse email traefik.exemple.tld: indiquer l’url choisie pour l’accès au dashboard Traefik admin:$$xxxxxxxxx$$xxxxxxx/: basic auth pour protéger l’accès au dashboard Pour générer le hash du password, vous pouvez utiliser la commande suivante pour doubler tous les caractères $: 1echo $(htpasswd -nb user password) | sed -e s/$/$$/g Le contenu du fichier /opt/traefik/docker-compose.yml: 1version: '3' 2services: 3 traefik: 4 image: traefik:v2.4.8 5 container_name: traefik 6 restart: unless-stopped 7 command: 8 - "--providers.docker=true" 9 - "--providers.docker.exposedbydefault=false" 10 - "--providers.file.directory=/etc/traefik/dynamic-conf" 11 - "--providers.file.watch=true" 12 - "--api.dashboard=true" 13 - "--entrypoints.web.address=:80" 14 - "--entrypoints.websecure.address=:443" 15 - "--entrypoints.web.http.redirections.entrypoint.to=websecure" 16 - "--entrypoints.web.http.redirections.entrypoint.scheme=https" 17 - "--certificatesResolvers.letsencrypt.acme.email=email@exemple.tld" 18 - "--certificatesResolvers.letsencrypt.acme.storage=acme.json" 19 - "--certificatesResolvers.letsencrypt.acme.tlsChallenge=true" 20 labels: 21 - "traefik.enable=true" 22 - "traefik.http.routers.dashboard.rule=Host(`traefik.exemple.tld`)" 23 - "traefik.http.routers.dashboard.service=api@internal" 24 - "traefik.http.routers.dashboard.entrypoints=websecure" 25 - "traefik.http.routers.dashboard.middlewares=auth-dashboard" 26 - "traefik.http.middlewares.auth-dashboard.basicauth.users=admin:$$xxxxxxxxx$$xxxxxxx/" 27 - "traefik.http.routers.dashboard.tls=true" 28 - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt" 29 networks: 30 - traefik_lan 31 ports: 32 - 80:80 33 - 443:443 34 volumes: 35 - /var/run/docker.sock:/var/run/docker.sock:ro 36 - $PWD/config/acme.json:/acme.json 37 - $PWD/config:/etc/traefik:ro 38networks: 39 traefik_lan: 40 external: true On peut maintenant lancer le conteneur Traefik: 1docker-compose -f /opt/traefik/docker-compose.yml up -d et vérifier que tout fonctionne correctement: https://traefik.exemple.tld Utiliser Traefik Pour exposer un service frontalisé en HTTPS par Traefik, il suffit de déclarer quelques labels. Voici un exemple avec un simple serveur NGINX: 1version: '3' 2services: 3 app: 4 container_name: mysite 5 image: nginx:latest 6 restart: unless-stopped 7 networks: 8 - traefik_lan 9 volumes: 10 - app:/usr/share/nginx/html:ro 11 expose: 12 - "80" 13 environment: 14 - NGINX_HOST=mysite.exemple.tld 15 - NGINX_PORT=80 16 labels: 17 - "traefik.enable=true" 18 - "traefik.http.routers.blog.rule=Host(`mysite.exemple.tld`)" 19 - "traefik.http.routers.blog.entrypoints=websecure" 20 - "traefik.http.routers.blog.tls=true" 21 - "traefik.http.routers.blog.tls.certresolver=letsencrypt" 22networks: 23 traefik_lan: 24 external: true 25volumes: 26 app: Améliorer la configuration TLS: Pour finir cet article sur Traefik, nous allons profiter la fonctionnalité de configuration dynamique pour sécurisé la partie TLS. Pour cela, il suffit d’ajouter un fichier /opt/traefik/config/dynamic-conf/tls.toml. Le dossier /opt/traefik/config/dynamic-conf est en bind mount dans le conteneur Traefik et la directivement providers.file.watch=true" permet de prendre compte tous les événements dans ce dossier. 1[tls] 2 [tls.options] 3 [tls.options.default] 4 minVersion = "VersionTLS12" 5 sniStrict = true 6 cipherSuites = [ 7 "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", 8 "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", 9 "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", 10 "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", 11 "TLS_AES_256_GCM_SHA384", 12 "TLS_CHACHA20_POLY1305_SHA256" 13 ] 14 curvePreferences = ["CurveP521","CurveP384"]

Nouvelle config Traefik
Déjà 10 mois que mon infra Docker / Traefik ronronne tranquillement…il...
Source: Bruno Levasseur
Ansible
Dans le cadre de la mise en place d’une formation Docker, j’ai récemment dû installer une dizaine de serveurs identiques. C’est l’occasion idéale pour rédiger un petit article sur Ansible… Photo by Clément Hélardot on Unsplash Ansible fait parti de la famille des outils de gestion de configuration à l’instar de Puppet, SaltStach et Chef. Il s’inscrit dans la mouvance IaC (Infrastructure As Code) qui permet de gérer son infrastructure à partir de fichiers descripteurs facilement versionnables dans un Git. Par rapport à ces concurrents, Ansible se distingue par: un fonctionnement “agentless” qui repose sur SSH une faible courbe d’apprentissage un fonctionnement en mode push stateless (ansible ne stocke pas l’état des serveurs) indempotence (résultat identique à chaque exécution) Un petit tour du coté de Google Trends permet de mesurer l’évolution de l’intérêt pour les différents challengers depuis 2015: Le fonctionnement Ansible utilise un simple fichier d’inventaire au format INI pour se connecter en SSH sur les machines à configurer . La liste des tâches à réaliser est décrite dans un fichier YAML appelé playbook. Ansible utilise des petits programmes appelés modules ou plugins pour exécuter les tâches sur les machines. Avec Ansible, vous disposez de plus de 4000 modules qui permettent de pratiquement tout gérer. Pour cette entrée en matière, nous allons nous concentrer sur ces composants de bases : Inventaire: fichier au format INI qui contient la liste des machines à gerer (l’utilisation d’une database est possible) Node Mannager ( ou control node): la machine qui exécute les playbooks Playbook: un fichier au format YAML qui contient une liste de tâches ou de rôles à exécuter sur les machines de l’inventaire Rôle: un ensemble de tâches factorisables Module (ou plugins): bout de code généralement en python qui peut être utilisé directement en CLI ou dans une tâche de playbook Le contexte Pour cette mise en oeuvre, j’ai déployé 10 VMs sous Debian 10 avec un utilisateur ansible membre du groupe sudo configuré en mode nopasswd ( %sudo ALL=(ALL:ALL) NOPASSWD:ALL ). Cet utilisateur me permet de me connecter aux serveurs avec ma clé publique SSH ( en clair ma clé publique SSH est présente dans le fichier /home/ansible/.ssh/authorized_keys des serveurs ). L’installation Ansible est un outil écrit en python et la façon la plus de l’installer est d’utiliser PIP. Vous pouvez également utiliser votre gestionnaire de paquets habituel (voir la documentation en fonction de votre OS). Ansible s’installe uniquement sur le node manager qui peut être un serveur dédié ou simplement votre laptop. Une fois installé, je vérifie que tout fonctionne correctement: 1$ ansible --version 2ansible 2.10.8 3 4$ ansible all -i "localhost," -c local -m ping 5localhost | SUCCESS => { 6 "ansible_facts": { 7 "discovered_interpreter_python": "/usr/bin/python" 8 }, 9 "changed": false, 10 "ping": "pong" 11} La 2nd commande permet d’utiliser Ansible en mode ac-hoc avec le module ping en local ( -c local ). Creation de l’arborescence Pour ce projet, je vais créer une arborescence standard avec un répertoire Ansible, des sous-dossiers host_vars et group_vars ainsi qu’un fichier d’inventaire hosts. Pour plus de détails sur la structure des dossiers et les différentes alternatives, vous pouvez consulter cette documentation. 1$ mkdir Ansible && cd $_ 2$ mkdir host_vars group_vars roles 3$ touch hosts L’inventaire L’inventaire contient la liste des machines gérées par Ansible. Pour notre projet, j’utilise un simple fichier statique mais il est possible d’interroger une base de données. Pour plus de détails sur les différentes options d’utilisations de l’inventaire , vous pouvez consulter la documentation. Voici un exemple avec une classification par type mais on peut aussi envisager un découpage géographique, par système d’exploitation… 1[webservers] 2foo.example.tld 3bar.example.tld 4 5[dbservers] 6one.example.tld 7two.example.tld 8three.example.tld Pour notre cas, je vais renseigner le fichier hosts avec une section docker qui correspond à un groupe de serveurs ( docker-00 à docker-09 ): 1$ cat hosts 2[docker] 3docker-[00:09].exemple.tld Ansible en mode ad-hoc Pour tester notre inventaire, j’utilise Ansible en mode interactif (ad-hoc): 1$ ansible docker -i hosts -u <ssh_username> -m shell -a 'cat /etc/issue' 2 \____/ \______/ \_______________/ \______/ \_/ \______________/ 3 1 2 3 4 5 6 docker : détermine la portée dans le fichier d’inventaire ( ici le groupe docker), on peut aussi utiliser all -i hosts : spécifie le path du fichier d’inventaire ( ici dans le répertoire courant ) -u <ssh_username> : spécifie l’utilisateur autorisé à se connecter en ssh avec une clé publique (root par défaut) -m shell: indique l’utilisation du module shell -a: indique les arguments à passer au module cat /etc/issue: spécifie la commande à exécuter sur la machine distante 1$ ansible docker -i hosts -u <ssh_username> -m shell -a 'cat /etc/issue' 2 3docker-04.exemple.tld | CHANGED | rc=0 >> 4Debian GNU/Linux 10 n l 5 6docker-03.exemple.tld | CHANGED | rc=0 >> 7Debian GNU/Linux 10 n l 8 9docker-00.exemple.tld | CHANGED | rc=0 >> 10Debian GNU/Linux 10 n l 11 12docker-02.exemple.tld | CHANGED | rc=0 >> 13Debian GNU/Linux 10 n l 14 15docker-01.exemple.tld | CHANGED | rc=0 >> 16Debian GNU/Linux 10 n l 17 18docker-05.exemple.tld | CHANGED | rc=0 >> 19Debian GNU/Linux 10 n l 20 21docker-09.exemple.tld | CHANGED | rc=0 >> 22Debian GNU/Linux 10 n l 23 24docker-08.exemple.tld | CHANGED | rc=0 >> 25Debian GNU/Linux 10 n l 26 27docker-06.exemple.tld | CHANGED | rc=0 >> 28Debian GNU/Linux 10 n l 29 30docker-07.exemple.tld | CHANGED | rc=0 >> 31Debian GNU/Linux 10 n l On constate qu’Ansible exécute la commande cat /etc/issue sur les serveurs contenus dans la section docker en parallèle (5 par défauts mais configurable avec l’option -f 10 pour 10 threads parallèles ) On peut lancer des commandes nécessitant une élévation de privilèges via sudo avec l’option -b ( –become ). Dans cet exemple volontairement tronqué, j’utilise le module package pour installer l’outil htop : 1$ ansible docker -i hosts -u <ssh-username> -m package -a 'name=htop state=latest' -b 2docker-04.exemple.tld | CHANGED => { 3 "ansible_facts": { 4 "discovered_interpreter_python": "/usr/bin/python" 5 }, 6 "cache_update_time": 1617896165, 7 "cache_updated": false, 8 "changed": true, 9 "stderr": "", 10 "stderr_lines": [], 11 "stdout": "Reading package lists...nBuilding dependency tree... 12 "stdout_lines": [ 13 "Reading package lists...", 14 "Building dependency tree...", 15 "Reading state information...", 16 "Suggested packages:", 17 " strace", 18 "The following NEW packages will be installed:", 19 " htop", 20 "0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.", 21 "Need to get 92.8 kB of archives.", 22 "After this operation, 230 kB of additional disk space will be used.", 23 "Get:1 http://deb.debian.org/debian buster/main amd64 htop amd64 2.2.0-1+b1 [92.8 kB]", 24 "Fetched 92.8 kB in 0s (1950 kB/s)", 25 "Selecting previously unselected package htop.", 26 "(Reading database ... ", 27 "(Reading database ... 5%", 28 "(Reading database ... 50%", 29 "(Reading database ... 100%", 30 "(Reading database ... 37073 files and directories currently installed.)", 31 "Preparing to unpack .../htop_2.2.0-1+b1_amd64.deb ...", 32 "Unpacking htop (2.2.0-1+b1) ...", 33 "Setting up htop (2.2.0-1+b1) ...", 34 "Processing triggers for mime-support (3.62) ...", 35 "Processing triggers for man-db (2.8.5-2) ..." 36 ] 37} Si comme moi vous réutilisez souvent le même pool d’adresse IP pour vos labs, vous pouvez désactiver temporairement la vérification des clés SSH avec la commande export ANSIBLE_HOST_KEY_CHECKING=False Les Facts Ansible utilise les “Facts” pour collecter des informations sur les serveurs ( managed nodes). Ce mecanisme permet de peupler des variables ansible_xx utilisables dans les playbooks et des templates JinJa2. Pour consulter les listes des variables disponibles, vous pouvez utiliser le module setup: 1$ ansible docker-01.exemple.tld -i hosts -u <ssh_username> -m setup -a "filter=ansible_distribution*" 2docker-01.exemple.tld | SUCCESS => { 3 "ansible_facts": { 4 "ansible_distribution": "Debian", 5 "ansible_distribution_file_parsed": true, 6 "ansible_distribution_file_path": "/etc/os-release", 7 "ansible_distribution_file_variety": "Debian", 8 "ansible_distribution_major_version": "10", 9 "ansible_distribution_release": "buster", 10 "ansible_distribution_version": "10", 11 "discovered_interpreter_python": "/usr/bin/python" 12 }, 13 "changed": false 14} ici je limite le scope au serveur docker-01 et je filtre la sortie du module setup pour conserver uniquement les infos concernants la distribution. Vous pouvez essayer sans le filtre pour voir l’étendue des informations collectées. Les rôles Les rôles permettent de créer un ensemble de tâches facilement réutilisables. Ils correspondent à un ensemble de dossiers et fichiers dont la structure est normalisée. Vous pouvez directement importer un rôle mis à disposition par la communauté sur Ansible Galaxy avec la commande: 1$ ansible-galaxy install --roles-path <install_path> role_name Structure d’un rôle: 1├── README.md # description du rôle 2├── defaults # variables par défaut du rôle 3│ └── main.yml 4├── files # contient des fichiers à déployer 5├── handlers # actions déclenchées par une notification 6│ └── main.yml 7├── meta # metadonnées et notamment les dépendances 8│ └── main.yml 9├── tasks # contient la liste des tâches à exécuter 10│ └── main.yml 11├── templates # contient des templates au format Jinja2 12| └── template.j2 13├── tests 14│ ├── inventory 15│ └── test.yml 16└── vars # autres variables pour le rôle 17 └── main.yml Pour que le rôle soit facilement factorisable, il est important de bien réfléchir au découpage des tâches. Par exemple, il ne serait pas pertinent d’avoir un rôle unique pour la création de comptes locaux, l’installation d’un serveur apache et d’une base de données mariadb. Il faut mieux prévoir un rôle dédié pour chaque opération pour pouvoir l’utiliser individuellement. Pour ce besoin, je vais créer mon propre rôle avec la commande ansible-galaxy qui va générer la structure des répertoires. 1$ ansible-galaxy role init --init-path ./roles deb10_docker 2- Role deb10_docker was created successfully 3 4$ tree ./roles/ 5./roles/ 6└── deb10-docker 7 ├── README.md 8 ├── defaults 9 │ └── main.yml 10 ├── files 11 │ └── docker-cleanup 12 ├── handlers 13 │ └── main.yml 14 ├── meta 15 │ └── main.yml 16 ├── tasks 17 │ └── main.yml 18 ├── templates 19 ├── tests 20 │ ├── inventory 21 │ └── test.yml 22 └── vars 23 └── main.yml Toujours dans le répertoire Ansible, la commande va générer la structure du rôle deb10_docker dans le dossier roles On va maintenant pouvoir entrer dans le vif du sujet en listant les tâches à automatiser: installer les pré-requis ajouter le dépot Docker et la clé GPG installer docker-ce s’assurer que le service docker est activé et démarré installer docker-compose et ctop s’assurer que le groupe docker existe créer un utilisateur formation, l’affecter aux groupes sudo et docker et définir un password ajouter un petit script pour purger les conteneurs, images et volumes Docker ( pratique en formation !) L’écriture des tâches peut paraître complexe au premier abord mais fondamentalement cela se résume à: donner une description de la tâche (affichée pendant l’exécution, ça facilite le debug) identifier le module appropié pour réaliser la tâche (vous pouvez utiliser Ansible Galaxy pour trouver des exemples) utiliser la documentation du module qui propose souvent de nombreux exemples 1- name : description de la tâche 2 module_name: # nom du module à utiliser 3 paramètre_1: valeur 4 paramètre_2: valeur Pour cela, je vais éditer le fichier ./roles/deb10_docker/tasks/main.yml qui doit contenir la liste des tâches à accomplir. 1--- 2# tasks file for deb10_docker 3 4# Etape 1 5- name: install requirements 6 ansible.builtin.package: 7 name: ['apt-transport-https', 'ca-certificates', 'curl', 'gnupg', 'lsb-release'] 8 update_cache: yes 9 10# Etape 2 11- name: add Docker GPG key 12 ansible.builtin.apt_key: url=https://download.docker.com/linux/debian/gpg 13 14- name: Add Docker Repository 15 ansible.builtin.apt_repository: 16 repo: deb [arch=amd64] https://download.docker.com/linux/debian buster stable 17 state: present 18 filename: 'docker' 19 20# Etape 3 21- name: install Docker CE 22 ansible.builtin.package:

Ansible
Dans le cadre de la mise en place d’une formation Docker, j’ai récemment...
Source: Bruno Levasseur
DevOps
Définition
Le DevOps est une approche de développement logiciel qui vise à améliorer la collaboration entre les équipes de développement (Dev) et d'exploitation (Ops) au sein d'une organisation. Le terme "DevOps" est une contraction de "Development" (développement) et "Operations" (exploitation). L'objectif principal du DevOps est d'accélérer le cycle de développement, de déploiement et de mise en production des logiciels tout en assurant une plus grande fiabilité et une meilleure qualité.
Les principaux aspects du DevOps sont les suivants :
- Collaboration : Le DevOps encourage une communication et une collaboration étroites entre les équipes de développement et d'exploitation. Cela aide à éliminer les silos organisationnels et à favoriser une compréhension mutuelle des objectifs et des contraintes de chaque équipe.
- Automatisation : L'automatisation est au cœur du DevOps. Les tâches répétitives et manuelles sont automatisées autant que possible, ce qui permet de réduire les erreurs humaines, d'accélérer les processus et de garantir une cohérence dans les déploiements.
- Intégration continue (CI) : Dans le cadre du DevOps, les développeurs intègrent fréquemment leur code dans une base commune. Chaque intégration est automatiquement testée, ce qui permet de détecter rapidement les erreurs et de les corriger.
- Livraison continue (CD) : La livraison continue consiste à automatiser le processus de déploiement des applications. Les modifications apportées au code sont automatiquement déployées dans un environnement de test, puis dans l'environnement de production lorsque les tests sont concluants.
- Surveillance et rétroaction : Le DevOps implique une surveillance continue des performances de l'application en production. Les données de surveillance aident à détecter les problèmes rapidement et à prendre des mesures correctives. De plus, les commentaires des utilisateurs sont pris en compte pour améliorer constamment l'application.
- Sécurité : La sécurité est un aspect essentiel du DevOps. Les pratiques de sécurité sont intégrées dès le début du processus de développement, et des contrôles de sécurité sont automatisés dans le pipeline de livraison continue pour détecter les vulnérabilités rapidement.
En adoptant le DevOps, les organisations visent à accélérer leur capacité à fournir des logiciels de haute qualité tout en réduisant les risques et les coûts associés aux déploiements. Cette approche favorise également une culture de collaboration, d'amélioration continue et d'agilité au sein de l'entreprise.