亚马逊AWS官方博客

使用 AWS Transit Gateway 在 Amazon EKS 实现组播通信

概述

随着量化交易及高频交易的出现,越来越多普通投资者也渴望能享受快速的证券交易服务。业界头部券商秉持社会责任感与科技普惠金融的使命感积极研发新一代交易核心系统并把速度提升作为重要目标。组播通信以其传输速度及总线型的易高可用架构优势,赢得券商交易核心研发团队青睐,并作为核心系统速度提升的重要手段。AWS 在 Transit Gateway 上提供了云组播服务,然而当客户以 EKS 为底座的容器化交易系统在云上部署时,却遇到了 EKS 默认网络插件 VPC CNI 不支持组播的尴尬局面。为不影响现代化交易系统的云上部署,寻找基于 AWS 原生服务来支持 EKS 集群组播通信的方案已迫在眉睫。

本篇博客将介绍一个使用 AWS Transit Gateway 作为组播能力基座,通过增加额外 ENI 的方式为 EKS 集群提供组播通信能力的方案。

证券交易提速与组播技术

证券交易提速

证券交易一般指投资者在端末下单到证券交易所完成交易的全过程,交易全链路与涉及相关主要系统为券商客户端、券商交易系统、券商柜台系统、交易所相关系统等。

交易提速主要是分为系统之间的网络传输提速及处理系统本身提速两大块儿。从业内实践情况来看,一个为全链路追求极速的方向:网络传输方面会常见光纤专线、网络路径优化、网络协议优化、光纤网卡等提速技术手段,处理系统方面会在硬件上使用定制高性能物理机(高频 CPU、低延迟大内存、FPGA 加速卡等)及软件上使用 BIOS 与 OS 定制、进程与 CPU 内核绑定、无锁编程等技术,更有甚者会使用券商或交易所提供的 co-location(把处理系统放到交易所机房)、DMA(Direct Market Access,越过券商大部分系统仅经过风控检查后就进入交易所系统)特殊服务来提升速度。全链路极速方向需要较大的投入成本,践行者多为大型的私募、基金、券商自营等资金实力雄厚的大型组织;另一个方向聚焦在处理系统(如交易核心的柜台系统)本身提速,借助于系统架构改善及网络协议优化等手段,来达到相对较低成本的系统提速,普惠投资者大众。践行者主要为技术实力雄厚、具有自主研发能力的券商企业。

组播技术在证券交易领域的应用

组播技术(Multicast)是一种网络通信方式,使用 UDP 协议通信,它允许单个发送者以组地址的形式向多个接收者同时传输数据。这使它在需要同时向多个接收者高效传输相同数据的场景中特别有价值。在证券交易领域中,它就最常见的使用场景是交易所的交易行情的高效分发,另一个场景就是本案中的交易核心系统的处理提速。

组播在 AWS

由于公有云网络的复杂性及独特性,AWS VPC 原生不支持组播通信。

AWS Transit Gateway

AWS Transit Gateway (TGW)是一个高度可用、弹性可扩展的云端虚拟路由器,专为运行数千个分布在不同 VPC、AWS 区域和本地网络的企业客户设计 。它作为网络中转枢纽,可以连接 VPC、VPN、AWS Direct Connect 等网络资源,让客户能够通过中央枢纽简化网络互连 。该服务在 2019 年推出了组播网络支持,这是云网络中具有创新性的产品,为大型银行、证券交易所和媒体公司提供关键网络服务。

TGW 支持组播的工作原理为:TGW 作为组播路由器,支持在附加 VPC 的子网之间路由组播流量,实现从发送实例到多个接收实例的数据传输。

TGW 组播的典型使用方式为:1)先创建 TGW;2)使用该 TGW 创建组播域;3)创建 VPC Attachment 把组播通信的 VPC 附加到该 TGW 上;4)把 VPC 里需要组播通信的子网关联到创建的组播域上;5)设置需要组播通信的实例的安全组(详见官网手册)。

AWS EKS 与组播

Amazon Elastic Kubernetes Service (EKS) 是一个托管服务,让客户能够在 AWS 上轻松运行 Kubernetes,而无需成为 Kubernetes 运维专家。该服务完全管理每个集群的 Kubernetes 控制平面的可用性和可扩展性,自动执行所有集群管理操作,包括版本升级、Kubernetes 主节点和 etcd 持久层的扩展,以及检测和替换不健康的主节点。

Amazon EKS (Elastic Kubernetes Service) 的默认网络插件为 AWS VPC CNI,由 AWS 负责开发维护并开源,它负责为 Kubernetes 集群中的 Pod 提供网络连接,并允许 Kubernetes Pod 直接使用 AWS VPC 网络,在高性能、安全性、可见性、兼容性上具有显著优势,是许多客户 EKS 集群的首选默认网络插件。当前 VPC CNI 插件还未支持组播。

使用 TGW 在 EKS 上实现组播

解决方案架构

鉴于金融客户更倾向采用有厂商支持的服务,且常见同一个 EKS 集群中存在大量非组播通信与组播通信 workload 共存的情况,整体方案用 VPC CNI 为集群主网络插件,然后用其它方式增加组播通信能力。方案架构参考如下:

方案说明:主网络插件仍使用 VPC CNI 插件,组播通信基础能力仍由 TGW 提供,把组播域子网的 ENI 添加到 Node 上,以 HostDevice+自定义 IPAM 插件形式为 EKS 集群提供组播通信能力,以 Multus 插件提供多网络平面支持,使 Pod 既可以使用 VPC CNI 进行单播通信,又可以使用 HostDevice 插件联动 TGW 进行组播通信。

方案部署验证

【准备条件】

1)VPC 及子网。VPC 的子网合理划分,将用于组播的子网与 EKS 集群子网分开,专网专用。

2)操作机 EC2、EKS 集群。准备 VPC 内的操作机 1 台,并在上面安装 aws cli(安装参考官网手册)及操作 EKS 集群的工具 eksctl 与 kubectl(安装参考官网手册)。

【部署步骤】

1)TGW 与组播域创建

创建 TGW,并将准备的 VPC 与之附加,然后使用该 TGW 创建 IGMP 组播域,并将规划的组播子网与组播域关联,构建基础组播环境。具体操作步骤可参考官网手册

创建好的 IGMP 组播域参考下图:

2)创建 EKS 集群

使用之前规划好的 VPC 与 EKS 子网来创建集群。可使用 eksctl 创建:

eksctl create cluster -f {cluster_define_yaml_filepath}

yaml 文件定义可参考如下:

3)检查并设置 EKS 集群 VPC ENI 插件的网卡预热参数

VPC CNI 插件默认情况下会开启网卡预热机制,即在 NodeGroup 创建之初,会在 Node 上自动添加而外的 ENI 网卡。该特性由 WARM_ENI_TARGET 参数控制,默认数值为 1,即会在 NodeGroup 的某一 Node 上额外添加 1 张 ENI。这个动作会导致各 Node 上 ENI 数量不一致,为后续组播配置带来不便。可显式设置该参数为 0,即不预热,保证新建 NodeGroup中的各 Node 都仅一张 ENI。

kubectl set env daemonset aws-node -n kube-system WARM_ENI_TARGET=0

4)创建用于支持组播通信的 NodeGroup

使用 eksctl 创建 NodeGroup(需指定 Node 的 SSH 登陆 Key 以便后续操作)

eksctl create nodegroup -f {nodegroup_define_yaml_filepath}

yaml 文件定义可参考如下:

5)创建组播 ENI、配置组播安全组、设置关键标签,并将 ENI 附加到 Node 上

用控制台或 AWS CLI 创建组播 ENI(数量与用于支持组播通信的 NodeGroup 的 Node 数相同)。有两点需特别注意:

注意点 1:要为组播 ENI 网卡设置支持组播通信的安全组(具体规则参考官网手册

注意点 2:附加到 Node 的 ENI 默认会被 VPC CNI 插件管理,需为 ENI 设置如下标签以便它脱离 VPC CNI 的管控:

标签名 标签值
node.k8s.amazonaws.com/no_manage true

用控制台或 AWS CLI 把所创建 ENI 附加到 Node 上,可 SSH 登陆到 Node 上确认可以看到名为 eth1 的网卡。

6)安装 Multus 插件

参考该步骤修改 Auth ConfigMap,参考该步骤安装 Multus 插件。

使用 kubectl 命令确认安装成功(在 kube-system 命名空间中看到名为 kube-multus-xxxx 的 pod 运行正常)。

7)设置 Node 的 IGMP 内核参数及配置自定义 IPAM 插件

登陆到各 Node 上设置如下内核参数:

sudo sysctl -w net.ipv4.conf.all.force_igmp_version=2 
sudo sysctl -w net.ipv4.conf.default.force_igmp_version=2

注意:如果希望这种设置持久生效,可把两个参数添加到 /etc/sysctl.conf 文件末尾,并执行如下命令:

sudo sysctl -p

将自定义开发的 IPAM 拷贝到 Node 到 /opt/cni/bin 目录,并设置为可运行:

sudo cp {your ipam file path} /opt/cni/bin/{ipam_filename}
sudo chmod +x /opt/cni/bin/{ipam_filename}

说明:此处的 ipam 插件主要功能是把 Node 组播 ENI 的 IP 设置给 pod 的组播网卡,让程序可以通过该 IP 访问组播网卡。

生产环境上 ipam 插件建议参考 CNI 规范实现,此处验证用 sample 代码如下:

#!/bin/bash
# 需要chmod +x赋予执行权限
# 配置时需要放在CNI插件目录(通常是/opt/cni/bin/)
# 日志函数
log() {
   echo "{\"timestamp\":\"$(date -u '+%Y-%m-%dT%H:%M:%SZ')\",\"message\":\"$1\"}" >&2
}
# 获取EC2子网CIDR的函数
get_ec2_interface_info() {
    local mac=$1
    # 获取 IMDSv2 token
    local token=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
    if [ -z "$token" ]; then
        log "Failed to get IMDSv2 token"
        exit 1
    fi
    # 获取该MAC对应的IP地址(local-ipv4s可能返回多个IP,取第一个)
    local ip=$(curl -s -H "X-aws-ec2-metadata-token: $token" http://169.254.169.254/latest/meta-data/network/interfaces/macs/${mac}/local-ipv4s | head -n 1)
    if [ -z "$ip" ]; then
        log "Failed to get IP for MAC $mac"
        exit 1
    fi
    # 获取子网CIDR
    local cidr=$(curl -s -H "X-aws-ec2-metadata-token: $token" http://169.254.169.254/latest/meta-data/network/interfaces/macs/${mac}/subnet-ipv4-cidr-block)
    if [ -z "$cidr" ]; then
        log "Failed to get subnet CIDR from metadata service"
        exit 1
    fi
    local mask=$(echo "$cidr" | cut -d'/' -f2)
    echo "$ip/$mask"
}
# 获取容器内接口MAC地址
get_container_mac() {
    local ifname=$1
    local netns_path=$2
    local netns=$(basename "$netns_path")
    # 在容器网络命名空间中获取MAC地址
    local mac=$(ip netns exec "$netns" ip link show "$ifname" | grep ether | awk '{print $2}')
    if [ -z "$mac" ]; then
        log "Failed to get MAC address for interface $ifname"
        exit 1
    fi
    echo $mac
}
# 处理CNI命令
case "$CNI_COMMAND" in
    ADD)
        # 检查必要的环境变量
        if [ -z "$CNI_IFNAME" ] || [ -z "$CNI_NETNS" ]; then
            log "Missing required CNI variables"
            exit 1
        fi
        log "Processing ADD for interface $CNI_IFNAME in netns $CNI_NETNS"
        # 获取容器MAC地址
        mac_addr=$(get_container_mac "$CNI_IFNAME" "$CNI_NETNS")
        log "Got container MAC address: $mac_addr"
        # 获取EC2子网IP和CIDR
        iface_ip=$(get_ec2_interface_info $mac_addr)
        log "Got Interface IP: $iface_ip"
        # 构造CNI返回结果
        cat << EOF
{
    "cniVersion": "0.3.1",
    "interfaces": [
        {
            "name": "$CNI_IFNAME"
        }
    ],
    "ips": [
        {
            "version": "4",
            "address": "$iface_ip"
        }
    ]
}
EOF
        ;;
    DEL)
        # 对于DELETE操作,简单返回成功
        log "Processing DEL for interface $CNI_IFNAME"
        cat << EOF
{
    "cniVersion": "0.3.1"
}
EOF
        ;;
    *)
        log "Unsupported CNI command: $CNI_COMMAND"
        exit 1
        ;;
esac
exit 0

8)为组播通信部署 NetworkAttachmentDefinition

NetworkAttachmentDefinition 定义 Yaml 文件如下所示:

apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
  name: hostdevice-multicast-network
  namespace: kube-system
spec:
  config: '
  {
    "cniVersion": "0.3.1",
    "type": "host-device",
    "device": "eth1",
    "ipam": {
      "type": "{ipam_filename}"
    }
  }'

注:此处的 ipam_filename 需与 node 上 /opt/cni/bin/ 的 ipam_filename 完全相同。

kubectl apply -f {NetworkAttachmentDefinition_filepath}

9)创建需要使用组播的 Pod

在 Pod 定义文件的 metadata 的 annotations 中设置 k8s.v1.cni.cncf.io/networks 属性值为上述 NetworkAttachmentDefinition,即可让该 Pod 具有组播通信能力。

Yaml 文件关键部分参考:

apiVersion: v1
kind: Pod
metadata:
  name: multitool-with-iperf-muti01
  annotations:
    k8s.v1.cni.cncf.io/networks: {hostdevice-multicast-network}
  labels:
    app: multitool-with-iperf-muti01
spec:
 …

至此,该 EKS 集群已经具有了 Pod 之间组播通信及 Pod 与 VM 之间的组播通信能力。

【测试结果验证】

1)创建三个支持组播的 Pod

测试 pod 定义文件贺核心部分如下所示:

 spec:
  …
  containers:
  - name: multitool
    image: praqma/network-multitool
    command: ["/bin/bash"]
    args:
    - "-c"
    - "apk update && apk add iperf && /usr/sbin/nginx -g 'daemon off;' & while true; do sleep 3600; done"
…

2)三个组播 Pod 之间、与组播 EC2 均可成功组播通信,与非组播 Pod 可以单播通信

pod3 以如下命令指定用组播网卡 IP 向组播地址 224.0.0.13 发送 UDP 报文:

iperf -B 10.9.1.40 -c 224.0.0.13 -u --ttl 5 -t 3600 -b10M

pod1、pod2 以如下命令指定组播网卡从组播地址 224.0.0.13 接收组播 UDP 报文:

iperf -s -u -B 224.0.0.13%net1 -i 1

结果如下图所示:pod1、pod2 能成功接收到组播报文

此时查看 TGW 的组播域控制台,可以看到已出现上述命令动态创建的组播(224.0.0.13)组的活动信息:

此时在组播子网内启动 1 台 EC2,进行组播报文发送,pod1、2、3 均能成功接收:

此时再启动两个非组播 pod,使用 curl 访问 http 服务,也能够通信成功:

总结

本博客介绍了一个在 VPC CNI 插件不支持组播的情况下,以 AWS Transit Gateway 基础,通过增加额外 ENI 的方式为 EKS 集群提供组播通信能力同时又不影响 VPC CNI 插件使用的方案。该方案为客户扫除了容器化的交易核心系统在 AWS 上线部署的障碍,对于类似需要在以 VPC CNI 为主网络插件同时需要组播能力的场景都有借鉴意义。

本篇作者

崔保亮

亚马逊云科技金融行业解决方案架构师

刘瀚文

亚马逊云科技产品部网络技术高级专家

郑金霞

亚马逊云科技金融行业解决方案架构师

胡仲海

华泰证券架构委员会架构师

Ricky Chu

华泰金控(香港)资讯科技部基础设施项目经理