Introduction
Ce guide explique l'architecture Linux en termes pratiques et montre comment construire un petit service HTTP local que vous pourrez étendre dans des conteneurs et de l'orchestration. Vous verrez comment le noyau, les processus, les systèmes de fichiers, le réseau et l'init (souvent systemd) interagissent, comment les requêtes traversent la pile, les points de défaillance courants et les arbitrages opérationnels. À la fin, vous aurez un exemple fonctionnel supervisé par systemd, placé derrière Nginx, containerisé avec Docker et, en option, orchestré avec Kubernetes. Pour garder la découvrabilité, nous mentionnerons les notions clés comme Linux architecture, Linux components, Linux data flow, Linux design et Linux operations.
Architecture Linux en un coup d'œil
De bas en haut :
- Le matériel exécute des instructions et gère les interruptions.
- Le noyau Linux gère l'ordonnancement CPU, la mémoire, les périphériques, les systèmes de fichiers et le réseau ; il expose une interface d'appels système.
- La bibliothèque C (glibc, etc.) et les runtimes de langage encapsulent les syscalls.
- Le système d'initialisation (souvent systemd) amène la machine à un état utilisable et supervise les services.
- L'espace utilisateur contient les shells (Bash), démons et applications.
- Les espaces de noms de fichiers exposent les ressources sous /, avec /proc et /sys pour l'état du noyau.
- Le réseau fournit des sockets via le noyau, avec interfaces, routes, pare-feu et résolution de noms.
Ces couches forment un modèle cohérent : les processus en espace utilisateur interagissent avec le noyau via des descripteurs de fichiers et des syscalls, tandis que systemd contrôle le cycle de vie des services.
Flux de contrôle et flux de données
Contrôle :
- Un processus appelle le noyau via des syscalls (open, read, write, socket, connect, accept, fork, execve).
- systemd utilise les cgroups pour placer les services dans des groupes à ressources contrôlées et les redémarrer en cas d'échec.
- Les signaux (SIGTERM, SIGKILL) contrôlent la vie des processus. Les timers, sockets et units systemd structurent le démarrage.
Données :
- L'espace utilisateur lit/écrit des descripteurs (fichiers, pipes, sockets). Les E/S disque passent par le VFS vers les pilotes de FS et couches bloc. Les E/S réseau passent par l'API socket vers les piles TCP/UDP et les pilotes de la carte réseau.
- Flux web typique : client → NIC → TCP/IP noyau → accept() du serveur → handler utilisateur → système de fichiers ou amont → write() → TCP/IP noyau → NIC → client.
Astuces d'inspection :
uname -a # noyau et plate-forme
ps -ef | grep APP # arborescence des processus
ls -l /proc/$/fd # descripteurs du shell courant
ss -ltnp # sockets et écouteurs
sudo journalctl -xe # messages système récents
Exemple pratique : un petit service HTTP
Nous allons implémenter un service HTTP JSON minimal lié à 127.0.0.1:5000, le gérer avec systemd, placer Nginx devant, puis le containeriser.
Créez /opt/app/app.py :
#!/usr/bin/env python3
from http.server import BaseHTTPRequestHandler, HTTPServer
import json
class H(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/healthz":
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.end_headers()
self.wfile.write(b"ok\n")
return
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
body = {"message": "hello", "path": self.path}
self.wfile.write(json.dumps(body).encode("utf-8"))
if __name__ == "__main__":
HTTPServer(("127.0.0.1", 5000), H).serve_forever()
Installation et smoke test :
sudo mkdir -p /opt/app && sudo chown -R "$USER":"$USER" /opt/app
chmod +x /opt/app/app.py
/opt/app/app.py & # test rapide
curl -s http://127.0.0.1:5000/ | jq . || curl -s http://127.0.0.1:5000/
kill %1
Cela met en place un chemin de requête simple, observable de bout en bout.
Gestion avec systemd et Bash
Utilisez systemd pour superviser le service et Bash pour automatiser.
Créez /etc/systemd/system/hello.service :
[Unit]
Description=Hello HTTP service
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/opt/app
ExecStart=/usr/bin/env python3 /opt/app/app.py
Restart=on-failure
RestartSec=2s
AmbientCapabilities=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
Activez et démarrez :
sudo systemctl daemon-reload
sudo systemctl enable --now hello.service
sudo systemctl status hello.service --no-pager
curl -s http://127.0.0.1:5000/ | head -n1
Script d'aide Bash, /usr/local/bin/helloctl :
#!/usr/bin/env bash
set -euo pipefail
case "${1:-}" in
start) sudo systemctl start hello.service ;;
stop) sudo systemctl stop hello.service ;;
status) systemctl --no-pager status hello.service ;;
logs) sudo journalctl -u hello.service -n 200 -f ;;
reload) sudo systemctl restart hello.service ;;
*) echo "usage: $0 {start|stop|status|logs|reload}"; exit 2 ;;
esac
Installez et inspectez les flux :
sudo install -m 0755 /usr/local/bin/helloctl /usr/local/bin/helloctl
ss -ltnp | grep 5000
ps -o pid, ppid, cmd, pmem, etime -C python3
sudo journalctl -u hello.service -n 50 --no-pager
Points de contrôle flux de contrôle/données :
- Contrôle : systemd → ExecStart → processus → signaux lors des restart/stop.
- Données : client → 127.0.0.1:5000 → app → réponse.
Mise en frontal avec Nginx
Placez Nginx devant pour terminaison et routage HTTP. Installez Nginx depuis votre gestionnaire de paquets, puis créez /etc/nginx/conf.d/hello.conf :
server {
listen 80;
server_name _;
location /healthz {
proxy_pass http://127.0.0.1:5000/healthz;
}
location /api/ {
proxy_pass http://127.0.0.1:5000/;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location / {
root /usr/share/nginx/html;
index index.html;
}
}
Testez et rechargez :
sudo nginx -t && sudo systemctl reload nginx
curl -s http://127.0.0.1/api/ | head -n1
Flux de données désormais : client → Nginx (port 80) → loopback → app (5000) → Nginx → client.
Containerisation avec Docker
Emballez l'app pour la portabilité.
Créez /opt/app/requirements.txt (vide pour la stdlib) et Dockerfile :
FROM python:3.11-slim
WORKDIR /opt/app
COPY app.py .
EXPOSE 5000
USER 1000:1000
CMD ["python", "/opt/app/app.py"]
Construisez et exécutez :
cd /opt/app
sudo docker build -t hello-app: local .
sudo docker run --rm -p 5000:5000 --name hello hello-app: local &
curl -s http://127.0.0.1:5000/ | head -n1
sudo docker stop hello
Vous pouvez aussi confier le conteneur à systemd pour unifier les opérations :
[Service]
ExecStart=/usr/bin/docker run --rm --name hello -p 5000:5000 hello-app: local
ExecStop=/usr/bin/docker stop hello
Restart=on-failure
Orchestration avec Kubernetes (optionnel)
Si vous utilisez un cluster local (kind ou minikube), déployez :
hello-deploy.yaml :
apiVersion: apps/v1
kind: Deployment
metadata: { name: hello }
spec:
replicas: 2
selector: { matchLabels: { app: hello } }
template:
metadata: { labels: { app: hello } }
spec:
containers:
- name: hello
image: hello-app: local
imagePullPolicy: IfNotPresent
ports: [{ containerPort: 5000 }]
hello-svc.yaml :
apiVersion: v1
kind: Service
metadata: { name: hello }
spec:
selector: { app: hello }
ports:
- name: http
port: 80
targetPort: 5000
protocol: TCP
type: ClusterIP
Appliquez et testez via un port-forward :
kubectl apply -f hello-deploy.yaml -f hello-svc.yaml
kubectl port-forward svc/hello 8080:80 &
curl -s http://127.0.0.1:8080/ | head -n1
Observabilité, points de défaillance, arbitrages
Commandes clés :
- État système : uname -a, uptime, dmesg --level err, warn
- Processus : ps -ef, pstree -a, systemctl status, journalctl -u NAME
- Réseau : ip addr, ip route, ss -ltnp, dig, curl -v
- Fichiers/descripteurs : lsof -p PID, ls -l /proc/PID/fd
Défaillances courantes :
- Ports et bind : mauvaise adresse (0.0.0.0 vs 127.0.0.1), port occupé, règles de pare-feu.
- Permissions : ports privilégiés, droits de fichiers, refus SELinux/AppArmor.
- Limites de ressources : pression mémoire (OOM), ulimit nofile trop bas, throttling CPU.
- Crashs et redémarrages : dépendances manquantes, différences d'environnement, gestion des signaux.
- Proxy : incohérence upstream Nginx, timeouts, limites d'en‑tête/corps.
- Conteneurs : image manquante, mauvais tag, mappages de ports, overlay FS, DNS en conteneur.
- Kubernetes : probes de readiness en échec, selectors non concordants, ressources insuffisantes.
Arbitrages (design et operations) :
- Service direct vs derrière Nginx : simplicité vs TLS centralisé, routage, cache.
- Processus natif vs conteneur : intégration et rapidité vs packaging et isolation.
- Supervision systemd vs runtime conteneur : outils OS natifs vs workflows centrés image.
- Orchestration : scalabilité et déploiements maîtrisés vs complexité ajoutée.
Workflow récapitulatif
Avancez par étapes pour clarté et confiance :
- Vérifier la plate-forme
- Noyau et distro : uname -a; cat /etc/os-release
- Ports libres : ss -ltn | grep -E ":(80|5000)" || true
- Construire l'app
- Créer /opt/app/app.py et l'exécuter au premier plan
- Tester avec curl http://127.0.0.1:5000/
- Ajouter la supervision
- Créer hello.service, activer et démarrer
- Valider via systemctl status et journalctl -u hello.service
- Ajouter le proxy
- Installer Nginx, ajouter hello.conf, recharger
- Valider le chemin proxy : curl http://127.0.0.1/api/
- Containeriser
- Écrire le Dockerfile, construire hello-app: local
- Lancer le conteneur, valider avec curl
- Unifier les opérations
- Décider si systemd lance l'app directement ou gère docker run
- Documenter start/stop/logs dans helloctl
- Orchestration optionnelle
- Si besoin, déployer sur un cluster local et port-forward pour tester
À chaque étape, préférez de petits changements testables avec rollback clair.
Plan pilote local
Objectif : valider la pile sur un poste unique avec des critères nets.
Périmètre (restreint) :
- Un hôte, trafic loopback uniquement
- App sur 127.0.0.1:5000, Nginx sur :80 proxyant /api/
- Endpoint de santé basique sur /healthz
Plan (environ 60-90 minutes) :
- Smoke test de l'app
- Lancer /opt/app/app.py au premier plan ; curl / et /healthz
- Succès si les deux renvoient 200 et les corps attendus
- Supervision systemd
- Installer hello.service ; démarrer ; confirmer le restart après kill
- Succès si le service redémarre après pkill et que les logs sont visibles
- Proxy Nginx
- Configurer hello.conf ; curl /api/ et /healthz
- Succès si les chemins proxy renvoient 200 et un contenu correct
- Vérification conteneur
- Construire hello-app: local ; exécuter ; curl l'endpoint local
- Succès si l'app conteneurisée se comporte à l'identique
Mesures :
- Latence p50 < 20 ms en local :
for i in {1..20}; do time -p curl -s http://127.0.0.1/api/ >/dev/null; done
- Empreinte mémoire < 100 Mo de RSS pour le processus app
Critères de sortie :
- Tous les checks réussissent ; analyse d'échec documentée avec extraits de commandes
Ce pilote est volontairement petit, mesurable et facile à inspecter avant tout déploiement plus large.
Conclusion
Vous avez cartographié l'architecture Linux du noyau à l'espace utilisateur, retracé le flux de contrôle et de données à travers processus, systèmes de fichiers et réseau, et construit un service HTTP pratique, supervisé par systemd, placé derrière Nginx, packagé dans Docker, avec une option Kubernetes. Vous avez aussi vu les points de défaillance courants et comment observer/déboguer. Pour aller plus loin : activer le TLS dans Nginx, ajouter une journalisation structurée et une tâche CI basique qui construit et exécute l'image localement. Gardez des changements petits, validez chaque étape et capturez ce qui a fonctionné pour répliquer le modèle sur le prochain service.