
mTLS 在 AI 服务间通信落地内部流量也应该加密一、内部流量裸奔一个被默认忽视的安全缺口推理服务集群的拓扑很典型API Gateway → 推理调度器 → 模型推理 Pod。三个服务都在同一个 Kubernetes 集群内Service Mesh 也没配通信走的是 HTTP 明文。技术决策写的备注是内网流量不需要 TLS。这个假设在云原生环境里已经不成立了。同一个 Node 上的容器共享内核如果任何一个 Pod 被攻破——比如一个带漏洞的 Node.js 前端应用——攻击者就可以在宿主机上抓包。模型推理请求中的用户数据和返回结果经过同一个网络命名空间全部可见。不止是横向移动风险。模型推理的请求体可能包含用户上传的图片、文档等个人数据企业内部系统返回的业务数据拼接的 prompt 内容模型返回的含敏感信息的生成文本这些数据如果在不加密的 TCP 连接上传输任何一个能接触到集群网络的实体——包括其他租户的 Pod、节点上的调试工具——都可以窃听。内部加密不是多此一举。mTLS 在服务网格中的重要性在于它不仅加密流量还提供身份验证。每个服务用自己的证书证明我是推理调度器而不是仅仅依赖网络策略做 IP 白名单。二、mTLS 握手流程与服务身份绑定mTLS 与单向 TLS 的差异在于客户端也需要出示证书。这提供了双向的、非网络地址的身份验证。sequenceDiagram participant C as API Gatewaybr/(客户端) participant S as 推理调度器br/(服务端) participant CA as 证书颁发机构 C-S: ClientHello (支持的密码套件) S-C: ServerHello 服务端证书 C-C: 验证服务端证书br/(CA 签名 SAN) C-S: 客户端证书 S-S: 验证客户端证书br/(CA 签名 SAN) S-C: 握手完成 C--S: 加密的应用数据 Note over C,S: 双向身份确认 加密通道已建立证书中的 Subject Alternative NameSAN是关键的身份标识字段。不应该用 IP 地址作为 SAN——在 Kubernetes 中 Pod IP 是动态的。应该使用 SPIFFE ID 格式URI: spiffe://cluster.local/ns/inference/sa/model-schedulerSPIFFE 标准把服务身份与运行时环境绑定集群名 命名空间 ServiceAccount。即使 Pod 迁移到新节点、换了 IP身份不变。Istio 和 Linkerd 等 Service Mesh 自动处理证书轮换。默认 24 小时有效期的证书每 12 小时自动轮换一次不需要应用代码改动。这是 Service Mesh 比手动管理证书最大的价值。但对于非网格环境如没有 Istio 的裸 K8s需要自建证书管理和自动化轮换。三、Go 实现的 mTLS GRPC 服务端与客户端package mtls import ( crypto/tls crypto/x509 os time google.golang.org/grpc google.golang.org/grpc/credentials ) // ServerTLSConfig 构建服务端 mTLS 配置 // 要求客户端必须出示有效证书 func ServerTLSConfig( certFile, keyFile, caCertFile string, ) (*tls.Config, error) { // 加载服务端证书和私钥 cert, err : tls.LoadX509KeyPair(certFile, keyFile) if err ! nil { return nil, fmt.Errorf(加载服务端证书失败: %w, err) } // 加载 CA 证书池信任的客户端证书签发者 caCert, err : os.ReadFile(caCertFile) if err ! nil { return nil, fmt.Errorf(读取 CA 证书失败: %w, err) } caCertPool : x509.NewCertPool() if !caCertPool.AppendCertsFromPEM(caCert) { return nil, fmt.Errorf(解析 CA 证书失败) } return tls.Config{ Certificates: []tls.Certificate{cert}, ClientAuth: tls.RequireAndVerifyClientCert, // 要求并验证客户端证书 ClientCAs: caCertPool, MinVersion: tls.VersionTLS13, // 强制 TLS 1.3 // 证书有效期检查——过期证书在握手阶段拒绝 VerifyConnection: func(cs tls.ConnectionState) error { now : time.Now() for _, pc : range cs.PeerCertificates { if now.Before(pc.NotBefore) { return fmt.Errorf(客户端证书尚未生效) } if now.After(pc.NotAfter) { return fmt.Errorf(客户端证书已过期) } } return nil }, }, nil } // ClientTLSConfig 构建客户端 mTLS 配置 func ClientTLSConfig( certFile, keyFile, caCertFile, serverName string, ) (*tls.Config, error) { cert, err : tls.LoadX509KeyPair(certFile, keyFile) if err ! nil { return nil, fmt.Errorf(加载客户端证书失败: %w, err) } caCert, err : os.ReadFile(caCertFile) if err ! nil { return nil, fmt.Errorf(读取 CA 证书失败: %w, err) } caCertPool : x509.NewCertPool() if !caCertPool.AppendCertsFromPEM(caCert) { return nil, fmt.Errorf(解析 CA 证书失败) } return tls.Config{ Certificates: []tls.Certificate{cert}, RootCAs: caCertPool, ServerName: serverName, // 验证服务端证书的 SAN MinVersion: tls.VersionTLS13, }, nil } // NewSecureGRPCServer 创建带 mTLS 的 gRPC 服务 func NewSecureGRPCServer( tlsCfg *tls.Config, ) *grpc.Server { creds : credentials.NewTLS(tlsCfg) return grpc.NewServer(grpc.Creds(creds)) }代码中两个关键设计ClientAuth: tls.RequireAndVerifyClientCert确保客户端必须提供有效证书不会降级为非 mTLS 连接VerifyConnection回调额外检查证书有效期防止在证书轮换窗口内使用过期证书四、mTLS 的运维成本与性能影响证书管理的复杂性。自建 PKI 的最大成本不是配置 TLS而是证书轮换。每个服务需要在不中断连接的情况下完成证书热更新。Go 的crypto/tls支持通过tls.Config.GetCertificate回调实现动态选择证书tlsCfg.GetCertificate func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { return certManager.GetLatest(), nil }TLS 握手延迟。TLS 1.3 将握手从 2-RTT 降到 1-RTT甚至支持 0-RTT 恢复。一次新连接的 mTLS 握手大约增加 3-8ms。对于推理 API 这种 RPC 密集的服务建议使用长连接复用避免频繁握手。不适合 mTLS 的场景延迟极度敏感的 GPU 间通信NCCL 走 RDMA 时加密会破坏零拷贝路径与外部第三方 API 通信时对方可能不支持 mTLS大批量数据传输时TLS 加密的 CPU 开销会成为瓶颈一种折中是用 NodeLocal DNS 和 NetworkPolicy 做网络层隔离作为基础mTLS 作为纵深防御的一层。两个机制互补而非替代。五、总结内部流量不加密不是零信任架构。mTLS 同时提供了传输加密和身份验证是服务间通信的安全基线。三个落地步骤PKI 建立使用 cert-manager 或 Service Mesh 内置的 CA 自动签发和轮换证书强制 mTLS在网格策略中设置PeerAuthentication为STRICT模式监控证书健康对证书过期时间设置告警确保轮换机制正常运行加密不是负担。把安全做在通信层应用代码不需要感知加密逻辑。