5.8.1 Istio 服务网格

5.8.1 Istio 服务网格 #

服务网格(Service Mesh)是处理服务间通信的基础设施层,它将服务间的通信逻辑从应用代码中分离出来,通过透明的代理层提供连接、安全、监控和管理功能。Istio 是目前最流行的服务网格解决方案之一。

服务网格核心概念 #

什么是服务网格 #

服务网格是一个专用的基础设施层,用于处理微服务架构中服务与服务之间的通信。它通过轻量级网络代理(通常以 sidecar 模式部署)来实现服务发现、负载均衡、故障恢复、指标收集和监控等功能。

服务网格的核心特征:

  1. 透明性:对应用程序透明,无需修改业务代码
  2. 可观测性:提供详细的服务间通信指标和追踪
  3. 安全性:自动化的 mTLS 和访问控制
  4. 流量管理:智能路由和流量控制
  5. 策略执行:统一的策略管理和执行

Sidecar 模式 #

Sidecar 模式是服务网格的核心架构模式,每个服务实例都会伴随一个代理容器:

# 示例:带有 Istio sidecar 的 Pod 配置
apiVersion: v1
kind: Pod
metadata:
  name: productpage
  labels:
    app: productpage
spec:
  containers:
    - name: productpage
      image: productpage:v1
      ports:
        - containerPort: 9080
    - name: istio-proxy # Sidecar 代理
      image: istio/proxyv2:1.18.0
      # 代理配置...

Istio 架构详解 #

整体架构 #

Istio 采用分层架构设计,主要包含数据平面和控制平面:

┌─────────────────────────────────────────────────────────┐
│                    控制平面 (Control Plane)              │
├─────────────────────────────────────────────────────────┤
│  Pilot        │  Citadel      │  Galley       │  Mixer  │
│  (服务发现)    │  (安全管理)    │  (配置管理)    │  (策略)  │
└─────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────┐
│                    数据平面 (Data Plane)                 │
├─────────────────────────────────────────────────────────┤
│  Envoy Proxy  │  Envoy Proxy  │  Envoy Proxy  │  ...   │
│  (Sidecar)    │  (Sidecar)    │  (Sidecar)    │        │
└─────────────────────────────────────────────────────────┘

核心组件 #

1. Pilot(服务发现和配置管理)

Pilot 负责服务发现和流量管理配置的分发:

// 示例:Go 应用中的服务注册
package main

import (
    "context"
    "fmt"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

type ServiceRegistry struct {
    serviceName string
    port        int
}

func (sr *ServiceRegistry) Register() error {
    // 在 Kubernetes 环境中,服务注册通过 Service 资源自动完成
    fmt.Printf("Service %s registered on port %d\n", sr.serviceName, sr.port)
    return nil
}

func main() {
    registry := &ServiceRegistry{
        serviceName: "user-service",
        port:        8080,
    }

    if err := registry.Register(); err != nil {
        panic(err)
    }

    // 启动 HTTP 服务
    mux := http.NewServeMux()
    mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Write([]byte("OK"))
    })

    mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        w.Write([]byte(`{"users": ["alice", "bob"]}`))
    })

    server := &http.Server{
        Addr:    ":8080",
        Handler: mux,
    }

    // 优雅关闭
    go func() {
        sigChan := make(chan os.Signal, 1)
        signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
        <-sigChan

        ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
        defer cancel()
        server.Shutdown(ctx)
    }()

    fmt.Println("Server starting on :8080")
    if err := server.ListenAndServe(); err != http.ErrServerClosed {
        panic(err)
    }
}

2. Citadel(安全管理)

Citadel 负责证书管理和身份验证:

# 示例:启用 mTLS 的策略配置
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: production
spec:
  mtls:
    mode: STRICT # 强制 mTLS
---
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: user-service-policy
  namespace: production
spec:
  selector:
    matchLabels:
      app: user-service
  rules:
    - from:
        - source:
            principals: ["cluster.local/ns/production/sa/frontend"]
      to:
        - operation:
            methods: ["GET"]
            paths: ["/users"]

3. Envoy Proxy(数据平面代理)

Envoy 是高性能的 C++ 代理,处理所有的网络流量:

// 示例:Go 应用中处理 Envoy 注入的头部信息
package main

import (
    "fmt"
    "net/http"
)

func handleRequest(w http.ResponseWriter, r *http.Request) {
    // 获取 Istio/Envoy 注入的追踪头部
    traceID := r.Header.Get("x-trace-id")
    spanID := r.Header.Get("x-span-id")

    fmt.Printf("Processing request - TraceID: %s, SpanID: %s\n", traceID, spanID)

    // 传播追踪头部到下游服务
    client := &http.Client{}
    req, _ := http.NewRequest("GET", "http://downstream-service/api", nil)

    // 复制重要的头部信息
    headers := []string{
        "x-request-id",
        "x-b3-traceid",
        "x-b3-spanid",
        "x-b3-parentspanid",
        "x-b3-sampled",
        "x-b3-flags",
        "x-ot-span-context",
    }

    for _, header := range headers {
        if value := r.Header.Get(header); value != "" {
            req.Header.Set(header, value)
        }
    }

    resp, err := client.Do(req)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    defer resp.Body.Close()

    w.WriteHeader(http.StatusOK)
    w.Write([]byte("Request processed successfully"))
}

func main() {
    http.HandleFunc("/api", handleRequest)
    fmt.Println("Server starting on :8080")
    http.ListenAndServe(":8080", nil)
}

Istio 安装和配置 #

安装 Istio #

1. 下载和安装 Istio CLI

# 下载 Istio
curl -L https://istio.io/downloadIstio | sh -
cd istio-1.18.0
export PATH=$PWD/bin:$PATH

# 验证安装
istioctl version

2. 安装 Istio 到 Kubernetes 集群

# 预检查集群
istioctl x precheck

# 安装 Istio(使用默认配置)
istioctl install --set values.defaultRevision=default

# 或者使用自定义配置
istioctl install --set values.pilot.traceSampling=100.0

3. 启用 Sidecar 自动注入

# 为命名空间启用自动注入
kubectl label namespace default istio-injection=enabled

# 验证注入状态
kubectl get namespace -L istio-injection

部署示例应用 #

让我们部署一个完整的微服务应用来演示 Istio 的功能:

# bookinfo-app.yaml
apiVersion: v1
kind: Service
metadata:
  name: productpage
  labels:
    app: productpage
    service: productpage
spec:
  ports:
    - port: 9080
      name: http
  selector:
    app: productpage
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: productpage-v1
  labels:
    app: productpage
    version: v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: productpage
      version: v1
  template:
    metadata:
      labels:
        app: productpage
        version: v1
    spec:
      containers:
        - name: productpage
          image: docker.io/istio/examples-bookinfo-productpage-v1:1.17.0
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 9080
          env:
            - name: SERVICE_VERSION
              value: v1
---
# Gateway 配置
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: bookinfo-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
    - port:
        number: 80
        name: http
        protocol: HTTP
      hosts:
        - "*"
---
# VirtualService 配置
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: bookinfo
spec:
  hosts:
    - "*"
  gateways:
    - bookinfo-gateway
  http:
    - match:
        - uri:
            exact: /productpage
        - uri:
            prefix: /static
        - uri:
            exact: /login
        - uri:
            exact: /logout
        - uri:
            prefix: /api/v1/products
      route:
        - destination:
            host: productpage
            port:
              number: 9080

Go 应用的 Istio 集成 #

创建一个支持 Istio 特性的 Go 微服务:

// main.go - 支持 Istio 的 Go 服务
package main

import (
    "context"
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "os"
    "strconv"
    "time"

    "github.com/gorilla/mux"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/attribute"
    "go.opentelemetry.io/otel/exporters/jaeger"
    "go.opentelemetry.io/otel/sdk/resource"
    "go.opentelemetry.io/otel/sdk/trace"
    semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
)

type Product struct {
    ID          int    `json:"id"`
    Name        string `json:"name"`
    Description string `json:"description"`
    Price       float64 `json:"price"`
}

type ProductService struct {
    products []Product
}

func NewProductService() *ProductService {
    return &ProductService{
        products: []Product{
            {ID: 1, Name: "Laptop", Description: "High-performance laptop", Price: 999.99},
            {ID: 2, Name: "Mouse", Description: "Wireless mouse", Price: 29.99},
            {ID: 3, Name: "Keyboard", Description: "Mechanical keyboard", Price: 79.99},
        },
    }
}

func (ps *ProductService) GetProducts(w http.ResponseWriter, r *http.Request) {
    // 创建 span 用于追踪
    tracer := otel.Tracer("product-service")
    ctx, span := tracer.Start(r.Context(), "get-products")
    defer span.End()

    // 添加属性到 span
    span.SetAttributes(
        attribute.String("service.name", "product-service"),
        attribute.String("http.method", r.Method),
        attribute.String("http.url", r.URL.String()),
    )

    // 模拟一些处理时间
    time.Sleep(100 * time.Millisecond)

    // 检查是否有故障注入头部(用于测试)
    if faultDelay := r.Header.Get("x-fault-delay"); faultDelay != "" {
        if delay, err := strconv.Atoi(faultDelay); err == nil {
            time.Sleep(time.Duration(delay) * time.Millisecond)
        }
    }

    if faultAbort := r.Header.Get("x-fault-abort"); faultAbort != "" {
        if code, err := strconv.Atoi(faultAbort); err == nil {
            http.Error(w, "Fault injection", code)
            return
        }
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(ps.products)

    log.Printf("Served %d products", len(ps.products))
}

func (ps *ProductService) GetProduct(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    id, err := strconv.Atoi(vars["id"])
    if err != nil {
        http.Error(w, "Invalid product ID", http.StatusBadRequest)
        return
    }

    tracer := otel.Tracer("product-service")
    ctx, span := tracer.Start(r.Context(), "get-product")
    defer span.End()

    span.SetAttributes(
        attribute.Int("product.id", id),
    )

    for _, product := range ps.products {
        if product.ID == id {
            w.Header().Set("Content-Type", "application/json")
            json.NewEncoder(w).Encode(product)
            return
        }
    }

    http.Error(w, "Product not found", http.StatusNotFound)
}

// 健康检查端点
func healthCheck(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("OK"))
}

// 就绪检查端点
func readinessCheck(w http.ResponseWriter, r *http.Request) {
    // 这里可以检查依赖服务的状态
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("Ready"))
}

// 初始化 OpenTelemetry
func initTracer() func() {
    // 创建 Jaeger exporter
    exp, err := jaeger.New(jaeger.WithCollectorEndpoint(
        jaeger.WithEndpoint("http://jaeger-collector:14268/api/traces"),
    ))
    if err != nil {
        log.Printf("Failed to create Jaeger exporter: %v", err)
        return func() {}
    }

    // 创建 trace provider
    tp := trace.NewTracerProvider(
        trace.WithBatcher(exp),
        trace.WithResource(resource.NewWithAttributes(
            semconv.SchemaURL,
            semconv.ServiceNameKey.String("product-service"),
            semconv.ServiceVersionKey.String("v1.0.0"),
        )),
    )

    otel.SetTracerProvider(tp)

    return func() {
        ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
        defer cancel()
        tp.Shutdown(ctx)
    }
}

func main() {
    // 初始化追踪
    cleanup := initTracer()
    defer cleanup()

    service := NewProductService()

    r := mux.NewRouter()

    // API 路由
    api := r.PathPrefix("/api/v1").Subrouter()
    api.HandleFunc("/products", service.GetProducts).Methods("GET")
    api.HandleFunc("/products/{id:[0-9]+}", service.GetProduct).Methods("GET")

    // 健康检查路由
    r.HandleFunc("/health", healthCheck).Methods("GET")
    r.HandleFunc("/ready", readinessCheck).Methods("GET")

    // 添加中间件来记录请求
    r.Use(loggingMiddleware)

    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }

    fmt.Printf("Product service starting on port %s\n", port)
    log.Fatal(http.ListenAndServe(":"+port, r))
}

// 日志中间件
func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()

        // 记录请求信息
        log.Printf("Started %s %s", r.Method, r.URL.Path)

        // 记录重要的头部信息
        if traceID := r.Header.Get("x-trace-id"); traceID != "" {
            log.Printf("TraceID: %s", traceID)
        }

        next.ServeHTTP(w, r)

        log.Printf("Completed %s %s in %v", r.Method, r.URL.Path, time.Since(start))
    })
}

对应的 Kubernetes 部署配置:

# product-service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: product-service
  labels:
    app: product-service
spec:
  replicas: 2
  selector:
    matchLabels:
      app: product-service
  template:
    metadata:
      labels:
        app: product-service
        version: v1
    spec:
      containers:
        - name: product-service
          image: product-service:v1
          ports:
            - containerPort: 8080
          env:
            - name: PORT
              value: "8080"
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /ready
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 5
          resources:
            requests:
              memory: "64Mi"
              cpu: "50m"
            limits:
              memory: "128Mi"
              cpu: "100m"
---
apiVersion: v1
kind: Service
metadata:
  name: product-service
  labels:
    app: product-service
spec:
  ports:
    - port: 80
      targetPort: 8080
      name: http
  selector:
    app: product-service

验证 Istio 功能 #

检查 Sidecar 注入 #

# 部署应用
kubectl apply -f product-service.yaml

# 检查 Pod 是否包含 sidecar
kubectl get pods -o wide
kubectl describe pod <pod-name>

# 查看 sidecar 配置
kubectl exec <pod-name> -c istio-proxy -- pilot-agent request GET config_dump

查看服务网格状态 #

# 检查 Istio 配置
istioctl proxy-config cluster <pod-name>
istioctl proxy-config listener <pod-name>
istioctl proxy-config route <pod-name>

# 查看服务网格拓扑
istioctl proxy-status

通过本节的学习,你已经掌握了 Istio 服务网格的基本概念、架构和部署方法。在下一节中,我们将深入学习如何使用 Istio 进行流量管理和策略配置。