ARCHITECTURE
Intermédiaire

Haute Disponibilité : Principes, Patterns et Architectures

Florian Courouge
18 min de lecture
3,473 mots
0 vues
Haute Disponibilité
HA
SLA
Résilience
Architecture
Kubernetes
Failover

Haute Disponibilité : Principes, Patterns et Architectures

La haute disponibilité (HA) est la capacité d'un système à rester opérationnel malgré les pannes de ses composants. Dans un monde où une minute d'indisponibilité peut coûter des milliers d'euros, maîtriser ces concepts est essentiel.

Les fondamentaux

Définitions clés

Terme Définition Exemple
Disponibilité % de temps où le service est opérationnel 99.9% = ~8.76h d'arrêt/an
SLA Service Level Agreement - engagement contractuel "99.95% de disponibilité mensuelle"
SLO Service Level Objective - objectif interne "99.99% visé"
SLI Service Level Indicator - métrique mesurée "Temps de réponse < 200ms"
RTO Recovery Time Objective - temps max de restauration "Retour en ligne en 15 min"
RPO Recovery Point Objective - perte de données acceptable "Max 5 min de transactions perdues"
MTBF Mean Time Between Failures "30 jours entre pannes"
MTTR Mean Time To Recovery "15 min pour réparer"

Les "9" de disponibilité

┌─────────────────────────────────────────────────────────────────┐
│              NIVEAUX DE DISPONIBILITÉ (SLA)                     │
├──────────────┬──────────────────┬───────────────────────────────┤
│  SLA         │  Downtime/an     │  Cas d'usage                  │
├──────────────┼──────────────────┼───────────────────────────────┤
│  99%         │  3.65 jours      │  Apps internes non critiques  │
│  99.9%       │  8.76 heures     │  Apps métier standard         │
│  99.95%      │  4.38 heures     │  E-commerce, SaaS             │
│  99.99%      │  52.56 minutes   │  Paiement, santé              │
│  99.999%     │  5.26 minutes    │  Télécoms, finance critique   │
│  99.9999%    │  31.54 secondes  │  Systèmes de défense          │
└──────────────┴──────────────────┴───────────────────────────────┘

Formule de disponibilité

Disponibilité = MTBF / (MTBF + MTTR)

Exemple:
- MTBF = 720 heures (30 jours)
- MTTR = 0.5 heures (30 min)
- Disponibilité = 720 / (720 + 0.5) = 99.93%

:::tip Améliorer la disponibilité :

  • Augmenter le MTBF → meilleure qualité, redondance
  • Réduire le MTTR → automatisation, monitoring, runbooks :::

Patterns de haute disponibilité

1. Redondance active-passive (Failover)

┌─────────────────────────────────────────────────────────────────┐
│                    ACTIVE-PASSIVE (FAILOVER)                    │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│           Load Balancer / VIP                                   │
│                   │                                              │
│         ┌─────────┴─────────┐                                   │
│         │                   │                                    │
│         ▼                   ▼                                    │
│   ┌──────────┐        ┌──────────┐                              │
│   │  ACTIVE  │        │ STANDBY  │                              │
│   │  Server  │◄──────►│  Server  │  ← Heartbeat                │
│   │  (Write) │        │ (Ready)  │                              │
│   └────┬─────┘        └────┬─────┘                              │
│        │                   │                                     │
│        ▼                   ▼                                     │
│   ┌──────────┐        ┌──────────┐                              │
│   │ Primary  │───────►│ Replica  │  ← Réplication sync          │
│   │   DB     │        │   DB     │                              │
│   └──────────┘        └──────────┘                              │
│                                                                  │
│   En cas de panne du serveur actif :                            │
│   1. Heartbeat détecte la panne                                 │
│   2. Standby promu en Active                                    │
│   3. VIP/DNS bascule vers nouveau Active                        │
│   4. RTO typique : 30s - 5min                                   │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Avantages :

  • Simple à implémenter
  • Coût modéré (1 serveur en standby)
  • Pas de split-brain possible

Inconvénients :

  • Ressources standby sous-utilisées
  • Temps de failover non nul
  • Single point of failure potentiel (load balancer)

2. Redondance active-active

┌─────────────────────────────────────────────────────────────────┐
│                       ACTIVE-ACTIVE                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│                    Global Load Balancer                         │
│                          │                                       │
│            ┌─────────────┼─────────────┐                        │
│            │             │             │                         │
│            ▼             ▼             ▼                         │
│      ┌──────────┐  ┌──────────┐  ┌──────────┐                   │
│      │ ACTIVE-1 │  │ ACTIVE-2 │  │ ACTIVE-3 │                   │
│      │ (R/W)    │  │ (R/W)    │  │ (R/W)    │                   │
│      └────┬─────┘  └────┬─────┘  └────┬─────┘                   │
│           │             │             │                          │
│           └─────────────┼─────────────┘                         │
│                         │                                        │
│                         ▼                                        │
│              ┌─────────────────────┐                            │
│              │   Distributed DB    │                            │
│              │   (Multi-Master)    │                            │
│              │   ou Event Store    │                            │
│              └─────────────────────┘                            │
│                                                                  │
│   Tous les nœuds traitent le trafic simultanément              │
│   Si un nœud tombe, les autres absorbent la charge             │
│   RTO : ~0 (pas de failover explicite)                         │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Avantages :

  • Utilisation optimale des ressources
  • Scalabilité horizontale
  • Pas de temps de failover

Inconvénients :

  • Complexité accrue (cohérence des données)
  • Coût plus élevé
  • Risque de split-brain si mal configuré

3. Architecture multi-région

┌─────────────────────────────────────────────────────────────────┐
│                    MULTI-RÉGION (GEO-HA)                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│                     Global DNS (GeoDNS)                         │
│                           │                                      │
│           ┌───────────────┼───────────────┐                     │
│           │               │               │                      │
│           ▼               ▼               ▼                      │
│    ┌────────────┐  ┌────────────┐  ┌────────────┐              │
│    │  EUROPE    │  │  US-EAST   │  │  ASIA      │              │
│    │  (Paris)   │  │  (Virginia)│  │  (Tokyo)   │              │
│    ├────────────┤  ├────────────┤  ├────────────┤              │
│    │ App Tier   │  │ App Tier   │  │ App Tier   │              │
│    │ ┌──┐ ┌──┐  │  │ ┌──┐ ┌──┐ │  │ ┌──┐ ┌──┐  │              │
│    │ │P1│ │P2│  │  │ │P1│ │P2│ │  │ │P1│ │P2│  │              │
│    │ └──┘ └──┘  │  │ └──┘ └──┘ │  │ └──┘ └──┘  │              │
│    ├────────────┤  ├────────────┤  ├────────────┤              │
│    │ DB Tier    │  │ DB Tier    │  │ DB Tier    │              │
│    │ (Primary)  │◄─┤ (Replica)  │◄─┤ (Replica)  │              │
│    └────────────┘  └────────────┘  └────────────┘              │
│           │               │               │                      │
│           └───────────────┴───────────────┘                     │
│                    Cross-Region                                  │
│                    Replication                                   │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Implémentation pratique

Kubernetes : Configuration HA native

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-backend
  namespace: production
spec:
  # Haute disponibilité : minimum 3 replicas
  replicas: 3

  # Stratégie de déploiement sans interruption
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1        # 1 pod supplémentaire pendant update
      maxUnavailable: 0  # Jamais en dessous de 3 replicas

  selector:
    matchLabels:
      app: api-backend

  template:
    metadata:
      labels:
        app: api-backend
    spec:
      # Répartition sur plusieurs nodes
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchLabels:
                  app: api-backend
              topologyKey: kubernetes.io/hostname
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                labelSelector:
                  matchLabels:
                    app: api-backend
                topologyKey: topology.kubernetes.io/zone

      # Répartition sur plusieurs zones
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchLabels:
              app: api-backend

      containers:
        - name: api
          image: registry.company.com/api:v2.1.0
          ports:
            - containerPort: 8080

          # Health checks critiques pour la HA
          livenessProbe:
            httpGet:
              path: /health/live
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 10
            failureThreshold: 3

          readinessProbe:
            httpGet:
              path: /health/ready
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 5
            failureThreshold: 3

          # Ressources garanties
          resources:
            requests:
              memory: "512Mi"
              cpu: "250m"
            limits:
              memory: "1Gi"
              cpu: "1000m"

---
# PodDisruptionBudget : garantir un minimum de pods disponibles
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api-backend-pdb
  namespace: production
spec:
  minAvailable: 2  # Au moins 2 pods toujours up
  selector:
    matchLabels:
      app: api-backend

PostgreSQL : Configuration HA avec Patroni

# Configuration Patroni pour PostgreSQL HA
scope: postgres-cluster
namespace: /postgresql/

restapi:
  listen: 0.0.0.0:8008
  connect_address: ${POD_IP}:8008

etcd3:
  hosts:
    - etcd-0.etcd:2379
    - etcd-1.etcd:2379
    - etcd-2.etcd:2379

bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576  # 1MB max lag pour failover

    postgresql:
      use_pg_rewind: true
      use_slots: true
      parameters:
        max_connections: 200
        shared_buffers: 2GB
        effective_cache_size: 6GB
        work_mem: 64MB
        maintenance_work_mem: 512MB
        wal_level: replica
        hot_standby: "on"
        max_wal_senders: 10
        max_replication_slots: 10
        hot_standby_feedback: "on"
        synchronous_commit: "on"  # Crucial pour RPO=0

  initdb:
    - encoding: UTF8
    - data-checksums

postgresql:
  listen: 0.0.0.0:5432
  connect_address: ${POD_IP}:5432
  data_dir: /var/lib/postgresql/data

  authentication:
    replication:
      username: replicator
      password: ${REPLICATION_PASSWORD}
    superuser:
      username: postgres
      password: ${POSTGRES_PASSWORD}

  pg_hba:
    - host replication replicator 0.0.0.0/0 md5
    - host all all 0.0.0.0/0 md5

watchdog:
  mode: automatic
  device: /dev/watchdog
  safety_margin: 5

Kafka : Réplication et ISR

# Configuration Kafka pour haute disponibilité

# Identité du broker
broker.id=1
listeners=PLAINTEXT://0.0.0.0:9092
advertised.listeners=PLAINTEXT://kafka-1.kafka:9092

# Réplication - CRITIQUE pour la HA
default.replication.factor=3
min.insync.replicas=2

# Paramètres de leadership
auto.leader.rebalance.enable=true
leader.imbalance.check.interval.seconds=300
leader.imbalance.per.broker.percentage=10

# Durabilité des données
log.flush.interval.messages=10000
log.flush.interval.ms=1000

# Tolérance aux pannes
unclean.leader.election.enable=false
replica.lag.time.max.ms=30000
replica.lag.max.messages=10000

# Configuration ZooKeeper (ou KRaft)
zookeeper.connect=zk-0.zk:2181,zk-1.zk:2181,zk-2.zk:2181
zookeeper.session.timeout.ms=18000
Properties props = new Properties();

// Connexion au cluster
props.put("bootstrap.servers", "kafka-1:9092,kafka-2:9092,kafka-3:9092");

// Durabilité maximale
props.put("acks", "all");  // Attendre confirmation de tous les ISR
props.put("retries", Integer.MAX_VALUE);
props.put("retry.backoff.ms", 100);
props.put("max.in.flight.requests.per.connection", 5);

// Idempotence pour éviter les duplicatas
props.put("enable.idempotence", true);

// Timeouts adaptés
props.put("delivery.timeout.ms", 120000);  // 2 minutes
props.put("request.timeout.ms", 30000);
props.put("linger.ms", 5);

KafkaProducer<String, String> producer = new KafkaProducer<>(props);

Patterns de résilience applicative

Circuit Breaker

import time
from enum import Enum
from dataclasses import dataclass, field
from typing import Callable, TypeVar, Optional
from functools import wraps

T = TypeVar('T')

class CircuitState(Enum):
    CLOSED = "closed"      # Fonctionnement normal
    OPEN = "open"          # Court-circuit activé
    HALF_OPEN = "half_open"  # Test de récupération

@dataclass
class CircuitBreaker:
    """
    Implémentation du pattern Circuit Breaker.
    Protège les appels vers des services externes instables.
    """
    failure_threshold: int = 5
    recovery_timeout: int = 30
    half_open_max_calls: int = 3

    _state: CircuitState = field(default=CircuitState.CLOSED, init=False)
    _failure_count: int = field(default=0, init=False)
    _last_failure_time: float = field(default=0, init=False)
    _half_open_calls: int = field(default=0, init=False)

    @property
    def state(self) -> CircuitState:
        if self._state == CircuitState.OPEN:
            if time.time() - self._last_failure_time >= self.recovery_timeout:
                self._state = CircuitState.HALF_OPEN
                self._half_open_calls = 0
        return self._state

    def call(self, func: Callable[..., T], *args, **kwargs) -> T:
        """Exécute une fonction protégée par le circuit breaker."""
        if self.state == CircuitState.OPEN:
            raise CircuitBreakerOpenError(
                f"Circuit ouvert. Réessayer dans "
                f"{self.recovery_timeout - (time.time() - self._last_failure_time):.0f}s"
            )

        try:
            result = func(*args, **kwargs)
            self._on_success()
            return result
        except Exception as e:
            self._on_failure()
            raise

    def _on_success(self):
        if self._state == CircuitState.HALF_OPEN:
            self._half_open_calls += 1
            if self._half_open_calls >= self.half_open_max_calls:
                self._state = CircuitState.CLOSED
                self._failure_count = 0
        else:
            self._failure_count = 0

    def _on_failure(self):
        self._failure_count += 1
        self._last_failure_time = time.time()

        if self._failure_count >= self.failure_threshold:
            self._state = CircuitState.OPEN
        elif self._state == CircuitState.HALF_OPEN:
            self._state = CircuitState.OPEN

class CircuitBreakerOpenError(Exception):
    pass

# Décorateur pour simplifier l'usage
def circuit_breaker(
    failure_threshold: int = 5,
    recovery_timeout: int = 30
):
    cb = CircuitBreaker(
        failure_threshold=failure_threshold,
        recovery_timeout=recovery_timeout
    )

    def decorator(func: Callable[..., T]) -> Callable[..., T]:
        @wraps(func)
        def wrapper(*args, **kwargs) -> T:
            return cb.call(func, *args, **kwargs)
        wrapper.circuit_breaker = cb
        return wrapper
    return decorator

# Utilisation
@circuit_breaker(failure_threshold=3, recovery_timeout=60)
def call_external_api(endpoint: str) -> dict:
    """Appel à une API externe protégé par circuit breaker."""
    import requests
    response = requests.get(endpoint, timeout=5)
    response.raise_for_status()
    return response.json()

Retry avec backoff exponentiel

import time
import random
from functools import wraps
from typing import Callable, Type, Tuple

def retry_with_backoff(
    max_retries: int = 3,
    base_delay: float = 1.0,
    max_delay: float = 60.0,
    exponential_base: float = 2,
    jitter: bool = True,
    retryable_exceptions: Tuple[Type[Exception], ...] = (Exception,)
):
    """
    Décorateur de retry avec backoff exponentiel et jitter.

    Args:
        max_retries: Nombre maximum de tentatives
        base_delay: Délai initial en secondes
        max_delay: Délai maximum en secondes
        exponential_base: Base de l'exponentielle (2 = doubling)
        jitter: Ajouter du bruit aléatoire pour éviter thundering herd
        retryable_exceptions: Exceptions qui déclenchent un retry
    """
    def decorator(func: Callable):
        @wraps(func)
        def wrapper(*args, **kwargs):
            last_exception = None

            for attempt in range(max_retries + 1):
                try:
                    return func(*args, **kwargs)
                except retryable_exceptions as e:
                    last_exception = e

                    if attempt == max_retries:
                        break

                    # Calcul du délai avec backoff exponentiel
                    delay = min(
                        base_delay * (exponential_base ** attempt),
                        max_delay
                    )

                    # Jitter : ajouter 0-50% de variation aléatoire
                    if jitter:
                        delay = delay * (0.5 + random.random())

                    print(f"Tentative {attempt + 1}/{max_retries} échouée. "
                          f"Retry dans {delay:.2f}s. Erreur: {e}")

                    time.sleep(delay)

            raise last_exception

        return wrapper
    return decorator

# Utilisation
@retry_with_backoff(
    max_retries=5,
    base_delay=1.0,
    retryable_exceptions=(ConnectionError, TimeoutError)
)
def fetch_data_from_service(url: str) -> dict:
    """Récupère des données avec retry automatique."""
    import requests
    response = requests.get(url, timeout=10)
    response.raise_for_status()
    return response.json()

Bulkhead (isolation)

from concurrent.futures import ThreadPoolExecutor, TimeoutError
from functools import wraps
from typing import Callable, Dict

class Bulkhead:
    """
    Pattern Bulkhead : isole les ressources pour éviter
    qu'une défaillance ne se propage.
    """
    _instances: Dict[str, 'Bulkhead'] = {}

    def __init__(self, name: str, max_concurrent: int = 10, timeout: float = 30.0):
        self.name = name
        self.max_concurrent = max_concurrent
        self.timeout = timeout
        self._executor = ThreadPoolExecutor(
            max_workers=max_concurrent,
            thread_name_prefix=f"bulkhead-{name}"
        )

    @classmethod
    def get_or_create(cls, name: str, **kwargs) -> 'Bulkhead':
        if name not in cls._instances:
            cls._instances[name] = cls(name, **kwargs)
        return cls._instances[name]

    def execute(self, func: Callable, *args, **kwargs):
        """Exécute une fonction dans le bulkhead."""
        future = self._executor.submit(func, *args, **kwargs)
        try:
            return future.result(timeout=self.timeout)
        except TimeoutError:
            future.cancel()
            raise BulkheadTimeoutError(
                f"Bulkhead '{self.name}' timeout après {self.timeout}s"
            )

class BulkheadTimeoutError(Exception):
    pass

def bulkhead(name: str, max_concurrent: int = 10, timeout: float = 30.0):
    """Décorateur bulkhead pour isoler les appels."""
    def decorator(func: Callable):
        bh = Bulkhead.get_or_create(name, max_concurrent=max_concurrent, timeout=timeout)

        @wraps(func)
        def wrapper(*args, **kwargs):
            return bh.execute(func, *args, **kwargs)
        return wrapper
    return decorator

# Utilisation : isolation des appels par service
@bulkhead(name="payment-service", max_concurrent=5, timeout=10.0)
def process_payment(order_id: str, amount: float) -> bool:
    """Traitement paiement isolé des autres services."""
    # ... appel au service de paiement
    pass

@bulkhead(name="inventory-service", max_concurrent=20, timeout=5.0)
def check_inventory(product_id: str) -> int:
    """Vérification stock isolée."""
    # ... appel au service d'inventaire
    pass

Monitoring et alerting HA

Métriques clés à surveiller

groups:
  - name: high-availability
    rules:
      # Alerte sur disponibilité des pods
      - alert: HighPodUnavailability
        expr: |
          (
            kube_deployment_status_replicas_available
            / kube_deployment_spec_replicas
          ) < 0.5
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Moins de 50% des pods disponibles"
          description: "{{ $labels.deployment }} a {{ $value | humanizePercentage }} de disponibilité"

      # Alerte sur latence élevée
      - alert: HighLatencyP99
        expr: |
          histogram_quantile(0.99,
            sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service)
          ) > 2
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Latence P99 > 2s"
          description: "{{ $labels.service }} P99 latency: {{ $value | humanizeDuration }}"

      # Alerte sur taux d'erreur
      - alert: HighErrorRate
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
          / sum(rate(http_requests_total[5m])) by (service)
          > 0.01
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Taux d'erreur > 1%"
          description: "{{ $labels.service }} error rate: {{ $value | humanizePercentage }}"

      # Alerte sur réplication Kafka
      - alert: KafkaUnderReplicatedPartitions
        expr: kafka_server_replicamanager_underreplicatedpartitions > 0
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Partitions Kafka sous-répliquées"
          description: "{{ $value }} partitions ne sont pas correctement répliquées"

      # Alerte sur lag de réplication PostgreSQL
      - alert: PostgresReplicationLag
        expr: pg_replication_lag_seconds > 30
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "Lag de réplication PostgreSQL élevé"
          description: "Lag de {{ $value | humanizeDuration }} sur {{ $labels.instance }}"

Dashboard de disponibilité

# Extraits de configuration Grafana
panels:
  - title: "SLA Mensuel"
    type: "stat"
    targets:
      - expr: |
          (1 - (
            sum(increase(http_requests_total{status=~"5.."}[30d]))
            / sum(increase(http_requests_total[30d]))
          )) * 100
    fieldConfig:
      defaults:
        unit: "percent"
        thresholds:
          - value: 99.9
            color: "green"
          - value: 99.5
            color: "yellow"
          - value: 99
            color: "red"

  - title: "Temps de réponse P50/P95/P99"
    type: "timeseries"
    targets:
      - expr: histogram_quantile(0.50, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))
        legendFormat: "P50"
      - expr: histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))
        legendFormat: "P95"
      - expr: histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))
        legendFormat: "P99"

  - title: "Disponibilité par service"
    type: "table"
    targets:
      - expr: |
          sum by (service) (
            rate(http_requests_total{status!~"5.."}[24h])
          ) / sum by (service) (
            rate(http_requests_total[24h])
          ) * 100

Checklist haute disponibilité

## Infrastructure
- [ ] Minimum 3 replicas pour les services critiques
- [ ] Distribution sur plusieurs zones/régions
- [ ] Load balancer redondant
- [ ] DNS avec failover automatique
- [ ] Stockage répliqué (3 copies minimum)

## Application
- [ ] Health checks (liveness + readiness)
- [ ] Graceful shutdown implémenté
- [ ] Circuit breaker sur appels externes
- [ ] Retry avec backoff exponentiel
- [ ] Timeouts configurés partout
- [ ] Idempotence des opérations critiques

## Base de données
- [ ] Réplication synchrone ou async selon RPO
- [ ] Failover automatique testé
- [ ] Backups réguliers et testés
- [ ] Point-in-time recovery configuré

## Monitoring
- [ ] Métriques SLI collectées
- [ ] Alertes sur SLO configurées
- [ ] Dashboard de disponibilité
- [ ] Logs centralisés
- [ ] Tracing distribué

## Processus
- [ ] Runbooks documentés
- [ ] Tests de chaos réguliers
- [ ] Post-mortems après incidents
- [ ] Capacity planning trimestriel

Conclusion

La haute disponibilité n'est pas un état mais un processus continu qui combine :

  1. Architecture résiliente : Redondance, distribution, isolation
  2. Code défensif : Circuit breakers, retries, timeouts
  3. Monitoring proactif : SLI/SLO/SLA bien définis
  4. Culture d'amélioration : Post-mortems, tests de chaos

:::tip Règle d'or : Chaque niveau de "9" supplémentaire coûte environ 10x plus cher. Définissez vos SLO en fonction de l'impact business réel, pas de l'ego technique. :::


Ressources complémentaires

F

Florian Courouge

Expert DevOps & Kafka | Consultant freelance specialise dans les architectures distribuees et le streaming de donnees.

Articles similaires