xpeditis2.0/infra/prod/k8s/base/04-backend.yaml
2026-09-07 21:40:50 +02:00

217 lines
6.9 KiB
YAML

# =============================================================================
# Backend NestJS
# =============================================================================
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: xpeditis-backend
namespace: xpeditis-prod
labels:
app.kubernetes.io/name: xpeditis-backend
app.kubernetes.io/component: api
app.kubernetes.io/part-of: xpeditis
spec:
replicas: 2
revisionHistoryLimit: 5
strategy:
type: RollingUpdate
rollingUpdate:
# Aucun pod retire avant qu'un remplacant ne soit pret : zero coupure.
maxUnavailable: 0
maxSurge: 1
selector:
matchLabels:
app.kubernetes.io/name: xpeditis-backend
template:
metadata:
labels:
app.kubernetes.io/name: xpeditis-backend
app.kubernetes.io/component: api
app.kubernetes.io/part-of: xpeditis
annotations:
# Force le redemarrage des pods quand la configuration change : sans
# cela, modifier le ConfigMap ne produit aucun effet visible.
# La valeur est recalculee par scripts/deploy.sh.
xpeditis.com/config-checksum: "PLACEHOLDER"
spec:
imagePullSecrets:
- name: regcred
# 2 replicas sur 1 noeud : la contrainte est "preferred", sinon le second
# pod resterait indefiniment en Pending. Elle deviendra effective le jour
# ou un second noeud sera ajoute (phase 2 du plan de charge).
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app.kubernetes.io/name: xpeditis-backend
securityContext:
runAsNonRoot: true
runAsUser: 1001
runAsGroup: 1001
fsGroup: 1001
seccompProfile:
type: RuntimeDefault
# 60 s : laisse le temps aux requetes en cours et aux connexions
# WebSocket de se fermer proprement.
terminationGracePeriodSeconds: 60
containers:
- name: backend
# Le tag est remplace au deploiement (kubectl set image).
image: rg.fr-par.scw.cloud/weworkstudio/xpeditis-backend:latest
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 4000
protocol: TCP
envFrom:
- configMapRef:
name: xpeditis-backend-config
- secretRef:
name: xpeditis-backend-secrets
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
# Le demarrage inclut l'attente de PostgreSQL puis la verification
# des migrations : la sonde de demarrage laisse jusqu'a 150 s avant
# de declarer le pod perdu, sans penaliser les redemarrages rapides.
startupProbe:
httpGet:
path: /api/v1/health/live
port: http
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 30
timeoutSeconds: 3
livenessProbe:
httpGet:
path: /api/v1/health/live
port: http
periodSeconds: 20
timeoutSeconds: 5
failureThreshold: 3
# LIMITE CONNUE : /health/ready renvoie toujours "ready" sans tester
# PostgreSQL ni Redis (health.controller.ts). Un pod incapable de
# joindre la base sera donc declare pret. Correctif recommande apres
# la mise en ligne : @nestjs/terminus avec des indicateurs reels.
readinessProbe:
httpGet:
path: /api/v1/health/ready
port: http
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
resources:
requests:
cpu: 300m
memory: 512Mi
limits:
cpu: 1500m
memory: 1536Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
# NON active volontairement : le conteneur ecrit dans /app/logs et
# dans /app/src/infrastructure/storage/csv-storage/rates (chemin
# resolu au runtime par le chargeur de grilles CSV). Monter des
# emptyDir par-dessus masquerait les fichiers livres dans l'image.
readOnlyRootFilesystem: false
lifecycle:
preStop:
exec:
# Laisse Traefik retirer le pod de son pool avant que le
# processus ne commence a refuser des connexions.
command: ["sh", "-c", "sleep 10"]
---
apiVersion: v1
kind: Service
metadata:
name: xpeditis-backend
namespace: xpeditis-prod
labels:
app.kubernetes.io/name: xpeditis-backend
annotations:
# Sessions collantes : obligatoires pour Socket.IO. La negociation
# long-polling echoue si deux requetes d'un meme handshake atterrissent sur
# des replicas differents.
#
# ATTENTION -- limite non resolue par ce reglage : le gateway
# (notifications.gateway.ts) garde la carte userId -> sockets EN MEMOIRE et
# n'utilise pas @socket.io/redis-adapter. Une notification emise par le
# replica A n'atteint donc pas un utilisateur connecte au replica B.
# Cf. docs/mise-en-prod/15-exploitation-incidents.md, "Points de vigilance".
traefik.ingress.kubernetes.io/service.sticky.cookie: "true"
traefik.ingress.kubernetes.io/service.sticky.cookie.name: "xpd_be"
traefik.ingress.kubernetes.io/service.sticky.cookie.secure: "true"
traefik.ingress.kubernetes.io/service.sticky.cookie.httponly: "true"
traefik.ingress.kubernetes.io/service.sticky.cookie.samesite: "lax"
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: xpeditis-backend
ports:
- name: http
port: 4000
targetPort: http
protocol: TCP
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: xpeditis-backend
namespace: xpeditis-prod
spec:
minAvailable: 1
selector:
matchLabels:
app.kubernetes.io/name: xpeditis-backend
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: xpeditis-backend
namespace: xpeditis-prod
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: xpeditis-backend
minReplicas: 2
# Plafond a 4 : au-dela, le CPX41 sature. Passer a 3 noeuds avant d'augmenter
# (cf. 15-exploitation-incidents.md, section montee en charge).
maxReplicas: 4
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleUp:
stabilizationWindowSeconds: 60
scaleDown:
# Descente lente : evite de retirer un pod juste avant un nouveau pic.
stabilizationWindowSeconds: 600