Aller au contenu

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/v1
kind: Cluster
metadata:
name: boutique-db
spec:
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/v1
kind: Deployment
metadata:
name: boutique-cache
spec:
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: v1
kind: Service
metadata:
name: boutique-cache
spec:
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/v1
kind: Deployment
metadata:
name: boutique
spec:
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: v1
kind: Service
metadata:
name: boutique
labels: {app: boutique} # sélectionné par le ServiceMonitor
spec:
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/v1
kind: Ingress
metadata:
name: boutique
annotations:
cert-manager.io/issuer: letsencrypt
spec:
rules:
- host: boutique.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service: {name: boutique, port: {number: 8080}}
tls:
- hosts: [boutique.example.com]
secretName: boutique-tls

Pourquoi : 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/v1
kind: ServiceMonitor
metadata:
name: boutique
spec:
selector:
matchLabels: {app: boutique}
endpoints:
- port: http
path: /metrics
interval: 30s
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: boutique
spec:
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/v1
kind: PodDisruptionBudget
metadata:
name: boutique
spec:
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.

ComposantRequestsBundles€ / mois
App × 2200 m / 256 Mi chacun2 × 211,52 €
PostgreSQL × 2250 m / 512 Mi chacun3 × 217,28 €
Redis100 m / 256 Mi25,76 €
Stockage2 × 10 Gi14,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.