Manifestes pour faire tourner le stack Task Manager sur un cluster Kind local.
Voir ../docs/infra/ pour les explications détaillées et la correspondance
docker-compose → Kubernetes.
docker,kind,kubectlinstallés.- Les ports 80/443 de l'hôte libres (mappés vers les NodePort 30080/30443 de la Gateway).
| Fichier | Rôle |
|---|---|
kind-config.yaml |
Cluster Kind 4 nœuds (1 control-plane + 3 workers) + mapping 80→30080 / 443→30443 |
namespace.yaml |
Namespace task-manager + labels Pod Security Admission (enforce: baseline, warn/audit: restricted) |
rbac.yaml |
Un ServiceAccount par workload, sans jeton monté, sans aucun droit |
secrets.yaml |
Secrets divers (RedisInsight encryption key) - RabbitMQ utilise le secret généré par l'opérateur (rabbitmq-default-user) |
database.yaml |
Cluster CNPG (3 instances, anti-affinité) + Pooler (2) |
rabbitmq.yaml |
Cluster RabbitMQ (3 nœuds, géré par l'opérateur officiel) |
infra.yaml |
Valkey, Mailpit, Prometheus, RedisInsight |
api-config.yaml |
ConfigMap partagée API/worker |
migrate-job.yaml |
Job Alembic (alembic upgrade head) |
api-deployment.yaml |
API FastAPI + Service |
worker-deployment.yaml |
Worker d'e-mails |
frontend-deployment.yaml |
Frontend Nginx + Service |
gateway.yaml |
GatewayClass eg + EnvoyProxy (NodePort figé) + Gateway task-manager |
httproutes.yaml |
Une HTTPRoute pour l'app, une par console de dev (*.localhost) |
network-policies.yaml |
Segmentation réseau (qui peut parler à qui) |
hpa.yaml |
Autoscaling horizontal : api 2→6, frontend 2→4 (CPU 70 % / mém. 80 %) |
pdb.yaml |
PodDisruptionBudget minAvailable: 1 (api, frontend, mail-worker) |
deploy.sh |
Déploiement complet et idempotent (opérateurs, images, workloads) |
clean.sh |
Suppression du cluster Kind (--images pour purger les images) |
db-backup.sh |
Backup/restore logique de la base (pg_dump) dans db-backups/ |
Tout est automatisé par deploy.sh (à la racine du dépôt, exécuté depuis ../) :
cd ..
./k8s/deploy.shOu manuellement, dans l'ordre :
# 1. Cluster + Envoy Gateway (install.yaml embarque les CRD Gateway API).
# --server-side : les CRD dépassent la limite de l'annotation last-applied.
kind create cluster --config k8s/kind-config.yaml
kubectl apply --server-side -f https://github.com/envoyproxy/gateway/releases/download/v1.9.1/install.yaml
kubectl rollout status deployment/envoy-gateway -n envoy-gateway-system --timeout=300s
# 2. Metrics Server (requis par le HPA ; sans le patch TLS, il ne remonte rien sur Kind).
# ⚠️ --kubelet-insecure-tls désactive la vérification du certificat kubelet :
# contournement Kind uniquement, jamais sur un vrai cluster.
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
kubectl patch -n kube-system deployment metrics-server --type=json \
-p '[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'
kubectl -n kube-system rollout status deployment metrics-server --timeout=120s
# 2b. Opérateur CloudNativePG (vérifier la dernière version sur le repo CNPG)
kubectl apply --server-side -f \
https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.30/releases/cnpg-1.30.0.yaml
kubectl wait --for=condition=Available deployment/cnpg-controller-manager -n cnpg-system --timeout=180s
# 2c. Opérateur RabbitMQ Cluster (version v2.15.0)
RABBITMQ_OPERATOR_VERSION="v2.15.0"
kubectl apply -f \
"https://github.com/rabbitmq/cluster-operator/releases/download/${RABBITMQ_OPERATOR_VERSION}/cluster-operator.yml"
kubectl wait --for=condition=Available deployment/rabbitmq-cluster-operator -n rabbitmq-system --timeout=180s
# 3. Build + load des images dans Kind
docker build -t task-manager-api:latest ./backend
docker build -t task-manager-frontend:latest --target production \
--build-arg VITE_MAIL_UI_URL=http://mailpit.localhost \
--build-arg VITE_REDIS_INSIGHT_URL=http://redisinsight.localhost \
./frontend
kind load docker-image task-manager-api:latest task-manager-frontend:latest --name task-manager
# 4. Namespace, secrets, BDD, dépendances
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/rbac.yaml
kubectl apply -f k8s/secrets.yaml
kubectl apply -f k8s/database.yaml
kubectl wait --for=condition=Ready cluster/task-manager-db -n task-manager --timeout=300s
kubectl apply -f k8s/infra.yaml
kubectl apply -f k8s/rabbitmq.yaml
kubectl wait --for=condition=Available rabbitmqcluster/rabbitmq -n task-manager --timeout=300s
# 5. Migrations, puis config + workloads
kubectl apply -f k8s/migrate-job.yaml
kubectl wait --for=condition=complete job/db-migrate -n task-manager --timeout=180s
kubectl apply -f k8s/api-config.yaml
kubectl apply -f k8s/api-deployment.yaml
kubectl apply -f k8s/frontend-deployment.yaml
kubectl apply -f k8s/worker-deployment.yaml
kubectl apply -f k8s/hpa.yaml
kubectl apply -f k8s/pdb.yaml
kubectl apply -f k8s/gateway.yaml
kubectl wait --for=condition=Programmed gateway/task-manager -n task-manager --timeout=300s
kubectl apply -f k8s/httproutes.yaml
kubectl apply -f k8s/network-policies.yamlL'application est accessible sur http://localhost.
Les consoles de dev sont exposées par la Gateway sur des hostnames dédiés —
une HTTPRoute chacune, hostnames étant un champ de la route et non de la
règle :
| Console | URL | Identifiants |
|---|---|---|
| 🖥️ Application | http://localhost | — |
| 📬 Mailpit (webmail de test) | http://mailpit.localhost | — |
| 🐰 RabbitMQ (management) | http://rabbitmq.localhost | Générés automatiquement par l'opérateur (secret rabbitmq-default-user) |
| 🔎 RedisInsight (cache Valkey) | http://redisinsight.localhost | — |
| 📊 Prometheus | http://prometheus.localhost | — |
Résolution DNS — avec
systemd-resolved, les noms*.localhostpointent déjà vers127.0.0.1: rien à configurer. Sinon, ajouter dans/etc/hosts:127.0.0.1 mailpit.localhost rabbitmq.localhost redisinsight.localhost prometheus.localhost
Boutons de l'en-tête du frontend — les raccourcis Mailpit / RedisInsight de l'app sont figés au build du frontend.
deploy.shpasse déjà les bons--build-arg(VITE_MAIL_UI_URL,VITE_REDIS_INSIGHT_URL) : rien à faire. En build manuel, reprendre la commande de l'étape 3 — sinon les boutons pointent verslocalhost:8025/localhost:5540.
Backup logique simple : 1 backup = 1 fichier .sql.gz dans db-backups/
(sur ton disque, ignoré par Git). Se connecte au primaire via kubectl exec.
./k8s/db-backup.sh backup # dump → db-backups/task_manager-<horodatage>.sql.gz
./k8s/db-backup.sh list # liste les dumps
./k8s/db-backup.sh restore <fichier> # applique un dump (demande confirmation)
./k8s/db-backup.sh restore <fichier> -y # sans confirmationLe dump inclut le schéma (avec alembic_version) et les données ; la
restauration recrée proprement les objets (--clean --if-exists) puis les
recharge. Pas de PITR — on restaure à l'instant d'un dump.
SecurityContext — les pods qu'on maîtrise sont durcis :
- api / worker / migrate :
runAsNonRoot(uid 1000),readOnlyRootFilesystem,allowPrivilegeEscalation: false,capabilities: drop [ALL], seccompRuntimeDefault./tmpmonté enemptyDir(gunicorn/temp). - frontend : image
nginx-unprivileged(uid 101, écoute 8080), même durcissement. - postgres / pooler : déjà non-root (uid 26) via l'opérateur CNPG — non modifiés.
- Restent en défaut : valkey, rabbitmq, mailpit, prometheus, redisinsight
(images tierces ;
readOnlyRootFilesystemdemande une validation par image).
NetworkPolicy (network-policies.yaml) — segmentation testée (kindnet
l'applique). Une politique d'ingress par cible ; tout le reste est refusé en entrée :
gateway (envoy) → frontend → api → ┬→ pooler → postgres
├→ rabbitmq
├→ valkey
└→ mailpit
Le frontend ne peut jamais joindre postgres/pooler directement (vérifié : un pod non listé sur la base time-out ; l'api passe). L'egress (dont le DNS) reste libre.
Après un changement de code, reconstruire + recharger + redémarrer :
docker build -t task-manager-api:latest ./backend
kind load docker-image task-manager-api:latest --name task-manager
kubectl rollout restart deployment/api deployment/mail-worker -n task-managerkind delete cluster --name task-manager