Customiser Talos avec des extensions
Lorsque je présente Talos, je commence souvent par montrer la vitesse de déploiement des clusters, la légèreté de l’OS, la sécurité, la simplicité de déploiement et je termine en précisant que malgré le fait qu’il soit minimaliste, il est tout de même extensible.
Pour rappel, Talos est un OS dédié au déploiement de nœud Kubernetes. Il est immuable et minimaliste, sa particularité étant qu’il ne se manage pas comme un Linux classique à travers du SSH mais plutôt via une API gRPC qui permet de gérer l’OS de manière sécurisée. Ainsi, cette API vous permet de réaliser les opérations d’installation, de mise à jour, de réinitialisation, de configuration et de gestion des nœuds Kubernetes.
Ainsi, je souhaite vous montrer à travers cet article comment customiser Talos pour l’adapter à vos besoins.
Pourquoi installer des extensions ?
Talos étant un OS minimaliste, il n’embarque pas l’ensemble des outils que vous pourriez attendre d’un OS classique. Si vous souhaitez installer un agent EDR (comme CrowdStrike), un driver pour carte NVIDIA, ou tout autre programme qui ne peut pas tourner dans Kubernetes (incluant les static-pods) : vous allez devoir passer par des extensions.
Il existe deux manières d’installer une extension sur Talos :
- Via une image customisée de Talos incluant déjà l’extension ;
- Ou en précisant une image OCI contenant l’extension à installer.
Chaque méthode correspond à un usage différent.
L’image customisée est utile lorsque l’extension est nécessaire pour que Talos puisse fonctionner en mode maintenance (lorsqu’il est dans l’attente de sa configuration). Un exemple courant de ce cas pourrait être celui d’un driver (e.g. une carte RAID) dont la présence serait obligatoire pour que Talos détecte le périphérique (s’il ne trouve pas de disque, il n’est pas possible de l’installer).
L’autre méthode, consistant à installer le nœud à l’aide d’une image OCI spécifique, est utile lorsque l’extension doit être active alors que le nœud est déjà en cours de fonctionnement. Par exemple un RuntimeClass pour lancer des MicroVMs dans Kubernetes, un VPN Tailscale…
Évidemment, il est possible d’utiliser les deux méthodes en même temps. Dans le cas du driver RAID, il faudra qu’il soit présent dans l’image de base de Talos pour que le disque soit reconnu, puis le réinstaller via l’image OCI pour que l’extension soit conservée après l’installation de l’OS. Ainsi, nous en avons autant besoin avant, qu’après l’installation.
Point important à garder en tête. Si on précise dans la configuration de Talos une image apportant une extension (dans le champ machine.install.image
ou via une upgrade), celle-ci va remplacer les extensions déjà présentes.


En bref, voici ce qu’il faut retenir :
- Si vous installez une image customisée de Talos et utilisez l’image OCI par défaut (
ghcr.io/siderolabs/installer:v1.x.x
), l’extension sera conservée après l’installation du nœud. - Si vous installez une image customisée de Talos et utilisez une image OCI différente (dans la configuration ou durant une mise à jour), l’extension ne sera pas conservée.
Maintenant, comment fait-on pour installer une extension ?
Prenons un exemple concret, j’utilise souvent Proxmox dans mes labs. C’est un hyperviseur que j’apprécie pour sa flexibilité et sa simplicité d’utilisation. Notamment, il y a une petite feature très pratique, l’affichage des IPs des VM dans l’interface web. Pour cela, il faut installer un agent sur chaque VM afin qu’elles remontent cette information.
Ainsi, nous devons installer l’agent qemu-guest-agent
sur nos nœuds. Voyons comment faire cela.
Installer une image customisée de Talos
La méthode la plus simple pour créer cette image contenant déjà l’extension est de passer par Factory. Ce site vous permet de remplir un formulaire afin de créer une image avec vos besoins (architecture, kernel args, version de Talos, extensions).
Vous aurez une page vous demandant de cocher les différentes extensions que vous souhaitez installer, il vous suffit alors de cocher qemu-guest-agent
et de valider.
En fonction de comment vous voulez installer votre machine, vous aurez le choix entre :
- Télécharger l’image ISO ;
- Télécharger l’image disque (raw) ;
- Utiliser un script PXE.
Votre demande d’image est associée à un ID (ce4c980550dd2ab1b17bbf2b08801c7eb59418eafe8f279833297925d67c7515
dans mon cas) que vous pouvez utiliser pour retrouver l’image à tout moment. Il est également possible de scripter la génération des images en utilisant l’API de Factory. Lorsque vous terminez de remplir le formulaire, vous obtenez un récap de votre demande “as code” en YAML :
customization:
systemExtensions:
officialExtensions:
- siderolabs/qemu-guest-agent
$ yq eval -o=json customization.yaml > customization.json # Convert it to JSON
$ curl -s -X POST https://factory.talos.dev/schematics \
-H "Content-Type: application/json" \
-d @customization.json
{"id":"ce4c980550dd2ab1b17bbf2b08801c7eb59418eafe8f279833297925d67c7515"}
À partir de là, vous pouvez facilement intégrer la génération de l’image dans un pipeline ou un script d’automatisation… Ou continuer à utiliser l’interface web de Factory, c’est comme vous le souhaitez 😄.



Ainsi, je vais importer l’image dans Promox. En effet, ma VM étant déjà créée, je n’ai qu’à importer l’image disque sur un disque spécifique et définir l’ordre de démarrage sur ce périphérique.
ssh root@<proxmoxIP>
schematicid=ce4c980550dd2ab1b17bbf2b08801c7eb59418eafe8f279833297925d67c7515
wget https://factory.talos.dev/image/${schematicid}/v1.9.5/nocloud-amd64.raw.xz
xz -d nocloud-amd64.raw.xz
qm disk import 301 ./nocloud-amd64.raw zfs --target-disk scsi0
qm disk resize 301 scsi0 +30G
qm set 301 --boot order='scsi0'
qm start 301
Ma machine est prête et l’adresse IP est bien remontée dans l’interface web de Proxmox.
$ talosctl get extensions -e 192.168.32.89 -n 192.168.32.89 --insecure
NODE NAMESPACE TYPE ID VERSION NAME VERSION
runtime ExtensionStatus 0 1 qemu-guest-agent 9.2.0
runtime ExtensionStatus 1 1 schematic ce4c980550dd2ab1b17bbf2b08801c7eb59418eafe8f279833297925d67c7515
Toutefois, Talos n’est pas encore installé. Nous pouvons alors générer la configuration et l’appliquer directement sur le nœud.
talosctl gen config test https://192.168.32.89:6443 # Use the default image ghcr.io/siderolabs/installer:v1.x.x
export TALOSCONFIG=./talosconfig
talosctl config endpoint 192.168.32.89
talosctl config node 192.168.32.89
talosctl apply -f controlplane.yaml
talosctl bootstrap
Comme nous n’avons pas utilisé une image OCI comportant une extension, celle présente dans notre image customisée est conservée.
$ talosctl get extensions
NODE NAMESPACE TYPE ID VERSION NAME VERSION
192.168.32.89 runtime ExtensionStatus 0 1 qemu-guest-agent 9.2.0
192.168.32.89 runtime ExtensionStatus 1 1 schematic ce4c980550dd2ab1b17bbf2b08801c7eb59418eafe8f279833297925d67c7515
Testons maintenant la mise à jour de notre nœud via une nouvelle image OCI générée par Factory mais sans l’extension qemu-guest-agent
avec la configuration customization: {}
(une image sans aucun paramètre modifié, ni extension). J’obtiens l’ID 376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba
:
schematicid=376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba
talosctl upgrade -i factory.talos.dev/installer/${schematicid}:v1.9.5
Notez que la version de Talos (ici v1.9.5) est présent dans le tag de l’image (et non dans le schematic), signifiant que vous pouvez garder le même id à chaque mise à jour de Talos (via un talosctl upgrade).
Une fois la mise à jour terminée, je peux vérifier que l’extension qemu-guest-agent
n’est plus présente sur le noeud.
$ talosctl get extensions
NODE NAMESPACE TYPE ID VERSION NAME VERSION
192.168.32.89 runtime ExtensionStatus 0 1 schematic 376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba
Depuis le Proxmox, je peux voir que l’agent n’est plus présent sur la VM :
Mais concrètement, c’est quoi une extension ?
Nous avons discuté des extensions, mais nous ne nous sommes pas vraiment penchés sur le sujet de qu’est-ce qu’une extension ?
Une extension est un conteneur privilégié qui va avoir son propre filesystem présent dans l’hôte. La racine du conteneur est située dans /usr/local/lib/containers/<extension>/
et sa définition dans /usr/local/etc/containers/<extension>.yaml
.
$ talosctl ls /usr/local/lib/containers/qemu-guest-agent
NODE NAME
192.168.32.89 .
192.168.32.89 dev
192.168.32.89 etc
192.168.32.89 lib
192.168.32.89 proc
192.168.32.89 qemu-ga
192.168.32.89 run
192.168.32.89 sbin
192.168.32.89 sys
192.168.32.89 system
192.168.32.89 usr
192.168.32.89 var
$ talosctl ls /usr/local/etc/containers/
NODE NAME
192.168.32.89 .
192.168.32.89 qemu-guest-agent.yaml
Au démarrage de Talos, il va lire les fichiers présents dans /usr/local/etc/containers/
et créer un conteneur pour chaque fichier YAML présent.
Regardons maintenant un peu plus en détail le fichier YAML de définition :
# talosctl cat /usr/local/etc/containers/qemu-guest-agent.yaml
name: qemu-guest-agent
depends:
- path: /system/run/machined/machine.sock
- path: /dev/virtio-ports/org.qemu.guest_agent.0
container:
entrypoint: ./qemu-ga
mounts:
# Shared libraries.
- source: /lib
destination: /lib
type: bind
options:
- bind
- ro
- source: /usr/lib
destination: /usr/lib
type: bind
options:
- bind
- ro
# State files.
- source: /system/run/qemu-guest-agent
destination: /var/run
type: bind
options:
- rshared
- rbind
- rw
# Device files.
- source: /dev
destination: /dev
type: bind
options:
- rshared
- rbind
- rw
# `/sbin/init` talks to `machined`.
- source: /system/run/machined/machine.sock
destination: /system/run/machined/machine.sock
type: bind
options:
- rshared
- rbind
- ro
- source: /sbin/init
destination: /sbin/shutdown
type: bind
options:
- bind
- ro
restart: always
Ce n’est pas plus complexe que de remplir un docker-compose.yaml
, on y retrouve les mêmes concepts :
name
: le nom de l’extension ;depends
: les dépendances de l’extension (les fichiers qui doivent être présents avant le démarrage de celle-ci);- un entrypoint ;
mounts
: les points de montage de l’hôte vers le conteneur ;restart
: la politique de redémarrage de l’extension.
side-note (todo), la partie depends
est très intéressante et est assez puissante. Vous pouvez spécifier des fichiers (comme ci-dessus), mais également la présence de la configuration, le status d’un service… Les possibilités sont nombreuses, vous en saurez plus ici.
On commence à démystifier un peu le sujet des extensions. Toutefois, il reste un petit point à aborder : comment injecter un fichier de configuration dans une extension ?
Injecter une configuration dans une extension
Dans le cas de l’extension qemu-guest-agent
, il n’y a pas de configuration. Mais qu’en est-il d’une extension comme tailscale
? Celle-ci permet de connecter vos nœuds à un réseau VPN Tailscale (par exemple, si vous voulez rendre accessible l’API Talos sans exposer le nœud).
Dans ce cas, je peux jouer avec l’objet ExtensionServiceConfig
pour injecter la configuration dans l’extension via des variables d’environnement ou des fichiers de configuration.
apiVersion: v1alpha1
kind: ExtensionServiceConfig
name: tailscale
environment:
- TS_AUTHKEY=<your auth key>
Puis appliquer ce patch sur la (ou les) machine(s) de mon cluster.
talosctl patch mc -p @tailscale-config.yaml
D’autres extensions auront également besoin de fichiers de configuration. Voici un exemple avec l’extension nut-client
qui permet de gérer les onduleurs via le protocole NUT (Network UPS Tools). Dans ce cas, on peut injecter un fichier de configuration dans l’extension via le patch suivant :
apiVersion: v1alpha1
kind: ExtensionServiceConfig
name: nut-client # Name of the extension service.
# The config files for the extension service.
configFiles:
- content: MONITOR ${upsmonHost} 1 remote username password # The content of the extension service config file.
mountPath: /usr/local/etc/nut/upsmon.conf # The mount path of the extension service config file.
# The environment for the extension service.
environment:
- NUT_UPS=upsname
Évidemment, les fichiers de configuration et les variables d’environnement sont à adapter en fonction de l’extension que vous utilisez. Ainsi, pensez à toujours vérifier la documentation de l’extension pour savoir ce qu’il faut injecter.
On a fait un beau bout de chemin pour comprendre ce qu’est une extension, mais le meilleur moyen de comprendre ces concepts reste de créer notre propre extension. Let’s go !
Créer une extension
Mon besoin : en ce moment, je m’amuse pas mal avec une stack de Chaos-Testing nommé Chaos-Mesh. En gros, c’est un outil qui va provoquer des erreurs dans votre cluster Kubernetes pour tester la résilience de vos applications. Chaos-Mesh peut agir depuis l’intérieur du cluster ou depuis l’extérieur en passant par un agent installé sur chaque nœud, mon but est alors de l’installer sur mes nœuds Talos.

Chaosd fonctionne comme un serveur qui va écouter sur un port et recevoir des requêtes HTTP pour exécuter des actions sur le nœud.
Chaosd : Comment ça fonctionne ?
Essayons déjà de comprendre comment fonctionne Chaosd dans un contexte normal. Lorsque vous installez chaosd
et lancez chaosd server
, il va écouter sur le port 31767
pour l’HTTP (et 31768
si on configure l’HTTPS). Les certificats SSL sont générés directement par Chaos-Mesh et stockés dans un secret Kubernetes.
kubectl get secret chaos-mesh-chaosd-client-certs -n chaos-mesh \
-o "jsonpath={.data['ca\.crt']}" | base64 -d > ca.crt
kubectl get secret chaos-mesh-chaosd-client-certs -n chaos-mesh \
-o "jsonpath={.data['ca\.key']}" | base64 -d> ca.key
mkdir -p out
./bin/chaosctl pm generate --cacert=./ca.crt --cakey=./ca.key --path ./out
ls out
chaosd.crt chaosd.key
chaosd server --cert ./out/chaosd.crt --key ./out/chaosd.key
Suite à ça, Chaosd va également créer le fichier chaosd.dat
, une base de données SQLite qui va contenir les informations sur les tests lancés sur le nœud.
En bref, on va avoir besoin de :
- Lancer Chaosd avec la commande
chaosd server
en précisant les certificats TLS en argument (si on veut utiliser l’HTTPS) ; - Injecter les certificats TLS dans le conteneur (via un ExtensionServiceConfig) ;
- Sauvegarder la base de données SQLite dans un volume persistant (via un mount).
La partie de la persistance est un peu plus tricky que prévu, car il va directement utiliser le chemin du binaire chaosd
. Il faut donc prévoir de le lancer directement dans le volume (et comme celui-ci est forcément vide au premier démarrage, cela implique de copier le binaire dans le volume).
Créer notre image OCI
Créer une extension Talos n’est pas exactement comme créer le Dockerfile d’une application traditionnelle. Talos va utiliser cet artéfact OCI pour créer le filesystem de l’image dans /var/lib/containers/
et récupérer les fichiers contenant les métadonnées et la définition du conteneur. Concrètement, une archive .tar.gz conviendrait tout à fait (l’usage de l’OCI simplifie seulement le packaging et la distribution de l’extension).
Voici l’architecture de l’image :
/manifest.yaml
/rootfs/usr/local/etc/containers/chaosd.yaml
/rootfs/usr/local/lib/containers/chaosd/
├── chaosd
├── lib64
└── lib
Ce qu’il faut retenir, c’est que le chemin /rootfs/
correspond au filesystem de Talos, on peut ainsi modifier Talos (dans le cas où on souhaite ajouter un driver, module kernel, etc.) ou créer un conteneur (comme dans notre cas).
Commençons par le plus simple, créer le fichier metadata manifest.yaml
. Ce fichier va permettre de définir le nom de l’extension, la version, etc.
version: v1alpha1
metadata:
name: chaosd
version: "VERSION"
author: qjoly
description: chaosd is a chaos engineering tool controlled by ChaosMesh
compatibility:
talos:
version: ">= v1.8.0"
Le champ version
est modifié directement dans mon pipeline de CI/CD qui va générer l’image OCI.
Maintenant, créons le DockerFile
. Je vais faire un premier stage pour construire le binaire chaosd, et un second qui respecte le format attendu par Talos.
FROM golang:1.20.14-bullseye AS builder
RUN apt-get update && apt-get install -y wget
ENV CHAOSD_VERSION=v1.4.0
WORKDIR /
RUN wget https://github.com/chaos-mesh/chaosd/archive/refs/tags/$CHAOSD_VERSION.tar.gz && \
mkdir /chaosd && \
tar xfz $CHAOSD_VERSION.tar.gz -C /chaosd --strip-components=1
WORKDIR /chaosd
RUN make chaosd
RUN make chaos-tools
FROM scratch
COPY --from=builder /chaosd/bin/chaosd /rootfs/usr/local/lib/containers/chaosd/chaosd
COPY --from=builder /chaosd/bin/tools /rootfs/usr/local/lib/containers/chaosd/usr/bin
COPY --from=builder /bin/dd /rootfs/usr/local/lib/containers/chaosd/bin/dd
COPY --from=builder /lib /rootfs/usr/local/lib/containers/chaosd/lib
COPY --from=builder /lib64 /rootfs/usr/local/lib/containers/chaosd/lib64
COPY chaosd.yaml /rootfs/usr/local/etc/containers/chaosd.yaml
COPY manifest.yaml /
WORKDIR /rootfs/usr/local/lib/containers/chaosd/
J’ai utilisé la même image de base (golang:1.20.14-bullseye
) que dans les pipelines de Chaosd.
Analysons un peu le Dockerfile :
- On construit le binaire
chaosd
et les chaos-tools (des exécutables qui peuvent être appelés par chaosd. E.g.stress-ng
oumemStress
). - On construit l’image finale en copiant le binaire et les dépendances dans le bon répertoire.
- On copie le fichier de définition
chaosd.yaml
(pas encore créé, on le fait plus tard) et le fichier de métadonnéesmanifest.yaml
dans le bon répertoire.
Simple, non ? Je me suis basé sur une image Scratch afin de respecter la philosophie minimaliste de Talos, mais j’aurai pu utiliser une alpine ou debian sans aucun problème (si jamais vous avez besoin de plus de dépendances).
Remarquez qu’on ne précise pas de CMD
ou d’ENTRYPOINT
, c’est normal, cette image OCI n’est pas lancée comme un conteneur (pour rappel, Talos va utiliser le contenu de cette image dans son filesystem). C’est à nous de le définir dans le fichier de définition chaosd.yaml
.
name: chaosd
depends:
- network:
- addresses
- connectivity
container:
entrypoint: /chaosd
args:
- server
# In scratch, there is no PATH variable, we need it to allow the chaosd to find the tools
environment:
- PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
security:
# Temporary solution to create the SQLite database anywhere
# We will fix it in the future
writeableRootfs: true
mounts:
# Needed for chaos testing on disk
- source: /dev
destination: /dev
type: bind
options:
- bind
- rw
# Where chaosd will store the SQLite database
- source: /var/lib/chaosd
destination: /var/lib/chaosd
type: bind
options:
- bind
- rw
restart: always
Comme pour le DockerFile
, prenons le temps d’analyser ce fichier de définition :
- Le conteneur ne démarre que si le réseau est disponible sur la machine Talos.
- Le conteneur est lancé avec la commande
chaosd server
(l’entrypoint est le binaire chaosd suivi de l’argumentserver
). - Il a les permissions d’écrire dans le filesystem (pour créer la base de données SQLite), on y reviendra plus tard.
docker build -t ghcr.io/qjoly/talos.chaosd.extension/app:latest .
docker push ghcr.io/qjoly/talos.chaosd.extension/app:latest
Maintenant que nous avons notre artéfact OCI (et qu’il est disponible sur un registre), il ne reste plus qu’une ultime étape pour pouvoir l’installer sur une machine Talos.
### Créer une image Talos
Eh oui, si usuellement, on utilise factory.talos.dev pour créer une image Talos, il n’est pas possible de l’utiliser pour ajouter notre extension puisqu’il ne référence que les extensions officielles. Il va donc falloir s’appuyer sur imager
, le projet sur lequel s’appuie Factory pour créer les images. Avec lui, on peut se créer une image Talos avec les extensions de notre choix (qu’elles soient officielles ou non).
TALOS_VERSION=v1.9.4
ARCH=amd64
IMAGE_EXT=ghcr.io/qjoly/talos.chaosd.extension/app:latest
PROFILE=installer
docker run --rm -t -v /dev:/dev --privileged -v "$PWD/_out:/out" "ghcr.io/siderolabs/imager:$TALOS_VERSION" --arch "${ARCH}" --system-extension-image ${IMAGE_EXT} "${PROFILE}"
Note : -e GITHUB_TOKEN=${{ secrets.GHCR_PAT }}
si jamais l’image est privée.
Le profil installer
est celui qui va permettre de créer une image OCI que nous pourrons utiliser dans le cadre d’une installation de Talos ou d’une mise à jour. Suite à cette commande, on obtient une archive tar contenant l’image Talos intégrant notre extension. Si nous avions voulu créer une image ISO, il aurait fallu utiliser le profil iso
, ou metal
pour une image disque (doc).
$ ls _out
installer-amd64.tar
$ docker load -i ./_out/installer-${ARCH}.tar
Loaded image: ghcr.io/siderolabs/installer:v1.9.4
$ docker tag ghcr.io/siderolabs/installer:v1.9.4 \
ghcr.io/qjoly/talos.chaosd.extension/installer:${TALOS_VERSION}
$ docker push ghcr.io/qjoly/talos.chaosd.extension/installer:${TALOS_VERSION}
Cette image ghcr.io/qjoly/talos.chaosd.extension/installer:v1.9.4
est justement ce dont nous avons besoin pour installer notre extension.
Installer l’extension
Je vais prendre une image officielle de Talos (ici une v1.8.4
, c’est un peu vieux mais on va justement la mettre à jour) qui va me servir de cobaye.
Je vais vous faire l’installation en speedrun :
talosctl gen secrets
talosctl gen config coffee-talos https://192.168.32.86:6443 \
--install-image ghcr.io/qjoly/talos.chaosd.extension/installer:v1.9.4
talosctl apply -f controlplane.yaml -e 192.168.32.86 -n 192.168.32.86 --insecure
Remarque
Si vous avec une machine déjà installée, vous pouvez directement la mettre à jour via :
talosctl upgrade -i ghcr.io/qjoly/talos.chaosd.extension/installer:v1.9.4
Une fois l’installation terminée, on peut vérifier que l’extension est bien présente sur le nœud via talosctl services
ou talosctl logs
.
$ talosctl -e 192.168.32.86 -n 192.168.32.86 --talosconfig talosconfig services
NODE SERVICE STATE HEALTH LAST CHANGE LAST EVENT
192.168.32.86 apid Running OK 2h16m25s ago Health check successful
192.168.32.86 auditd Running OK 2h16m38s ago Health check successful
192.168.32.86 containerd Running OK 2h16m38s ago Health check successful
192.168.32.86 cri Running OK 2h16m25s ago Health check successful
192.168.32.86 dashboard Running ? 2h16m27s ago Process Process(["/sbin/dashboard"]) started with PID 2068
192.168.32.86 etcd Running OK 2h16m20s ago Health check successful
192.168.32.86 ext-chaosd Running ? 2h16m26s ago Started task ext-chaosd (PID 2199) for container ext-chaosd
192.168.32.86 kubelet Running OK 2h16m23s ago Health check successful
192.168.32.86 machined Running OK 2h16m38s ago Health check successful
192.168.32.86 syslogd Running OK 2h16m37s ago Health check successful
192.168.32.86 trustd Running OK 2h16m24s ago Health check successful
192.168.32.86 udevd Running OK 2h16m28s ago Health check successful
$ talosctl -e 192.168.32.86 -n 192.168.32.86 --talosconfig talosconfig logs ext-chaosd
192.168.32.86: Chaosd Server Version: version.Info{GitVersion:"v0.0.0-master+$Format:%h$", GitCommit:"$Format:%H$", BuildDate:"2025-04-20T16:54:36Z", GoVersion:"go1.20.14", Compiler:"gc", Platform:"linux/amd64"}
192.168.32.86: [GIN-debug] POST /api/attack/process --> github.com/chaos-mesh/chaosd/pkg/server/httpserver.(*HttpServer).createProcessAttack-fm (4 handlers)
192.168.32.86: [GIN-debug] POST /api/attack/stress --> github.com/chaos-mesh/chaosd/pkg/server/httpserver.(*HttpServer).createStressAttack-fm (4 handlers)
192.168.32.86: [GIN-debug] POST /api/attack/network --> github.com/chaos-mesh/chaosd/pkg/server/httpserver.(*HttpServer).createNetworkAttack-fm (4 handlers)
192.168.32.86: [GIN-debug] POST /api/attack/disk --> github.com/chaos-mesh/chaosd/pkg/server/httpserver.(*HttpServer).createDiskAttack-fm (4 handlers)
192.168.32.86: [GIN-debug] POST /api/attack/clock --> github.com/chaos-mesh/chaosd/pkg/server/httpserver.(*HttpServer).createClockAttack-fm (4 handlers)
192.168.32.86: [GIN-debug] POST /api/attack/jvm --> github.com/chaos-mesh/chaosd/pkg/server/httpserver.(*HttpServer).createJVMAttack-fm (4 handlers)
192.168.32.86: [GIN-debug] POST /api/attack/kafka --> github.com/chaos-mesh/chaosd/pkg/server/httpserver.(*HttpServer).createKafkaAttack-fm (4 handlers)
192.168.32.86: [GIN-debug] POST /api/attack/vm --> github.com/chaos-mesh/chaosd/pkg/server/httpserver.(*HttpServer).createVMAttack-fm (4 handlers)
192.168.32.86: [GIN-debug] POST /api/attack/redis --> github.com/chaos-mesh/chaosd/pkg/server/httpserver.(*HttpServer).createRedisAttack-fm (4 handlers)
192.168.32.86: [GIN-debug] POST /api/attack/user_defined --> github.com/chaos-mesh/chaosd/pkg/server/httpserver.(*HttpServer).createUserDefinedAttack-fm (4 handlers)
192.168.32.86: [GIN-debug] DELETE /api/attack/:uid --> github.com/chaos-mesh/chaosd/pkg/server/httpserver.(*HttpServer).recoverAttack-fm (4 handlers)
192.168.32.86: [GIN-debug] GET /api/experiments/ --> github.com/chaos-mesh/chaosd/pkg/server/httpserver.(*HttpServer).listExperiments-fm (4 handlers)
192.168.32.86: [GIN-debug] GET /api/experiments/:uid/runs --> github.com/chaos-mesh/chaosd/pkg/server/httpserver.(*HttpServer).listExperimentRuns-fm (4 handlers)
192.168.32.86: [GIN-debug] Listening and serving HTTP on 0.0.0.0:31767
L’extension est fonctionnelle et on peut l’utiliser directement depuis Chaos-Mesh (il n’y a pas d’exposition de port à configurer, l’extension utilise les ports de l’hôte).
🎉 Tada, on a notre extension !
Mais je crois qu’une étape de notre checklist n’est pas respectée !
Rappelez-vous, chaosd
va créer une base de données SQLite pour stocker les informations sur les tests. Cette base doit être sauvegardée dans un montage sur le nœud.
Gestion de la persistance
Notre principale préoccupation est que chaosd
va créer ce fichier chaosd.dat
dans le même répertoire que le binaire. On a bien configuré le mountpoint /var/lib/chaosd
, mais au démarrage du conteneur, le binaire n’est pas présent dans celui-ci. Il faut donc copier le binaire dans le volume avant de le lancer.
Usuellement, c’est à ce moment qu’il faut créer un entrypoint en bash qui va copier le binaire dans le volume et lancer chaosd
. Mais, cela nous oblige à copier les binaires de bash
, cp
, mkdir
, ce qui est dommage.
Exemple de ce que ça aurait pu donner :
#!/bin/bash
mkdir -p /var/lib/chaosd
cp /chaosd /var/lib/chaosd
/chaosd server
Mais, comme dit au-dessus, ce n’est pas forcément le plus optimal. Je vais donc utiliser un petit hack en développant un script en Go qui va faire ce travail à ma place. L’avantage étant que nous n’aurons qu’un seul binaire à copier dans l’image finale.
package main
import (
"fmt"
"os"
"os/exec"
)
func main() {
sourcePath := "/chaosd"
destPath := "/var/lib/chaosd/chaosd"
input, err := os.ReadFile(sourcePath)
if err != nil {
fmt.Printf("Error while reading %s: %v\n", sourcePath, err)
os.Exit(1)
}
err = os.WriteFile(destPath, input, 0755)
if err != nil {
fmt.Printf("Error while writing %s: %v\n", destPath, err)
os.Exit(1)
}
cmd := exec.Command(destPath, "server")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
fmt.Printf("Error while executing chaosd: %v\n", err)
os.Exit(1)
}
}
Si j’avais su qu’un jour je dirais qu’un script Go est plus simple qu’un script bash…
On va devoir modifier le DockerFile pour compiler ce script :
FROM golang:1.20.14-bullseye AS builder
RUN apt-get update && apt-get install -y wget
ENV CHAOSD_VERSION=v1.4.0
WORKDIR /
RUN wget https://github.com/chaos-mesh/chaosd/archive/refs/tags/$CHAOSD_VERSION.tar.gz && \
mkdir /chaosd && \
tar xfz $CHAOSD_VERSION.tar.gz -C /chaosd --strip-components=1
WORKDIR /chaosd
RUN make chaosd
RUN make chaos-tools
+WORKDIR /entrypoint
+COPY ./src/entrypoint.go /entrypoint/entrypoint.go
+RUN CGO_ENABLED=1 GOOS="" GOARCH="" go build -ldflags '-s -w' entrypoint.go
FROM scratch
COPY --from=builder /chaosd/bin/chaosd /rootfs/usr/local/lib/containers/chaosd/chaosd
COPY --from=builder /chaosd/bin/tools /rootfs/usr/local/lib/containers/chaosd/usr/bin
COPY --from=builder /bin/dd /rootfs/usr/local/lib/containers/chaosd/bin/dd
COPY --from=builder /lib /rootfs/usr/local/lib/containers/chaosd/lib
COPY --from=builder /lib64 /rootfs/usr/local/lib/containers/chaosd/lib64
+COPY --from=builder /entrypoint/entrypoint /rootfs/usr/local/lib/containers/chaosd/entrypoint
COPY chaosd.yaml /rootfs/usr/local/etc/containers/chaosd.yaml
COPY manifest.yaml /
WORKDIR /rootfs/usr/local/lib/containers/chaosd/
En dans le chaosd.yaml
, on va modifier l’entrypoint pour pointer vers le binaire du même nom (en bonus, on peut rendre le filesystem non modifiable, ce qui est plus sécurisé) :
name: chaosd
depends:
- network:
- addresses
- connectivity
- configuration: true
container:
- entrypoint: /chaosd
- args:
- - server
+ entrypoint: /entrypoint
security:
- writeableRootfs: true
+ writeableRootfs: false
# ...
On peut maintenant reconstruire l’image OCI via imager
et faire une mise à jour via talosctl upgrade -i ghcr.io/qjoly/talos.chaosd.extension/installer:v1.9.4
.
On peut vérifier que la base de données est bien présente dans le volume :
$ talosctl -e 192.168.32.86 -n 192.168.32.86 --talosconfig talosconfig ls /usr/local/lib/containers/chaosd/var/lib/chaosd
NODE NAME
192.168.32.86 .
Ah, raté ?.
Au début, j’ai été surpris de ne pas voir les fichiers chaosd
et chaosd.dat
, mais en réalité, il faut plutôt vérifier le contenu du répertoire coté Talos (et non celui du conteneur).
talosctl -e 192.168.32.86 -n 192.168.32.86 --talosconfig talosconfig ls /var/lib/chaosd
NODE NAME
192.168.32.86 .
192.168.32.86 chaosd
192.168.32.86 chaosd.dat
Impeccable ! Plus aucun risque de perdre la base de données 🤩 !
Il reste à présent un dernier point à aborder, je souhaite configurer le HTTPS sur l’API de Chaosd. Pour cela, je vais devoir envoyer les certificats TLS dans le conteneur. Comment fait-on cela ?
Injecter des fichiers de configuration
Pour rappel, nous pouvons passer des informations à l’extension via des fichiers de configuration ou des variables d’environnement. Je peux créer un fichier de configuration chaosd-config.yaml
qui va injecter les certificats TLS dans le conteneur.
Je vais ainsi modifier mon script golang se chargeant de l’entrypoint. Maintenant, si les variables d’environnement CHAOSD_CERT
et CHAOSD_KEY
sont présentes, je vais ajouter les arguments --cert
et --key
à la commande chaosd
.
package main
import (
"fmt"
"os"
"os/exec"
)
func main() {
sourcePath := "/chaosd"
destPath := "/var/lib/chaosd/chaosd"
input, err := os.ReadFile(sourcePath)
if err != nil {
fmt.Printf("Error while reading %s: %v\n", sourcePath, err)
os.Exit(1)
}
err = os.WriteFile(destPath, input, 0755)
if err != nil {
fmt.Printf("Error while writing %s: %v\n", destPath, err)
os.Exit(1)
}
var cmd *exec.Cmd
if os.Getenv("CHAOSD_CERT") != "" && os.Getenv("CHAOSD_KEY") != "" {
cmd = exec.Command(destPath, "server",
"--cert", os.Getenv("CHAOSD_CERT"),
"--key", os.Getenv("CHAOSD_KEY"))
} else {
cmd = exec.Command(destPath, "server")
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
fmt.Printf("Error while executing chaosd: %v\n", err)
os.Exit(1)
}
}
Vous connaissez la chanson, reconstruction de l’image OCI et mise à jour de l’image Talos…
Dans l’état actuel, ça n’a pas changé grand-chose puisque ces variables ne sont pas injectées dans le conteneur, ce qui veut aussi dire qu’on supporte le mode HTTP. Pour y remédier, créons le patch chaosd-config.yaml
qui va injecter les variables d’environnement dans le conteneur suivi des certificats TLS.
apiVersion: v1alpha1
kind: ExtensionServiceConfig
name: chaosd
configFiles:
- content: |
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEApiYthtiAkBMDcwLzhsLb2/EWE5MicLoB2J+RAea4KcS/mr1z
CnwtIQYhFESrWWgPjSV0PcVkcwkG/CTsejjrkbeS396xUy+8PHw7qMBkzd7LZV8M
pL/QTIZRou4tdtTtTU6zYV+88elwvREUEoIe8CpGSolQz47VKshFo/w7jW5s+XD8
1SPKDkdqwPgcIbthMHXjRT1kyO2ZfCYIbHyP7kdVp2FBCiSOdmSsA/A6fJRo6ThK
18GAs53DJUKHkTuLz48+ROBXrjOdRqH18oMtFT1fxE3OgkzwZGFRRWJ4fZGppszP
IZ+UGuR/05TuunC7EeJOpPOsmL6536/r3mb4HwIDAQABAoIBAClMYBCejAZD052o
5CNhGAkpedVPw0XF1mOj9gVE9g3by8yIvqmiiA0nWt2Q3A6TIRpybtxTzk4RtlzC
SM1wjI1h1e4zilwlB2L5dlLRz7ykXmZKI/hnfT0oPe6DyWU6M+n9X/UaOPrgjZdR
QW4ATfRgbaxlSWLuG+pQcY77SUoz+/b4bvftH5gfT2yrXwqWLCuZZ0jQWIGjgzWL
rZhQ+QAhmUHO8zNofRKoKyCqahkayDoQ6wn6qkEoGirig8p8cRxrrAcIjlzj30z4
ijnR6JpzQcXtNR/zq/RzXk4Y+uS1/cwtEuLO2QNRXurEWC5kg2+A//fEPRVnXhDO
145qlAECgYEAzSfQsqPBViYq6ToILJQAE74JIubGVTHInsNKen+RvkRL7ozXrX6V
wPw5P1iH4f2/dpOpKVd1vudPC5NHxJX0TVQm9TviyWLs7rGKhVCYumEfQHTfrNZ4
G5rR4rzvB5J+sSFz9GayJr8O7HZdxlCj7vWgasWTPcM6GhtPtKbjWwECgYEAz1OV
krJvoS2e+vlk3HHVeNsYMKJ8cjgRQl3aV5UfmeuxhQjezK4KsYSFqhJBO/7lhVoZ
N+WZu2ObgxDqeFmo75QRX5dMPEUEjK/vgTDLw/IBlH7PzOq1/+Bbn2piI4qp1A8f
upQtlExdUcxdXLuyTeLLwHhQO6PBu6Sd8Gp98x8CgYEAjAWSwXpW6K+gNhKvpY1W
CCN6JOIRl/Akl4d+++C53nzKvoROaFGvcEo14HBSPgJsfUgG+vqAmM1S+/mH0Drw
xR6cgGBhR0ZHpBp1CsyZkQvcwqeCSnsO+vhOLEz7b9Wits64T6UQDrX0P5wCIOrF
GMF4vacf+dWXgF4HpqsJHgECgYBVhAx17dPNj5u7uZK4uttqdjSMGvlpw2abEUs2
RPQ78NPQI9CCbGdMwXTAqbbuoDzbvEYaoEHA4V0LurZ73b+o0lI4M5fmZDF0Nj17
9DyYHgI41fWweD+Jw2kHYEIKlu9l4LcdpnEGclkrnDgGQAVTyvBv+zBc6TbHxya+
O66n0QKBgHhVA659qlG0dc6Phm1cesK3idBCXPR+u+g/SPCPT7XNKV1OrESdekJU
sB6bypmwgHO+E1Vq7hA4zSmI3ek1DRpkay9S1IvM3ar5TmeroetCRKINPJlfFaZZ
MVMZu/6v4DolA7FRZ5LjZwUKh9fZka51apoCodZO9b8MHwcEPjpV
-----END RSA PRIVATE KEY-----
mountPath: /chaosd.key
- content: |
-----BEGIN CERTIFICATE-----
MIIDJDCCAgygAwIBAgIIRMyfZzD2UmswDQYJKoZIhvcNAQELBQAwFDESMBAGA1UE
AxMJY2hhb3NkLWNhMB4XDTI1MDQyMDA5NDE0N1oXDTMwMDQxOTEwMDExMFowIDEe
MBwGA1UEAxMVY2hhb3NkLmNoYW9zLW1lc2gub3JnMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEApiYthtiAkBMDcwLzhsLb2/EWE5MicLoB2J+RAea4KcS/
mr1zCnwtIQYhFESrWWgPjSV0PcVkcwkG/CTsejjrkbeS396xUy+8PHw7qMBkzd7L
ZV8MpL/QTIZRou4tdtTtTU6zYV+88elwvREUEoIe8CpGSolQz47VKshFo/w7jW5s
+XD81SPKDkdqwPgcIbthMHXjRT1kyO2ZfCYIbHyP7kdVp2FBCiSOdmSsA/A6fJRo
6ThK18GAs53DJUKHkTuLz48+ROBXrjOdRqH18oMtFT1fxE3OgkzwZGFRRWJ4fZGp
pszPIZ+UGuR/05TuunC7EeJOpPOsmL6536/r3mb4HwIDAQABo24wbDAOBgNVHQ8B
Af8EBAMCBaAwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBQm3C1nO5HX/xOS/6nT
xMo9sOjSzDArBgNVHREEJDAighVjaGFvc2QuY2hhb3MtbWVzaC5vcmeCCWxvY2Fs
aG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAb+pWowwbmiwjFiPzwSd7HCTnsE0D3U95
UavZuk077GvoCHDUB3zlzjZ6pooPBVhF7zN1pd5PhrqLaicBuNd/Kx8LJyUARck3
v7akyFLkq+pKi3Cagb45G6g+H62nbSdNqQ7dzZo171d3X9dV+bcM9ahQUpOjpet0
nC2bttvQ9++QDBSIZBjkGdAx0buGYnrcmFy29DM/U2Y5mQ2/XRNxhUCVrh4R1Vnf
ooQM82sZ/L/lenPLTiHk+HhZ+a0s0VP2JEiLlRxiPDJ676aT5na0DDU5aRfn1Ehf
+jlFx62ATCnqQRPwsx9T5a4jgvaGiHhvdz5H3qk1PAUjohcYc+Aofg==
-----END CERTIFICATE-----
mountPath: /chaosd.crt
environment:
- CHAOSD_CERT=/chaosd.crt
- CHAOSD_KEY=/chaosd.key
Pour vérifier que les fichiers sont bien présents dans le conteneur, faisons la commmande suivante :
$ talosctl -e 192.168.32.86 -n 192.168.32.86 ls /usr/local/lib/containers/chaosd | grep chaosd
192.168.32.86 chaosd
192.168.32.86 chaosd.crt
192.168.32.86 chaosd.key
Il ne reste plus qu’à redémarrer le service ext-chaosd
pour prendre en compte les modifications.
$ talosctl -e 192.168.32.86 -n 192.168.32.86 --talosconfig ./talosconfig services ext-chaosd restart
$ talosctl -e 192.168.32.86 -n 192.168.32.86 --talosconfig ./talosconfig logs ext-chaosd
192.168.32.86: [GIN-debug] Listening and serving HTTPS on 0.0.0.0:31768
Notre extension est maintenant terminée et fonctionnelle. Il manque certains points pour supporter plus de fonctionnalités dans chaosd
, gardons en tête que ce n’est qu’un proof of concept (et un prétexte pour vous parler des extensions Talos).
Conclusion
Je sais d’avance que ce sujet ne va pas toucher beaucoup de monde. Tout d’abord, parce qu’il s’adresse aux utilisateurs de Talos (si ce n’est pas le cas, qu’est-que vous attendez encore ?), mais aussi parce que les extensions officielles couvrent déjà pas mal de cas d’usage.
Néanmoins, durant certaines présentations de Talos, j’ai pu voir des personnes restant perplexes parce que “Si une feature est manquante dans Talos, je suis complètement bloqué”. Cet article est donc un complément de réponse à ces personnes.
Talos est pensé pour être extensible et vous avez le champ libre pour ajouter vos propres fonctionnalités.