Aller au contenu

Déploiement

mic-struct ne dicte pas le déploiement. Voici les modèles observés sur les services qui l'utilisent.

Mode autonome — python -m

Chaque service généré a un point d'entrée :

python -m my_service

Il démarre uvicorn sur Settings.host:Settings.port (défaut 0.0.0.0:8000).

Conteneur Docker

Dockerfile minimal :

FROM python:3.14-slim

WORKDIR /app

# Build multi-stage optionnel pour réduire la taille
COPY pyproject.toml ./
RUN pip install --no-cache-dir .

COPY src/ ./src/

ENV PORT=8000
EXPOSE 8000

CMD ["python", "-m", "my_service"]

Build et lancement :

docker build -t my-service:latest .
docker run -p 8000:8000 \
    -e DATABASE_URL=postgresql+psycopg://... \
    -e SERVICE_AUTH_ENABLED=true \
    -e SERVICE_AUTH_SECRETS=secret1,secret2 \
    my-service:latest

Worker en parallèle

Pour les services avec un worker (mail-out par ex.), un deuxième conteneur démarre le worker :

# Image identique, seul le point d'entrée diffère
CMD ["python", "-m", "my_service.worker"]

docker-compose.yml :

services:
  api:
    image: my-service:latest
    command: python -m my_service
    ports: ["8000:8000"]

  worker:
    image: my-service:latest
    command: python -m my_service.worker
    depends_on: [api, postgres]

Kubernetes — Deployment + Service + ServiceMonitor

Squelette Helm (à adapter) :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-service
spec:
  replicas: 2
  selector: { matchLabels: { app: my-service } }
  template:
    metadata: { labels: { app: my-service } }
    spec:
      containers:
        - name: api
          image: registry.example.com/my-service:1.0.0
          ports: [{ containerPort: 8000 }]
          env:
            - name: DATABASE_URL
              valueFrom: { secretKeyRef: { name: my-service-db, key: url } }
            - name: SERVICE_AUTH_SECRETS
              valueFrom: { secretKeyRef: { name: my-service-auth, key: secrets } }
          livenessProbe:
            httpGet: { path: /health, port: 8000 }
            periodSeconds: 10
          readinessProbe:
            httpGet: { path: /health, port: 8000 }
            periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector: { app: my-service }
  ports: [{ port: 80, targetPort: 8000 }]
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: my-service
spec:
  selector: { matchLabels: { app: my-service } }
  endpoints:
    - port: http
      path: /metrics
      interval: 30s

Probes k8s

  • livenessProbe → /health : si KO, k8s redémarre le pod.
  • readinessProbe → /health : si KO, k8s ne route pas le trafic.

/health retourne 200 dès que l'app a fini son prepare() (DB connectable + schéma OK). Avant : 503 implicite (pod en CrashLoopBackOff).

Migrations Alembic

Convention : un job k8s pré-deploy qui exécute alembic upgrade head avant que le Deployment soit promu :

apiVersion: batch/v1
kind: Job
metadata:
  name: my-service-migrate-{{ .Release.Revision }}
spec:
  template:
    spec:
      restartPolicy: OnFailure
      containers:
        - name: alembic
          image: registry.example.com/my-service:1.0.0
          command: ["alembic", "upgrade", "head"]

Helm pre-upgrade hook fait l'affaire.

Secrets

Stack : OpenBao + ExternalSecrets Operator. Les secrets sont résolus à l'admission du Pod, pas codés en dur dans les valeurs Helm.

Observabilité

  • /metrics collecté par Prometheus via ServiceMonitor.
  • Logs JSON sur stdout → fluentbit / promtail → Loki.
  • Sentry / GlitchTip configurés côté initialisation du logging (à câbler côté service consommateur, MIC ne le fait pas pour vous).