5.8.1 Istio 服务网格 #
服务网格(Service Mesh)是处理服务间通信的基础设施层,它将服务间的通信逻辑从应用代码中分离出来,通过透明的代理层提供连接、安全、监控和管理功能。Istio 是目前最流行的服务网格解决方案之一。
服务网格核心概念 #
什么是服务网格 #
服务网格是一个专用的基础设施层,用于处理微服务架构中服务与服务之间的通信。它通过轻量级网络代理(通常以 sidecar 模式部署)来实现服务发现、负载均衡、故障恢复、指标收集和监控等功能。
服务网格的核心特征:
- 透明性:对应用程序透明,无需修改业务代码
- 可观测性:提供详细的服务间通信指标和追踪
- 安全性:自动化的 mTLS 和访问控制
- 流量管理:智能路由和流量控制
- 策略执行:统一的策略管理和执行
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 进行流量管理和策略配置。