Skip to main content

Istio 1.31 生产实战:Java、Go、Rust 的 REST、gRPC、零信任与流量治理

Rainy
雨落无声,代码成诗 —— 致力于技术与艺术的极致平衡
Rainy
57 MIN READ... VIEWS

外部使用 REST,不代表内部也必须使用 REST;内部使用 gRPC,也不代表每个语言都要各自维护证书、重试、负载均衡和访问控制。Istio 的价值,是把跨语言的网络治理收敛到统一的数据平面和策略层。

本文不从 Bookinfo 示例开始,而是搭建一条更接近真实生产系统的链路:

外部客户端
→ HTTPS / REST
→ Istio Ingress Gateway
→ Java Web API
→ gRPC
→ Go Order Service
→ gRPC
→ Rust Inventory Service
→ PostgreSQL

三个服务只实现业务协议,Istio 负责入口、东西向 mTLS、工作负载身份、流量分配、超时、异常实例摘除和基础遥测;应用仍负责业务权限、幂等、事务、数据加密和 Trace Context 传播。

Istio 生产网络拓扑:公网请求通过 HTTPS 443 进入独立 Ingress Gateway,经 HTTP 8080 到达 Java,再以 gRPC 9090/9091 和 mTLS 连接 Go、Rust 服务,最后通过 TLS 访问 PostgreSQL;Istiod 和可观测平台分别承担控制与遥测

版本基线

本文按 Istio 1.31 的稳定 API 编写。Istio 1.31 官方支持 Kubernetes 1.32–1.36;如果集群版本不同,应先查看 Supported Releases,不要只复制安装命令。


一、先建立正确心智模型

1.1 Istio 不是什么

Istio 不是新的 RPC 框架,也不是数据库代理,更不会自动修复业务事务。它不会替你完成:

  • Protobuf API 设计;
  • 用户、租户、订单归属等业务授权;
  • 数据库行级权限与 SQL 审计;
  • 非幂等写操作的去重;
  • 分布式事务、Saga 或 Outbox;
  • 应用内部函数、数据库查询和队列消费的 Span。

Istio 是建立在 Kubernetes 网络之上的 Service Mesh。它把通用的通信能力放进代理和控制面,让 Java、Go、Rust 等不同技术栈获得一致的治理语义。

1.2 控制平面与数据平面

  • Istiod:发现服务与 Endpoint,计算路由和安全策略,通过 xDS 下发配置,并为工作负载签发身份;
  • Envoy Sidecar:实际处理入站与出站流量,执行 TLS、负载均衡、路由、授权和遥测;
  • 应用容器:仍监听自己的 HTTP/gRPC 端口,不直接接触 Istio 证书。

控制平面不在每个业务请求的数据路径中。Istiod 短暂不可用时,已有代理仍能使用最后一次成功下发的配置;但新 Pod、证书轮换和配置变化会受影响。

1.3 Sidecar 还是 Ambient

Istio 现在同时提供 Sidecar 与 Ambient 数据平面:

模式基础数据平面L4 mTLS / 身份L7 REST/gRPC 路由方法/路径级授权适合场景
Sidecar每 Pod Envoy支持原生支持原生支持精细治理、成熟生产系统
Ambient 基础模式每节点 ztunnel支持不支持不支持只需要 L4 加密与身份
Ambient + Waypointztunnel + Waypoint支持支持支持希望减少 Sidecar,同时保留 L7

官方文档明确说明:Ambient 中只需要 L4 mTLS 和授权时不必部署 Waypoint;要执行 L7 策略才需要 Waypoint。本文为了演示 gRPC 方法匹配、重试和金丝雀,选择 Sidecar 模式。不要同时给同一个 Namespace 设置 istio-injection=enabledistio.io/dataplane-mode=ambient


二、实战架构与工程边界

生产方案不能只画服务调用关系,还要把每层“负责什么、不能替代什么”说明清楚。下面按接入、服务、网格、安全、数据与流量、可观测与运维六层拆解,并明确 Mesh、应用和数据库之间的职责边界。

Istio 生产技术方案:从公网接入、跨语言 REST 与 gRPC 服务、Envoy 网格治理、工作负载身份与最小权限,到数据库保护、灰度发布和可观测运维的六层落地方案

2.1 服务职责

组件语言对外协议职责
web-apiJava 21 / Spring BootREST :8080接收外部请求、校验业务参数、调用订单 gRPC
order-serviceGogRPC :9090编排下单、调用库存、写订单数据库
inventory-serviceRust / TonicgRPC :9091幂等扣减库存、返回剩余数量
postgresPostgreSQLTCP/TLS :5432订单与库存持久化

2.2 请求经过哪些安全边界

Internet
└─ HTTPS + JWT
└─ Ingress Gateway:TLS 终止、JWT 初筛、入口路由
└─ mTLS / SPIFFE Identity
└─ Java:用户与租户业务授权
└─ gRPC + mTLS
└─ Go:订单幂等与编排
└─ gRPC + mTLS
└─ Rust:库存幂等与并发控制
└─ SQL TLS + DB Role
└─ PostgreSQL

Istio 的身份是工作负载身份,例如:

spiffe://cluster.local/ns/shop/sa/web-api
spiffe://cluster.local/ns/shop/sa/order-service
spiffe://cluster.local/ns/shop/sa/inventory-service

它回答“哪个工作负载正在调用”,JWT 回答“哪个外部用户正在调用”,数据库角色回答“哪个应用身份可以操作哪些表”。三层不能互相替代。

2.3 推荐目录

istio-shop/
├── proto/
│ └── shop.proto
├── services/
│ ├── web-api-java/
│ ├── order-service-go/
│ └── inventory-service-rust/
├── deploy/
│ ├── base/
│ ├── istio/
│ ├── security/
│ ├── observability/
│ └── overlays/
│ ├── dev/
│ ├── staging/
│ └── production/
└── Makefile

Proto、应用、Kubernetes 基础资源与 Istio 策略分开管理,方便独立测试和回滚。


三、统一契约:一份 Proto 服务三种语言

proto/shop.proto
syntax = "proto3";

package shop.v1;

option java_multiple_files = true;
option java_package = "dev.rainlib.shop.v1";
option go_package = "example.com/istio-shop/gen/shop/v1;shopv1";

service OrderService {
rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse);
rpc GetOrder(GetOrderRequest) returns (GetOrderResponse);
}

service InventoryService {
rpc Reserve(ReserveRequest) returns (ReserveResponse);
}

message CreateOrderRequest {
string request_id = 1;
string user_id = 2;
string sku = 3;
int32 quantity = 4;
}

message CreateOrderResponse {
string order_id = 1;
string status = 2;
}

message GetOrderRequest {
string order_id = 1;
}

message GetOrderResponse {
string order_id = 1;
string status = 2;
}

message ReserveRequest {
string request_id = 1;
string sku = 2;
int32 quantity = 3;
}

message ReserveResponse {
bool accepted = 1;
int32 remaining = 2;
}

request_id 不是装饰字段。只要网关、客户端或业务代码存在重试,创建订单和扣库存就必须具备幂等键。字段编号发布后不要复用;删除字段时使用 reserved

生成代码建议统一放进 CI,而不是让每种语言各自手动运行不同版本的 protoc。更成熟的团队可以使用 Buf 管理 breaking change、lint 和多语言生成。


四、Java:外部 REST 转内部 gRPC

4.1 依赖与 Channel

Java 服务使用 Spring Boot 暴露 REST,使用 grpc-java 调用 Go。示例只展示关键依赖,实际项目应通过 BOM 锁定一组兼容版本。

services/web-api-java/pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
</dependency>
GrpcClientConfig.java
@Configuration
class GrpcClientConfig {
@Bean(destroyMethod = "shutdownNow")
ManagedChannel orderChannel() {
return ManagedChannelBuilder
.forAddress("order-service.shop.svc.cluster.local", 9090)
.usePlaintext()
.build();
}

@Bean
OrderServiceGrpc.OrderServiceBlockingStub orderStub(
ManagedChannel orderChannel) {
return OrderServiceGrpc.newBlockingStub(orderChannel);
}
}

这里的 usePlaintext() 是有意为之:

Java App -- plaintext --> Local Envoy
Local Envoy -- Istio mTLS --> Remote Envoy
Remote Envoy -- plaintext --> Go App

证书和私钥只进入代理,不进入业务容器。若应用自己再启用 TLS,需要明确是端到端 TLS、TLS passthrough 还是双重加密,否则很容易出现协议误判。

Channel 必须长生命周期复用,不能每个 REST 请求创建一次。

4.2 REST Controller

OrderController.java
@RestController
@RequestMapping("/api/orders")
class OrderController {
private final OrderServiceGrpc.OrderServiceBlockingStub orderStub;

OrderController(OrderServiceGrpc.OrderServiceBlockingStub orderStub) {
this.orderStub = orderStub;
}

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
OrderView create(
@RequestHeader("Idempotency-Key") String requestId,
@AuthenticationPrincipal Jwt jwt,
@RequestBody @Valid CreateOrderBody body) {
var request = CreateOrderRequest.newBuilder()
.setRequestId(requestId)
.setUserId(jwt.getSubject())
.setSku(body.sku())
.setQuantity(body.quantity())
.build();

try {
var response = orderStub
.withDeadlineAfter(3, TimeUnit.SECONDS)
.createOrder(request);

return new OrderView(response.getOrderId(), response.getStatus());
} catch (StatusRuntimeException error) {
throw mapGrpcStatus(error.getStatus());
}
}
}

Java 应用仍然验证 JWT 并执行用户、租户和订单归属授权。Istio 的 JWT/AuthorizationPolicy 是基础设施门禁,不应成为唯一的业务权限实现。

Deadline 由入口业务预算产生,沿 gRPC 链路传递。总预算 3 秒时,不应给下游每层都配置 3 秒。


五、Go:gRPC 编排、幂等和订单持久化

5.1 建立库存客户端

当前 grpc-go 推荐使用 grpc.NewClient;在 Sidecar 模式下,应用到本地代理使用明文凭证:

internal/app/app.go
inventoryConn, err := grpc.NewClient(
"inventory-service.shop.svc.cluster.local:9091",
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
return fmt.Errorf("create inventory client: %w", err)
}

inventoryClient := shopv1.NewInventoryServiceClient(inventoryConn)

明文只适用于流量必经 Sidecar 的前提。若本地开发、VM 或 Pod 未注入代理,则应使用应用 TLS,不能因为生产计划上 Mesh 就全局禁用传输安全。

5.2 实现 CreateOrder

internal/grpc/order_server.go
type OrderServer struct {
shopv1.UnimplementedOrderServiceServer
inventory shopv1.InventoryServiceClient
db *pgxpool.Pool
}

func (s *OrderServer) CreateOrder(
ctx context.Context,
req *shopv1.CreateOrderRequest,
) (*shopv1.CreateOrderResponse, error) {
if req.GetRequestId() == "" || req.GetSku() == "" || req.GetQuantity() <= 0 {
return nil, status.Error(codes.InvalidArgument, "invalid order")
}

inventoryCtx, cancel := context.WithTimeout(ctx, 800*time.Millisecond)
defer cancel()

reserved, err := s.inventory.Reserve(inventoryCtx, &shopv1.ReserveRequest{
RequestId: req.GetRequestId(),
Sku: req.GetSku(),
Quantity: req.GetQuantity(),
})
if err != nil {
return nil, status.Errorf(codes.Unavailable, "inventory: %v", err)
}
if !reserved.GetAccepted() {
return nil, status.Error(codes.FailedPrecondition, "insufficient stock")
}

var orderID string
err = s.db.QueryRow(ctx, `
INSERT INTO orders (request_id, user_id, sku, quantity, status)
VALUES ($1, $2, $3, $4, 'CREATED')
ON CONFLICT (request_id)
DO UPDATE SET request_id = EXCLUDED.request_id
RETURNING id
`, req.GetRequestId(), req.GetUserId(), req.GetSku(), req.GetQuantity()).Scan(&orderID)
if err != nil {
return nil, status.Error(codes.Internal, "persist order")
}

return &shopv1.CreateOrderResponse{
OrderId: orderID,
Status: "CREATED",
}, nil
}

ON CONFLICT 保护重复订单,但这段简化代码仍有“库存成功、订单写库失败”的一致性窗口。生产方案应使用 Saga/补偿、Outbox 或把库存预留设计成可确认/可释放状态,而不是指望 Istio 重试解决事务。

5.3 启动 gRPC 与 Health

cmd/server/main.go
listener, err := net.Listen("tcp", ":9090")
if err != nil {
log.Fatal(err)
}

server := grpc.NewServer()
shopv1.RegisterOrderServiceServer(server, orderServer)

healthServer := health.NewServer()
healthServer.SetServingStatus(
"shop.v1.OrderService",
healthpb.HealthCheckResponse_SERVING,
)
healthpb.RegisterHealthServer(server, healthServer)

if err := server.Serve(listener); err != nil {
log.Fatal(err)
}

收到 SIGTERM 后先把 Health 设为 NOT_SERVING,再执行 GracefulStop(),超时后才 Stop()


六、Rust:Tonic gRPC 与库存并发控制

6.1 依赖和代码生成

services/inventory-service-rust/Cargo.toml
[dependencies]
tokio = { version = "1", features = ["macros", "rt-multi-thread", "signal"] }
tonic = { version = "0.14", features = ["transport"] }
tonic-prost = "0.14"
tonic-health = "0.14"
prost = "0.14"
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "uuid"] }
uuid = { version = "1", features = ["v4"] }

[build-dependencies]
tonic-prost-build = "0.14"
build.rs
fn main() -> Result<(), Box<dyn std::error::Error>> {
tonic_prost_build::configure()
.compile_protos(&["../../proto/shop.proto"], &["../../proto"])?;
Ok(())
}

Tonic 0.14 建立在 Tokio、Hyper、Tower 之上,支持 HTTP/2、超时、并发限制和背压。依赖升级时应以同一 Tonic minor 的官方示例为准。

6.2 幂等库存预留

src/service.rs
use tonic::{Request, Response, Status};

pub struct InventoryRpc {
pool: sqlx::PgPool,
}

#[tonic::async_trait]
impl InventoryService for InventoryRpc {
async fn reserve(
&self,
request: Request<ReserveRequest>,
) -> Result<Response<ReserveResponse>, Status> {
let input = request.into_inner();
if input.request_id.is_empty() || input.sku.is_empty() || input.quantity <= 0 {
return Err(Status::invalid_argument("invalid reservation"));
}

let mut tx = self.pool.begin().await
.map_err(|_| Status::internal("begin transaction"))?;

if let Some(remaining) = sqlx::query_scalar::<_, i32>(
"SELECT remaining FROM inventory_reservations WHERE request_id = $1"
)
.bind(&input.request_id)
.fetch_optional(&mut *tx)
.await
.map_err(|_| Status::internal("read idempotency record"))? {
tx.commit().await.map_err(|_| Status::internal("commit"))?;
return Ok(Response::new(ReserveResponse {
accepted: true,
remaining,
}));
}

let remaining = sqlx::query_scalar::<_, i32>(r#"
UPDATE inventory
SET available = available - $1
WHERE sku = $2 AND available >= $1
RETURNING available
"#)
.bind(input.quantity)
.bind(&input.sku)
.fetch_optional(&mut *tx)
.await
.map_err(|_| Status::internal("reserve stock"))?;

let Some(remaining) = remaining else {
return Err(Status::failed_precondition("insufficient stock"));
};

sqlx::query(r#"
INSERT INTO inventory_reservations (request_id, sku, quantity, remaining)
VALUES ($1, $2, $3, $4)
"#)
.bind(&input.request_id)
.bind(&input.sku)
.bind(input.quantity)
.bind(remaining)
.execute(&mut *tx)
.await
.map_err(|_| Status::internal("write idempotency record"))?;

tx.commit().await.map_err(|_| Status::internal("commit"))?;
Ok(Response::new(ReserveResponse {
accepted: true,
remaining,
}))
}
}

request_id 必须有数据库唯一约束。只在内存中去重无法跨 Pod,也无法承受重启。

6.3 启动服务与标准 Health

src/main.rs
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let pool = sqlx::PgPool::connect(&std::env::var("DATABASE_URL")?).await?;
let inventory = InventoryRpc { pool };

let (mut reporter, health_service) = tonic_health::server::health_reporter();
reporter
.set_serving::<InventoryServiceServer<InventoryRpc>>()
.await;

tonic::transport::Server::builder()
.concurrency_limit_per_connection(512)
.add_service(health_service)
.add_service(InventoryServiceServer::new(inventory))
.serve("0.0.0.0:9091".parse()?)
.await?;

Ok(())
}

七、Kubernetes 部署:协议命名决定 Istio 是否看得懂 gRPC

7.1 Namespace 与 ServiceAccount

deploy/base/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: shop
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: web-api
namespace: shop
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: order-service
namespace: shop
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: inventory-service
namespace: shop

每个工作负载使用独立 ServiceAccount。所有服务共用 default ServiceAccount,会让 mTLS 有加密却没有有意义的身份边界。

7.2 Service:显式声明协议

deploy/base/services.yaml
apiVersion: v1
kind: Service
metadata:
name: web-api
namespace: shop
spec:
selector:
app: web-api
ports:
- name: http
appProtocol: http
port: 8080
targetPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: order-service
namespace: shop
spec:
selector:
app: order-service
ports:
- name: grpc
appProtocol: grpc
port: 9090
targetPort: 9090
---
apiVersion: v1
kind: Service
metadata:
name: inventory-service
namespace: shop
spec:
selector:
app: inventory-service
ports:
- name: grpc
appProtocol: grpc
port: 9091
targetPort: 9091

这是整篇文章最容易被忽略的配置。

Istio 可以代理任意 TCP,但要执行 gRPC 的方法路由、重试、授权和请求级负载均衡,必须把端口识别为 HTTP/2/gRPC。appProtocol 与端口名同时存在时,官方规则是 appProtocol 优先。

如果端口叫 tcp-service 且没有 appProtocol: grpc,Envoy 会把它当成不透明 TCP。此时一条 HTTP/2 长连接可能持续固定到一个后端,方法级策略也不会按预期工作。

7.3 为什么这里不需要 Headless Service

没有 Service Mesh 时,Java gRPC 客户端解析普通 Service 只能看到 ClusterIP,长连接负载可能固定。Sidecar 模式下,应用到 ClusterIP 的连接会被本地 Envoy 截获;Istiod 把真实 Endpoint 下发给 Envoy,Envoy 在 L7 为每个 gRPC Stream 选择后端。

前提仍然是:

  • 源与目标 Pod 都正确注入 Sidecar;
  • Service 端口被识别为 gRPC;
  • 流量没有通过注解或 iptables 排除代理;
  • 客户端没有使用 Istio 无法识别的自定义隧道。

7.4 gRPC 的真实控制链路与数据链路

“gRPC 由谁做服务发现”经常被混成一个问题。实际上这里同时存在两类 gRPC 长连接:

  1. 业务 gRPC:Java、Go、Rust 之间的订单和库存调用;
  2. xDS gRPC:Envoy 与 Istiod 之间的控制连接,用来同步 Listener、Route、Cluster 和 Endpoint。

Istiod 不转发业务请求,也不会进入 Java 到 Go 的数据链路。它的工作是监听 Kubernetes 资源,把控制配置编译后推送给数据平面。

7.4.1 控制链路:Kubernetes → Istiod → Envoy

Istiod 下发的核心 xDS 资源,以及证书链路中与本地 Agent 配合的资源包括:

xDS 资源解决的问题gRPC 场景
LDS代理监听哪些端口、使用哪些 Filter识别 9090 是 HTTP/2/gRPC
RDS请求匹配什么路由/shop.v1.OrderService/CreateOrder 匹配策略
CDS有哪些逻辑上游 Cluster创建 order-serviceinventory-service 上游
EDS每个 Cluster 有哪些真实 Endpoint下发 Ready Pod IP 与端口
SDSEnvoy 使用什么证书和密钥istio-agent 向 Istiod CA 申请并轮换证书,再通过本地 SDS 提供给 Envoy

当一个新的 order-service Pod Ready 后,真实变化过程是:

Pod Ready
→ Kubernetes 更新 EndpointSlice
→ Istiod 监听到 Endpoint 变化
→ Istiod 重新计算 EDS
→ 通过 xDS 长连接推送给相关 Envoy
→ Envoy 更新本地上游 Endpoint 池

Istiod 暂时不可用时,已有 Envoy 仍可使用最后一次成功下发的配置继续代理流量;但新 Pod、Endpoint 变化、证书轮换和策略更新会受到影响。

7.4.2 数据链路:Java → Envoy → Go

Java 调用 order-service.shop.svc.cluster.local:9090 时,真实路径如下:

展开到地址层面就是:

Java gRPC Client
→ DNS 得到 order-service 的 ClusterIP,例如 10.96.20.15
→ 尝试连接 10.96.20.15:9090
→ Pod 网络中的 iptables / Istio CNI 把连接重定向到本地 Envoy
→ Envoy 根据 EDS 选择 10.244.1.21:9090、10.244.2.18:9090 等 Pod IP
→ 客户端 Envoy 与服务端 Envoy 建立 Istio mTLS
→ 服务端 Envoy 解密并转发到 Go 容器的 9090

在典型 Sidecar iptables 路径中,流量会在 kube-proxy 对 ClusterIP 完成 Service NAT 前进入 Envoy;Envoy 根据 EDS 直接连接目标 Pod IP。因此,Mesh 内被代理的 gRPC 请求通常不是由 kube-proxy 选择最终后端。不同 CNI 或 eBPF 实现的截获位置可能不同,但最终的 Mesh Endpoint 选择仍由数据平面完成。

7.4.3 DNS、Istiod、Envoy 和 Java 各负责什么

组件负责内容不负责什么
Kubernetes API / EndpointSlice保存 Service、Pod 与 Ready Endpoint 的关系不执行 gRPC 负载均衡
CoreDNS把 Service 域名解析为 ClusterIP,或为 Headless Service 返回 Pod IP不执行 Istio 路由、mTLS 和熔断
Istiod监听服务注册表,计算 CDS/EDS/LDS/RDS,提供 CA 能力并推送控制配置不处理业务请求数据包
客户端 Envoy根据 Endpoint、路由和策略选择后端,执行 mTLS、重试、超时和熔断不理解订单、租户和库存业务语义
Java gRPC解析逻辑目标、复用 Channel、传播 Deadline、处理业务错误使用普通 ClusterIP 时不需要发现所有 Pod
kube-proxy为未进入 Mesh 或绕过 Sidecar 的 ClusterIP 流量执行 Service 转发不执行 gRPC 方法级治理

所以在 Sidecar Mesh 中,更准确的说法是:

DNS:找到逻辑服务入口
EDS:把真实 Pod 列表分发给代理
Envoy:为新 RPC / Stream 选择具体 Pod
Java:维护业务 Channel、Deadline、幂等和错误处理

DNS 查询是应用建立连接前的动作。即使 Java 解析到了某个 IP,Envoy 也可能忽略这个 IP,改用来自 EDS 的 Endpoint 或代理自己维护的 DNS 结果。

Sidecar 模式默认仍由 Pod 的 /etc/resolv.conf 指向 CoreDNS。若显式启用 ISTIO_META_DNS_CAPTURE,应用发出的 DNS 查询会先进入本地 Istio DNS Proxy:代理能从本地服务注册表回答时直接返回,否则转发给上游 DNS。这个功能改变的是“谁回答应用 DNS 查询”,不会把 Kubernetes Service 的 Endpoint 选择重新交给 Java。

外部服务是另一条路径。ServiceEntry resolution: DNS 会让 Envoy 自己周期解析目标域名并维护地址集合,这与 Java/JDK 的 DNS 缓存相互独立,也不同于 Kubernetes Service 使用的 EDS。当前 Istio 文档中该代理解析周期固定为 30 秒;不要把原生 Envoy dnsRefreshRate 字段直接照搬成 Istio ServiceEntry 配置。

7.4.4 ClusterIP、Headless 与 Mesh 的差异

场景DNS 返回最终选择 Pod 的组件对 gRPC 长连接的影响
无 Mesh + ClusterIP一个虚拟 IPkube-proxy,通常按 TCP 连接一条 HTTP/2 长连接可能长期固定到一个 Pod
无 Mesh + Headless多个 Pod IPgrpc-java DNS Resolver + round_robinJava 可以建立多个子连接并分发 RPC
Sidecar Mesh + ClusterIP一个虚拟 IPEnvoy,Endpoint 来自 Istiod EDS新 RPC/Stream 可在多个 Pod 间分配
Sidecar Mesh + Headless多个 Pod IPJava 先选 IP,Envoy 仍可能处理流量容易形成客户端与代理双层选择,配置规模也更大
外部 ServiceEntry resolution: DNS由外部 DNS 决定Envoy 周期解析并维护结果与应用自身 DNS 缓存相互独立

Headless Service 不是错误方案,但它更适合:

  • StatefulSet 需要稳定实例身份;
  • 客户端必须连接指定分片或节点;
  • 没有 Sidecar,需要 grpc-java 自己发现多个 Pod;
  • 一致性哈希或实例亲和由客户端协议负责;
  • 确实需要暴露独立 Pod DNS 名称。

如果只是希望普通 Java gRPC 调用均匀落到多个 Deployment Pod,优先使用普通 ClusterIP Service,让 Envoy 根据 EDS 做实例级选择。

不要配置三层负载均衡

不要同时让 Spring Cloud LoadBalancer、grpc-java round_robin 和 Envoy 都做实例级负载均衡。迁移阶段可以暂时兼容,稳定后应确定唯一的 Endpoint 选择层。Sidecar 模式通常让 Java 只连接逻辑 Service,由 Envoy 负责真实 Pod 选择。

7.4.5 一个 Java Channel 为什么仍能分发到多个 Pod

gRPC 把一次调用映射为一个 HTTP/2 Stream。客户端可以只维护一个指向本地 Envoy 的长连接,而 Envoy 对每个新 Stream 执行路由和上游 Host 选择:

一个 Java ManagedChannel
├─ CreateOrder RPC ──> Go Pod A
├─ GetOrder RPC ──> Go Pod B
├─ CreateOrder RPC ──> Go Pod C
└─ GetOrder RPC ──> Go Pod A

但已经建立的客户端流、服务端流或双向流不会在执行过程中迁移:

已经开始的双向 Streaming RPC
└─ 在整个 Stream 生命周期内固定到同一个后端 Pod

当 Endpoint 被移除或异常摘除时:

  • 新 RPC 不再分配给该 Endpoint;
  • Envoy 开始排空对应连接池;
  • 已存在的长 Stream 通常继续到完成、超时或连接断开;
  • 应用仍需配置 Stream 最大生命周期、心跳、断线重连和幂等恢复;
  • Pod 下线必须配合 readiness、terminationGracePeriodSeconds 和 gRPC Graceful Shutdown。

Outlier Detection 的异常实例判断发生在每个 Envoy 数据平面本地,Istiod 只负责下发策略,不会实时参与每次熔断决定。

7.4.6 Java 客户端的推荐配置

普通 Service 即可:

GrpcClientConfig.java
ManagedChannel channel = ManagedChannelBuilder
.forAddress("order-service.shop.svc.cluster.local", 9090)
.usePlaintext()
.build();

这里的 usePlaintext() 只描述 Java 容器到本地 Sidecar 的应用连接。跨 Pod 的实际传输仍由 Envoy 使用 Istio mTLS:

Java -- plaintext --> Client Envoy
Client Envoy -- gRPC + mTLS --> Server Envoy
Server Envoy -- plaintext --> Go

Java 仍然负责业务 Deadline:

OrderServiceGrpc.OrderServiceBlockingStub stub = baseStub
.withDeadlineAfter(800, TimeUnit.MILLISECONDS);

Envoy 的超时应与应用 Deadline 分层设计,不能让 Java、grpc-java 和 Istio 各自执行无上限重试。

7.4.7 如何验证真实分发层

先看 Java 看到的 DNS:

JAVA_POD=$(kubectl -n shop get pod -l app=web-api \
-o jsonpath='{.items[0].metadata.name}')

kubectl -n shop exec "$JAVA_POD" -c web-api -- \
nslookup order-service.shop.svc.cluster.local

普通 ClusterIP Service 应只返回一个虚拟 IP。再看 Kubernetes 控制面的 Ready Endpoint:

kubectl -n shop get endpointslice \
-l kubernetes.io/service-name=order-service \
-o wide

最后看 Java Pod 中 Envoy 实际收到的 Endpoint:

istioctl proxy-config endpoints "$JAVA_POD" -n shop --port 9090

istioctl proxy-config clusters "$JAVA_POD" -n shop \
--fqdn order-service.shop.svc.cluster.local \
--port 9090

istioctl proxy-status

如果出现下面的结果:

nslookup:只有一个 ClusterIP
EndpointSlice:包含多个 Ready Pod IP
proxy-config endpoints:包含相同的多个 Pod IP

就说明逻辑名称由 CoreDNS 解析,而真实 Pod 发现和分发由 Istiod EDS + Envoy 数据平面完成。

如果 EndpointSlice 正常但 proxy-config endpoints 缺少实例,应检查 Istiod 同步、Sidecar 配置范围和 exportTo;如果两者都缺实例,应先检查 Service selector、Pod readiness 和 EndpointSlice,而不是先修改 Java DNS 策略。

7.5 Deployment 核心配置

deploy/base/inventory-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: inventory-service
namespace: shop
spec:
replicas: 3
selector:
matchLabels:
app: inventory-service
version: v1
template:
metadata:
labels:
app: inventory-service
version: v1
service.istio.io/canonical-name: inventory-service
service.istio.io/canonical-revision: v1
annotations:
proxy.istio.io/config: '{ "holdApplicationUntilProxyStarts": true }'
spec:
serviceAccountName: inventory-service
terminationGracePeriodSeconds: 40
containers:
- name: inventory-service
image: example/inventory-service:1.0.0
ports:
- name: grpc
containerPort: 9091
readinessProbe:
grpc:
port: 9091
service: shop.v1.InventoryService
periodSeconds: 5
failureThreshold: 2
livenessProbe:
grpc:
port: 9091
periodSeconds: 10
failureThreshold: 3
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: "1"
memory: 512Mi

Java 与 Go Deployment 使用各自 ServiceAccount、端口和探针即可。不要忘记为 istio-proxy 设置经过压测的资源,Sidecar 不是零成本。


八、安装 Istio 1.31 与注入 Sidecar

8.1 安装前检查

curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.31.0 sh -
cd istio-1.31.0
export PATH="$PWD/bin:$PATH"

istioctl x precheck
istioctl version

不要在生产直接使用 demo profile。官方把 default 作为生产起点,把 demo 定位为功能评估。

8.2 使用 Revision 安装

istioctl install \
--set profile=default \
--set revision=1-31-0 \
-y

istioctl tag set prod --revision 1-31-0

kubectl label namespace shop istio.io/rev=prod --overwrite
kubectl rollout restart deployment -n shop

Revision Tag 让 Namespace 绑定稳定标签 prod,升级时只需移动 Tag,不需要把版本号写进每个 Namespace。

检查注入结果:

kubectl -n shop get pods
kubectl -n shop get pod <pod-name> -o jsonpath='{.spec.containers[*].name}'
istioctl x check-inject -n shop deployment/inventory-service
istioctl proxy-status

Pod 应包含业务容器和 istio-proxy。如果仍是 1/1,不要继续验证 mTLS,因为它根本没有进入 Sidecar Mesh。

生产网关独立管理

为方便实战,本文使用 default profile 自带入口网关。官方生产建议把 Gateway 与控制平面解耦,可用 minimal profile 安装控制面,再通过 Helm 或独立 Deployment 管理网关,使其扩缩容、升级和故障域独立。


九、外部 REST 与原生 gRPC:统一 Ingress 方案

9.1 TLS Secret

证书应由 cert-manager 或外部密钥系统签发。示例把 REST 与原生 gRPC 放在不同域名,Secret 创建在入口网关所在的 istio-system Namespace:

kubectl -n istio-system create secret tls shop-api-tls \
--cert=api.example.test.crt \
--key=api.example.test.key

kubectl -n istio-system create secret tls shop-grpc-tls \
--cert=grpc.example.test.crt \
--key=grpc.example.test.key

不要把私钥写进 Git。

9.2 Gateway:同时监听 REST 与原生 gRPC

deploy/istio/gateway.yaml
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: shop-api
namespace: shop
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 443
name: https
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: shop-api-tls
hosts:
- api.example.test
- port:
number: 443
name: grpc-https
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: shop-grpc-tls
hosts:
- grpc.example.test

两个 Server 都在 443 监听,通过 TLS SNI 区分证书和 Host。gRPC 客户端必须通过 ALPN 协商 h2;Gateway 负责 TLS 终止,但后端是否使用 HTTP/2 仍取决于 Kubernetes Service 是否显式声明 appProtocol: grpc 或端口名 grpc-*

Gateway 只定义监听、TLS 和 Host,不定义后端业务路由。

9.3 REST VirtualService

deploy/istio/web-route.yaml
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: web-api
namespace: shop
spec:
hosts:
- api.example.test
gateways:
- shop-api
http:
- match:
- uri:
prefix: /api/
timeout: 5s
route:
- destination:
host: web-api.shop.svc.cluster.local
port:
number: 8080

没有匹配的 /admin、Actuator 或内部 gRPC 路径不会被公开。

9.4 原生 gRPC Ingress 的真实链路

原生 gRPC 客户端只需要知道一个稳定入口:

grpc.example.test:443

业务服务不维护公网 DNS,不解析后端 Pod,也不订阅 Endpoint。完整链路分成两个独立的发现层:

这里有两个完全不同的 DNS/发现边界:

发现对象管理者返回结果业务服务是否参与
grpc.example.test公网 DNS、云平台或 ExternalDNSLoadBalancer IP/Hostname不参与
order-service.shop.svc.cluster.localKubernetes Service Registry逻辑 ClusterIP服务只声明 Service,不维护解析器
order-service 的多个 PodIstiod EDSReady Pod IP 列表不参与
每次新 gRPC RPC 的后端选择Ingress Gateway Envoy一个健康 Endpoint不参与

公网 DNS 只负责把客户端送到 Gateway,不应该返回业务 Pod IP。Gateway Envoy 也不需要通过 Headless DNS 发现后端;Istiod 已经把标准 ClusterIP Service 的 Ready Endpoint 通过 EDS 下发给它。

如果使用 ExternalDNS,可以由平台控制器根据 Gateway 或 LoadBalancer Service 自动维护 grpc.example.test,仍然不需要 Java、Go 或 Rust 服务调用 DNS Provider API。

9.5 gRPC VirtualService:按方法路由

gRPC 方法在 HTTP/2 中表现为 POST /package.Service/Method,因此可以用 VirtualService 的 HTTP 路由匹配方法。下面只对幂等查询 GetOrder 配置一次有界重试,创建订单不由 Mesh 自动重试:

deploy/istio/grpc-route.yaml
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: order-grpc-ingress
namespace: shop
spec:
hosts:
- grpc.example.test
gateways:
- shop-api
http:
- name: get-order
match:
- uri:
exact: /shop.v1.OrderService/GetOrder
timeout: 3s
retries:
attempts: 2
perTryTimeout: 1s
retryOn: cancelled,deadline-exceeded,internal,resource-exhausted,unavailable
route:
- destination:
host: order-service.shop.svc.cluster.local
port:
number: 9090
- name: create-order
match:
- uri:
exact: /shop.v1.OrderService/CreateOrder
timeout: 5s
route:
- destination:
host: order-service.shop.svc.cluster.local
port:
number: 9090

路由目标仍然是普通 Kubernetes Service,而不是 Pod IP 或 Headless Service。Istio 根据 Service Registry 创建 Envoy Cluster,并把 Endpoint 池下发给 Gateway。

如果服务包含长时间运行的 Streaming RPC,应为 Streaming 方法建立单独 Route,设置合理的长超时或显式关闭 Mesh Route Timeout,同时依靠应用 Deadline、心跳和最大 Stream 生命周期兜底。不要把 Unary RPC 的 3s 超时直接套在双向流上。

Gateway 后端协议必须显式声明

入口连接通过 ALPN 使用 HTTP/2,不代表 Gateway 到后端也会自动使用 HTTP/2。后端 Service 必须使用 appProtocol: grpcappProtocol: http2grpc-*/http2-* 端口名,否则 Gateway 可能按 HTTP/1.1 转发,最终出现 UNAVAILABLE、协议重置或 503。

9.6 外部 Java gRPC 客户端

外部 Java 客户端只连接统一入口:

ExternalOrderClient.java
ManagedChannel channel = ManagedChannelBuilder
.forAddress("grpc.example.test", 443)
.useTransportSecurity()
.build();

它的 DNS Resolver 最多只会看到 Gateway 的公网地址,不会看到 order-service Pod:

外部 Java:解析 grpc.example.test → Gateway
Ingress Envoy:EDS 发现 order-service Pod → 选择 Endpoint
Go 服务:只监听 9090 → 不管理 DNS 或实例列表

客户端仍应复用长生命周期 Channel,并负责 Deadline、认证 Metadata、幂等键和流式重连:

Metadata headers = new Metadata();
Metadata.Key<String> authorization = Metadata.Key.of(
"authorization",
Metadata.ASCII_STRING_MARSHALLER
);
headers.put(authorization, "Bearer " + accessToken);

OrderServiceGrpc.OrderServiceBlockingStub stub =
MetadataUtils.attachHeaders(baseStub, headers)
.withDeadlineAfter(800, TimeUnit.MILLISECONDS);

JWT 通过 gRPC Metadata 的 authorization 传输,本质上仍是 HTTP Authorization Header。可以在 Gateway 执行 RequestAuthenticationAuthorizationPolicy,按 Host 与 /shop.v1.OrderService/* 限制公开方法;业务服务继续校验租户、资源归属和幂等语义。

共享 Gateway 上一旦存在 ALLOW 类型的 AuthorizationPolicy,未命中任何 ALLOW 规则的其他 Host 也可能被拒绝。生产环境应维护完整允许集合,或为不同信任域部署独立 Gateway,避免一个团队的策略影响其他入口。

9.7 三种 TLS 模式怎么选

模式公网段Gateway 到服务能否做 gRPC L7 治理适用场景
Gateway TLS 终止,推荐客户端 TLSIstio mTLS可以:方法路由、JWT、灰度、指标大多数 API 和微服务入口
Gateway 双向 TLS客户端 mTLSIstio mTLS可以B2B、设备、合作方证书认证
TLS Passthrough端到端 TLS密文透传到应用只能基于 SNI/TCP,无法在 Gateway 解析 gRPC 方法应用必须持有服务证书或法规要求端到端加密

推荐链路为:

External Client
-- TLS / HTTP2 --> Ingress Gateway
-- Istio mTLS --> Backend Sidecar
-- plaintext gRPC --> Application

这样公网证书只由 Gateway 管理,内部工作负载证书由 Istio 自动轮换,业务容器不保存私钥,同时保留完整的 gRPC L7 路由和可观测性。

TLS Passthrough 虽然能让应用端到端持有 TLS,但 Gateway 看不到 HTTP/2 Path、JWT Header 和 gRPC Status,方法级授权、重试、灰度和指标能力都会明显下降。

9.8 集中式 gRPC Ingress 的优缺点

优点生产价值
服务不管理 DNS 和 Pod 列表扩缩容、Pod 重建、跨节点变化对应用透明
客户端只使用稳定域名和 443SDK 配置简单,防火墙和证书策略统一
统一 TLS、JWT、mTLS 与访问控制私钥、身份和公开方法集中治理
Gateway 使用 EDS 直接感知 Ready Endpoint不受 Java DNS TTL 和长时间缓存影响
支持方法级路由、金丝雀和故障注入无需把发布策略写进每种语言客户端
指标、Access Log 和 Trace 入口统一更容易按 Host、Method、gRPC Status 定位问题
缺点处理方式
多一跳代理,增加延迟和资源成本压测 Gateway CPU、内存、连接数和 P99
Gateway 可能成为容量瓶颈或故障域独立 HPA、PDB、跨节点/可用区副本和容量余量
HTTP/2 长连接可能造成 Gateway 副本负载不均关注连接数而非只看 RPS,设置客户端连接轮换策略
Streaming RPC 会长期固定 Gateway 与后端配置心跳、最大 Stream 生命周期、优雅排空和重连
云 LoadBalancer 可能提前关闭空闲 HTTP/2 连接对齐 LB Idle Timeout、gRPC Keepalive 与 Envoy Timeout
重试策略集中后可能放大写请求只重试幂等方法,并限制尝试次数与总预算
共享 Gateway 配置错误影响多个服务按团队/信任域拆分 Gateway,变更前运行 istioctl analyze
TLS 终止后 Gateway 可读取 L7 Metadata对高敏场景使用 mTLS、专用 Gateway 或 Passthrough

原生 gRPC Ingress 适合移动端、桌面端、后端系统和受控 SDK。浏览器不能直接使用完整的原生 gRPC HTTP/2 语义,通常需要 gRPC-Web、Connect 或继续通过本文的 REST API 入口。

9.9 生产部署与验证

Ingress Gateway 应与 Istiod 解耦,至少配置:

  • 两个或更多副本;
  • HPA、PDB、Pod Anti-Affinity 或 Topology Spread;
  • 独立 ServiceAccount 和最小权限;
  • CPU、内存、下游连接数、活跃 Stream 数和连接建立速率告警;
  • preStop、readiness 和足够的 terminationGracePeriodSeconds
  • 与云 LoadBalancer 对齐的 Keepalive、Idle Timeout 和连接排空时间;
  • 只暴露允许公开的 gRPC Service/Method,不暴露 Reflection、管理接口和内部 Health 细节。

先确认公网 DNS 只指向 Gateway:

dig +short grpc.example.test

kubectl -n istio-system get service istio-ingressgateway \
-o wide

验证 TLS 与 HTTP/2 ALPN:

openssl s_client \
-connect grpc.example.test:443 \
-servername grpc.example.test \
-alpn h2 </dev/null

输出应包含协商结果 ALPN protocol: h2。使用本地 Proto 调用真实方法:

grpcurl \
-cacert ca.crt \
-H "authorization: Bearer $ACCESS_TOKEN" \
-import-path proto \
-proto shop.proto \
-d '{"orderId":"order-001"}' \
grpc.example.test:443 \
shop.v1.OrderService/GetOrder

最后确认 Gateway 收到了多个后端 Endpoint,而不是依赖 Headless DNS:

GATEWAY_POD=$(kubectl -n istio-system get pod \
-l istio=ingressgateway \
-o jsonpath='{.items[0].metadata.name}')

istioctl proxy-config clusters "$GATEWAY_POD" -n istio-system \
--fqdn order-service.shop.svc.cluster.local \
--port 9090

istioctl proxy-config endpoints "$GATEWAY_POD" -n istio-system \
--port 9090

验收标准是:

公网 DNS:只返回 Gateway / LoadBalancer 地址
Gateway Cluster:存在 order-service.shop.svc.cluster.local:9090
Gateway Endpoint:包含多个 Ready Pod IP
业务 Pod:不配置公网 DNS Provider,也不维护 Endpoint 列表

9.10 阿里云 MSE Ingress:通过注解暴露 gRPC

如果入口已经统一使用阿里云 MSE 云原生网关,可以用标准 Kubernetes Ingress 暴露原生 gRPC,不需要再让业务服务维护公网 DNS 或 Pod 地址。

MSE 中要分清两个组件:

MSE Ingress Controller:控制面,监听 Ingress / Service 并生成网关配置
MSE 云原生网关:数据面,真正处理 TLS、HTTP/2、gRPC 路由和负载均衡

MSE Ingress Controller 本身不转发业务流量。使用前应先把 ACK 集群添加为云原生网关的服务来源,打开“监听 K8s Ingress”,并把监听的 IngressClass 配置为 mse

9.10.1 Service 与 Ingress 配置

MSE 兼容 Nginx Ingress 的后端协议注解。显式写法是:

nginx.ingress.kubernetes.io/backend-protocol: "GRPC"

完整示例:

deploy/mse/order-grpc-ingress.yaml
apiVersion: v1
kind: Service
metadata:
name: order-service
namespace: shop
spec:
selector:
app: order-service
ports:
- name: grpc
appProtocol: grpc
protocol: TCP
port: 9090
targetPort: 9090
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: order-grpc
namespace: shop
annotations:
nginx.ingress.kubernetes.io/backend-protocol: "GRPC"
spec:
ingressClassName: mse
tls:
- hosts:
- grpc.example.test
secretName: shop-grpc-tls
rules:
- host: grpc.example.test
http:
paths:
- path: /shop.v1.OrderService
pathType: Prefix
backend:
service:
name: order-service
port:
number: 9090

注解中的 GRPC 描述的是:

MSE 云原生网关 → order-service:9090

这一段必须使用 HTTP/2 gRPC。它不会自动创建公网 DNS、签发 TLS 证书或配置 JWT,也不会让 MSE 获得 Istio 工作负载证书。

MSE 官方还支持根据 Service Port Name 自动识别协议:端口名包含 grpchttp2 时,可以不写 backend-protocol: "GRPC"。本文同时声明 name: grpcappProtocol: grpc 和显式注解,目的是让 Kubernetes、Istio、MSE 以及阅读配置的人都得到一致的协议信息。

如果同一个 Ingress 同时包含 HTTP/1.1 和 gRPC 后端,不建议使用作用于整个资源的 backend-protocol: "GRPC" 注解。应拆成两个 Ingress,避免普通 HTTP Route 也被当作 gRPC 转发。

9.10.2 gRPC Path 如何匹配

MSE 可以使用标准 Ingress Path 匹配 gRPC 方法。gRPC Path 格式为:

/package.Service/Method

因此:

path: /shop.v1.OrderService
pathType: Prefix

会覆盖:

/shop.v1.OrderService/CreateOrder
/shop.v1.OrderService/GetOrder

如果只允许公开单个方法,应使用 Exact

path: /shop.v1.OrderService/GetOrder
pathType: Exact

管理接口、Reflection、内部 Health 和未声明的方法不应使用根路径 / 整体暴露。

9.10.3 MSE 模式下谁负责 DNS 与实例发现

各层职责如下:

对象管理者业务服务是否参与
grpc.example.test 公网记录DNS 平台、ExternalDNS 或运维系统不参与
Ingress Host、Path 和后端协议Git 中的 Ingress YAML不参与
ACK Service 与 EndpointKubernetes + MSE 服务来源服务只声明 Service/Pod
网关到实例的负载均衡MSE 云原生网关不维护实例列表
gRPC Deadline、幂等和业务鉴权Java/Go/Rust 应用应用负责业务语义

MSE 可以回写 Ingress Status 中的 CLB 地址,再由 ExternalDNS 或发布平台维护域名。不要让订单服务直接调用云 DNS API,也不要把 Pod IP 写进应用配置。

MSE 控制台手工创建的域名和路由可能比 Ingress 配置具有更高优先级。生产环境应确定唯一配置源,避免 Git 中的 Ingress 与控制台路由发生漂移。

9.10.4 与 Istio STRICT mTLS 的组合

MSE 云原生网关默认不是 Istio Mesh 中具有 SPIFFE 身份的工作负载。如果它直接连接启用了 STRICT 的 Sidecar 服务:

MSE Gateway -- 普通 gRPC --> Server Envoy -- 要求 Istio mTLS --> 拒绝

不能因为 Ingress 上写了 backend-protocol: "GRPC",就认为后端已经具备 Istio mTLS。生产上有三种架构选择:

方案链路优点缺点
MSE 直接连接业务服务MSE → Service/Sidecar → App跳数少、配置简单入口端口需要允许非 Istio mTLS,缺少来源 ServiceAccount 身份
MSE 串联 Istio IngressMSE → Istio Gateway → Mesh Service保留 STRICT mTLS、AuthorizationPolicy 和统一 Mesh 身份两层网关、额外延迟与排障复杂度
MSE 连接专用桥接服务MSE → DMZ Bridge → Mesh Service隔离外部信任边界,桥接服务以明确 ServiceAccount 进入 Mesh多一个需要维护和扩容的组件

如果选择 MSE 直接连接,可以只对明确的工作负载端口设置 PERMISSIVE,而不是关闭整个 Namespace 的 mTLS:

deploy/security/order-mse-peer-authentication.yaml
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: order-mse-ingress
namespace: shop
spec:
selector:
matchLabels:
app: order-service
portLevelMtls:
9090:
mode: PERMISSIVE

portLevelMtls 使用的是工作负载容器端口,不是 Service 的映射端口。该方案允许明文与 Istio mTLS 同时进入 9090,会削弱入口的零信任边界,必须同时具备:

  • VPC、安全组和 NetworkPolicy 只允许 MSE 网关网段访问;
  • Gateway 或应用层 JWT/OIDC 校验;
  • 精确公开的 gRPC Method;
  • 限流、连接数、消息大小和超时控制;
  • 日志中区分外部 MSE 流量与内部 Mesh 流量。

因为 MSE 直连没有 Istio Source Principal,下面这种只允许 Istio ServiceAccount 的策略不会匹配 MSE:

source:
serviceAccounts:
- istio-system/istio-ingressgateway-service-account

如果安全基线要求所有业务服务保持 STRICT,优先使用“MSE → Istio Ingress”或专用桥接服务,不要为了方便把整个 Namespace 改为 PERMISSIVE

9.10.5 MSE Ingress 的优缺点

优点说明
兼容标准 Ingress 和常用 Nginx 注解现有 Ingress 配置迁移成本较低
托管数据面网关实例、弹性和高可用由云产品承载
ACK 与注册中心服务来源业务应用不需要维护 DNS 或实例列表
内置路由、限流、认证和可观测能力减少自建网关组件数量
可按 gRPC Path 配置路由支持 Service/Method 级公开边界
缺点处理方式
依赖阿里云 MSE 能力和注解语义把平台差异封装在独立 Ingress 模板中
与 Istio STRICT mTLS 不天然互通使用 Istio Ingress/Bridge,或仅对专用端口做受控例外
控制台配置可能覆盖 Ingress坚持 GitOps 单一配置源并定期审计漂移
托管网关不在 Pod 内,无法直接使用 istioctl proxy-config使用 MSE 路由、监控、日志和链路诊断能力
长连接和 Streaming 仍可能产生实例负载不均监控连接数、活跃 Stream、Keepalive 和优雅下线
注解作用域较粗HTTP 与 gRPC 使用独立 Ingress 资源

9.10.6 验证 MSE gRPC Ingress

确认 MSE Controller 已接管资源并写入网关地址:

kubectl -n shop get ingress order-grpc -o wide
kubectl -n shop describe ingress order-grpc

确认后端端口和 Endpoint:

kubectl -n shop get service order-service -o yaml

kubectl -n shop get endpointslice \
-l kubernetes.io/service-name=order-service \
-o wide

确认公网 DNS、TLS 和 HTTP/2:

dig +short grpc.example.test

openssl s_client \
-connect grpc.example.test:443 \
-servername grpc.example.test \
-alpn h2 </dev/null

最后使用本地 Proto 发起真实调用:

grpcurl \
-cacert ca.crt \
-H "authorization: Bearer $ACCESS_TOKEN" \
-import-path proto \
-proto shop.proto \
-d '{"orderId":"order-001"}' \
grpc.example.test:443 \
shop.v1.OrderService/GetOrder

同时在 MSE 控制台检查路由命中、后端实例、gRPC Status、网关访问日志和延迟指标。如果出现 HTTP 200 但客户端报告 gRPC 协议错误,应优先检查 backend-protocol: "GRPC"、Service Port Name、ALPN h2 和后端是否真的监听 HTTP/2。

Istio 也支持 Kubernetes Gateway API,并计划逐步把它作为默认流量 API。已有平台若统一使用 Gateway API,可以使用 GatewayClass: istio 和对应的协议 Route 资源替代上述 Istio Gateway/VirtualService;不要在同一个入口同时维护多套重复规则。


十、零信任:mTLS、JWT 与方法级授权

10.1 强制 Namespace 内 mTLS

deploy/security/peer-authentication.yaml
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: default
namespace: shop
spec:
mtls:
mode: STRICT

STRICT 表示目标工作负载只接受 Istio mTLS 流量。Sidecar 的 Auto mTLS 会自动为 Mesh 内目标启用客户端 mTLS,通常不需要再给每个服务写 tls.mode: ISTIO_MUTUAL

PeerAuthentication 解决“连接是否拥有可信工作负载身份”,不决定“这个身份可以调用什么”。

10.2 验证外部 JWT

deploy/security/request-authentication.yaml
apiVersion: security.istio.io/v1
kind: RequestAuthentication
metadata:
name: web-api-jwt
namespace: shop
spec:
selector:
matchLabels:
app: web-api
jwtRules:
- issuer: https://id.example.test/
jwksUri: https://id.example.test/.well-known/jwks.json
forwardOriginalToken: true

RequestAuthentication 会验证携带的 JWT,但默认不会拒绝“完全没带 Token”的请求。要强制登录,必须再配 AuthorizationPolicy。

10.3 默认拒绝

deploy/security/default-deny.yaml
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: default-deny
namespace: shop
spec: {}

空规则意味着 Namespace 中没有显式 ALLOW 的流量全部拒绝。建议先在测试环境或 dry-run 中观察,确认监控、探针和平台组件所需访问已列入允许规则,再逐步启用。

10.4 允许入口网关调用 Java REST

先查询实际入口网关 ServiceAccount:

kubectl -n istio-system get deploy istio-ingressgateway \
-o jsonpath='{.spec.template.spec.serviceAccountName}'
deploy/security/allow-web-api.yaml
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: allow-web-api
namespace: shop
spec:
selector:
matchLabels:
app: web-api
action: ALLOW
rules:
- from:
- source:
serviceAccounts:
- istio-system/istio-ingressgateway-service-account
requestPrincipals:
- "https://id.example.test/*"
to:
- operation:
methods: ["POST"]
paths: ["/api/orders"]
when:
- key: request.auth.claims[scope]
values: ["order:create"]

网关 ServiceAccount 名称会随安装方式变化,不能盲抄示例。

10.5 Java 只允许调用 Go 的指定 gRPC 方法

deploy/security/allow-order-service.yaml
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: allow-order-service
namespace: shop
spec:
selector:
matchLabels:
app: order-service
action: ALLOW
rules:
- from:
- source:
serviceAccounts: ["shop/web-api"]
to:
- operation:
methods: ["POST"]
paths:
- /shop.v1.OrderService/CreateOrder
- /shop.v1.OrderService/GetOrder

gRPC 在 HTTP/2 上的 Method 总是 POST,Path 是 /package.service/method。这也是 Service 端口必须被识别为 gRPC/HTTP2 的原因。

10.6 Go 只允许调用 Rust Reserve

deploy/security/allow-inventory-service.yaml
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: allow-inventory-service
namespace: shop
spec:
selector:
matchLabels:
app: inventory-service
action: ALLOW
rules:
- from:
- source:
serviceAccounts: ["shop/order-service"]
to:
- operation:
methods: ["POST"]
paths: ["/shop.v1.InventoryService/Reserve"]

10.7 Istio 授权不能替代业务授权

Istio 能判断:

  • JWT 是否由受信 Issuer 签发;
  • 请求是否来自入口网关;
  • 调用方是否是 shop/web-api
  • 是否正在调用某个 REST Path 或 gRPC Method。

它不适合判断:

  • 用户是否属于订单对应租户;
  • 用户是否拥有指定订单;
  • SKU 是否允许在当前区域销售;
  • 当前余额是否允许扣款;
  • 数据库某行是否可见。

这些仍应在应用和数据库层实现。需要统一外部策略时,可以评估 Envoy ext_authz 对接 OPA,但要明确延迟、可用性和失败模式。


十一、从网络到数据:数据库边界怎么治理

11.1 PostgreSQL 不会因为进 Mesh 就获得 SQL 权限

Istio 可以代理 PostgreSQL TCP 连接,但默认不理解 SQL 语义。它无法替代:

  • PostgreSQL Role 与 Grant;
  • Row Level Security;
  • Schema Migration 权限隔离;
  • SQL 审计;
  • 连接池容量和慢查询治理;
  • 数据库原生 TLS。

推荐边界:

Kubernetes NetworkPolicy
→ 只允许 order/inventory Pod 访问 5432
Database TLS
→ 应用验证数据库证书
Separate DB Roles
→ order_writer / inventory_writer / migration_owner
Application Transactions
→ 幂等、乐观锁、行锁、唯一约束
Audit / Backup
→ 数据库原生能力

11.2 NetworkPolicy 示例

deploy/base/postgres-network-policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: postgres-ingress
namespace: data
spec:
podSelector:
matchLabels:
app: postgres
policyTypes: ["Ingress"]
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: shop
podSelector:
matchExpressions:
- key: app
operator: In
values: ["order-service", "inventory-service"]
ports:
- protocol: TCP
port: 5432

NetworkPolicy 能限制 Pod/Namespace 网络来源,但不能直接用 ServiceAccount 匹配,因此它与 Istio 工作负载授权是互补关系。

11.3 Secret 与迁移账号

数据库凭证应来自 External Secrets、Secrets Store CSI 或云密钥服务,而不是写进 Deployment YAML。运行时账号不能拥有 ALTER TABLE;迁移使用独立 Job 与独立 Role,并在发布流水线中串行执行。

11.4 外部托管数据库

如果 PostgreSQL 在集群外:

  • 使用数据库驱动原生 TLS 并校验证书;
  • 用固定 DNS 名与连接池;
  • 通过 NetworkPolicy、云防火墙或 Egress Gateway 限制出口;
  • 不要认为 Sidecar mTLS 能延伸到未加入 Mesh 的托管数据库;
  • 不要对事务写自动配置通用 TCP 重试。

十二、外部依赖:ServiceEntry 与 Egress

假设 Go 服务需要访问外部支付 REST API:

deploy/istio/payment-service-entry.yaml
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
name: payment-api
namespace: shop
spec:
hosts:
- payments.example.net
location: MESH_EXTERNAL
resolution: DNS
ports:
- number: 443
name: https
protocol: TLS

默认 ALLOW_ANY 会让未知外部地址通过 Sidecar,但无法获得完整治理。可以先为已知依赖建立 ServiceEntry,再灰度切换 REGISTRY_ONLY

istioctl install <原安装参数> \
--set meshConfig.outboundTrafficPolicy.mode=REGISTRY_ONLY

注意:REGISTRY_ONLY 不是完整的安全防火墙。被攻陷的 Pod 可能尝试绕过 Sidecar,因此还需要 NetworkPolicy/云防火墙把业务 Pod 的出口限制到 Egress Gateway 或已知地址。

外部 HTTPS 的最终证书校验仍由 TLS 发起方负责。若希望统一 TLS Origination、固定出口 IP、审计和出口授权,应增加 Istio Egress Gateway,而不是只写 ServiceEntry。


十三、内部 gRPC 流量治理

Istio 请求治理与生产闭环:JWT 请求依次通过入口网关、Java、Go、Rust 和 PostgreSQL,每一跳标明协议、端口和职责;流量策略、可观测信号与运维动作组成持续反馈闭环

13.1 DestinationRule:端点策略与版本子集

deploy/istio/order-destination-rule.yaml
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: order-service
namespace: shop
spec:
host: order-service.shop.svc.cluster.local
trafficPolicy:
loadBalancer:
simple: LEAST_REQUEST
connectionPool:
tcp:
maxConnections: 100
connectTimeout: 500ms
http:
http2MaxRequests: 1000
maxRequestsPerConnection: 10000
outlierDetection:
splitExternalLocalOriginErrors: true
consecutiveLocalOriginFailures: 5
interval: 5s
baseEjectionTime: 30s
maxEjectionPercent: 50
subsets:
- name: stable
labels:
version: v1
- name: canary
labels:
version: v2

DestinationRule 在路由完成后执行负载均衡、连接池和异常实例摘除。Subset 本身不会接收流量,必须由 VirtualService 引用。

连接池数值不是越大越好。应从 Pod 容量、gRPC 并发 Stream、下游数据库连接数和 SLO 反推,并通过压测验证。

13.2 VirtualService:稳定版 90%,金丝雀 10%

deploy/istio/order-virtual-service.yaml
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: order-service
namespace: shop
spec:
hosts:
- order-service.shop.svc.cluster.local
http:
- name: get-order
match:
- uri:
exact: /shop.v1.OrderService/GetOrder
timeout: 2s
retries:
attempts: 2
perTryTimeout: 700ms
retryOn: connect-failure,reset,refused-stream,unavailable
route:
- destination:
host: order-service.shop.svc.cluster.local
subset: stable
weight: 90
- destination:
host: order-service.shop.svc.cluster.local
subset: canary
weight: 10

- name: create-order
match:
- uri:
exact: /shop.v1.OrderService/CreateOrder
timeout: 3s
route:
- destination:
host: order-service.shop.svc.cluster.local
subset: stable
weight: 90
- destination:
host: order-service.shop.svc.cluster.local
subset: canary
weight: 10

读取方法 GetOrder 可以在总 Deadline 内做受限重试。创建订单默认不配置代理重试,除非端到端幂等已被数据库唯一约束和业务状态机证明。

13.3 重试只有一个预算

客户端重试 3 次 × Istio 重试 3 次 × 服务代码重试 3 次
= 最坏 27 次下游 attempt

选择一个主要重试层,并监控 logical request 与 attempt 的比例。重试只处理瞬时错误,不能用于参数错误、库存不足、权限拒绝和数据库唯一冲突。

13.4 Circuit Breaking 的真实含义

Istio/Envoy 的连接池上限、Pending Request 上限和 Outlier Detection 常被统称为熔断,但它们解决的问题不同:

  • Connection Pool:限制资源占用;
  • Outlier Detection:临时摘除持续失败的实例;
  • Retry Budget:限制额外 attempt;
  • Application Bulkhead:限制业务线程、队列和下游并发。

网格策略无法替代应用的有界队列和数据库连接池。


十四、可观测性:Mesh 看见网络,应用看见业务

14.1 三类信号

信号Sidecar 能提供应用必须补充
Metrics请求数、状态、延迟、请求/响应大小、源/目标工作负载订单成功率、库存不足、DB Pool、队列长度
Trace代理入站/出站网络 SpanController、gRPC Handler、SQL、缓存、业务阶段
LogEnvoy Access Log、Response Flags、上游地址request_id、order_id、业务错误与审计事件

14.2 Telemetry API

先在 Istio 安装配置中声明 OpenTelemetry Collector:

deploy/observability/istio-operator.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
meshConfig:
extensionProviders:
- name: otel-tracing
opentelemetry:
service: otel-collector.observability.svc.cluster.local
port: 4317

再对 shop Namespace 启用 Trace 和错误 Access Log:

deploy/observability/telemetry.yaml
apiVersion: telemetry.istio.io/v1
kind: Telemetry
metadata:
name: shop-default
namespace: shop
spec:
tracing:
- providers:
- name: otel-tracing
randomSamplingPercentage: 10
accessLogging:
- providers:
- name: envoy
filter:
expression: response.code >= 400 || response.code == 0

生产不要默认 100% Trace。采样率应结合 QPS、错误保留策略和存储成本,并让上游已做出的采样决定继续传播。

14.3 Trace Context 必须由应用传播

Envoy 能产生网络 Span,但完整的 Java → Go → Rust → SQL Trace 仍依赖应用传播 Context:

  • Java:OpenTelemetry Java Agent 或 Spring Boot instrumentation;
  • Go:HTTP/gRPC instrumentation,使用 otelgrpc 拦截器;
  • Rust:tracingtracing-opentelemetry 与 Tonic/Tower middleware;
  • 跨协议:统一使用 W3C traceparent,在 gRPC Metadata 中传递。

如果 Java 收到 REST Trace,却新建 gRPC 请求时丢掉 Context,Kiali 仍能显示服务拓扑,但 APM 中会出现两条断裂 Trace。

不要把 Token、邮箱、文件名或订单 ID 直接放进 Prometheus label;高基数和敏感信息泄漏会同时发生。

14.4 Kiali 与生产监控

Istio quick-start Addon 适合实验,不是生产监控方案。官方说明 quick-start Prometheus 只有很短的保留时间。生产应接入现有 Prometheus/Thanos/Mimir、OpenTelemetry Collector、Tempo/Jaeger 和日志平台,并配置容量、保留、告警与多租户边界。


十五、日常管理与故障定位

15.1 上线前静态检查

istioctl analyze --all-namespaces
istioctl analyze --use-kube=false deploy/**/*.yaml
kubectl diff -f deploy/

istioctl analyze 是只读诊断,能发现未注入 Namespace、找不到目标 Service、冲突配置等问题,但它不能证明真实流量和数据库事务正确。

15.2 查看 xDS 同步

istioctl proxy-status

CDS、LDS、EDS、RDS 应处于 SYNCED。单个代理持续 STALE 时,先查代理与 Istiod 连接、版本和配置拒绝原因。

15.3 从 Java Pod 追踪到 Go Endpoint

JAVA_POD=$(kubectl -n shop get pod -l app=web-api \
-o jsonpath='{.items[0].metadata.name}')

istioctl proxy-config listeners "$JAVA_POD" -n shop
istioctl proxy-config routes "$JAVA_POD" -n shop
istioctl proxy-config clusters "$JAVA_POD" -n shop \
--fqdn order-service.shop.svc.cluster.local
istioctl proxy-config endpoints "$JAVA_POD" -n shop \
--cluster 'outbound|9090||order-service.shop.svc.cluster.local'

正确排障顺序:

Listener:有没有截获端口?
→ Route:有没有匹配 gRPC Path?
→ Cluster:目标服务和 subset 是否存在?
→ Endpoint:Pod IP 是否健康?
→ TLS/Auth:身份与策略是否允许?
→ Application:服务端实际返回什么?

15.4 查看授权结果

istioctl x authz check "$JAVA_POD" -n shop
kubectl -n shop logs "$JAVA_POD" -c istio-proxy --tail=200

HTTP 403 通常是授权策略;UFUHUT 等 Response Flag 分别指向连接失败、无健康上游、上游超时等不同问题。不要看到 503 就直接重启所有 Pod。

15.5 Revision 金丝雀升级

Istio 官方推荐使用 Canary Upgrade,而不是直接原地覆盖控制面:

# 新版本 istioctl
istioctl x precheck
istioctl install --set profile=default --set revision=1-32-0 -y
istioctl tag set prod-canary --revision 1-32-0

kubectl label namespace shop-canary istio.io/rev=prod-canary --overwrite
kubectl rollout restart deployment -n shop-canary
istioctl proxy-status

验证新 Revision 后,再移动生产 Tag、滚动重建数据平面,最后删除旧 Revision。控制面可以比数据平面领先一个版本,但官方仍建议通过 Revision 避免不必要的版本偏差。


十六、完整验收与故障演练

16.1 入口 TLS 和 JWT

# LoadBalancer 环境;本地集群可换成端口转发或 NodePort
export INGRESS_IP=$(kubectl -n istio-system get service istio-ingressgateway \
-o jsonpath='{.status.loadBalancer.ingress[0].ip}')

# 无 Token:应为 403
curl --resolve api.example.test:443:$INGRESS_IP \
https://api.example.test/api/orders \
-X POST \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: demo-001' \
-d '{"sku":"sku-001","quantity":1}'

# 正确 Token:应创建或返回同一幂等订单
curl --resolve api.example.test:443:$INGRESS_IP \
https://api.example.test/api/orders \
-X POST \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: demo-001' \
-d '{"sku":"sku-001","quantity":1}'

重复第二次请求,订单与库存不能重复扣减。

16.2 非授权工作负载调用 gRPC

在一个使用其他 ServiceAccount 的 Mesh Pod 中调用:

grpcurl -plaintext \
-d '{"requestId":"deny-001","sku":"sku-001","quantity":1}' \
inventory-service.shop.svc.cluster.local:9091 \
shop.v1.InventoryService/Reserve

应被 AuthorizationPolicy 拒绝。然后从 order-service 身份调用,应被允许。

16.3 验证 mTLS

istioctl x describe pod "$JAVA_POD" -n shop

istioctl proxy-config clusters "$JAVA_POD" -n shop \
--fqdn order-service.shop.svc.cluster.local \
--port 9090 -o json

describe 应报告客户端与目标工作负载都使用 mTLS;Cluster JSON 中对应上游应包含 Istio 管理的 TLS transport socket。再结合代理日志或 connection_security_policy="mutual_tls" 指标确认真实请求。只看到 PeerAuthentication YAML 不等于实际连接已使用 mTLS。

16.4 验证 90/10 金丝雀

持续发送足够多的 GetOrder,按目标版本统计:

sum by (destination_canonical_revision) (
rate(istio_requests_total{
destination_service_name="order-service"
}[5m])
)

短窗口不会精确等于 90/10。应在足够样本量下观察趋势,同时比较两版本错误率与 P99。

16.5 故障演练矩阵

演练预期现象关键证据
删除一个 Go Pod新请求转向其他 Endpoint,在途请求按优雅停机完成EDS、503、终止日志
Rust Health 变为 NOT_SERVINGEndpoint 摘除,Go 不再发新请求Endpoint 状态、上游选择
Rust 延迟 2 秒Deadline 生效,调用不会无限堆积P99、DEADLINE_EXCEEDED、active requests
Canary 连续失败Outlier Detection 临时摘除实例Kiali、Envoy cluster stats
移除 Java → Go ALLOWgRPC 返回权限拒绝AuthorizationPolicy 与代理日志
Istiod 重启存量流量继续,新配置短暂无法下发proxy-status、控制面指标
OTel Collector 不可用业务流量继续,遥测出现丢弃/积压Sidecar 与 Collector 指标
PostgreSQL 达到连接上限应用快速失败或背压,不触发无限重试DB pool、gRPC status、队列

十七、常见生产坑

后果正确处理
把 Istio 拼成 Isito 后搜索配置文档与资源难以对应产品名统一为 Istio
使用 demo profile 直接生产高采样、资源和组件布局不适合生产default 起步,网关独立评估
Namespace 打标签后不重建 Pod旧 Pod 没有 Sidecar滚动重启并检查容器数量
所有服务共用 default SAmTLS 有加密但身份无区分每个工作负载独立 ServiceAccount
gRPC Service 端口未命名被识别成 TCP,方法路由和授权失效name: grpc + appProtocol: grpc
有 Istio 又改 Headless Service增加复杂度但未解决真正问题先确认 Sidecar L7 Endpoint LB
应用 TLS 与 Istio mTLS 混用双重加密、协议误判或握手失败明确 TLS 终止点和 passthrough
只创建 RequestAuthentication无 Token 请求仍可能通过配套 AuthorizationPolicy
一上来全 Namespace default-deny探针、监控和依赖一起中断先盘点、dry-run、灰度启用
只按 IP 做授权Pod 重建后规则漂移使用 ServiceAccount 工作负载身份
把 Mesh Auth 当业务权限越权读取他人数据应用继续校验用户、租户和资源归属
给所有 gRPC 方法统一重试写操作重复执行仅幂等方法 + 总预算 + 幂等键
Java/Go/Rust 各自再重试三次重试指数放大选定一个主要重试层
熔断参数照抄大厂小集群中所有实例被摘除从副本数和容量压测推导
把 503 都归因于应用实际可能无 Endpoint、TLS 或授权失败Listener → Route → Cluster → Endpoint 排查
认为 Sidecar 自动产生完整 TraceSQL 和业务 Span 缺失、Trace 断链应用插桩并传播 Context
把订单 ID 放 Prometheus label高基数拖垮监控进入 Trace/Log,不进入常规 label
REGISTRY_ONLY 当安全防火墙Pod 仍可能绕过代理出口NetworkPolicy + Egress Gateway
让应用运行账号执行迁移凭证泄漏后可修改 Schemamigration owner 与 runtime role 分离
原地升级控制面故障回滚窗口小Revision + Canary Upgrade

十八、生产交付清单

安装与生命周期

  • Istio 与 Kubernetes 版本在官方支持矩阵内;
  • 使用 Revision/Tag 管理控制面和注入;
  • Ingress Gateway 独立 HPA、PDB、资源与故障域;
  • 业务 Pod、Sidecar 和 Gateway 都有资源 requests/limits;
  • Pod 优雅停机覆盖 readiness 摘流和 gRPC drain;
  • 升级、回滚和证书轮换经过演练。

协议与流量

  • REST、gRPC、TCP 端口显式声明协议;
  • 公网 DNS 只指向 Ingress Gateway,不向客户端暴露业务 Pod IP;
  • 原生 gRPC Ingress 已验证 TLS SNI、ALPN h2 和后端 HTTP/2;
  • 使用 MSE Ingress 时,Service Port 与 backend-protocol: "GRPC" 语义一致;
  • MSE 直连 Sidecar 时已验证 STRICT mTLS 与 AuthorizationPolicy 边界;
  • gRPC Deadline 从入口向下游传播;
  • 重试只用于可重试且幂等的方法;
  • Connection Pool、Outlier Detection 与应用容量一致;
  • Canary 同时观察错误率、延迟、资源与业务 KPI;
  • 配置变更经过 istioctl analyze

安全

  • 每个工作负载独立 ServiceAccount;
  • PeerAuthentication 从 PERMISSIVE 灰度到 STRICT;
  • RequestAuthentication 与 AuthorizationPolicy 配套;
  • Namespace 或工作负载执行默认拒绝;
  • 管理接口不暴露到公共 Gateway;
  • NetworkPolicy 防止绕过 Sidecar;
  • 应用继续执行租户和资源级授权;
  • 数据库 Role、TLS、RLS、迁移权限独立。

可观测性

  • Istio Metrics 接入生产 Prometheus;
  • Access Log 采样/过滤并做敏感信息治理;
  • Java、Go、Rust 都传播统一 Trace Context;
  • Trace 采样率与存储预算匹配;
  • 对 xDS 不同步、无健康上游、mTLS 失败、403/503 和重试放大告警;
  • 仪表盘能按服务、版本、来源、目标与 gRPC status 下钻。

十九、结语

Istio 的生产价值不是“给每个 Pod 塞一个 Envoy”,而是建立统一的通信控制面:

外部:HTTPS + REST + JWT
内部:gRPC + Workload Identity + mTLS
流量:Route + Timeout + Retry Budget + Outlier Detection
权限:ServiceAccount + AuthorizationPolicy + Application Authorization
数据:NetworkPolicy + DB TLS + Role + Transaction
观测:Mesh Telemetry + Application OpenTelemetry
运维:Revision + Analyze + Proxy Config + Failure Drill

最重要的边界是:Mesh 管网络与工作负载身份,应用管业务语义,数据库管数据权限与一致性。 把三层职责分清,Java、Go、Rust 才能共享治理能力,而不会把基础设施策略变成另一套难以调试的业务代码。


官方参考资料

Logo
RainLib

Exploring the frontiers of technology, design, and distributed systems. Building tools for the future developers.

Suggestions & Feedback

© 2026 RainLib. Built for the Future.
All rights reserved.
System Normal