10/11/2025
Context
Back to school after a two-year gap. And there, surprise: I realize I've progressed enormously technically during that period. Labs and courses are now just enjoyable to follow.
This week, a DevSecOps lab introduced me to threat modeling via the STRIDE framework. Objective: analyze a simplified e-commerce platform, identify potential threats, prioritize them, and propose countermeasures.
First contact with Threat Dragon (I prefer Mermaid, but whatever). Also my first time hearing about the STRIDE model.
So this is what i've learned:
Threat modeling is a method that follows four steps:
1. Map the architecture and trust boundaries
We model the flows: User → Frontend → Backend → Database / Stripe. Each arrow represents a potentially vulnerable exchange zone.
2. Apply STRIDE to list threats
STRIDE is an acronym grouping six recurring threat categories:
| Category | Target | Example in our e-commerce |
|---|---|---|
| Spoofing | Identity / authentication | Bypass login or validate fake API token |
| Tampering | Data or code alteration | SQL injection to modify product prices |
| Repudiation | Lack of traceability | No logs of admin actions |
| Information disclosure | Exposure of confidential data | Card numbers stored in plaintext |
| Denial of service | Availability blocking | Request flood saturating the backend |
| Elevation of privilege | Rights escalation | User granting themselves admin rights |
This framework forces systematic thinking. Every system component is scrutinized through these six angles.
3. Prioritize risks
Not all threats are equal. We evaluate each threat on two axes: likelihood and impact. This produces a prioritization matrix:
| Threat | Likelihood | Impact | Priority |
|---|---|---|---|
| Manipulate URL/API → bypass auth | High | High | High |
| SQL injection → modify DB | Medium | High | Medium |
| No logs → impossible to trace | Low | Medium | Low |
| Unencrypted sensitive data | Low | Very High | High |
| Request flood → backend down | Medium | High | Medium |
| Frontend-only role check | High | High | High |
4. Define countermeasures via Prevent / Detect / Respond
For each priority threat, we define three defense layers:
Example: Spoofing (authentication bypass)
- Prevent: All backend endpoints require a valid token.
- Detect: Logs of failed access attempts (HTTP 401/403).
- Respond: Automatic IP blocking after X attempts.
This logic transforms an abstract list of vulnerabilities into an actionable security roadmap.
With this lab I've also created atomic notes for each concept (Threat Modeling, STRIDE, Risk Prioritization, Prevent-Detect-Respond) in my knowledge management system.
Goal: being able to retrace the logic in 6 months without starting from scratch.
Microsoft Threat Modeling Tool Documentation
02/11/2025
Context
This week was intense on a governance project in a large IT group. Less hands-on technical work, more structural stuff I can't really detail yet since I just started.
But I want to share something I learned recently while battling with sysdig-inspect installation. That tool gave me hell to install, but obstacles are the way, right?
The struggle led me to understand binary compatibility properly. I now try to "anabolize" interesting concepts into atomic notes when I encounter them. Here's one of those notes.
Exploration
Binary compatibility boils down to this: a binary is compatible if all the libraries and system calls it expects exist and behave the same on the target system.
The tricky part: glibc and musl are two different C standard library implementations. Think of them as different dialects of the same language. A program compiled against glibc may completely misunderstand musl's idioms, and vice versa.
Concrete consequence: heavy applications (Java, Chrome, complex CLI tools) often fail on musl-based images like Alpine because they were compiled expecting glibc.
One solution: multi-stage builds. Compile with FROM debian-slim (glibc), then copy the binary to FROM alpine (musl) for a lighter runtime image. But even that doesn't always work if the binary has deep glibc dependencies.
Lab: Running a glibc Binary on Alpine
I tested this with a simple experiment: run a Debian-compiled ls command inside Alpine.
Start an Alpine container:
docker run --rm -it alpine shDownload a Debian binary package:
wget https://ftp.debian.org/debian/pool/main/c/coreutils/coreutils_9.1-1_amd64.debExtract the ls binary:
apk add --no-cache binutils tar gzip
ar x coreutils_9.1-1_amd64.deb
tar --xform='s|./bin/ls|./bin/new-ls|' -xf data.tar.xz ./bin/lsTry to run it:
./bin/new-lsResult:
./bin/new-ls: not found
Wait, the file exists. What's happening?
The binary was compiled against glibc, but Alpine only provides musl. The system literally doesn't know where to find libc.so.6 (glibc's C library).
Check dependencies:
ldd ./bin/new-lsOutput:
/lib64/ld-linux-x86-64.so.2 (0x7ecb2f59b000)
Error loading shared library libselinux.so.1: No such file or directory
libc.so.6 => /lib64/ld-linux-x86-64.so.2 (0x7ecb2f59b000)
The binary expects glibc's libc.so.6, which doesn't exist on Alpine.
Compare with Alpine's native ls:
ldd $(which ls)Output:
/lib/ld-musl-x86_64.so.1 (0x7f4d...)
This one links to musl, Alpine's own libc implementation.
Key Discovery
What struck me: the error message "not found" is misleading. The file exists, but the dynamic linker can't find the libraries it needs. It's not about the binary itself, it's about its runtime dependencies.
This explains why so many Docker builds fail mysteriously when switching base images. It's not always about missing packages. Sometimes it's about incompatible C library dialects.
26/10/2025
This week, still deep in the forensics toolchain.
While building a sidecar container meant to trigger Sysdig whenever Falco detects a specific rule pattern, I hit a wall: how to make Sysdig run inside a container, with enough visibility to capture system calls?
To do that, I needed to containerize Sysdig itself, inside an image, inside a pod.
That experiment forced me to understand, from the ground up, how Falco and Sysdig correlate kernel syscalls with container and orchestrator metadata (PID, namespace, pod, service).
That “under-the-hood” curiosity turned into a full-blown lab: 398 lines!
Enrichment is the process where a tool (like Sysdig or Falco) observes kernel syscalls and correlates them with container and orchestrator context (PID, namespace, pod, service).
It forms the observability bridge between kernel syscalls and Kubernetes metadata.
Sysdig attaches to the kernel via a probe and reads system calls.
To enrich each event, it needs access to:
| Purpose | Required | Layer |
|---|---|---|
| Capture raw syscalls | /dev (probe) + privileged mode |
Kernel |
| Correlate PIDs / namespaces | /proc, /sys/module |
Host |
| Container metadata | /var/run/containerd.sock |
Runtime |
| Orchestrator metadata | ServiceAccount + RBAC | API server |
Each mount adds a new layer of visibility.
Goal: verify how each resource (mount, privilege, or permission) expands Sysdig visibility.
Context: Ubuntu 24.04 host with Sysdig probe loaded.
Build image:
FROM debian:bookworm-slim AS builder
RUN apt-get update && \
apt-get install -y curl gnupg && \
curl -s https://download.sysdig.com/stable/install-sysdig | bash || true && \
apt-get clean && rm -rf /var/lib/apt/lists/*
FROM debian:bookworm-slim
COPY --from=builder /usr/bin/sysdig /usr/bin/sysdig
COPY --from=builder /usr/share/sysdig /usr/share/sysdig
COPY --from=builder /lib/x86_64-linux-gnu/libm.so.6 /lib/x86_64-linux-gnu/
COPY --from=builder /lib/x86_64-linux-gnu/libc.so.6 /lib/x86_64-linux-gnu/
COPY --from=builder /lib/x86_64-linux-gnu/libdl.so.2 /lib/x86_64-linux-gnu/
COPY --from=builder /lib64/ld-linux-x86-64.so.2 /lib64/docker build -t bilalguirre/falco-watcher:multistage .Check probe presence on the host:
ls -l /dev/scap*If empty:
sudo depmod -a
sudo modprobe scapdocker run --rm -it bilalguirre/falco-watcher:multistage bashsysdigExpected:
error opening device /dev/scap0
Reason: no access to /dev.
Run the container with access to the Sysdig device.
docker run --rm -it --privileged -v /dev:/dev bilalguirre/falco-watcher:multistage bashCheck Sysdig basic output.
sysdig | headSysdig collects syscall events.
10 06:08:41.223772823 0 <NA> (1919) < poll res=0 fds=3:?0 6:?0 7:?0 5:?0 8:?0 9:?0
11 06:08:41.223840889 0 <NA> (1919) > ioctl fd=9 request=540F argument=7FFC20BB15D4
12 06:08:41.223843630 0 <NA> (1919) < ioctl res=0
13 06:08:41.223848344 0 <NA> (1919) > openat dirfd=-100(AT_FDCWD) name=/proc/6609/cmdline flags=1(O_RDONLY) mode=0
...
Because the device is now mounted, Sysdig can access the kernel probe:
ls -l /dev/scap*cr-------- 1 root root 241, 0 Oct 24 06:45 /dev/scap0
cr-------- 1 root root 241, 1 Oct 24 06:45 /dev/scap1
But there is still no process or container context.
The <NA> fields show that Sysdig cannot yet correlate kernel events with namespaces or containers.
sysdig -p "%proc.pid %proc.name %container.name" | headOutput example
-1 <NA> host
-1 <NA> host
-1 <NA> host
-1 <NA> host
-1 <NA> host
-1 <NA> host
-1 <NA> host
-1 <NA> host
-1 <NA> host
-1 <NA> host
Process and container context are missing.
Add the host’s /proc and /sys/module mounts to give Sysdig visibility into process and kernel module information.
docker run --rm -it --privileged \
-v /dev:/dev \
-v /proc:/proc \
-v /sys/module:/sys/module \
bilalguirre/falco-watcher:multistage bashCheck Sysdig output.
sysdig -p "%proc.pid %proc.name %container.name" | headSysdig now correlates kernel events with host processes.
742 k3s-server host
4882 containerd-shim host
...
Because /proc is mounted, Sysdig can read the process table and associate syscalls with running PIDs.
The /sys/module mount provides kernel module information without exposing the full /sys filesystem.
However, container metadata is still missing.
The container.name field only shows host, meaning Sysdig cannot yet link syscalls to external containers.
sysdig -p "%proc.pid %proc.name %container.name" | grep -v hostOutput example
(no output)
No container names or IDs are detected because the runtime socket (/var/run/docker.sock) is not yet mounted.
This socket is required for Sysdig to query the container runtime and enrich events with container-level context.
Next, add the runtime socket to enable container metadata enrichment.
Mount the Docker runtime socket to give Sysdig access to container metadata.
docker run --rm -it --privileged \
-v /dev:/dev \
-v /proc:/proc \
-v /sys/module:/sys/module \
-v /var/run/docker.sock:/var/run/docker.sock \
bilalguirre/falco-watcher:multistage bashCheck Sysdig process-to-container mapping.
sysdig -p "%proc.pid %proc.name %container.name %container.image.repository %container.id" | headSysdig now displays container names, images, and IDs.
6885 sysdig quirky_bohr bilalguirre/falco-watcher d81f072b8362
6886 head quirky_bohr bilalguirre/falco-watcher d81f072b8362
...
Because the Docker socket is mounted, Sysdig can query the container runtime directly.
It retrieves container metadata (name, ID, runtime engine) and correlates syscalls with their originating containers.
Verify that the socket is visible inside the container.
ls -l /var/run/docker.socksrw-rw---- 1 root docker 0 Oct 25 09:17 /var/run/docker.sock
Container metadata enrichment is now active.
Sysdig can correlate kernel syscalls with process, container, and namespace context.
Next, to enrich with Kubernetes metadata (k8s.ns.name, k8s.pod.name), add RBAC access to the API server in a cluster environment.
To let Sysdig display Kubernetes metadata (k8s.ns.name, k8s.pod.name, etc.), the pod must authenticate to the Kubernetes API and request pod information.
This is achieved through a ServiceAccount and ClusterRoleBinding with read-only access.
Create the RBAC manifest.
cat << 'EOF' > rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: sysdig-viewer
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: sysdig-viewer
rules:
- apiGroups: [""]
resources: ["pods", "namespaces"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: sysdig-viewer
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: sysdig-viewer
subjects:
- kind: ServiceAccount
name: sysdig-viewer
namespace: default
EOFApply it.
kubectl apply -f rbac.yamlCreate the Sysdig pod manifest.
cat << 'EOF' > pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: watcher-pod
spec:
serviceAccountName: sysdig-viewer
containers:
- name: watcher-container
image: bilalguirre/falco-watcher:multistage
command: ["/bin/sh", "-c", "sleep 3600"]
securityContext:
privileged: true
volumeMounts:
- mountPath: /dev
name: dev-fs
- mountPath: /proc
name: proc-fs
- mountPath: /sys/module
name: sys-fs
- mountPath: /var/run/k3s/containerd/containerd.sock
name: runtime-socket
volumes:
- name: dev-fs
hostPath:
path: /dev
- name: proc-fs
hostPath:
path: /proc
- name: sys-fs
hostPath:
path: /sys/module
- name: runtime-socket
hostPath:
path: /var/run/k3s/containerd/containerd.sock
EOFDeploy and connect.
kubectl apply -f pod.yaml && kubectl get pods -w
kubectl exec -it watcher-pod -- bashRun Sysdig with Kubernetes enrichment fields.
sysdig -p "%proc.pid %proc.name %container.name %k8s.ns.name %k8s.pod.name" | headExpected output
10266 sysdig watcher-container default watcher-pod
10261 sysdig falco-watcher falco falco-v46s5
10267 head watcher-container default watcher-pod
...
Sysdig now enriches each syscall with the namespace and pod that generated it.
The link between kernel events, containers, and Kubernetes metadata is complete!
When done, delete resources.
kubectl delete -f pod.yaml
kubectl delete -f rbac.yamlEach added mount or permission expands Sysdig’s understanding of the runtime environment.
| Layer added | Mount or config | Visibility gained | Example field(s) |
|---|---|---|---|
| Kernel access | /dev + --privileged |
Capture of raw syscalls | write fd=1 size=42 |
| Process layer | /proc |
Host process correlation | proc.pid, proc.name |
| Kernel module info | /sys/module |
Access to probe info | evt.type, evt.category |
| Container runtime | /var/run/docker.sock |
Container metadata enrichment | container.name, container.id |
| Kubernetes API (RBAC) | ServiceAccount + ClusterRoleBinding |
Orchestrator context | k8s.ns.name, k8s.pod.name |
flowchart TD
%% === STRUCTURE ===
K["**Kernel Layer**
(/dev/scap*)"]
H["**Host Layer**
(/proc, /sys/module)"]
C["**Container Runtime Layer**
(/var/run/docker.sock)"]
O["**Orchestrator Layer**
(K8s API + RBAC)"]
%% === FLOW ===
K --> H
H --> C
C --> O
O --> |"Kubernetes context"| Oout
%% === OBSERVABLE OUTPUTS ===
Kout(["*10 (1919) < poll res=0 ...*"])
Hout(["*proc.pid, evt.type*"])
Cout(["*container.name, container.image.repository*"])
Oout(["*k8s.ns.name, k8s.pod.name*"])
%% === LINKS OUTPUTS TO LAYERS ===
K ---|"Raw syscalls"| Kout
H --- |"PIDs / Namespaces"| Hout
C --- |"Container metadata"| Cout
19/10/2025
Context
This week, full immersion into Falco. And not just a standard installation: I wanted to understand how it really works before deploying anything.
Starting point: the official guide suggests deployment via Helm. But Helm, for now, isn't my approach. I have this (healthy, I think) obsession with mastering what I deploy. Can't satisfy myself with a helm install without understanding what's happening underneath.
Digging into Falco's GitHub repo, I found the raw manifests. Mission: strip down these manifests to keep only what's strictly necessary for my baseline detection PoC. After quite a bit of headache, I made it work.
Exploration
What really interested me: how does Falco capture system events in real time? What's the actual difference with Sysdig?
Falco is a real-time syscall observer, built on the same capture engine as Sysdig: kernel probe, libscap, and libsinsp.
The mechanism relies on a probe injected into the kernel. This probe intercepts syscall activity (like open(), execve(), or connect()) and can take two forms: a kernel module (falco.ko) or an eBPF program.
The probe writes summaries of each event into a shared ring buffer in RAM. This buffer is ephemeral, it doesn't persistently store anything. It only serves as a communication tunnel between the kernel and the Falco process.
Falco runs in user space and continuously reads from this buffer. Each event then goes through the rule engine:
- Extract relevant fields (
user,pid,proc.name,k8s.ns.name, etc.) - Evaluate conditions defined in YAML rules
- If a rule matches → generate an alert
Everything happens in memory, in real time. No syscalls written to disk.
Core Components
| Component | Layer | Role |
|---|---|---|
falco.ko / eBPF probe |
Kernel space | Captures syscalls (driver) |
libscap |
User space | Reads events from /dev/falco (the buffer) |
libsinsp |
User space | Decodes syscall fields into readable context |
falco binary |
User space | Runs the rule engine and emits alerts |
Key Discovery
Falco and Sysdig share the same capture infrastructure, but with different purposes.
- Falco interprets events on the fly and generates instant alerts
- Sysdig captures and records traces for later forensic analysis
Together, they form a complete chain:
Falco = live detection → Sysdig = capture and replay → Analyst = forensic investigation
flowchart TD
subgraph KERNEL["Kernel Space"]
A["Container Process
*(execve)*"]
A --> B["**Probe**
**Captures** Syscall"]
B --> D["Shared Ring Buffer (RAM)"]
end
subgraph USER["User Space"]
D --> E["**libscap**
**Reads** from /dev/falco"]
E --> F["**libsinsp**
**Decodes** (proc.name, etc.)"]
F --> G["Falco"]
G --> H["**Alert**"]
F --> I["Sysdig CLI/OSS"]
I --> J["*.scap* file **Stored**"]
end
Now that I understand the underlying mechanics, I can fine-tune my Falco configuration according to my actual needs.
More importantly: I know exactly what's running in my cluster and why!
→ Stripped-down Falco manifests
11/10/2025
Note: Starting from this entry, I'm switching this learning journal to English. It's part of my ongoing effort to improve my technical English after spending a month in Bali working on it.
Context
Back to journaling after two and a half months away!
And for good reason: finishing my systems engineer position (leaving behind a complete repo of docs and scripts for my successor), spent a month in Bali working on my English, returned to France early September, started a new cybersecurity job on the 8th, moved into my first solo apartment on the 19th.
Life turned upside down.
Now I start to getting back into rhythm. This week, while debugging Falco on Kubernetes, I encountered a behavior that initially confused me: alerts only appearing after restarting the DaemonSet.
Exploration
The issue came from a performance optimization mechanism: event buffering.
Most processes (especially those written in C) don't write their output directly to the terminal or logs in real time. They first store data in an internal buffer, then "flush" this buffer when:
- The buffer is full,
- The process explicitly calls
fflush(), - Or the process terminates (via SIGTERM for example).
This reduces the number of system calls, improving performance. But in a containerized environment where we observe via stdout, it creates "bursts" of logs that all appear together when the container stops or restarts.
Concrete case with Falco
kubectl logs daemonsets/falco -c falco -f
# No alerts visible in real time
kubectl rollout restart daemonset falco
# Events suddenly appear, with past timestampsFalco buffers its stdout by default. Alerts are indeed generated, but they remain stuck in the buffer until shutdown.
The solution: force unbuffered mode in the container args:
args:
- /usr/bin/falco
- --unbufferedResult: events now display as they occur.
Key Discovery
What struck me is that this behavior can make you believe logs are missing when they're just... waiting.
During debugging, I was convinced Falco wasn't detecting anything. In reality, it was detecting everything, just not saying so until told to stop.
Impact and Next Steps
Now, whenever I configure an observability tool in Kubernetes, I systematically check if it offers an unbuffered mode.
27/07/2025
Cette semaine marque une transition personnelle : je finis bientôt mon contrat d'ingénieur système pour basculer vers un nouveau poste. C'est le moment parfait pour "anaboliser", comme en musculation où l'on alterne phases de destruction et de reconstruction. J'ai pris des notes sur plein de choses, accumulé énormément d'expérience et de nouvelles façons de penser l'automatisation. Et en ce moment je mets pas mal de choses en place (j'en parlerai dans une autre entrée).
Mais ce n'est pas le sujet de cette semaine. Je profite du fait d'avoir accumulé beaucoup d'apprentissages avec mon projet k8s-ai-bootstrap (que je n'ai pas touché depuis une semaine, première fois que ça m'arrive !) pour retransmettre quelque chose, notamment sur la mise en place de la brique TLS/Ingress.
Le défi : HTTPS automatique
Mon objectif était simple : exposer mon API avec un certificat TLS automatique via cert-manager, en passant par l'ingress Traefik. Simple sur le papier, beaucoup moins dans la réalité.
Premier piège : beaucoup de documentation utilise NGINX comme ingress controller. Sauf que k3d intègre Traefik par défaut. Résultat, j'ai failli installer NGINX en plus avant de réaliser qu'avoir plusieurs ingress controllers dans le même cluster demande une configuration spécifique avec ingressClassName: traefik dans le manifest.
Ingress vs Load Balancer : clarification
Deux concepts à distinguer :
- Load Balancer : composant réseau qui reçoit le trafic entrant et le distribue vers les backends
- Ingress : ressource Kubernetes qui définit les règles de routage HTTP vers les services du cluster
L'Ingress n'est qu'une configuration, il faut un Ingress Controller (Traefik, NGINX) pour l'exécuter. Dans k3d, Traefik joue les deux rôles : load balancer intégré ET ingress controller.
Le piège de la configuration k3d
Point crucial découvert à mes dépens : sans exposer explicitement les ports 80 et 443 à la création du cluster, l'ingress HTTPS ne fonctionne pas.
k3d cluster create ai-bootstrap \
--port 80:80@loadbalancer \
--port 443:443@loadbalancerCette ligne map les ports du load balancer k3d vers la machine hôte. Sans ça, impossible d'accéder à mon API depuis l'extérieur en HTTPS.
ClusterIssuer : la clé de l'automatisation
Pour les certificats, j'ai opté pour un ClusterIssuer plutôt qu'un simple Issuer. La différence :
- Issuer : limité à un namespace
- ClusterIssuer : disponible pour tout le cluster
Avec cert-manager + ClusterIssuer, mon certificat se génère automatiquement dès que l'ingress est déployé. Plus besoin d'intervention manuelle.
Le résultat
Mon API est maintenant accessible en HTTPS avec un certificat auto-signé. Le pipeline fonctionne : ingress → Traefik → service → pods. La prochaine étape sera d'intégrer Let's Encrypt et de tester sur GKE.
Mais avant ça, on finit cette anabolisation dans le travail dans lequel je suis à fond !
20/07/2025
Cette semaine, j'ai ajouté l'autoscaling horizontal à ma stack IA Kubernetes. Ce qui devait être un simple kubectl apply s'est transformé en une leçon sur la gestion des ressources dans k8s.
Le piège des ressources manquantes
En voulant déployer un HorizontalPodAutoscaler pour mon API, j'ai découvert une règle fondamentale : sans resources.requests.cpu dans le manifest du deployment, le HPA ne peut pas calculer l'utilisation CPU du pod.
Dans spec.containers.resources, la distinction est claire :
requests: ressource garantie au pod → Kubernetes place le pod sur un nœud qui a cette capacité disponiblelimits: plafond de consommation → dépassement = OOMKilled
Pour dimensionner ces valeurs, kubectl top pod -n <namespace> est devenu mon meilleur ami. Cette commande affiche la consommation réelle et aide à calibrer des requests/limits réalistes.
Pods vs Nœuds : l'équation de l'autoscaling
L'autre révélation de la semaine : l'autoscaling horizontal des pods n'a de sens que si l'autoscaling vertical des nœuds est activé. Pods = logique, Nœuds = physique. Le scheduler fait le matching entre les deux, mais si les nœuds sont à saturation, créer plus de pods ne sert à rien.
Du coup, dans ma resource Terraform google_container_cluster, j'ai activé l'autoscaling des nœuds. Maintenant, quand mes pods ont besoin de plus de place, GKE provisionne automatiquement de nouveaux nœuds.
Le résultat
Ma stack gère désormais automatiquement la charge. Quand l'utilisation CPU dépasse 70%, de nouveaux pods se déploient. Si besoin, de nouveaux nœuds apparaissent. Quand la charge redescend, tout se redimensionne à la baisse.
C'est exactement ce genre de mécanique qui rend Kubernetes si puissant : je n'ai plus à deviner les besoins en ressources, le cluster s'adapte tout seul.
13/07/2025
Je suis allé sous des dalles d'une salle pour installer un set de caméras de 10 000€, mais ce n'est pas le sujet !
Ce qui m'a marqué cette semaine, c'est l'implémentation du GitOps avec ArgoCD et la migration de mon cluster Kubernetes local k3d vers GKE.
Je ne connaissais pas du tout le GitOps avant, et le fait d'avoir pratiqué m'a vraiment donné une bonne idée de la puissance du concept.
Le principe : plus besoin de faire de kubectl apply ! Si c'est dans Git, c'est dans le cluster ; si ce n'est pas dans Git, ça n'existe pas. Un opérateur GitOps tourne dans le cluster et synchronise automatiquement l'état entre Git et Kubernetes.
Dans mon cas, l'opérateur c'est ArgoCD avec un nouveau type de ressource : les Application.
La mise en pratique : j'ai créé un namespace argocd, installé ArgoCD dessus et appliqué mes fichiers d'application. À partir de là, TOUT mon cluster se synchronise avec mon dépôt Git.
Ce qui rend vraiment le truc magique, c'est quand j'ai voulu migrer sur Google Kubernetes Engine. Je me suis dit "OK, ça va prendre un certain temps." Et non ! J'ai fait exactement ce que je viens de décrire et tout mon cluster local était dans le cloud. J'avais l'impression que rien ne changeait quand je faisais un kubectl, car l'endpoint de l'API pointait maintenant sur celui de Google et plus en local.
En plus, ce qui est beau avec le DevOps, c'est que le cluster Google aussi se crée "tout seul" : juste besoin d'un terraform apply.
J'ai documenté parfaitement tout ça (avec tous les tests GitOps qui vont bien : sync, rollback, prune) dans le fichier associé du dossier docs de mon projet : https://github.com/bilalgu/k8s-ai-bootstrap/tree/main/docs
06/07/2025
Cette semaine incarne la grosse avancée du nouveau projet que j'ai mentionné la semaine dernière : k8s-ia-bootstrap !
À la suite de devops-bootstrap, qui peut encore être développé mais où je sortais de plus en plus du 80/20 de l'apprentissage, je me suis "confronté" au marché en allant voir d'autres freelances et des CTOs, et en discutant avec eux.
J'ai eu de super retours, notamment sur le fait que :
"J'axerais personnellement plus ma stack sur Kubernetes avec containers plutôt qu'ansible/packer/vm (histoire de vendre un peu de rêve)."
et
"Après si tu veux te démarquer et sortir du lot, choisis-toi un secteur ou une spécialisation genre :
- finops, t'es là pour optimiser les coûts au max,
- infra pour faire tourner de l'IA,
- dans le contexte actuel, t'orienter vers du setup/de la migration vers des providers fr (Scaleway notamment)
Il y a beaucoup de demande sur ces 3 spés mais y'en a plein d'autres"
Donc autant allier l'utile à l'agréable : aller là où il y a de la demande et aussi explorer des aspects que je ne connais pas et qui m'intriguent, notamment l'aspect "infra pour faire de l'IA".
Je suis donc parti sur cette direction avec comme axe :
Déployer automatiquement une API IA simple (FastAPI + Hugging Face) sur un cluster Kubernetes local, avec CI/CD GitHub Actions, observabilité (Prometheus + Grafana + Loki), et sécurité de base (RBAC, secrets, network policies), pour démontrer ma capacité à builder une stack cloud-native sécurisée de bout en bout.
Et je suis donc fier d'annoncer qu'après environ 2 semaines de travail acharné, j'ai ma release v1.0.0 !!!!! : https://github.com/bilalgu/k8s-ai-bootstrap/releases/tag/v1.0.0
Une stack complète qui inclut : API IA (FastAPI + Hugging Face), CI/CD automatisé, sécurité K8s (RBAC, secrets, network policies), et observabilité (Loki + Grafana).
L'objectif ? Aider les CTOs et startups à déployer rapidement une API IA sécurisée tout en maîtrisant les bases Kubernetes — bref, gagner du temps et éviter les erreurs manuelles.
Bien entendu, sur le chemin de ce développement j'ai appris pas mal de choses, notamment sur Kubernetes, l'IA, Docker, Python, le réseau (il y aura toujours des choses reliées au réseau haha).
Mais à mon avis ça sera le sujet d'une nouvelle entrée car là je me rends compte que j'ai déjà pas mal écrit !
29/06/2025
Toujours dans l'optique de me développer et d'apprendre, je suis en train de basculer doucement vers un nouveau projet que je présenterai bientôt. En pratique, je me suis confronté à plein de problèmes, et j'en ressors avec pas mal de leçons :
Notamment sur Kubernetes : j'ai toujours vu ça un peu de loin, mais maintenant je commence vraiment à avoir une idée de ce que c'est.
Alors oui, c'est un orchestrateur de containers, mais au fond qu'est-ce qui le différencie d'un docker-compose ?
Disons que déjà, la principale différence c'est que docker-compose agit sur une seule machine alors que Kubernetes agit sur un cluster, donc un ensemble de machines.
Mais résumer Kubernetes à ça est trop réducteur. Il a notamment, comparé à docker-compose :
- un principe d'auto-réparation
- un load balancing intégré
- et une observabilité native
Et comme il est lourd et difficile d'installer localement Kubernetes (c'est fait pour le cloud), on utilise généralement des versions allégées de Kubernetes comme k3s par exemple, et k3d qui permet de lancer k3s dans des containers.
23/05/2025
Encore une semaine riche !
Notamment dans les autres aspects de ma vie où j'ai dû allouer pas mal de temps, sachant que j'avais aussi mes responsabilités d'ingénieur système et cybersécurité. Mais quand j'avais du temps libre, j'ai continué à faire avancer mon projet DevOps bootstrap et j'ai attaqué (et fini !) le bloc logging et dashboards.
Observabilité : logging vs monitoring
Une distinction importante que j'ai mieux comprise cette semaine : le logging et les dashboards sont différents du monitoring et de l'alerting, bien que tout ça s'inscri dans le domaine de "l'observabilité". Avant d'avoir réellement mis les mains dans le cambouis, ce n'était pas évident pour moi.
J'ai donc mis en place la stack classique :
- Promtail (collecte des logs)
- Loki (stockage et indexation des logs)
- Grafana (visualisation et dashboards)
Debugging : le piège du curl
Un point qui m'a marqué : quand j'essayais de tester certains endpoints comme http://<EC2_PUBLIC_IP>/monitoring/grafana, bien que dans le browser l'endpoint était fonctionnel, en ligne de commande curl me renvoyait :
405 Method Not AllowedLa cause : curl envoie par défaut une requête de type GET, mais certains endpoints n'acceptent que des méthodes spécifiques.
Solution : spécifier explicitement la méthode HTTP, par exemple :
curl -X POST ...15/06/2025
Cette semaine, le DNS a volé mon temps !
Je me suis creusé la tête sur un problème bien spécifique : comment faire en sorte que lorsque Ansible lance docker-compose, mon nom de domaine soit bien propagé dans la plupart des resolvers DNS (en particulier celui de Traefik), pour qu'il puisse me générer un certificat HTTPS à coup sûr.
Le contexte : j'ai pris un nom de domaine sur Dynadot, et j'ai réussi via l'API Dynadot à automatiquement associer une nouvelle IP à mon domaine. Cette nouvelle IP est celle de ma VM EC2 qui est générée à chaque terraform destroy + apply (ce que je fais constamment durant mes tests).
Je n'ai pas encore résolu à 100% mon problème, mais voici ce que j'ai appris :
- La propagation DNS prend du temps
Une modification d'enregistrement DNS ne la rend pas visible immédiatement. Il faut attendre que le DNS se "propage" à travers les différents resolvers.
D'ailleurs, petite astuce pour récupérer son IP publique :
curl -s ifconfig.me- Let's Encrypt a des limites
Let's Encrypt limite le nombre de certificats pour le même nom de domaine dans un laps de temps donné.
Pendant le développement, utiliser absolument l'environnement de staging ! : https://letsencrypt.org/docs/staging-environment/
- Piège avec les variables Bash
En Bash, $(...) écrase les sauts de ligne, ce qui peut rendre grep inefficace :
LOGS=$(docker logs traefik)
echo $LOGS | grep "ERR.*Unable to obtain"
# → Rien ne s'afficheAlors que directement :
docker logs traefik | grep "ERR.*Unable to obtain"
# → Fonctionne correctementMa solution actuelle
Pour contourner cette histoire de DNS, Ansible lance un script bash qui inspecte les logs du container Traefik. S'il détecte une erreur de certificat, il relance automatiquement toute la stack.
08/06/2025
Cette semaine, le "highlight" a été la réinitialisation d'un serveur Rocky vers une version spécifique avec son noyau.
La post-installation du serveur : il était là le vrai challenge !
-
J'avais fait un backup des fichiers des interfaces réseau
/etc/sysconfig/network-scripts/ifcfg-*. Je me suis rendu compte qu'avec SELinux activé, il fallait corriger les étiquettes avecrestorecon. Restaurer les contextes SELinux lors de copie de fichiers, on y pense pas directement ! (D'ailleurs, faire gaffe avec la méthode de remise des fichiers d'interfaces réseau, car l'UID des connexions NMCLI change) -
En parlant de backup, je me suis même fait une espèce de petit script qui fait un audit de la machine avant réinitialisation pour rapidement la remettre comme il faut :
DEST="preinstall_audit_$(date +%Y_%m_%d)"
mkdir -p "$DEST" "$DEST/network-scripts"
echo "[+] Collecte des infos dans $DEST"
lsblk > "$DEST/01_lsblk.txt"
blkid > "$DEST/02_blkid.txt"
vgs > "$DEST/03_vgs.txt"
lvs > "$DEST/04_lvs.txt"
pvs > "$DEST/05_pvs.txt"
cat /etc/fstab > "$DEST/06_fstab.txt"
cat /etc/exports > "$DEST/07_exports.txt"
mount | grep nfs > "$DEST/08_nfs_mounts.txt"
ip r > "$DEST/09_ip_route.txt"
rpm -qa > "$DEST/10_all_rpms.txt"
rpm -qa | grep -Ei 'sssd|ldap|nslcd|krb5|realm' > "$DEST/11_auth_related_rpms.txt"
cat /etc/sssd/sssd.conf > "$DEST/12_sssd_conf.txt"
mount > "$DEST/13_all_mounts.txt"
df -h > "$DEST/14_df_h.txt"
cp /etc/sysconfig/network-scripts/ifcfg-* "$DEST/network-scripts/"
ls -l "$DEST"- Pareil, pour remettre le serveur NFS et activer les bons services via
firewall-cmd, ou bien remettre le LDAP en faisant attention au fichiers PAM du style/etc/authselect/password-authfait toute la différence !
01/06/2025
Cette semaine j'ai pu faire tellement de trucs et apprendre énormément sur mon projet DevOps-Bootstrap !
D'ailleurs, j'ai affiné ma réflexion par rapport à ce projet. Si je devais le présenter, je dirais que c'est un projet proche de la production qui provisionne l'infrastructure, automatise la configuration et déploie des services web sécurisés et évolutifs en utilisant des conteneurs.
L'objectif c'est de construire une stack conçue pour les développeurs backend et les ingénieurs infrastructure.
J'ai énormément avancé et j'ai pu :
1. Mettre en place un reverse proxy avec Traefik : J'ai intégré Traefik dans ma stack (web/back/db) qui route dynamiquement :
/vers le serviceweb/apivers le serviceback
--> doc
2. Introduire le monitoring en temps réel : J'ai ajouté des métriques pour surveiller les performances au niveau des conteneurs :
cadvisor— expose les métriques des conteneurs et de l'hôteprometheus— collecte ces métriques
--> doc
Avec ce bloc terminé, la v2.0.0 est terminée !
3. Use Case : Backend API as a Service : J'ai fini la semaine en montrant comment un développeur backend peut déployer une API (Node.js + PostgreSQL) en quelques minutes avec la Stack.
--> doc
Actuellement, je travaille sur l'ajout d'un accès sécurisé à tous les services via HTTPS, en utilisant un vrai nom de domaine et la génération automatique de certificats avec Let's Encrypt (via Traefik).
J'ai déjà acheté le nom de domaine, mais il reste un point à automatiser : à chaque fois qu'on crée automatiquement une VM avec une nouvelle IP via Terraform, il faut encore changer manuellement le DNS dans le provider du nom de domaine.
En bref, on continue d'avancer, de progresser et d'apprendre !
25/05/2025
Cette semaine, j'ai super bien avancé dans mon projet (et le résultat me fait kiffer !)
J'ai fait évoluer l'architecture que je déploie automatiquement via Ansible vers quelque chose de beaucoup plus robuste : une stack complète orchestrée par docker-compose. L'approche est maintenant bien plus professionnelle et maintenable.
La nouvelle architecture se compose de trois services :
web- Frontend statique (HTML/CSS/JS servi par Nginx)back- Backend/API (Node.js avec Express)db- Base de données (PostgreSQL)
Cette stack se déploie entièrement via docker-compose, lui-même lancé par Ansible.
Un aspect qui m'a marqué, c'est les health checks. J'ai configuré un health check sur le service db pour que le backend ne démarre qu'une fois que PostgreSQL est prêt à accepter des connexions.
depends_on:
db:
condition: service_healthyÇa évite les erreurs de connexion au démarrage (que j'ai eues).
Pour les détails techniques, j'ai documenté cette évolution dans docs/06-architecture-v2
Et un petit aperçu du docker-compose pour finir :
services:
web:
build: ./web
ports:
- "80:80"
depends_on:
back:
condition: service_started
networks:
- app-net
back:
build: ./backend
ports:
- "8080:8080"
depends_on:
db:
condition: service_healthy
environment:
DB_HOST: db
DB_USER: appuser
DB_PASSWORD: mysecretpassword
DB_NAME: appdb
networks:
- app-net
db:
image: "postgres:alpine"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
interval: 5s
timeout: 5s
retries: 5
restart: always
environment:
POSTGRES_DB: appdb
POSTGRES_USER: appuser
POSTGRES_PASSWORD: mysecretpassword
volumes:
- pgdata:/var/lib/postgresql/data
networks:
- app-net
volumes:
pgdata:
networks:
app-net:18/05/2025
De retour de mon voyage, j'ai dû m'attaquer à un bon nombre de tickets qui se sont accumulés pendant la semaine et demie où j'étais absent. Mais j'ai quand même pu avancer sur mon projet :
J'ai créé un dossier complet de documentation (devops-bootstrap/docs) où j'ai détaillé chaque étape de mon projet jusqu'à présent :
- Provisioning
- Configuration
- Security hardening
J'ai réussi à "ansibiliser" mes configurations fail2ban de la semaine dernière.
J'ai également implémenté des règles de firewall avec nftables qui s'activent automatiquement au démarrage de la VM EC2 (toujours via Ansible). Pour ça j'ai :
- Créé un service systemd qui applique ces règles à chaque redémarrage
- Fait particulièrement attention à ne pas écraser les règles de firewall utilisées par SSH
11/05/2025
Une semaine assez calme car j'étais en voyage en Arabie Saoudite à l'aventure ! J'ai tout de même profité des quelques heures libres que j'avais pour continuer à avancer sur mon projet.
Toujours sur la partie sécurité, j'ai enfin pu mettre en place ma jail qui détecte les logs d'erreur SSH d'Amazon EC2. Voici à quoi ressemble le filtre que fail2ban va utiliser pour les connexions SSH :
[Definition]
prefregex = ^.*sshd\[<F-MLFID>\d+</F-MLFID>]: <F-CONTENT>.+</F-CONTENT>$
failregex =
^<F-NOFAIL>AuthorizedKeysCommand</F-NOFAIL> .+ failed, status \d+$
^Connection closed by .* <HOST> port \d+ \[preauth\]$
ignoreregex =
Le prefregex fait en sorte de capturer le contenu de plusieurs lignes qui ont le même sshd[PID]. Ensuite, on analyse ce contenu dans failregex : s'il y a un enchaînement d'un AuthorizedKeysCommand avec un "failed status" suivi d'un "connection closed" avec une IP, alors celle-ci est détectée. En fonction du maxretry configuré dans ma jail, l'adresse IP sera bannie ou non.
La balise <F-NOFAIL> est vraiment importante ici car, sans elle, comme la ligne "AuthorizedKeysCommand" ne contient pas d'adresse IP, fail2ban générerait un message d'erreur.
04/05/2025
J'ai pu pas mal avancer dans mon projet ! J'ai mis en place une pipeline CI/CD avec GitHub Actions (d'ailleurs j'ai dû créer une branche parallèle ci/test-pipeline, que j'ai ensuite mergée dans le main tellement j'ai dû faire de tests avec les git push).
J'ai restructuré l'ensemble, refait le README et finalisé la stack complète que j'avais imaginée pour une V1 de ce projet. J'en ai donc fait une première release.
Maintenant, j'ai imaginé les axes d'amélioration pour une V2 que j'ai créés dans une roadmap. Je travaille actuellement sur le partie sécurité. J'essaie de durcir la configuration SSH de la VM EC2 qui est créée automatiquement dans ma stack et aussi d'ajouter fail2ban dessus.
Sauf que... je suis en train de plonger dans les regex de fail2ban en essayant de mettre en place ma propre jail, car les logs d'échecs SSH d'Amazon EC2 ne sont pas les mêmes que sur SSH classique, et ça change tout !
27/04/2025
Au-delà de mon travail d'ingénieur système et cybersécurité, où j'ai dû m'engouffrer dans un code Python à plus de 1300 lignes qui interagit avec d'autres gros scripts pour déboguer un service, j'ai pu avancer sur mon projet cette semaine !
J'ai franchi plusieurs étapes :
- Mise en ligne du projet sur GitHub
- Provisionnement automatique d'une VM via Terraform
- Configuration de la VM avec Ansible pour installer Docker
- Déploiement d'un conteneur à partir d'un Dockerfile qui lance un site web statique Nginx (toujours avec Ansible)
Un simple terraform init, plan, apply suivi d'un ansible-playbook -i inventory.ini playbook.yml et boum ! Tout se déploie automatiquement.
À chaque étape, j'ai essayé de documenter proprement le projet avec un README. Pour voir l'avancée actuelle du projet, voici le dernier commit.
Mon prochain challenge : mettre en place une pipeline CI/CD avec GitHub Actions. L'objectif est que chaque push sur la branche main qui modifie certains fichiers dans l'arborescence déclenche automatiquement le déploiement sur ma VM.
20/04/2025
Pour rebondir sur la semaine précédente, après avoir passé plusieurs heures d'affilée à la lecture du livre sur les réseaux (qui est super intéressant et qui explique simplement des notions "complexes") - d'ailleurs, leur explication du modèle OSI est excellente ! - j'ai senti qu'il me manquait quand même de la pratique dans ma journée.
J'ai donc finalement franchi le cap et structuré la mise en place d'un projet centré sur l'apprentissage d'un domaine qui m'intéresse de plus en plus : le DevOps. Ce projet intègre des technologies d'Infrastructure as Code, de Configuration Management, de Conteneurisation et de Déploiement Continu (CI/CD).
Malheureusement, mes autres contraintes et responsabilités font que ce projet passe en dernière priorité :'(. Je n'ai pu avancer dessus que quelques soirs, mais au stade où j'en suis, j'ai réussi avec Terraform à créer une instance EC2 AWS avec un security group me permettant de me connecter en SSH avec une clé - et tout ça juste avec un terraform apply !
13/04/2025
Cette semaine a été "un peu moins dense", car je me suis principalement concentré sur la rédaction d'un long rapport de plus d'une trentaine de pages.
Au travail, j'ai réinstallé et configuré plusieurs machines. J'ai même pu récupérer des ordinateurs destinés à la réforme qui présentaient "seulement" des problèmes de BIOS ou de batterie. Un super outil qui m'a aidé et que je recommande vivement est Medicat - une véritable boîte à outils pour le diagnostic et la réparation de systèmes.
Par curiosité, la semaine dernière j'ai commencé à me former via la plateforme Fortinet, notamment sur la certification Fortinet Certified Fundamentals Cybersecurity. Mais honnêtement, après quelques heures, mon enthousiasme était limité. J'ai trouvé que cela "apportait peu de valeur" au vu de ma situation actuelle, comparé à d'autres actions que je pouvais entreprendre.
J'ai donc pivoté vers la lecture d'un nouveau livre (pour changer, haha) : Réseaux informatiques - Notions fondamentales. J'ai tout de suite accroché à ce contenu. Mais, la question de commencer à réaliser des projets commence vraiment à me trotter dans la tête - peut-être est-il temps de passer de la théorie à plus de pratique ?
06/04/2025
Toujours dans la continuité de la semaine dernière : on reste dans le thème des firewalls — mais cette fois-ci, avec un cas concret de NAT.
J'ai voulu expérimenter la mise en place d’un petit réseau virtuel entre deux machines, pour permettre à une VM Rocky Linux d’accéder à Internet en passant par une VM Debian agissant comme routeur NAT.
-
VM Debian a deux interfaces réseau :
- une en NAT via VirtualBox
- une en réseau privé hôte (connexion avec Rocky)
-
VM Rocky Linux a une seule interface, en réseau privé hôte
Il faut d'abord activer le forwarding pour que le noyau Linux autorise le passage de paquets entre interfaces réseau.
# Activer temporairement
echo 1 > /proc/sys/net/ipv4/ip_forward
# Activer de manière permanente
vi /etc/sysctl.conf
# → décommenter ou ajouter : net.ipv4.ip_forward = 1On indique à Rocky que Debian est sa passerelle par défaut :
ip route add default via 192.168.56.8 dev enp0s3Après cette configuration, Debian pouvait bien faire un ping vers l’extérieur (ping 8.8.8.8), mais Rocky… non :
[root@rocky ~]# ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) octets de données.
^C
--- statistiques ping 8.8.8.8 ---
2 paquets transmis, 0 reçus, 100% packet lossRocky envoie bien la requête vers 8.8.8.8 → Debian la relaie vers Internet, mais… le paquet garde comme IP source l’adresse privée de Rocky (192.168.56.9). Résultat : le serveur tente de répondre à une adresse qui n’existe pas sur Internet. D’où l’absence de réponse.
Solution : MASQUERADE
Cette règle a tout débloqué ! :
iptables -t nat -A POSTROUTING -o enp0s3 -j MASQUERADEDe cette façon, Debian remplace l’adresse source des paquets sortants par sa propre IP sur l’interface NAT (10.0.2.15). Ainsi, les réponses peuvent revenir sans problème, et Debian les retransmet ensuite à Rocky.
[root@rocky ~]# ping -c 3 8.8.8.8
64 octets de 8.8.8.8 : icmp_seq=1 ttl=61 temps=9.39 ms
64 octets de 8.8.8.8 : icmp_seq=2 ttl=61 temps=6.27 ms
64 octets de 8.8.8.8 : icmp_seq=3 ttl=61 temps=11.5 msOn peut le voir de manière plus concrète avec tcpdump (je fais un ping 8.8.8.8 via Rocky).
Avant la règle NAT :
root@debian:~# tcpdump -i enp0s3 -n host 8.8.8.8
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on enp0s3, link-type EN10MB (Ethernet), snapshot length 262144 bytes
15:56:58.962831 IP 192.168.56.9 > 8.8.8.8: ICMP echo request, id 9, seq 1, length 64
15:56:59.971352 IP 192.168.56.9 > 8.8.8.8: ICMP echo request, id 9, seq 2, length 64
15:57:00.995193 IP 192.168.56.9 > 8.8.8.8: ICMP echo request, id 9, seq 3, length 64Après la règle NAT :
root@debian:~# tcpdump -i enp0s3 -n host 8.8.8.8
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on enp0s3, link-type EN10MB (Ethernet), snapshot length 262144 bytes
15:56:08.551268 IP 10.0.2.15 > 8.8.8.8: ICMP echo request, id 8, seq 1, length 64
15:56:08.556382 IP 8.8.8.8 > 10.0.2.15: ICMP echo reply, id 8, seq 1, length 64
15:56:09.553435 IP 10.0.2.15 > 8.8.8.8: ICMP echo request, id 8, seq 2, length 64
15:56:09.562202 IP 8.8.8.8 > 10.0.2.15: ICMP echo reply, id 8, seq 2, length 64
15:56:10.555232 IP 10.0.2.15 > 8.8.8.8: ICMP echo request, id 8, seq 3, length 64
15:56:10.569560 IP 8.8.8.8 > 10.0.2.15: ICMP echo reply, id 8, seq 3, length 6430/03/2025
Cette semaine je n'ai pas eu l'occasion de me former car j'étais à fond dans le travail, mais j'ai appris et pratiqué plein de choses intéressantes.
J'ai notamment vu les "capabilities" Linux, un concept intéressant qui décompose les droits root en plusieurs "petits droits" qu'on peut ensuite attribuer à des binaires ou à des processus spécifiques.
Par exemple, si je souhaite manipuler des règles iptables via un script Python, au lieu de le lancer avec les droits root (ce qui risquerait de créer des conflits avec l'arborescence de fichiers créée par Python), je peux simplement donner la capability CAP_NET_ADMIN au binaire Python. Même si ça crée potentiellement un trou de sécurité dans le sens où n'importe qui exécutant Python pourrait interférer avec les règles du firewall.
J'ai aussi vu l'importance de la règle nft/iptables ct state ESTABLISHED,RELATED accept. Sans ça, les requêtes DNS ne fonctionnent pas correctement !
23/03/2025
100% de la lecture du livre de préparation à la certification LPIC-2 terminé (~900 pages) ! Tout en mettant en pratique chaque chapitre, les derniers modules qui m'ont marqué :
- Gestion des clients réseau : (OpenLDAP, DHCP)
- Serveurs de fichiers : (Samba, NFS)
- Services e-mail : Configuration de Postfix
La prochaine étape : déconstruire mes notes et préparer l'examen 201.
16/03/2025
Une semaine où j'ai jonglé entre formation, résolution de tickets et missions confiées.
Un cas intéressant cette semaine : un utilisateur est venu car son laptop ne se chargeait plus malgré le branchement de l'adaptateur secteur. En analysant la situation, on s'est rendu compte que l'adaptateur n'était pas assez puissant (130W) alors que le laptop nécessitait 180W.
Pour résoudre le problème on a :
- Ouvert le PC et démonté la batterie
- Remis en place la batterie
- Branché l'adaptateur secteur
Et là, surprise ! La batterie a été détectée et a commencé à se charger.
Avant ce cas, je ne pensais pas que la puissance d'un adaptateur jouait un rôle aussi crucial pour un bon fonctionnement de PC portable !
09/03/2025
Une semaine plus tranquille que les précédentes, ce qui m'a permis de me concentrer sur la lecture du livre de préparation à la certification LPIC-2. J'en suis à 60% de cet ouvrage de 882 pages.
Parmi les principaux sujets que j'ai vu cette semaine :
- Configuration réseau
- Maintenance système
- Noyau Linux
- Planification des ressources
- Serveurs de noms de domaine (DNS)
J'essaie de mettre en pratique au fur et à mesure les connaissances acquises. Actuellement, je suis sur la mise en place d'un serveur DNS de cache (appelé aussi "resolver" ou "caching name server").
02/03/2025
Après avoir décroché la certification LPIC-1, j'ai entamé la lecture de LINUX - Préparation à la certification LPIC-2. Pour l'instant, j'ai vu tout ce qui concerne le démarrage d'un système et les différents périphériques de stockage (NVMe, RAID, etc.). C'est marrant comme à chaque relecture, je comprends ces concepts de mieux en mieux !
Au travail, on m'a confié une nouvelle mission : démonter un ordinateur pour y installer une nouvelle carte graphique, puis le configurer dans notre DNS afin de l'utiliser comme serveur hébergeant un micro-service. Le côté hardware, c'est un domaine où j'ai peu d'expérience : savoir où placer la carte graphique et comment la connecter avec les nappes, c'était une grande première pour moi. Encore une occasion d'apprendre !
Et pour finir la semaine, ma petite sœur m'a appelé à la rescousse ce weekend. Son mini-PC Blackview MP60 refusait de démarrer sous Windows, restant bloqué sur un écran de dépannage après le BIOS. J'ai réussi à booter l'ordinateur avec un SystemRescue, puis à me connecter dessus en SSH pour analyser le disque contenant l'OS Windows. J'ai utilisé des commandes comme lsblk, blkid, fdisk et badblocks. Et en les lançant dans un tmux, j'ai pu me déconnecter de ma session SSH sans interrompre les interrompre.
Ce cas m'a fait prendre conscience des progrès que j'ai réalisés. Il y a quelques mois à je n'aurais jamais été capable de faire ça !
23/02/2025
Cette semaine j'ai eu l'opportunité de consacré pas mal de temps à la préparation de l'examen LPI 102, la deuxième partie de la certification LPIC-1.
Pour me préparer, je me suis entraîné sur des TPs et des QCMs que j'ai notamment générer via l'IA (Claude). Voici un exemple de prompt.
Lors de mes entraînements, je me suis attardé sur le concept de métrique dans les tables de routage. Ce cas d'étude m'a permis d'en apprendre plus sur le sujet.
En fin de semaine, j'ai décidé de passer l'examen LPI 102. Comme pour le 101, je me suis rendu dans un centre d'examen à Paris. Et bonne nouvelle : j'ai réussi l'épreuve avec un score de 620/800, soit 15,5/20 ! (La certification)
En parallèle, j'ai également progressé sur le sujet iptables/nftables abordé la semaine dernière. J'ai fait une documentation détaillant les configurations que j'ai mises en place. L'objectif est que mon collègue puisse facilement comprendre et prendre le relais.
16/02/2025
Une semaine riche !
Dans le cadre de mon travail, j’ai eu pour mission de sécuriser un microservice. Première étape : analyser son fonctionnement, comprendre son code et sa structure. J'ai donc fait pas mal de python !
J’ai notamment vu comment gérer des conteneurs Docker via des modules Python, mais aussi comment appliquer des règles iptables directement depuis un script.
En parlant d’iptables, j’ai approfondi mes connaissances sur son sujet. Une ressource qui m'a aidé : iptables : the two variants and their relationship with nftables.
Et enfin la semaine ce clôture samedi matin, où j’ai passé et validé l’examen LPI 101 : 580/800 (soit 14,5/20)
09/02/2025
Cette semaine j'ai eu l'occasion de pas mal me former. J’ai terminé le livre Linux - Préparation à la certification LPIC-1 (examens LPI 101 et LPI 102) et mis en pratique les connaissances acquises. Prochaine étape : passer l’examen LPI 101 (inscription déjà faite !).
En attendant, j’ai attaqué une autre lecture : UNIX and Linux System Administration Handbook.
Petite découverte : le parcours de Evi Nemeth, pionnière dans le monde UNIX/Linux, qui a marqué plusieurs générations d’administrateurs système.
02/02/2025
Après les tests effectués sur VMs la semaine dernière, j’ai enfin appliqué la migration en production. Notre cluster tournant sous CentOS 7 est maintenant en Rocky Linux 8 !
Mais tout ne s’est pas passé comme prévu…
L’un des problèmes les plus inattendus a été l’échec de dnf distrosync, qui affichait une erreur indiquant un manque d’espace sur le système de fichiers racine. Après quelques vérifications avec du, j’ai identifié le coupable : /var/cache/dnf.
Solution trouvée :
- J’ai déplacé /var/cache/dnf vers un stockage NFS.
- Et j’ai créé un lien symbolique avec
ln -spour pointer vers ce nouvel emplacement.
26/01/2025
Toujours dans mon activité d’ingénieur système, j’ai reçu comme mission de migrer nos serveurs (actuellement en CentOS 7) en Rocky Linux 8. Pour éviter tout impact en production, j’ai d’abord effectué ces tests sur une machine virtuelle qui se rapproche le plus des conditions en production.
Les ressources qui m’ont particulièrement aidées :
- Guide officiel Rocky Linux : migrate2rocky
- Guide de migration CentOS vers Rocky Linux
- Migrate CentOS 7/8 to Rocky Linux 8
La plus grande difficulté dans cette migration c'est les problèmes de dépendances des différents paquets… (et il y en a un paquet). Il faut aussi s'assurer de bien changer les dépôts correctement.
19/01/2025
Pour faire suite à la semaine dernière, on a réalisé qu'on utiliser bind9 alors que dans notre configuration actuelle, NetworkManager utilise déjà dnsmasq dans un de ses sous-processus. Après réflexion, on a donc décidé de configurer notre DNS interne avec dnsmasq.
Une ressource qui m’a particulièrement aidé dans cette tâche : linuxtricks.
Points importants rencontrés :
-
Conflit avec systemd-resolved : j’ai remarqué que systemd-resolved et dnsmasq entraient en conflit car ils écoutaient tous les deux sur le port 53.
-
Solution : j’ai configuré dnsmasq pour écouter sur le port 5353, et redirigé les requêtes DNS spécifiques via systemd-resolved vers l’interface où je souhaitais qu’il y ait le DNS de dnsmasq.
12/01/2025
Dans le cadre de mon contrat actuel en tant qu’ingénieur système, j’ai mis en place un DNS interne en utilisant BIND9. Une ressource particulièrement utile qui m’a aidé dans cette tâche : tutos.eu.
Pendant cette configuration, j’ai également rencontré un problème avec NetworkManager. En investiguant, j’ai découvert qu’il lançait une instance de dnsmasq. En inspectant le service et le binaire de dnsmasq, j’ai constaté, grâce à l’outil ldd, que certaines bibliothèques partagées n’étaient pas accessibles, car elles se trouvaient dans un répertoire géré par Snap. Pour résoudre ce problème, j’ai utilisé ldconfig afin de rendre ces répertoires accessibles au système.
05/01/2025
Après avoir terminé la lecture du précédent livre, qui s'inscrit dans une série d'ouvrages sur l'administration Linux, je me suis fixé comme objectif : obtenir la certification LPIC-1.
Je me suis donc procuré le livre Linux - Préparation à la certification LPIC-1 (examens LPI 101 et LPI 102), toujours de la même édition (que je trouve super d'ailleurs).
À la fin de cette semaine, j'ai progressé jusqu'à 69 % du livre. J'ai pris soin de pratiquer chaque fois qu'un concept intéressant apparaissait. Le livre propose à la fin de chaque chapitre un QCM "Validation des connaissances", qui comporte généralement une cinquantaine de questions, ainsi qu'environ quatre travaux pratiques pour mettre en application ce qui a été appris.
Deux expériences m'ont particulièrement marqué cette semaine :
-
Réparation d'une VM suite à une erreur critique
En pratiquant, j'ai accidentellement déplacé une bibliothèque système essentielle sur ma VM. Cela a rendu le système complètement inutilisable (même les commandes de base commecd,lsoumvne fonctionnaient plus). J'ai réussi à résoudre le problème en démarrant la VM avec une ISO de SystemRescue, en montant le système de fichiers et en le modifiant pour le "réparer". Ce qui était encore plus gratifiant, c'est que j'ai vraiment compris ce que je faisais pendant cette intervention ! -
Création d'un script shell
J'ai écrit un script shell qui prend en argument un fichier texte, lit deux nombres ligne par ligne, puis affiche leur somme. Voici le script :
[ $# -eq 1 -a -f $1 ] || exit 1
typeset -i var=0
while read a b
do
var=$a+$b
echo $a + $b = $var
done <$1
26/12/2024
Après avoir lu le livre Linux - De la ligne de commande à l'administration système, j'ai mis en place un serveur et un client Puppet avec un manifest appliquant une configuration minimaliste. Ce livre m’a permis m'a permis de réviser des principes fondamentaux de la sécurité Linux (en plus des droits d'accès) notamment :
- SELinux
- AppArmor
- Les ACL (Access Control Lists)
- Les Capabilities
J'ai également approfondi mes connaissances sur la conteneurisation avec Docker et l'orchestration avec Kubernetes. Chaque fois qu'un concept me semblait intéressant, je prenais le temps de l'expérimenter concrètement sur une ou plusieurs machines virtuelles, afin de solidifier mes acquis.