为实时AI特征平台构建基于Envoy的零信任mTLS数据平面架构


一个线上运行的实时特征平台,其核心价值在于能在毫秒级延迟内为成百上千个AI模型提供特征向量。数据从Kafka等流式源头涌入,经过处理服务写入高速存储,再由读取服务提供给模型进行推理。整个链路对延迟极其敏感,同时,特征数据作为企业的核心资产,其安全性不容有失。在一个默认不可信的“零信任”网络环境中,保障这条数据高速公路的安全,是架构设计中必须面对的硬骨头。

问题被明确地摆在台面上:如何在不显著牺牲性能的前提下,为这个由Python、Java、Go等多种语言构建的异构微服务体系,提供统一、强制、可动态管理的端到端双向认证加密(mTLS)?

方案A:应用层自行实现mTLS

最初的构想是让各个微服务在自己的代码中直接集成mTLS能力。例如,使用gRPC的服务可以利用其内置的TLS支持,而HTTP服务则可以在Web框架层面(如Java的Netty/Jetty,Python的Twisted)集成SSL/TLS库。

优势分析:

  1. 理论上的最低延迟: 数据在离开应用内存后直接进入内核的TCP/IP协议栈进行加密,没有额外的用户态代理转发,网络路径最短。
  2. 无外部依赖: 服务自身是独立的,不需要部署额外的代理组件,简化了部署拓扑。

劣势分析(在真实项目中,这些是致命的):

  1. 实现复杂性与不一致性: 不同语言、不同框架的TLS库API千差万别。要保证所有服务都正确、一致地配置了mTLS(例如,使用相同的加密套件、正确验证对端证书),是一项艰巨的任务,极易出错。一个常见的错误是,开发者在测试环境中为了方便禁用了主机名验证,而这个配置被遗忘并带到了生产环境。
  2. 证书管理的噩梦: 这是最核心的痛点。每个服务实例都需要自己的身份证书和私钥。如何安全地分发、存储这些密钥?证书过期后如何实现无缝轮换?如果手动操作,运维成本是灾难性的。如果自研自动化方案,等于是在重复造一个PKI(Public Key Infrastructure)轮子,其复杂性远超业务本身。
  3. 业务与安全逻辑强耦合: 安全逻辑侵入了业务代码。每当安全策略需要更新(比如更换根CA、调整支持的TLS版本),就需要修改、测试并重新部署大量微服务。这严重拖慢了迭代速度。
  4. 可观测性差: 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

优势分析:

  1. 应用透明,语言无关: 安全与业务彻底解耦。无论是Python、Java还是Go编写的服务,都无需改动一行代码即可获得mTLS能力。团队可以专注于业务逻辑开发。
  2. 策略集中管理: 所有mTLS策略(如强制开启、允许的Cipher Suites、证书验证逻辑)都在Envoy的配置中定义。这些配置可以由一个集中的控制平面生成和下发,保证了整个系统安全策略的一致性。
  3. 动态证书管理: Envoy通过其标准的Secret Discovery Service (SDS) API,可以从控制平面动态获取和轮换证书。整个过程对应用无感,无需重启服务,解决了方案A中最棘手的证书管理问题。
  4. 增强的可观测性: Envoy本身提供了极其丰富的遥测数据。我们可以从Envoy层面轻松获取TLS握手成功/失败次数、延迟、使用的密码套件、证书过期时间等关键指标,并将其接入统一的监控平台(如Prometheus)。

劣势分析:

  1. 引入额外延迟: 流量需要经过用户态的Envoy代理,相比内核直接处理,增加了一次网络转发。对于我们这种对P99延迟非常敏感的特征平台,必须对这个开销进行精确评估。
  2. 资源开销: 每个Pod都需要额外运行一个Envoy进程,会消耗一定的CPU和内存资源。在大规模部署时,这部分累积的资源成本需要纳入考量。
  3. 运维复杂性增加: 引入了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

关键配置解析:

  1. transport_socket: 这是在Envoy中配置TLS/mTLS的核心。DownstreamTlsContext 用于入站连接(作为服务端),UpstreamTlsContext 用于出站连接(作为客户端)。
  2. sds_config: 我们没有将证书和私钥的路径硬编码在配置中,而是通过sds_config告诉Envoy去哪里(sds_cluster)动态获取。name字段是请求证书的唯一标识,这里我们遵循了SPIFFE规范,使用服务的身份(Service Account)来命名,这是一个最佳实践。
  3. require_client_certificate: true: 在DownstreamTlsContext中开启此选项,强制要求连接到此Envoy的客户端必须提供有效的证书,否则TLS握手会失败。这是实现双向认证的关键。
  4. validation_context_sds_secret_config: 除了自己的身份证书,Envoy还需要知道信任哪些根CA证书来验证对端。这个配置同样通过SDS动态获取,使得整个信任域的根CA轮换成为可能。
  5. 透明劫持 (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平台的身份和访问控制机制。这是一个需要从全局视角审视的系统性安全问题。


  目录