亚马逊AWS官方博客

EKS 托管节点组自动设置 Pod 多子网网络

背景说明

客户的应用系统从 EC2 部署向容器化迁移的进程中,一方面,客户期望在应用逐渐迁移 EKS 的同时,能够逐步为 EKS 增添更多的子网。另一方面,客户希望区分节点子网和 Pod 子网,进行更精细的安全组控制。因此,客户使用 EKS 的自定义 Pod 网络方案。

然而,在 EKS 自定义 Pod 网络中,Node 与 Pod 子网分离。如果 Pod 使用的子网在一个可用区(AZ)中有多个子网,并且客户使用托管节点组管理节点时,将无法为 Pod 自动从多个子网匹配到某个特定子网上。针对这个挑战,本文将详细介绍一个新方案,在托管节点组启动节点的同时,自动实现 Pod 在 AZ 的多个子网中按需选择一个子网。

方案设计说明

EKS 自定义 Pod 网络场景,EKS 使用 CNI 插件来管理网络,EKS 部署 Pod 的时候为每个节点(Node)新增弹性网络接口(Secondary ENI),用于 Pod 之间的通信。

这个 Secondary ENI 的子网及其安全组定义设置在 EKS 的 ENIConfig 中,每一个子网都将对应一个单独的 ENIConfig,从而有一组 ENIConfig。EKS 托管节点组启动一个 Node 时,从一组 ENIConfig 中指定一个具体的 ENIConfig 使用,形成一个细致的网络安全体系。详细说明链接:https://docs.aws.amazon.com/eks/latest/userguide/cni-custom-network.html

EKS 为保证高可用通常跨 2,3 个可用区(AZ)。当每个 AZ 对应一个 Pod 子网,一个 ENIConfig 时,用户可以将 ENIConfig 的名字设定为相应 AZ 的名字,这样 EKS 系统会自动匹配,判断节点属于那个 AZ,自动使用与 AZ 名字相同的 ENIConfig。

然而,当每个 AZ 有多个 Pod 子网,对应多个 ENIConfig 时,EKS 不知道如何分配 ENIConfig,需要用户手工为节点打上 annotation 来指定 ENIConfig。如何避免用户手工操作,自动实现该功能呢?面临两个问题:

  • 问题 1,如何在 Node 启动时自动打上 annotation,设置 ENIConfig

手工设置 annotation,举例如下:

kubectl annotate node ip-192-168-0-126.us-west-2.compute.internal k8s.amazonaws.com/eniConfig=EniConfigName1

EKS 托管节点组上并没有提供针对 annotation 的接口,仅支持 Label 和 taints 的设置接口。如果我们在节点激活后,通过另一个管理工具实现打 annotation 的动作,我们首先需要开发这样一个管理程序,而且可能面临节点的启动时间已经有 Pod 分配,需要进一步驱逐 Pod 的复杂度。如果我们选择在节点内的 AMI 中实现 annotation,我们就需要对 AMI 进行定制,例如在 AMI 的启动脚本中安装 kubectl,配置连接和权限等操作。这些方法改动都较大。

然而,我们在分析 CNI 的配置时,发现了 ENI_CONFIG_LABLE_DEF,它能够让我们通过设置 Label,便捷地实现和 Annotation 类似的效果。值得注意的是,Label 的优先级低于 Annotation,详情见链接 https://github.com/aws/amazon-vpc-cni-k8s

在托管节点组中设置 Label 的方法,举例如下:

因此,我们可以优雅地通过对节点组的 Bootstrap 设置标签,实现 ENIConfig 的自动指定功能。

  • 问题 2,Node 应该指定哪个 ENIConfig

在 EKS 中,Pod 的 Secondary ENI 无法跨可用区 (AZ)间进行绑定节点。因此,我们需要计算节点所处的 AZ,然后匹配同一个 AZ 的 ENIConfigs。对于同一个 AZ 内的多个子网,多个 ENIConfigs,可以简化为,每个托管节点组内,固定地选一个特定的 ENIConfig。不同的节点组设置不同的 ENIConfig,从而通过不同节点组利用起来 AZ 内的多个子网。

以上逻辑,通过 EC2 User Data 中以 Bash 代码的形式增加判断逻辑,判断节点的 AZ,选取 ENIConfig。

操作流程说明

环境准备

1. 创建 EKS 集群和 Cloud9 的 EKS 命令行客户端,操作步骤参考 EKS workshop 链接:https://catalog.workshops.aws/eks-immersionday/en-US/introduction

2. 创建 EKS POD 使用的子网,以及配置 EKS 中的 ENIConfigs

每个 AZ 创建 2 个 POD 需要用的子网

为每个 pod 的子网创建 ENIConfig,Cloud9 Terminal 中执行如下语句,其中每个 AZ 选一个子网,将 ENIConfig 的名字命名为 AZ 的名字,AZ 内另一个子网 ENIConfig 的名字可以自定义设置(本例 ENIconfig 名字设为子网的名字)

eniConfName=ap-southeast-1a
new_subnet_id_1=subnet-08316a2d3e8be4f1c
cluster_security_group_id=sg-0a08c1f20ba2d31fa

cat >$eniConfName.yaml <<EOF
apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata: 
  name: $eniConfName
spec: 
  securityGroups: 
    - $cluster_security_group_id
  subnet: $new_subnet_id_1
EOF

kubectl apply -f $eniConf.yaml

查看建立好的 eniconfig

kubectl get ENIConfigs

配置 AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG 环境变量为 True

kubectl set env daemonset aws-node -n kube-system AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG=true

3. 创建一个节点组,拷贝托管节点组自动生成 Template 的变量值

选择 1 个节点

查看节点组自动创建的 Templte,EC2>EC2 launch Template > Advanced details

查看 Advanced details 中的 userData

拷贝 User data,记录以下 Key 的 Value

B64_CLUSTER_CA

API_SERVER_URL

K8S_CLUSTER_DNS_IP

4. 建立 EC2 launch Template,设置 User Data

在 Template 的 Advanced details 中添加 User data

User data 内容如下:

MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="//"

--//
Content-Type: text/x-shellscript; charset="us-ascii"
#!/bin/bash
set -ex

# user input area start

# set the node group name every time when you create a new node group
varnodegroup="ngNameXXX"

# plase set the AZ node cidr and corresponding eniconfig name for all AZ
declare -A myMap=(["172.31.16.0/20"]="pod-1a-subnet-3" ["172.31.32.0/20"]="pod-1b-subnet-3" ["172.31.0.0/20"]="pod-1c-subnet-3")

# set cluster info once , copy these value from  node group auto generated template
B64_CLUSTER_CA=LS0tXXXXXXXS0tLS0K
API_SERVER_URL=https://35XXXXXXXXXX50.gr7.ap-southeast-1.eks.amazonaws.com
K8S_CLUSTER_DNS_IP=10.100.0.10

# user input area end

# get node primary ip
my_ip=$(ip route get $K8S_CLUSTER_DNS_IP | awk -F"src " 'NR==1{split($2,a," ");print a[1]}')

# function of finding out which az the node belong to
function isIPbelongCidr
{
ip=$1
cidr=$2
max=`ipcalc -mbn $cidr |grep 'BROADCAST='|awk -F 'BROADCAST=' '{print $2}'`
min=`ipcalc -mbn $cidr |grep 'NETWORK='|awk -F 'NETWORK=' '{print $2}'`
MIN=`echo $min|awk -F"." '{printf"%.0f\n",$1*256*256*256+$2*256*256+$3*256+$4}'`
MAX=`echo $max|awk -F"." '{printf"%.0f\n",$1*256*256*256+$2*256*256+$3*256+$4}'`
IPvalue=`echo $ip|awk -F"." '{printf"%.0f\n",$1*256*256*256+$2*256*256+$3*256+$4}'`
if [ "$IPvalue" -ge "$MIN" ] && [ "$IPvalue" -le "$MAX" ]
then
echo "1"
return 1
else
echo "0"
return 0
fi
}

# get the eni config name
vareniconfig="NONE"
for key in ${!myMap[*]};do
echo $key
echo ${myMap[$key]}
if [ $(isIPbelongCidr "$my_ip" $key) -gt 0 ]
then
vareniconfig=${myMap[$key]}
break
fi
done

#set eniconf to node label
if [ $vareniconfig != "NONE" ]
then
/etc/eks/bootstrap.sh eksworkshop-eksctl --kubelet-extra-args "--node-labels=k8s.amazonaws.com/eniConfig=$vareniconfig,eks.amazonaws.com/nodegroup=$varnodegroup" --b64-cluster-ca $B64_CLUSTER_CA --apiserver-endpoint $API_SERVER_URL --dns-cluster-ip $K8S_CLUSTER_DNS_IP
#echo "end bootstrap"  >> /tmp/tmp20231020.txt
fi

#echo "test var" >> /tmp/tmp20231020.txt
#echo "my_ip is $my_ip" >> /tmp/tmp20231020.txt
#echo "vareniconfig is $vareniconfig" >> /tmp/tmp20231020.txt
#echo "varnodegroup is $varnodegroup" >> /tmp/tmp20231020.txt

--//--

在以上代码中,修改如下变量:

varnodegroup 设置为托管节点组的名字(本例节点组名字为 ngNameXXX);

myMap 是 MAP 数据结构,填写 Node 节点的子网 cidr,与之对应的 pod 子网 ENIConfig 的名字。Node 节点组选择几个子网,则对应的写几个 cidr,以及与之对应的 ENIconfig。如果不写 ENIconfig,则默认选择名字为 AZ 的 ENIConfig;

B64_CLUSTER_CA,API_SERVER_URL,K8S_CLUSTER_DNS_IP 这三个变量从 Default Template 中拷贝过来。

5. 创建托管节点组,节点组名字为 ngNameXXX

在创建托管节点组中选择 Template 为上一步创建的 Template,注意节点组的名字一定要对齐,其他参数保持默认值即可。

6. 查看效果

查看节点组的节点和 Label,Cloud9 执行如下命令:

kubectl get nodes --show-labels

如上图所示,看到 k8s.amazonaws.com/eniConfig 已经按照 Node 所在的 AZ 进行了正确的设置。注意,Node 经历了两个打 Label 的过程,节点的 Label 是逐渐打上去的,有可能会需要几分钟,Label 才会全。如果 Label 需要第一时间打上去,可以修改 Template User Data 中–kubelet-extra-args 的 label 设置。

查看 Pods,可以观察到 coredns 的 Pod IP

kubectl get pods -A -o wide

如上图所示,coredns-6996c975cb-tzgj8 所在的节点是 ip-172-31-25-222.ap-southeast-1.compute.internal,属于 node-1a-subnet-1 子网。Pod 的 IP 是 172.31.128.52,属于 172.31.128.0/20,处于子网是 pod-1a-subnet-3,与预期一致。

方案分阶段部署规划

在设计 EKS 时,运维和开发工程师会提前精心规划 EKS 网络节点,但是在实际生产环境中,常会涉及到 EC2 逐渐迁移到 EKS 的过程,在此期间,工程师不希望 EKS 一开始就预占所有 IP。因此,根据本文的指引,我们可以分三个阶段规划和部署 EKS。

第一阶段,EKS 将部署一个包含多个 AZ 的 Node 子网, 通常我们都会建议放在私有子网。在每个 AZ 中加入一个默认的 Pod 子网。每个 ENIConfig 的名称各自与 AZ 的名称匹配,不需要设置 k8s.amazonaws.com/eniConfig,节点会自动匹配 AZ 内的Pod。如下图所示:

第二阶段,随着应用数量的增加,每个 AZ 中添加了第二个 Pod 子网。这时设置 ENIConfig 的名称,通过利用 EC2 launch template,通过 Userdata 中的代码自动为 Node 上的 Pod 匹配第二个 Pod 子网。如下图所示,其中 Label k8s.amazonaws.com/eniConfig 简化为 enilabel。

第三阶段,随着应用的持续增长,EKS 将会添加第三个,以及更多的 Pod 子网,并设置更多的 ENIConfig。

  • Option1:为每个托管节点组指定 Pod 子网,仍然是每个 AZ 的 node,指定一个 ENIConfig,但是不同的托管节点组之间对应 Pod 子网不同。
  • Option2:为每个托管节点组指定 Pod 子网,每个 AZ 的 node,指定多个 ENIConfig,从 Option1 的一对一修改为一对多,用户可以设计匹配策略,比如 hash 映射等。在 UserData 的 myMap 中可以为 Value 指定多个值,用户可在 template 中自定义函数,比如 hansh 函数,从多个可用的 ENIConfig 中,选出一个 ENIConfig。Option2 的策略,如下图所示:

方案约束

实施方案时,注意以下内容:

1、建议每个托管节点组设置一个 Template;

2、部署规划第三阶段 Option2,需要修改 UserData 中代码,自定义一对多的筛选函数;

3、节点组自动生成的 Template 包含了两个打 Label 的流程,注意 Label 是逐渐打上去的,可能持续 2 分钟;

4、本方法没有设置 annotation,如果客户自己设置了 annotation,annotation 的优先级比 Label 高;

5、在该方案里,没有设置节点的 MaxPods,实际生产时需要根据节点的实例类型进行调整。

方案收益

本文设计并实现了 EKS 托管节点组自动根据 Node 所在 AZ 选择 Pod 子网的 ENIConfig 的功能,带来了以下显著的优点:

一、通过自定义网络,我们能够区分 Node 和 Pod 的 IP 地址池,使得网络安全组的控制实现更为精细,确保了网络的高度安全性。

二、在进行网络分析时,我们可以更加轻松地区分出管理节点的 Node 流量以及应用的 Pod 流量,为网络管理画像提供更为准确的数据。

三、在部署 EKS 时,可以灵活调整 Pod 的子网 IP 地址池,解决了规划时对精确性要求过高或者占用过多 IP 地址的问题,充分展现了网络的自由可扩展性。

四、我们通过 EC2 Launch Template 实现了这一切,既没有对 EKS 的 AMI 进行修改,也没有入侵节点应用,达到了兼顾灵活性和稳定性的最佳效果。

参考链接

官网 EKS custom network https://docs.aws.amazon.com/eks/latest/userguide/cni-custom-network.html

EKS immersionday https://catalog.workshops.aws/eks-immersionday/en-US/introduction

CNI GitHub https://github.com/aws/amazon-vpc-cni-k8s

EKS AMI bootstrap https://github.com/awslabs/amazon-eks-ami/blob/master/files/bootstrap.sh

本篇作者

王跃

AWS 解决方案架构师,负责基于 AWS 云平台的解决方案咨询和设计,在系统架构、大数据、网络、应用发领域有丰富的研发和实践经验。