Configuration de Routage Istio : En-têtes HTTP et Répartition de Charge

Dans une architecture microservices orchestrée par Kubernetes, Istio permet de gérer finement le flux des requêtes entrantes. L'objectif ici est de configurer un service intermédiaire (BFF) pour qu'il route le trafic vers différentes versions de pods selon des critères spécifiques.

Le flux fonctionne comme suit : les requêtes arrivent via une passerelle Istio. Un en-tête HTTP personnalisé (X-Deploy-Tag) détermine la destination. Si la valeur est feat-alpha, le trafic est dirigé vers la version de développement. Dans les autres cas, une répartition pondérée est appliquée : 20% vers la version stable prod-blue et 80% vers la version stable prod-green.

  1. Implémentation du Service en Go

Le service expose plusieurs endpoints pour tester le routage et simuler des appels vers un service back end.

Code Source

package main

import (
    "io"
    "net/http"
    "os"
    "time"

    log "github.com/sirupsen/logrus"
    "github.com/lestrrat-go/file-rotatelogs"
)

const appVersion = "feat-alpha"
var logger *log.Logger

func initLogger() {
    logger = log.New()
    logger.SetOutput(os.Stdout)
    logger.SetFormatter(&log.JSONFormatter{
        TimestampFormat: time.RFC3339,
    })
}

func main() {
    initLogger()
    
    // Définition des routes
    http.HandleFunc("/api/v1/orders/list", handleOrderList)
    http.HandleFunc("/api/v1/health/ping", handlePing)
    http.HandleFunc("/api/v1/health/fail", handleFail)

    logger.Infof("Démarrage du service version: %s sur le port 8080", appVersion)
    if err := http.ListenAndServe(":8080", nil); err != nil {
        logger.Fatal(err)
    }
}

func handleOrderList(w http.ResponseWriter, r *http.Request) {
    tag := extractTag(r)
    logger.WithFields(log.Fields{"version": appVersion, "endpoint": "list"}).Info("Tag reçu: ", tag)
    
    // Simulation d'appel au service backend
    data, err := callBackend(tag)
    if err != nil {
        logger.Error("Erreur appel backend: ", err)
        http.Error(w, "Backend Unavailable", http.StatusInternalServerError)
        return
    }
    w.Write(data)
}

func handlePing(w http.ResponseWriter, r *http.Request) {
    logger.Info("Ping reçu pour la version: ", appVersion)
    w.Write([]byte("OK"))
}

func handleFail(w http.ResponseWriter, r *http.Request) {
    logger.Warn("Demande d'échec reçue")
    w.WriteHeader(http.StatusInternalServerError)
    w.Write([]byte("Simulated Error"))
}

func extractTag(req *http.Request) string {
    values := req.Header.Values("X-Deploy-Tag")
    if len(values) > 0 {
        return values[0]
    }
    return "default"
}

func callBackend(branch string) ([]byte, error) {
    client := &http.Client{Timeout: 5 * time.Second}
    req, err := http.NewRequest("GET", "http://order-backend:8080/rpc/data", nil)
    if err != nil {
        return nil, err
    }
    req.Header.Set("X-Deploy-Tag", branch)
    
    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    
    return io.ReadAll(resp.Body)
}

Conteneurisation (Dockerfile)

Utilisation d'une construction multi-étapes pour optimiser la taille de l'image finale.

FROM golang:1.19 AS build-env

WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o app-server

FROM alpine:3.16

WORKDIR /srv
COPY --from=build-env /src/app-server .
RUN chmod +x app-server
EXPOSE 8080
CMD ["./app-server"]

Il est nécessaire de construire trois images distinctes en modifiant la constante appVersion dans le code source pour chaque build : feat-alpha, prod-blue et prod-green.

  1. Déploiement Kubernetes

Les ressources Kubernetes définissent le service réseau et les déploiements pour chaque version de l'application.

Service Kubernetes

apiVersion: v1
kind: Service
metadata:
  name: order-bff-service
spec:
  selector:
    app: order-bff-app
  ports:
  - name: http
    protocol: TCP
    port: 8080
    targetPort: 8080

Déploiements (Deplyoments)

Chaque version possède son propre déploiement identifié par un label version.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-bff-app-alpha
spec:
  selector:
    matchLabels:
      app: order-bff-app
      version: feat-alpha
  replicas: 1
  template:
    metadata:
      labels:
        app: order-bff-app
        version: feat-alpha
    spec:
      containers:
      - name: server
        image: order-bff:feat-alpha
        ports:
        - containerPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-bff-app-blue
spec:
  selector:
    matchLabels:
      app: order-bff-app
      version: prod-blue
  replicas: 1
  template:
    metadata:
      labels:
        app: order-bff-app
        version: prod-blue
    spec:
      containers:
      - name: server
        image: order-bff:prod-blue
        ports:
        - containerPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-bff-app-green
spec:
  selector:
    matchLabels:
      app: order-bff-app
      version: prod-green
  replicas: 1
  template:
    metadata:
      labels:
        app: order-bff-app
        version: prod-green
    spec:
      containers:
      - name: server
        image: order-bff:prod-green
        ports:
        - containerPort: 8080

  1. Configuration Istio

La logique de routage est définie via les ressources Istio : Gateway, VirtualService et DestinationRule.

Passerelle (Gateway)

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: public-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"

Règles de Routage (VirtualService)

Ce fichier définit la condition sur l'en-tête X-Deploy-Tag et la pondération pour le trafic restant.

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: order-bff-routing
spec:
  hosts:
  - "*"
  gateways:
  - public-gateway
  http:
  - match:
    - headers:
        X-Deploy-Tag:
          exact: "feat-alpha"
    route:
    - destination:
        host: order-bff-service
        subset: feat-alpha
  - route:
    - destination:
        host: order-bff-service
        subset: prod-blue
      weight: 20
    - destination:
        host: order-bff-service
        subset: prod-green
      weight: 80

Règles de Destination (DestinationRule)

Les subsets correspondent aux labels définis dans les déploiements Kubernetes.

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: order-bff-destination
spec:
  host: order-bff-service
  subsets:
  - name: feat-alpha
    labels:
      version: feat-alpha
  - name: prod-blue
    labels:
      version: prod-blue
  - name: prod-green
    labels:
      version: prod-green

  1. Validation et Tests

Appliquez les configurations dans l'espace de noms désiré.

kubectl apply -f . -n istio-demo

Vérifiez l'état des pods et des ressources Istio.

kubectl get pods -n istio-demo
kubectl get vs,dr,gw -n istio-demo

Utilisez l'outil istioctl pour inspecter la configuration appliquée sur un pod spécifique.

istioctl x describe pod <pod-name-alpha> -n istio-demo
istioctl x describe pod <pod-name-blue> -n istio-demo

Effectuez des requêtes curl pour observer le comportement. Sans en-tête, le trafic se répartit selon les poids. Avec l'en-tête spécifique, il est dirigé vers la version alpha.

# Test sans en-tête (répartition 20/80)
curl http://<gateway-ip>/api/v1/health/ping

# Test avec en-tête (routage vers feat-alpha)
curl -H "X-Deploy-Tag: feat-alpha" http://<gateway-ip>/api/v1/health/ping

Surveillez les logs des différents pods pour confirmer la réception des requêtes.

kubectl logs -f <pod-name-green> -c server -n istio-demo

Nettoyage des ressources après valiadtion.

kubectl delete -f . -n istio-demo

Étiquettes: Istio kubernetes golang Docker traffic-management

Publié le 21 août à 10h31