---
title: "Stack web complète"
description: "Une application de production assemblée avec les bonnes pratiques Kontainers : app répliquée, PostgreSQL HA, cache Redis, HTTPS, métriques, autoscaling et sauvegardes — testée sur la plateforme, budget chiffré."
source_url:
  html: https://docs.blackswift.cloud/exemples/stack-web-complete/
  md: https://docs.blackswift.cloud/exemples/stack-web-complete.md
---

# Stack web complète

> Une application de production assemblée avec les bonnes pratiques Kontainers : app répliquée, PostgreSQL HA, cache Redis, HTTPS, métriques, autoscaling et sauvegardes — testée sur la plateforme, budget chiffré.

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

```text
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é)

```yaml
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](https://docs.blackswift.cloud/deployer/postgresql-manage.md)) ; les volumes sont couverts par la
[sauvegarde plateforme quotidienne](https://docs.blackswift.cloud/operer/sauvegardes-et-restauration.md) — et
pour un point de reprise plus fin (PITR), CNPG sait
[archiver vers votre propre bucket S3](https://docs.blackswift.cloud/deployer/postgresql-manage.md#sauvegardes-vers-votre-stockage-s3).

## 2. Le cache Redis

```yaml
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](https://docs.blackswift.cloud/depanner/pod-en-erreur.md#oomkilled) par
la limite mémoire (= request sur Kontainers).

## 3. L'application

```yaml
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](https://docs.blackswift.cloud/operer/acceder-au-monitoring.md).

## 4. Exposition HTTPS

```yaml
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](https://docs.blackswift.cloud/deployer/exposer-en-https.md), certificat
automatique, et aucun label réseau requis
([le trafic passe par le proxy plateforme](https://docs.blackswift.cloud/comprendre/isolation-reseau.md)).

## 5. Observabilité et scaling

```yaml
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](https://docs.blackswift.cloud/operer/metriques-applicatives.md) alimentent
vos dashboards (inclus, fair use) ; l'[HPA](https://docs.blackswift.cloud/operer/scaling-et-disponibilite.md)
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](https://docs.blackswift.cloud/reference/quotas-et-limites.md).

| 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](https://docs.blackswift.cloud/reference/facturation.md).

<Aside type="note">
Additionnez large : même à 6 replicas, la stack reste sous 2,2 vCPU / 3,25 Gi de
requests — un seul namespace Kontainers héberge confortablement plusieurs stacks
de cette taille.
</Aside>
