Stack web complète
Cet exemple assemble tout ce que la plateforme offre, sur une application web réaliste : 2 replicas applicatifs derrière un Ingress HTTPS, PostgreSQL haute disponibilité (2 instances avec bascule automatique), cache Redis, métriques custom scrapées et autoscaling.
L’ensemble a été déployé et validé sur la plateforme (avec une image de démonstration à la place de l’application) : pods prêts, base saine, métriques collectées, le tout dans les quotas par défaut.
L’architecture
Internet ──HTTPS──▶ Ingress ──▶ boutique (×2, HPA 2→6) │ │ ▼ ▼ boutique-db (CNPG ×2) boutique-cache (Redis)Chaque choix est annoté — c’est le « pourquoi » qui fait les bonnes pratiques.
1. La base PostgreSQL (haute disponibilité)
apiVersion: postgresql.cnpg.io/v1kind: Clustermetadata: name: boutique-dbspec: instances: 2 # bascule automatique en cas de panne storage: {size: 10Gi, storageClass: ceph-block-rwo} resources: requests: {cpu: 250m, memory: 512Mi} limits: {memory: 512Mi} # = request : c'est la règle plateforme bootstrap: initdb: {database: boutique, owner: boutique}Pourquoi : l’opérateur gère la réplication et la bascule (détails) ; les volumes sont couverts par la sauvegarde plateforme quotidienne — et pour un point de reprise plus fin (PITR), CNPG sait archiver vers votre propre bucket S3.
2. Le cache Redis
apiVersion: apps/v1kind: Deploymentmetadata: name: boutique-cachespec: replicas: 1 selector: matchLabels: {app: boutique-cache} template: metadata: labels: {app: boutique-cache} spec: containers: - name: redis image: redis:7-alpine args: ["--maxmemory", "200mb", "--maxmemory-policy", "allkeys-lru"] ports: [{name: redis, containerPort: 6379}] resources: requests: {cpu: 100m, memory: 256Mi}---apiVersion: v1kind: Servicemetadata: name: boutique-cachespec: selector: {app: boutique-cache} ports: [{name: redis, port: 6379}]Pourquoi : un cache est reconstructible — pas de volume, pas de replicas.
maxmemory est calé sous la request pour que Redis évince ses clés
([LRU]) au lieu de se faire OOMKill par
la limite mémoire (= request sur Kontainers).
3. L’application
apiVersion: apps/v1kind: Deploymentmetadata: name: boutiquespec: replicas: 2 # survit à la perte d'un pod ou d'un nœud selector: matchLabels: {app: boutique} template: metadata: labels: {app: boutique} # PAS de label exposition: public — tout passe par l'Ingress spec: containers: - name: app image: registry.exemple.com/acme/boutique:2.1 # tag immuable ports: [{name: http, containerPort: 8080}] env: - name: DATABASE_URL valueFrom: secretKeyRef: {name: boutique-db-app, key: uri} # ← généré par CNPG, jamais écrit à la main - name: REDIS_URL value: redis://boutique-cache:6379 resources: requests: {cpu: 200m, memory: 256Mi} readinessProbe: httpGet: {path: /health, port: http} initialDelaySeconds: 3---apiVersion: v1kind: Servicemetadata: name: boutique labels: {app: boutique} # sélectionné par le ServiceMonitorspec: selector: {app: boutique} ports: [{name: http, port: 8080}]Pourquoi : le secret de connexion vient de CNPG (zéro mot de passe dans le
YAML) ; la readinessProbe conditionne le trafic ET les rollouts ; les requests
sont calées sur la consommation réelle observée au
monitoring.
4. Exposition HTTPS
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: boutique annotations: cert-manager.io/issuer: letsencryptspec: rules: - host: boutique.example.com http: paths: - path: / pathType: Prefix backend: service: {name: boutique, port: {number: 8080}} tls: - hosts: [boutique.example.com] secretName: boutique-tlsPourquoi : Ingress mutualisé inclus, certificat automatique, et aucun label réseau requis (le trafic passe par le proxy plateforme).
5. Observabilité et scaling
apiVersion: monitoring.coreos.com/v1kind: ServiceMonitormetadata: name: boutiquespec: selector: matchLabels: {app: boutique} endpoints: - port: http path: /metrics interval: 30s---apiVersion: autoscaling/v2kind: HorizontalPodAutoscalermetadata: name: boutiquespec: scaleTargetRef: {apiVersion: apps/v1, kind: Deployment, name: boutique} minReplicas: 2 maxReplicas: 6 metrics: - type: Resource resource: name: cpu target: {type: Utilization, averageUtilization: 80}---apiVersion: policy/v1kind: PodDisruptionBudgetmetadata: name: boutiquespec: minAvailable: 1 selector: matchLabels: {app: boutique}Pourquoi : les métriques custom alimentent vos dashboards (inclus, fair use) ; l’HPA absorbe les pics dans la limite du budget ; le PDB protège la disponibilité pendant les maintenances du cluster.
Le budget, chiffré
Requests totales au repos (validées sur la plateforme) : 1,3 vCPU / 2,25 Gi — 13 % du quota namespace.
| Composant | Requests | Bundles | € / mois |
|---|---|---|---|
| App × 2 | 200 m / 256 Mi chacun | 2 × 2 | 11,52 € |
| PostgreSQL × 2 | 250 m / 512 Mi chacun | 3 × 2 | 17,28 € |
| Redis | 100 m / 256 Mi | 2 | 5,76 € |
| Stockage | 2 × 10 Gi | — | 14,40 € |
| Total au repos | ≈ 49 € HT / mois |
Si l’HPA monte à 6 replicas en pic : +11,52 €/mois au prorata des heures de pic uniquement — c’est le principe de la facturation à l’heure.