如何实现gRPC Java从明文到mTLS的零停机升级?
gRPC Java 明文到mTLS的零停机部署方案实现
针对你的场景,两个方案均可以实现零停机升级,具体实现方式如下:
方案1:客户端适配明文/mTLS双模式,自动回退
完全可行。通过自定义客户端凭证和重试策略,可实现对新mTLS服务器优先使用加密连接,对旧明文服务器自动回退的逻辑。
实现步骤
- 创建自定义回退凭证:实现
ChannelCredentials,在TLS握手失败时切换到明文凭证 - 配置重试策略:确保握手失败后客户端会重新尝试连接,触发明文回退
- DNS服务发现:依赖gRPC内置的DNS解析,自动获取新上线的服务器地址
代码示例
import io.grpc.*; import io.grpc.netty.NettyChannelBuilder; import io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext; import io.grpc.netty.shaded.io.netty.channel.ChannelInboundHandlerAdapter; import io.grpc.netty.shaded.io.netty.handler.ssl.SSLHandshakeException; import io.grpc.TlsChannelCredentials; import java.nio.file.Paths; import java.util.concurrent.TimeUnit; public class FallbackChannelCredentials extends ChannelCredentials { private final ChannelCredentials primary; private final ChannelCredentials fallback; public FallbackChannelCredentials(ChannelCredentials primary, ChannelCredentials fallback) { this.primary = primary; this.fallback = fallback; } @Override public ChannelCredentials withoutBearerTokens() { return new FallbackChannelCredentials(primary.withoutBearerTokens(), fallback.withoutBearerTokens()); } @Override public void applyToCallCredentials(CallCredentials callCredentials) { primary.applyToCallCredentials(callCredentials); fallback.applyToCallCredentials(callCredentials); } @Override public void applyToBuilder(NettyChannelBuilder builder) { builder.handshakeTimeout(5, TimeUnit.SECONDS); // 先尝试mTLS凭证 builder.credentials(primary); // 添加握手失败监听器,触发回退 builder.channelInitializer(ch -> ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (cause instanceof SSLHandshakeException) { ctx.channel().close(); } super.exceptionCaught(ctx, cause); } })); } } // 客户端构建 public class GrpcClient { public static void main(String[] args) { TlsChannelCredentials tlsCreds = TlsChannelCredentials.newBuilder() .trustManager(Paths.get("ca-cert.pem")) .keyManager(Paths.get("client-cert.pem"), Paths.get("client-key.pem")) .build(); ChannelCredentials insecureCreds = InsecureChannelCredentials.create(); ManagedChannel channel = NettyChannelBuilder.forTarget("dns:///your-service.example.com") .defaultLoadBalancingPolicy("round_robin") .credentials(new FallbackChannelCredentials(tlsCreds, insecureCreds)) .retryPolicy(RetryPolicy.newBuilder() .setMaxAttempts(3) .setInitialBackoff(Duration.ofMillis(100)) .setMaxBackoff(Duration.ofSeconds(5)) .setBackoffMultiplier(2.0) .setRetryableStatusCodes(Status.Code.UNAVAILABLE, Status.Code.INTERNAL) .build()) .build(); // 使用channel创建stub并发起请求 } }
关键说明
- 客户端会优先尝试mTLS连接,握手失败后关闭当前连接,通过重试策略触发重新连接,此时自动使用明文凭证
- DNS解析会自动获取新上线的mTLS服务器实例,无需手动干预
方案2:单端口服务器同时支持明文和mTLS
完全可行。利用gRPC Java的TLS_AND_PLAINTEXT协商类型,可让服务器在同一个端口同时处理明文和加密连接,实现平滑过渡。
实现步骤
- 升级服务器配置:开启单端口同时支持明文和mTLS
- 逐步替换客户端:将所有客户端升级为mTLS模式
- 关闭明文支持:待所有客户端完成升级后,修改服务器配置仅保留mTLS
代码示例
第一步:服务器兼容双模式
import io.grpc.*; import io.grpc.netty.NettyServerBuilder; import io.grpc.TlsServerCredentials; import java.nio.file.Paths; public class GrpcServer { public static void main(String[] args) throws Exception { TlsServerCredentials tlsCreds = TlsServerCredentials.newBuilder() .keyManager(Paths.get("server-cert.pem"), Paths.get("server-key.pem")) .trustManager(Paths.get("ca-cert.pem")) // 启用mTLS客户端证书验证 .build(); Server server = NettyServerBuilder.forPort(50051) .negotiationType(NegotiationType.TLS_AND_PLAINTEXT) // 同时支持两种模式 .tlsCredentials(tlsCreds) .addService(new YourGrpcServiceImpl()) .build(); server.start(); server.awaitTermination(); } }
第二步:客户端升级为mTLS模式
import io.grpc.*; import io.grpc.netty.NettyChannelBuilder; import io.grpc.TlsChannelCredentials; import java.nio.file.Paths; public class GrpcClient { public static void main(String[] args) { TlsChannelCredentials tlsCreds = TlsChannelCredentials.newBuilder() .trustManager(Paths.get("ca-cert.pem")) .keyManager(Paths.get("client-cert.pem"), Paths.get("client-key.pem")) .build(); ManagedChannel channel = NettyChannelBuilder.forTarget("dns:///your-service.example.com") .defaultLoadBalancingPolicy("round_robin") .credentials(tlsCreds) .build(); // 使用channel创建stub并发起请求 } }
第三步:服务器禁用明文支持
待所有客户端完成升级后,修改服务器配置:
Server server = NettyServerBuilder.forPort(50051) .negotiationType(NegotiationType.TLS) // 仅支持mTLS .tlsCredentials(tlsCreds) .addService(new YourGrpcServiceImpl()) .build();
关键说明
TLS_AND_PLAINTEXT模式下,服务器会自动识别客户端的连接类型(明文或TLS)并处理- 整个升级过程中,旧客户端可继续用明文连接,新客户端用mTLS连接,无停机时间
内容的提问来源于stack exchange,提问作者Jason Pollard
相关产品推荐
相关产品推荐

