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.
- 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.
- 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
- 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
- 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