You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现gRPC Java从明文到mTLS的零停机升级?

gRPC Java 明文到mTLS的零停机部署方案实现

针对你的场景,两个方案均可以实现零停机升级,具体实现方式如下:

方案1:客户端适配明文/mTLS双模式,自动回退

完全可行。通过自定义客户端凭证和重试策略,可实现对新mTLS服务器优先使用加密连接,对旧明文服务器自动回退的逻辑。

实现步骤

  1. 创建自定义回退凭证:实现ChannelCredentials,在TLS握手失败时切换到明文凭证
  2. 配置重试策略:确保握手失败后客户端会重新尝试连接,触发明文回退
  3. 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协商类型,可让服务器在同一个端口同时处理明文和加密连接,实现平滑过渡。

实现步骤

  1. 升级服务器配置:开启单端口同时支持明文和mTLS
  2. 逐步替换客户端:将所有客户端升级为mTLS模式
  3. 关闭明文支持:待所有客户端完成升级后,修改服务器配置仅保留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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 22:50:36