Istio 1.31 生产实战:Java、Go、Rust 的 REST、gRPC、零信任与流量治理
外部使用 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 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 + Waypoint | ztunnel + Waypoint | 支持 | 支持 | 支持 | 希望减少 Sidecar,同时保留 L7 |
官方文档明确说明:Ambient 中只需要 L4 mTLS 和授权时不必部署 Waypoint;要执行 L7 策略才需要 Waypoint。本文为了演示 gRPC 方法匹配、重试和金丝雀,选择 Sidecar 模式。不要同时给同一个 Namespace 设置 istio-injection=enabled 和 istio.io/dataplane-mode=ambient。
二、实战架构与工程边界
生产方案不能只画服务调用关系,还要把每层“负责什么、不能替代什么”说明清楚。下面按接入、服务、网格、安全、数据与流量、可观测与运维六层拆解,并明确 Mesh、应用和数据库之间的职责边界。

2.1 服务职责
| 组件 | 语言 | 对外协议 | 职责 |
|---|---|---|---|
web-api | Java 21 / Spring Boot | REST :8080 | 接收外部请求、校验业务参数、调用订单 gRPC |
order-service | Go | gRPC :9090 | 编排下单、调用库存、写订单数据库 |
inventory-service | Rust / Tonic | gRPC :9091 | 幂等扣减库存、返回剩余数量 |
postgres | PostgreSQL | TCP/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 服务三种语言
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 锁定一组兼容版本。
<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>
@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
@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 模式下,应用到本地代理使用明文凭证:
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
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
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 依赖和代码生成
[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"
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 幂等库存预留
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
#[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
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:显式声明协议
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 长连接:
- 业务 gRPC:Java、Go、Rust 之间的订单和库存调用;
- 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-service、inventory-service 上游 |
| EDS | 每个 Cluster 有哪些真实 Endpoint | 下发 Ready Pod IP 与端口 |
| SDS | Envoy 使用什么证书和密钥 | 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 | 一个虚拟 IP | kube-proxy,通常按 TCP 连接 | 一条 HTTP/2 长连接可能长期固定到一个 Pod |
| 无 Mesh + Headless | 多个 Pod IP | grpc-java DNS Resolver + round_robin | Java 可以建立多个子连接并分发 RPC |
| Sidecar Mesh + ClusterIP | 一个虚拟 IP | Envoy,Endpoint 来自 Istiod EDS | 新 RPC/Stream 可在多个 Pod 间分配 |
| Sidecar Mesh + Headless | 多个 Pod IP | Java 先选 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 即可:
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 核心配置
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
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
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、云平台或 ExternalDNS | LoadBalancer IP/Hostname | 不参与 |
order-service.shop.svc.cluster.local | Kubernetes Service Registry | 逻辑 ClusterIP | 服务只声明 Service,不维护解析器 |
order-service 的多个 Pod | Istiod EDS | Ready 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 自动重试:
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 超时直接套在双向流上。
入口连接通过 ALPN 使用 HTTP/2,不代表 Gateway 到后端也会自动使用 HTTP/2。后端 Service 必须使用 appProtocol: grpc、appProtocol: http2 或 grpc-*/http2-* 端口名,否则 Gateway 可能按 HTTP/1.1 转发,最终出现 UNAVAILABLE、协议重置或 503。
9.6 外部 Java gRPC 客户端
外部 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 执行 RequestAuthentication 和 AuthorizationPolicy,按 Host 与 /shop.v1.OrderService/* 限制公开方法;业务服务继续校验租户、资源归属和幂等语义。
共享 Gateway 上一旦存在 ALLOW 类型的 AuthorizationPolicy,未命中任何 ALLOW 规则的其他 Host 也可能被拒绝。生产环境应维护完整允许集合,或为不同信任域部署独立 Gateway,避免一个团队的策略影响其他入口。
9.7 三种 TLS 模式怎么选
| 模式 | 公网段 | Gateway 到服务 | 能否做 gRPC L7 治理 | 适用场景 |
|---|---|---|---|---|
| Gateway TLS 终止,推荐 | 客户端 TLS | Istio mTLS | 可以:方法路由、JWT、灰度、指标 | 大多数 API 和微服务入口 |
| Gateway 双向 TLS | 客户端 mTLS | Istio 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 重建、跨节点变化对应用透明 |
客户端只使用稳定域名和 443 | SDK 配置简单,防火墙和证书策略统一 |
| 统一 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"
完整示例:
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 自动识别协议:端口名包含 grpc 或 http2 时,可以不写 backend-protocol: "GRPC"。本文同时声明 name: grpc、appProtocol: 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 与 Endpoint | Kubernetes + 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 Ingress | MSE → Istio Gateway → Mesh Service | 保留 STRICT mTLS、AuthorizationPolicy 和统一 Mesh 身份 | 两层网关、额外延迟与排障复杂度 |
| MSE 连接专用桥接服务 | MSE → DMZ Bridge → Mesh Service | 隔离外部信任边界,桥接服务以明确 ServiceAccount 进入 Mesh | 多一个需要维护和扩容的组件 |
如果选择 MSE 直接连接,可以只对明确的工作负载端口设置 PERMISSIVE,而不是关闭整个 Namespace 的 mTLS:
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
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
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 默认拒绝
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}'
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 方法
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
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 示例
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:
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 流量治理

13.1 DestinationRule:端点策略与版本子集
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%
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 | 代理入站/出站网络 Span | Controller、gRPC Handler、SQL、缓存、业务阶段 |
| Log | Envoy Access Log、Response Flags、上游地址 | request_id、order_id、业务错误与审计事件 |
14.2 Telemetry API
先在 Istio 安装配置中声明 OpenTelemetry Collector:
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:
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:
tracing、tracing-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 通常是授权策略;UF、UH、UT 等 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_SERVING | Endpoint 摘除,Go 不再发新请求 | Endpoint 状态、上游选择 |
| Rust 延迟 2 秒 | Deadline 生效,调用不会无限堆积 | P99、DEADLINE_EXCEEDED、active requests |
| Canary 连续失败 | Outlier Detection 临时摘除实例 | Kiali、Envoy cluster stats |
| 移除 Java → Go ALLOW | gRPC 返回权限拒绝 | AuthorizationPolicy 与代理日志 |
| Istiod 重启 | 存量流量继续,新配置短暂无法下发 | proxy-status、控制面指标 |
| OTel Collector 不可用 | 业务流量继续,遥测出现丢弃/积压 | Sidecar 与 Collector 指标 |
| PostgreSQL 达到连接上限 | 应用快速失败或背压,不触发无限重试 | DB pool、gRPC status、队列 |
十七、常见生产坑
| 坑 | 后果 | 正确处理 |
|---|---|---|
| 把 Istio 拼成 Isito 后搜索配置 | 文档与资源难以对应 | 产品名统一为 Istio |
使用 demo profile 直接生产 | 高采样、资源和组件布局不适合生产 | default 起步,网关独立评估 |
| Namespace 打标签后不重建 Pod | 旧 Pod 没有 Sidecar | 滚动重启并检查容器数量 |
| 所有服务共用 default SA | mTLS 有加密但身份无区分 | 每个工作负载独立 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 自动产生完整 Trace | SQL 和业务 Span 缺失、Trace 断链 | 应用插桩并传播 Context |
| 把订单 ID 放 Prometheus label | 高基数拖垮监控 | 进入 Trace/Log,不进入常规 label |
REGISTRY_ONLY 当安全防火墙 | Pod 仍可能绕过代理出口 | NetworkPolicy + Egress Gateway |
| 让应用运行账号执行迁移 | 凭证泄漏后可修改 Schema | migration 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 时已验证
STRICTmTLS 与 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 才能共享治理能力,而不会把基础设施策略变成另一套难以调试的业务代码。
官方参考资料
- Istio Supported Releases
- Install with istioctl
- Sidecar Injection
- Ambient Components and Waypoints
- Istio Traffic Management
- Protocol Selection
- Understanding DNS
- Understanding Traffic Routing
- DNS Proxying
- DestinationRule Reference
- Secure Ingress Gateway
- Authentication Policy
- RequestAuthentication Reference
- AuthorizationPolicy Reference
- ServiceEntry and Egress
- Telemetry API
- Istio Diagnostic Tools
- Canary Upgrades
- gRPC Java Basics
- gRPC Go
- Tonic
- MSE Ingress Annotation Reference
- Route gRPC Applications with MSE Cloud-native Gateway
- MSE Ingress Gateway Overview
- OpenTelemetry Context Propagation