gRPC - 05 - gRPC en production : load balancing, TLS et observabilité

Déployer gRPC en production : TLS, load balancing L7, health checks, interceptors et observabilité avec OpenTelemetry.

05 - gRPC en production : load balancing, TLS et observabilité

Ce que tu vas apprendre

  • Configurer TLS et mTLS pour sécuriser les appels gRPC
  • Résoudre le problème de load balancing avec HTTP/2
  • Mettre en place des interceptors (logging, auth, tracing)
  • Intégrer OpenTelemetry pour l'observabilité

Prerequisites

04 - Le streaming


TLS : chiffrer le trafic

En dev, on utilise grpc.credentials.createInsecure(). En production, c'est inacceptable. Le trafic gRPC entre services passe en clair sur le réseau -- n'importe qui sur le meme segment peut lire les donnees.

typescript// Serveur avec TLS
import * as fs from "fs";
import * as grpc from "@grpc/grpc-js";

const serverCreds = grpc.ServerCredentials.createSsl(
  fs.readFileSync("certs/ca.pem"),       // CA certificate
  [{
    cert_chain: fs.readFileSync("certs/server.pem"),  // Server cert
    private_key: fs.readFileSync("certs/server-key.pem")  // Server key
  }],
  false  // true = require client certificate (mTLS)
);

server.bindAsync("0.0.0.0:50051", serverCreds, (err) => {
  if (err) throw err;
  console.log("gRPC server running with TLS");
});
typescript// Client avec TLS
const channelCreds = grpc.credentials.createSsl(
  fs.readFileSync("certs/ca.pem")  // CA certificate pour verifier le serveur
);

const client = new TaskServiceClient("server:50051", channelCreds);

Mutual TLS (mTLS) : en plus de vérifier le serveur, le serveur vérifié le client. Chaque service a son propre certificat. C'est le standard pour la communication inter-services en microservices. Ca remplace l'authentification par token pour le trafic interne.

typescript// mTLS : le client presente aussi un certificat
const channelCreds = grpc.credentials.createSsl(
  fs.readFileSync("certs/ca.pem"),
  fs.readFileSync("certs/client-key.pem"),
  fs.readFileSync("certs/client.pem")
);

En pratique, la gestion des certificats est souvent delegee a un service mesh (Istio, Linkerd) ou a un outil comme cert-manager sur Kubernetes. Gerer les certificats a la main pour 15 services, c'est un cauchemar de rotation et d'expiration.

Le problème du load balancing

Avec HTTP/1.1 et REST, le load balancing est simple. Chaque requête est une connexion independante. Un load balancer L4 (TCP) distribue les connexions et ca marche.

Avec gRPC et HTTP/2, c'est différent. Une seule connexion TCP transporte tous les appels en multiplexage. Si un load balancer L4 distribue les connexions, un client se connecte a un seul serveur et tous ses appels vont la-bas. Les autres serveurs sont au chomage.

PROBLEME avec L4 (TCP) load balancing :

[Client] ---(1 connexion HTTP/2)---> [LB L4] ---> [Server A] (recoit tout)
                                                   [Server B] (recoit rien)
                                                   [Server C] (recoit rien)

Solutions :

1. Load balancer L7 (applicatif) : le LB comprend HTTP/2 et distribue les requêtes individuelles, pas les connexions. Envoy, Traefik, et Caddy (avec config gRPC) le font.

SOLUTION avec L7 (HTTP/2-aware) load balancing :

[Client] ---(1 connexion HTTP/2)---> [LB L7] ---> [Server A] (requete 1, 4)
                                                   [Server B] (requete 2, 5)
                                                   [Server C] (requete 3, 6)

2. Client-side load balancing : le client connaît la liste des serveurs et distribue lui-meme ses appels. gRPC supporte ca nativement avec des "resolvers" et des "load balancing policies".

typescript// Client-side load balancing avec round-robin
const client = new TaskServiceClient(
  "dns:///task-service.default.svc.cluster.local:50051",
  grpc.credentials.createInsecure(),
  { "grpc.service_config": JSON.stringify({
    loadBalancingConfig: [{ round_robin: {} }]
  })}
);

Sur paltemps.fr, Caddy fait deja office de reverse proxy. Si on ajoutait des services gRPC, Caddy pourrait aussi jouer le rôle de load balancer L7 pour gRPC.

Health checking

gRPC a un protocole de health checking standardise. Chaque service implemente le service grpc.health.v1.Health.

typescriptimport { HealthImplementation } from "grpc-health-check";

const healthImpl = new HealthImplementation({
  "": "SERVING",                    // Status global
  "taskmanager.TaskService": "SERVING"  // Status par service
});

server.addService(healthImpl.service, healthImpl);

// Plus tard, si le service est en difficulte :
healthImpl.setStatus("taskmanager.TaskService", "NOT_SERVING");

Kubernetes, Envoy, et les load balancers utilisent ce protocole pour retirer automatiquement les instances malades du routing. C'est l'équivalent du GET /health en REST, mais standardise.

Interceptors : le middleware de gRPC

Les interceptors sont l'équivalent des middlewares Express/Elysia. Ils s'executent avant et apres chaque appel RPC. Tu les utilises pour le logging, l'authentification, le tracing, et la validation.

typescript// Interceptor de logging cote serveur
function loggingInterceptor(
  methodDescriptor: any,
  call: any
) {
  const startTime = Date.now();
  const method = methodDescriptor.path;

  const original = call.handler;
  call.handler = (call: any, callback: any) => {
    original(call, (err: any, response: any) => {
      const duration = Date.now() - startTime;
      const status = err ? err.code : 0;
      console.log(JSON.stringify({
        method,
        duration_ms: duration,
        status,
        timestamp: new Date().toISOString()
      }));
      callback(err, response);
    });
  };
}

En pratique, les librairies comme nice-grpc ou grpc-js proposent des APIs d'interceptors plus propres :

typescript// Avec nice-grpc (syntaxe simplifiee)
import { createServer, ServerMiddlewareCall } from "nice-grpc";

async function* loggingMiddleware<Req, Res>(
  call: ServerMiddlewareCall<Req, Res>,
  context: any
) {
  const start = Date.now();
  try {
    const response = yield* call.next(call.request, context);
    console.log(`${call.method} - ${Date.now() - start}ms - OK`);
    return response;
  } catch (err) {
    console.log(`${call.method} - ${Date.now() - start}ms - ERROR`);
    throw err;
  }
}

OpenTelemetry : le tracing distribue

Quand un appel traverse 5 services, tu dois pouvoir suivre le chemin complet. OpenTelemetry (OTel) propage un trace ID a chaque appel gRPC via la metadata.

typescriptimport { NodeSDK } from "@opentelemetry/sdk-node";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-grpc";

const sdk = new NodeSDK({
  traceExporter: new OTLPTraceExporter({
    url: "http://jaeger:4317"
  }),
  instrumentations: [
    getNodeAutoInstrumentations()
  ]
});

sdk.start();
// L'instrumentation auto detecte @grpc/grpc-js et ajoute
// le tracing a tous les appels gRPC automatiquement

Avec cette config, chaque appel gRPC généré un span dans Jaeger ou Zipkin. Tu vois le temps de chaque étape, les erreurs, et la propagation entre services. C'est non negociable en microservices, comme je l'explique dans l'article sur l'infrastructure microservices.

Reflection : oui ou non en prod ?

La reflection gRPC permet aux outils (grpcurl, Postman) de découvrir les services sans fichier .proto. En dev, c'est indispensable. En prod, c'est un choix.

Pour la reflection en prod : facilite le debug, permet les outils de monitoring de découvrir les services automatiquement.

Contre la reflection en prod : expose la surface d'API a qui peut se connecter. Si tu fais du mTLS, c'est moins grave (seuls les services authentifies peuvent interroger la reflection).

Mon avis : active-la en prod si tu as du mTLS. Desactive-la sinon. Le debug en prod vaut le coup si c'est sécurisé.


Résumé

  • TLS obligatoire en production, mTLS pour le trafic inter-services
  • Load balancing L7 (Envoy, Traefik, Caddy) au lieu de L4 pour gRPC
  • Health checking standardise avec grpc.health.v1.Health
  • Interceptors pour le logging, l'auth et le tracing (équivalent des middlewares)
  • OpenTelemetry pour le tracing distribue -- non negociable en microservices

Article précédent : 04 - Le streaming Article suivant : 06 - gRPC vs REST : le comparatif définitif

Sources

Réservez un audit gratuit de 30 minutes. Je vous montre concrètement ce qu'on peut automatiser.