一个线上运行的实时特征平台,其核心价值在于能在毫秒级延迟内为成百上千个AI模型提供特征向量。数据从Kafka等流式源头涌入,经过处理服务写入高速存储,再由读取服务提供给模型进行推理。整个链路对延迟极其敏感,同时,特征数据作为企业的核心资产,其安全性不容有失。在一个默认不可信的“零信任”网络环境中,保障这条数据高速公路的安全,是架构设计中必须面对的硬骨头。
问题被明确地摆在台面上:如何在不显著牺牲性能的前提下,为这个由Python、Java、Go等多种语言构建的异构微服务体系,提供统一、强制、可动态管理的端到端双向认证加密(mTLS)?
方案A:应用层自行实现mTLS
最初的构想是让各个微服务在自己的代码中直接集成mTLS能力。例如,使用gRPC的服务可以利用其内置的TLS支持,而HTTP服务则可以在Web框架层面(如Java的Netty/Jetty,Python的Twisted)集成SSL/TLS库。
优势分析:
- 理论上的最低延迟: 数据在离开应用内存后直接进入内核的TCP/IP协议栈进行加密,没有额外的用户态代理转发,网络路径最短。
- 无外部依赖: 服务自身是独立的,不需要部署额外的代理组件,简化了部署拓扑。
劣势分析(在真实项目中,这些是致命的):
- 实现复杂性与不一致性: 不同语言、不同框架的TLS库API千差万别。要保证所有服务都正确、一致地配置了mTLS(例如,使用相同的加密套件、正确验证对端证书),是一项艰巨的任务,极易出错。一个常见的错误是,开发者在测试环境中为了方便禁用了主机名验证,而这个配置被遗忘并带到了生产环境。
- 证书管理的噩梦: 这是最核心的痛点。每个服务实例都需要自己的身份证书和私钥。如何安全地分发、存储这些密钥?证书过期后如何实现无缝轮换?如果手动操作,运维成本是灾难性的。如果自研自动化方案,等于是在重复造一个PKI(Public Key Infrastructure)轮子,其复杂性远超业务本身。
- 业务与安全逻辑强耦合: 安全逻辑侵入了业务代码。每当安全策略需要更新(比如更换根CA、调整支持的TLS版本),就需要修改、测试并重新部署大量微服务。这严重拖慢了迭代速度。
- 可观测性差: TLS握手失败、证书过期等安全相关的错误日志散落在各个服务的应用日志中,格式不一,难以进行统一的监控和告警。
在我们的场景下,一个特征平台可能有几十个微服务,由不同的团队维护。方案A带来的管理成本和安全风险,随着系统规模的扩大将呈指数级增长。因此,尽管它在理论上延迟最低,但在工程实践中基本不可行。
方案B:基于Envoy Sidecar的透明mTLS代理
该方案将网络通信安全能力从应用中剥离出来,下沉到基础设施层。具体做法是为每个微服务实例部署一个Envoy Proxy作为“边车(Sidecar)”代理。应用的所有出入流量都被透明地劫持并导向Envoy。Envoy负责代表应用处理所有mTLS相关的操作:建立连接、证书验证、加密解密。应用本身甚至不知道网络流量是被加密的,它只需要与本地的Envoy(localhost)进行明文通信。
graph TD
subgraph "Pod A (Feature Ingestion Service)"
A1[Ingestion App] --明文--> A2(Envoy Sidecar)
end
subgraph "Pod B (Feature Retrieval Service)"
B2(Envoy Sidecar) --明文--> B1[Retrieval App]
end
A2 --mTLS加密通信--> B2
subgraph "Control Plane (e.g., Istio, Custom)"
CP[Certificate Management / SDS]
end
CP --动态下发证书--> A2
CP --动态下发证书--> B2
优势分析:
- 应用透明,语言无关: 安全与业务彻底解耦。无论是Python、Java还是Go编写的服务,都无需改动一行代码即可获得mTLS能力。团队可以专注于业务逻辑开发。
- 策略集中管理: 所有mTLS策略(如强制开启、允许的Cipher Suites、证书验证逻辑)都在Envoy的配置中定义。这些配置可以由一个集中的控制平面生成和下发,保证了整个系统安全策略的一致性。
- 动态证书管理: Envoy通过其标准的Secret Discovery Service (SDS) API,可以从控制平面动态获取和轮换证书。整个过程对应用无感,无需重启服务,解决了方案A中最棘手的证书管理问题。
- 增强的可观测性: Envoy本身提供了极其丰富的遥测数据。我们可以从Envoy层面轻松获取TLS握手成功/失败次数、延迟、使用的密码套件、证书过期时间等关键指标,并将其接入统一的监控平台(如Prometheus)。
劣势分析:
- 引入额外延迟: 流量需要经过用户态的Envoy代理,相比内核直接处理,增加了一次网络转发。对于我们这种对P99延迟非常敏感的特征平台,必须对这个开销进行精确评估。
- 资源开销: 每个Pod都需要额外运行一个Envoy进程,会消耗一定的CPU和内存资源。在大规模部署时,这部分累积的资源成本需要纳入考量。
- 运维复杂性增加: 引入了Envoy和控制平面这两个新组件,整个系统的运维复杂度提升了。需要有能力管理和排查Envoy本身及其配置问题的团队。
最终决策与理由
权衡利弊,我们最终选择了方案B。理由如下:
对于一个复杂的、多团队协作的AI平台,标准化和可管理性的重要性远高于追求极致的、但难以维护的理论性能。方案B将安全这个通用的横切关注点标准化为基础设施能力,极大地降低了业务团队的心智负担,提升了开发效率和系统的整体安全性。
至于性能开销,通过实际压测,我们发现最新版本的Envoy在经过优化的配置下,对单跳P99延迟的影响在1-3毫秒之间。对于我们大部分特征获取场景(网络RTT本身就在10ms以上),这个延迟增量是可以接受的。资源开销也可以通过为Envoy设置合理的requests/limits来控制。最关键的是,方案B解决的是规模化后的工程确定性问题,这是一个架构层面的根本性胜利。
核心实现概览
核心在于为数据读(Retrieval)写(Ingestion)服务配置Envoy Sidecar,并依赖SDS进行证书管理。以下是一个生产级的Envoy配置,用于特征读取服务(feature-retrieval-svc)。
该服务监听在9000端口,并需要调用位于feature-db-writer-svc.default.svc.cluster.local:8000的数据库写入服务。
# envoy-retrieval-svc.yaml
# 这是一个生产级的Envoy配置,用于特征读取服务
# 它实现了mTLS的双向强制执行,并使用SDS动态加载证书
static_resources:
listeners:
# 入站监听器:负责接收来自其他服务的流量,并将其转发给本地应用
- name: inbound_listener
address:
socket_address:
address: 0.0.0.0
port_value: 15006 # Envoy实际监听的端口,通常由iptables规则透明劫持
filter_chains:
- filter_chain_match:
transport_protocol: "tls"
filters:
- name: envoy.filters.network.tcp_proxy
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
stat_prefix: inbound_tcp
cluster: local_app_cluster # 将解密后的流量转发给本地应用
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
common_tls_context:
# TLS证书配置,通过SDS动态获取
tls_sds_secret_configs:
- name: "spiffe://mydomain.com/ns/default/sa/feature-retrieval-sa" # 使用SPIFFE ID作为证书标识
sds_config:
resource_api_version: V3
api_config_source:
api_type: GRPC
transport_api_version: V3
grpc_services:
- envoy_grpc:
cluster_name: sds_cluster # 指向SDS服务器
# 验证客户端证书的配置
validation_context_sds_secret_config:
name: "spiffe://mydomain.com/validation_context" # 同样通过SDS获取根CA证书
sds_config:
resource_api_version: V3
api_config_source:
api_type: GRPC
transport_api_version: V3
grpc_services:
- envoy_grpc:
cluster_name: sds_cluster
require_client_certificate: true # 强制要求客户端提供证书,这是mTLS的核心
# 出站监听器:负责接收来自本地应用的流量,并将其转发给其他服务
- name: outbound_listener
address:
socket_address:
address: 0.0.0.0
port_value: 15001 # Envoy监听的另一个端口,用于劫持出站流量
filter_chains:
- filters:
- name: envoy.filters.network.tcp_proxy
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
stat_prefix: outbound_tcp
# 通过原始目标地址进行路由,这是透明劫持的关键
cluster: "outbound|8000||feature-db-writer-svc.default.svc.cluster.local"
clusters:
# 定义本地应用集群,用于接收inbound流量
- name: local_app_cluster
type: STATIC
connect_timeout: 1s
load_assignment:
cluster_name: local_app_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 9000 # 本地应用的实际监听端口
# 定义目标服务集群(数据库写入服务),用于处理outbound流量
- name: "outbound|8000||feature-db-writer-svc.default.svc.cluster.local"
type: ORIGINAL_DST # 使用原始目标地址进行负载均衡
connect_timeout: 2s
lb_policy: CLUSTER_PROVIDED
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
common_tls_context:
# 配置客户端证书,用于向对端服务证明自己的身份
tls_sds_secret_configs:
- name: "spiffe://mydomain.com/ns/default/sa/feature-retrieval-sa"
sds_config:
resource_api_version: V3
api_config_source:
api_type: GRPC
transport_api_version: V3
grpc_services:
- envoy_grpc:
cluster_name: sds_cluster
# 配置根CA证书,用于验证对端服务的证书
validation_context_sds_secret_config:
name: "spiffe://mydomain.com/validation_context"
sds_config:
resource_api_version: V3
api_config_source:
api_type: GRPC
transport_api_version: V3
grpc_services:
- envoy_grpc:
cluster_name: sds_cluster
# 定义SDS服务器集群
- name: sds_cluster
type: STRICT_DNS
connect_timeout: 1s
load_assignment:
cluster_name: sds_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
# 这里的地址指向Istio Pilot/Citadel或自定义的SDS服务器
address: sds-server.istio-system.svc.cluster.local
port_value: 15012
# 注意:Envoy与SDS服务器的连接本身也需要安全保障
# 实际生产中,这里通常会有一个bootstrap证书进行初始认证
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
common_tls_context:
validation_context:
trusted_ca:
filename: /var/run/secrets/istio/root-cert.pem
admin:
address:
socket_address:
address: 127.0.0.1
port_value: 15000
关键配置解析:
-
transport_socket: 这是在Envoy中配置TLS/mTLS的核心。DownstreamTlsContext用于入站连接(作为服务端),UpstreamTlsContext用于出站连接(作为客户端)。 -
sds_config: 我们没有将证书和私钥的路径硬编码在配置中,而是通过sds_config告诉Envoy去哪里(sds_cluster)动态获取。name字段是请求证书的唯一标识,这里我们遵循了SPIFFE规范,使用服务的身份(Service Account)来命名,这是一个最佳实践。 -
require_client_certificate: true: 在DownstreamTlsContext中开启此选项,强制要求连接到此Envoy的客户端必须提供有效的证书,否则TLS握手会失败。这是实现双向认证的关键。 -
validation_context_sds_secret_config: 除了自己的身份证书,Envoy还需要知道信任哪些根CA证书来验证对端。这个配置同样通过SDS动态获取,使得整个信任域的根CA轮换成为可能。 - 透明劫持 (
ORIGINAL_DST): 出站cluster类型被设置为ORIGINAL_DST,配合iptables规则,Envoy可以获取到被劫持流量的原始目标IP和端口,从而正确地将流量路由到feature-db-writer-svc,而无需在应用代码中修改目标地址为localhost。
架构的扩展性与局限性
这种基于Envoy的mTLS数据平面为我们的AI平台提供了一个坚实的安全底座。它的扩展性非常好,因为Envoy的能力远不止于mTLS。未来,我们可以在此基础上,通过下发不同的Envoy配置,轻松实现更复杂的L7流量策略,比如:基于JWT的请求级授权、对特定特征的访问进行速率限制、或者在模型版本迭代时进行精细的流量切分(金丝雀发布)。这些高级功能都可以在不触碰任何业务代码的情况下完成。
然而,这个架构并非没有局限性。首先,我们必须正视Envoy sidecar引入的性能开销。虽然单跳延迟增加不大,但在一个需要经过5、6个服务调用的复杂特征计算链路中,累积的延迟可能会变得显著。这要求我们必须持续对热点路径进行性能监控和优化,甚至在某些极端场景下,可能需要绕过Sidecar,采用性能更高的方案(如gRPC直连,但仅限于内部可信度极高的服务之间)。
其次,数据平面的稳定运行高度依赖一个健壮的控制平面。本文侧重于数据平面(Envoy)的实现,但一个生产级的控制平面(负责证书签发、配置下发、服务发现)本身就是一个复杂的分布式系统。它的高可用性和正确性至关重要,一旦控制平面出现故障,可能会导致新服务无法获取证书而启动失败,或者配置无法更新。
最后,身份认证的“根”问题。Envoy启动时,它如何安全地向SDS证明自己的身份以获取第一个证书?这个“引导身份”通常由其所在的平台(如Kubernetes)提供,通过挂载给Pod的Service Account Token来实现。整个链条的安全性,最终依赖于底层IaaS平台的身份和访问控制机制。这是一个需要从全局视角审视的系统性安全问题。