Dans la première partie, nous avons eu une vue globale de Tetragon. Nous avons également installé Tetragon sur un Linux tout seul pour comprendre de façon concrète à quoi sert cet outil.
Dans cette deuxième partie, nous allons continuer ce voyage avec Tetragon en voyant son installation dans Kubernetes, les différents composants qui sont alors installés.
On finira en observant un pod avec des logs json et en créant notre première tracing policy !
Installation de Tetragon sur Kubernetes#
Limite et installation de KinD#
Nous pouvons installer Tetragon dans tous les clusters Kubernetes où eBPF fonctionne. Nous allons l’installer sur KinD (Kubernetes in Docker).

KinD est une distribution Kubernetes permettant de tester des applications Kubernetes sur la même machine, elle repose sur Docker pour émuler les nœuds du cluster.
Par conséquent, contrairement à un Kubernetes classique, tous les nœuds KinD partagent un unique noyau Linux, celui de votre hôte. Ainsi quel que soit le nombre de nœuds créés, il n’y a qu’un seul noyau.
Comme nous l’avons vu dans la partie précédente, Tetragon est basé sur eBPF, des programmes s’exécutant sur le noyau Linux.
Ainsi il est inutile pour tester Tetragon d’installer sur plus d’un nœud avec KinD.
Pour installer KinD, il suffit de taper :
VERSION=v0.32.0
[ $(uname -m) = x86_64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/$VERSION/kind-linux-amd64
[ $(uname -m) = aarch64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/$VERSION/kind-linux-arm64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kindEn cas de problème, n’hésitez pas à consulter le site officiel.
Création du cluster KinD#
Je me suis basé sur la doc officiel pour écrire cette partie.
Créons ce fichier de configuration pour créer ce cluster :
1apiVersion: kind.x-k8s.io/v1alpha4
2kind: Cluster
3nodes:
4 - role: control-plane
5 extraMounts:
6 - hostPath: /proc
7 containerPath: /procHostextraMounts permet de créer des montages supplémentaires entre l’hôte et le cluster KinD.
Ici, on monte le répertoire /proc de la machine hôte dans le node Kubernetes. Cela permet d’accéder aux informations réelles du système (processus, PIDs, etc.).
Sans ce montage, Tetragon verrait uniquement le /proc du conteneur Docker, ce qui fausserait les événements observés via eBPF.
On crée alors le cluster Kubernetes :
kind create cluster --config kind-config.yamlInstallation de Tetragon#
Nous allons utiliser helm pour installer Tetragon. Pour indiquer le /proc de la machine hôte, nous allons donner une option à helm :
EXTRA_HELM_FLAGS=(--set tetragon.hostProcPath=/procHost)L’installation de Tetragon utilise sinon les options par défaut :
helm repo add cilium https://helm.cilium.io
helm repo update
helm install tetragon ${EXTRA_HELM_FLAGS[@]} cilium/tetragon -n kube-systemPour savoir quand Tetragon est enfin déployé, vous pouvez lancer la commande suivante :
kubectl rollout status -n kube-system ds/tetragon -wComment Tetragon est installé sur Kubernetes#
On va essayer de comprendre comment fonctionne Tetragon sur Kubernetes.
Regardons ce qu’on a installé avec helm.
Les pods#
Regardons déjà les pods :
kubectl get pod -n kube-system -l app.kubernetes.io/instance=tetragonNAME READY STATUS RESTARTS AGE
tetragon-operator-59bdd59db8-8865c 1/1 Running 0 47s
tetragon-vjgfb 2/2 Running 0 47sOn a deux pods différents :
tetragon-*, il est installé sur chaque nœud avec un DaemonSet. C’est un agent Tetragon.tetragon-operator-*, il est installé avec un Deployment. C’est l’opérateur Tetragon.
Détaillons le role de chacun.
L’agent Tetragon#
On voit également que le pod tetragon a deux containers qui tournent en même temps. Regardons le nom de ces containers :
kubectl get daemonset tetragon -n kube-system -o jsonpath='{.spec.template.spec.containers[*].name}'
export-stdout tetragon- le container export-stdout expose les événements générés par Tetragon
- le container tetragon est responsable de l’installation et de l’exécution des programmes eBPF
L’agent est le composant qui observe réellement le système sur chaque nœud.
L’opérateur Tetragon#
L’opérateur a un rôle de contrôle :
- Il surveille les Tracing Policies
- Il traduit ces policies en instructions
- Il demande aux agents d’installer les programmes eBPF correspondants
C’est donc lui qui fait le lien entre la configuration Kubernetes et l’exécution sur chacun des nœuds.
Les CRDs#
Comme nous venons le voir l’opérateur gère les tracings policies. Mais comment les installer sur Kubernetes ? La configuration se fait via des CRDs.
Regardons les CRDs qui ont été installées :
kubectl get crdLa commande répond alors :
NAME CREATED AT
tracingpolicies.cilium.io 2026-06-16T12:44:59Z
tracingpoliciesnamespaced.cilium.io 2026-06-16T12:45:02ZIl y a deux CRDs :
- Les tracing policies qui s’appliquent à tout le cluster
- Les tracing policies namespacé qui s’appliquent, comme son nom l’indique, à un namespace Kubernetes.
Tetragon up and Running#
Maintenant qu’on a vu ce qu’on installait réellement dans Kubernetes avec Tetragon, regardons comment l’utiliser.
Status#
Vous pouvez déjà vérifier que Tetragon fonctionne bien :
kubectl exec -n kube-system ds/tetragon -c tetragon -- \
tetra statusSi cela affiche :
Health Status: runningLes logs#
On va maintenant observer un pod nginx avec tetra :
kubectl exec -n kube-system ds/tetragon -c tetragon -- \
tetra getevents -o compact --pods nginxCe pod n’existant pas encore, il n’y a pas encore de log.
Sur un autre terminal, on crée le pod nginx :
kubectl run --image nginx nginxVoilà ce qu’on voit :
🚀 process default/nginx /usr/bin/basename /docker-entrypoint.d/20-envsubst-on-templates.sh
💥 exit default/nginx /usr/bin/basename /docker-entrypoint.d/20-envsubst-on-templates.sh 0
🚀 process default/nginx /usr/bin/awk -v filter= "END { for (name in ENVIRON) { print ( name ~ filter ) ? name : "" } }"
💥 exit default/nginx /usr/bin/awk -v filter= "END { for (name in ENVIRON) { print ( name ~ filter ) ? name : "" } }" 0
💥 exit default/nginx /docker-entrypoint.d/20-envsubst-on-templates.sh /docker-entrypoint.d/20-envsubst-on-templates.sh 0
🚀 process default/nginx /usr/bin/basename /docker-entrypoint.d/30-tune-worker-processes.sh
💥 exit default/nginx /usr/bin/basename /docker-entrypoint.d/30-tune-worker-processes.sh 0
🚀 process default/nginx /kind/bin/mount-product-files.sh /kind/bin/mount-product-files.sh
🚀 process default/nginx /usr/bin/jq -r .bundle
💥 exit default/nginx /usr/bin/jq -r .bundle 0
etcOn voit ainsi tous les différents binaires qui sont exécutés au démarrage du pod nginx.
On a déjà vu cet affichage dans la partie précédente. Je vous propose maintenant de regarder le log par défaut. Il faut donc relancer la commande tetra :
kubectl exec -n kube-system ds/tetragon -c tetragon -- \
tetra getevents --pods nginx | jqNous utilisons la commande jq qui permet de rendre un peu plus joli le json.
Maintenant connectons nous au pod :
kubectl exec -it nginx -- bashDu côté de la tetra cli, on voit alors :
1{
2 "process_exec": {
3 "process": {
4 "exec_id": "a2luZC1jb250cm9sLXBsYW5lOjExNTEwNDY3OTYwNTY6NjQwOQ==",
5 "pid": 6409,
6 "uid": 0,
7 "cwd": "/",
8 "binary": "/usr/bin/bash",
9 "flags": "execve rootcwd clone",
10 "start_time": "2026-06-16T13:29:38.388236227Z",
11 "auid": 4294967295,
12 "pod": {
13 "namespace": "default",
14 "name": "nginx",
15 "uid": "82d08485-bc2a-45ce-bf99-85e255fc5903",
16 "container": {
17 "id": "containerd://f29206e762bd035592b1ba0cc14a3d6deafa8dcc98bb974eaf5ca1ef38dffbea",
18 "name": "nginx",
19 "image": {
20 "id": "docker.io/library/nginx@sha256:608a100c71651bf5b773c89083b4a1ad7ef4b2bd05d7a7e552271e03123692ad",
21 "name": "docker.io/library/nginx:latest"
22 },
23 "start_time": "2026-06-16T13:22:31Z",
24 "pid": 40,
25 "security_context": {}
26 },
27 "pod_labels": {
28 "run": "nginx"
29 },
30 "workload": "nginx",
31 "workload_kind": "Pod"
32 },
33 "docker": "f29206e762bd035592b1ba0cc14a3d6",
34 "parent_exec_id": "a2luZC1jb250cm9sLXBsYW5lOjcwMDgyMTgyNjc0Nzo1NTQ3",
35 "tid": 6409,
36 "in_init_tree": false
37 },
38 "parent": {
39 "exec_id": "a2luZC1jb250cm9sLXBsYW5lOjcwMDgyMTgyNjc0Nzo1NTQ3",
40 "pid": 5547,
41 "uid": 0,
42 "cwd": "/run/containerd/io.containerd.runtime.v2.task/k8s.io/65418056615070bc5892ebbd73d5cc97f039f389a88c2fc06232f815ecc9c96e",
43 "binary": "/usr/local/bin/containerd-shim-runc-v2",
44 "arguments": "-namespace k8s.io -id 65418056615070bc5892ebbd73d5cc97f039f389a88c2fc06232f815ecc9c96e -address /run/containerd/containerd.sock",
45 "flags": "execve clone",
46 "start_time": "2026-06-16T13:22:17.386071799Z",
47 "auid": 4294967295,
48 "parent_exec_id": "a2luZC1jb250cm9sLXBsYW5lOjcwMDgxMzkzNTEwOTo1NTQw",
49 "tid": 5547,
50 "in_init_tree": false
51 }
52 },
53 "node_name": "kind-control-plane",
54 "time": "2026-06-16T13:29:38.388235451Z",
55 "node_labels": {
56 "beta.kubernetes.io/arch": "amd64",
57 "beta.kubernetes.io/os": "linux",
58 "kubernetes.io/arch": "amd64",
59 "kubernetes.io/hostname": "kind-control-plane",
60 "kubernetes.io/os": "linux",
61 "node-role.kubernetes.io/control-plane": ""
62 }
63}On voit tout un tas de données qu’on ne voyait pas avec la sortie standard précédente. Nous n’allons pas expliquer tous les champs mais regardons quand même les plus importants ou ceux qui m’ont paru le moins trivial à comprendre.
process_exec: signifie qu’un processus est lancé. On peut voir sur quel nœud vianode_name, l’heure de lancement avectimemais également le container runtime qui a lancé le container avecparent.processdonne les détails sur le processus lancé : sur quel container de quel pod par exempleexec_idest un identifiant qui va permettre de suivre ce processus jusqu’à ce qu’il s’éteigne. C’est en base64 :
echo 'a2luZC1jb250cm9sLXBsYW5lOjExNTEwNDY3OTYwNTY6NjQwOQ==' | base64 -d
kind-control-plane:1151046796056:64091151046796056correspond à la durée en nanoseconde depuis le démarrage de la machine jusqu’au démarrage du processus (un timestamp monotonic du kernel)
Construisons ma première tracing policy#
Pour finir ce chapitre, nous allons écrire une tracing policy. Son but ? Empêcher de lire le fichier /var/run/secrets/kubernetes.io/serviceaccount/token.
Pour cela, nous allons la construire progressivement.
La plus simple tracing policy du monde#
Cette première tracing policy va permettre d’observer tous les accès en lecture du système. Pour cela nous allons observer l’appel système openat (syscall).
1apiVersion: cilium.io/v1alpha1
2kind: TracingPolicy
3metadata:
4 name: "sys-openat"
5spec:
6 kprobes:
7 - call: "sys_openat"Détaillons rapidement les lignes :
kprobessont un type de programme eBPF qui permettent d’observer toutes les fonctions (les symboles si on veut être précis) du noyau Linux ;callpermet de définir la fonction qu’on veut observer.
Nous allons ainsi voir un message à chaque fois que la fonction sys_openat est appelé dans le noyau Linux. Cette fonction est lancée à chaque fois que le syscall openat est démarré.
Vérifions :
kubectl apply -f tp-openat-basic.yamlRelançons tetra de manière plus lisible :
kubectl exec -n kube-system ds/tetragon -c tetragon -- \
tetra getevents -o compact --pods nginxDans un autre terminal, connectons nous au pod nginx :
kubectl exec -it nginx -- bashQue voit-on dans les logs Tetragon ?
🚀 process default/nginx /usr/bin/bash
📬️ openat default/nginx /usr/bin/bash
📬️ openat default/nginx /usr/bin/bash
📬️ openat default/nginx /usr/bin/bash
📬️ openat default/nginx /usr/bin/bash
📬️ openat default/nginx /usr/bin/bash
📬️ openat default/nginx /usr/bin/bash
📬️ openat default/nginx /usr/bin/bash
📬️ openat default/nginx /usr/bin/bash
📬️ openat default/nginx /usr/bin/bash
📬️ openat default/nginx /usr/bin/bash
📬️ openat default/nginx /usr/bin/bash
📬️ openat default/nginx /usr/bin/bashOn voit qu’au démarrage de bash il lit pas mal de fichiers. Mais on ne voit pas lesquels.
Tracing policy un peu plus compliqué#
Pour cela on va devoir modifier la tracing policy.
Regardons l’armure de la fonction sys_open_at() :
int sys_openat(int dirfd, const char *path, int flags, mode_t mode)Il faut savoir lire une fonction en C :
int: correspond au code retour du syscallopen_at=> cela ne nous intéresse pas.int dirfd: correspond à un descripteur de fichier. C’est un entier (int).const char *path: correspond au chemin du fichier lu. Il est de typeconst char*oustringdans les langages de haut niveau
C’est exactement ce dernier paramètre qu’on veut avoir ! On va donc devoir rajouter les deux premiers arguments aux fichiers précédents.
Ainsi, on va utiliser args :
1apiVersion: cilium.io/v1alpha1
2kind: TracingPolicy
3metadata:
4 name: "sys-openat"
5spec:
6 kprobes:
7 - call: "sys_openat"
8 args:
9 - index: 0 #dirfd
10 type: int
11 - index: 1 #path
12 type: stringindexcorrespond à l’index de l’argument (on commence à 0 !)typecorrespond au type de l’argument
On peut traduire par :
- le premier argument est un descripteur de fichier, il est de type entier
- le deuxième argument est un le chemin du fichier lu, il est de type string.
Vérifions :
kubectl replace -f tp-openat-args.yamlReconnectons nous au pod nginx, on sait maintenant exactement les fichiers qui sont lus :
🚀 process default/nginx /usr/bin/bash
📬️ openat default/nginx /usr/bin/bash /etc/ld.so.cache
📬️ openat default/nginx /usr/bin/bash /lib/x86_64-linux-gnu/libtinfo.so.6
📬️ openat default/nginx /usr/bin/bash /lib/x86_64-linux-gnu/libc.so.6
📬️ openat default/nginx /usr/bin/bash /etc/nsswitch.conf
📬️ openat default/nginx /usr/bin/bash /etc/passwd
📬️ openat default/nginx /usr/bin/bash /etc/bash.bashrc
📬️ openat default/nginx /usr/bin/bash /root/.bashrc
📬️ openat default/nginx /usr/bin/bash /root/.bash_history
📬️ openat default/nginx /usr/bin/bash /usr/share/terminfo/x/xterm
📬️ openat default/nginx /usr/bin/bash /root/.inputrc
📬️ openat default/nginx /usr/bin/bash /etc/inputrcVérifions maintenant qu’on a bien accès au fichier /var/run/secrets/kubernetes.io/serviceaccount/token dans le pod :
cat /var/run/secrets/kubernetes.io/serviceaccount/tokeneyJhbGciOiJSUzI1NiIsImtpZCI6Ik9VRTBaVEFUTXNNaTRPOUZFTkZrbHRWVnZIQ0JfeUt1S0RRd2lLUnFtYncifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxODA4Mjc4OTA1LCJpYXQiOjE3NzY3NDI5MDUsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiMWI1ZGEwMjUtYzk2ZS00OTJjLWI2MTItZGE4YTUyY2ZmN2E1Iiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0Iiwibm9kZSI6eyJuYW1lIjoia2luZC1jb250cm9sLXBsYW5lIiwidWlkIjoiZmNmYmU3M2EtYzhhMy00YzRmLWFiMjgtZWZhYTM4MDNiNTgwIn0sInBvZCI6eyJuYW1lIjoibmdpbngiLCJ1aWQiOiIxYWU2NmU4YS02MWFjLTQ5NmEtYWQ3My03OGNlNmNlN2JlNTgifSwic2VydmljZWFjY291bnQiOnsibmFtZSI6ImRlZmF1bHQiLCJ1aWQiOiIzNTAwZWY5NC00MWU4LTQ2YjctYmE4ZC1jMmVmZjdkZmY5YmQifSwid2FybmFmdGVyIjoxNzc2NzQ2NTEyfSwibmJmIjoxNzc2NzQyOTA1LCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDpkZWZhdWx0In0.JdkU6I6CJ3DCZDgC3iDfPy9MOC6Y60a5E0RKmkhJduC9j8hqmfR1wssESiYmrx_QRA5YhnOY6qARJZfIEKM9g_XQr_ZvI7MBjiRduqofROWZz5gMpFt_E1FBZTFbZsplc2QWuylAUozo0EUha56oFfM5TYimKPbJfOTywijHnbcZcXAvvZX9GLyAIu20rgzK9iIdqj1dSa89K48To_uGZIaaQUhvrF7dh4PSJ67Xuvp2qOA3Cq-xkdbxO-GKmAGasKfoJq5GCvAvQOxc6Rx2iXBbFKnPpTjMzvI0Xfx19_9e5I7l1XhYexVgkHBtPHBTri9eKMxVqEvRQm69g-TgLQComment peut on l’en empêcher ?
Tracing policy final#
Pour cela, il suffit de réutiliser la tracing policy précédente et envoyer un SIGKILL au processus quand le path correspond à la valeur souhaité :
1apiVersion: cilium.io/v1alpha1
2kind: TracingPolicy
3metadata:
4 name: "sys-openat"
5spec:
6 kprobes:
7 - call: "sys_openat"
8 args:
9 - index: 0 #dirfd
10 type: int
11 - index: 1 #path
12 type: string
13 selectors:
14 - matchArgs:
15 - index: 1 #path
16 operator: "Equal"
17 values:
18 - "/var/run/secrets/kubernetes.io/serviceaccount/token"
19 matchActions:
20 - action: SigkillCette policy est relativement lisible : on sélectionne le deuxième argument qui correspond au chemin du fichier, s’il est égale à la valeur /var/run/secrets/kubernetes.io/serviceaccount/token, on lui envoie le signal Sigkill.
Vérifions :
kubectl replace -f tp-openat-enforcement.yamlkubectl exec -it nginx -- bash
cat /var/run/secrets/kubernetes.io/serviceaccount/tokenL’output est alors :
KilledAu niveau de Tetragon, on observe :
🚀 process default/nginx /usr/bin/bash
🚀 process default/nginx /usr/bin/cat /var/run/secrets/kubernetes.io/serviceaccount/token
📬️ openat default/nginx /usr/bin/cat /var/run/secrets/kubernetes.io/serviceaccount/token
💥 exit default/nginx /usr/bin/cat /var/run/secrets/kubernetes.io/serviceaccount/token SIGKILLC’est exactement ce qu’on voulait faire.
Nous avons continué notre apprentissage de Tetragon :
- l’installation sur un cluster Kubernetes ;
- son fonctionnement sur Kubernetes ;
- les logs en format json de Tetragon ;
- une petite initiation au tracing policy.
Dans le prochain article, nous allons approfondir le dernier point.




