为VPC扩展IP地址并配置EKS Pod使用独立的IP地址段

有关EKS系列文档和实验的汇总,请点击这里跳转

一、背景

1、关于EKS的默认CNI

AWS EKS默认使用AWS VPC CNI(了解更多点这里),所有的Pod都将自动获得一个本VPC内的IP地址,从外部网络看Pod,它们的表现就如同一个普通EC2。这是AWS EKS默认的网络模式,也是强烈推荐的使用模式。

2、需要额外IP的解决方案(三选一)

某些场景下,可能当前创建VPC时候预留IP地址过少,要大规模启动容器会遇到VPC内可用IP地址不足。此时有几个办法:

(1)方案一、创建全新的VPC运行EKS

由于云上可以随时创建多个VPC,并可通过多种方式实现VPC和应用之间的互通,因此创建一个独立的VPC运行新的应用是最快捷的解决地址不足的办法。EKS的命令行管理工具eksctl默认的参数就是创建一个全新VPC。当创建全新VPC后,一般可通过如下方式让两个VPC之间的服务互通:

  • 路由模式。如果两个VPC IP地址段不重叠且可路由,可通过VPC Peering或Transit Gateway打通两个VPC网络,实现三层和四层协议的全面互通;
  • 通过公网方式。在一个VPC上的应用前配置好ELB并对外发布服务,然后对ELB可限制来源IP白名单,仅允许另一个VPC访问;
  • 通过内网方式。在一个VPC上配置PrivateLink映射Endpoint Service,并在另一个VPC内提供Endpoint服务。

除此以外,可能还有其他方式用于实现跨VPC的应用互访,再次不逐个罗列。

(2)方案二、为VPC扩展IP地址并配置EKS Pod使用独立的IP地址段

VPC和EKS都支持使用扩展地址段。在此方案下,继续使用EKS默认的VPC CNI,首先为现有VPC扩展IP地址,并配置EKS使用扩展IP地址。本方案影响较小,过度平滑,不需要额外创建VPC,也不需要重新部署EKS网络CNI。需要注意的是,扩展IP地址存在范围限制,并不是任意IP都可以添加到VPC的扩展范围内,请注意参考这里文档描述的限制范围。如果此IP段不可接受,则因考虑其他方案。

(3)方案三、更换Kubenetus社区的CNI并配置EKS Pod使用非VPC IP地址

如果希望EKS上的Pod完全不使用本VPC的IP地址,这可以更换EKS的CNI网络插件,官方文档这里做了介绍。在集群创建后,可删除默认的AWS VPC CNI,然后安装WeaveNet等插件。

本文描述方案二,即为VPC扩展IP。

3、为VPC扩展IP地址并配置EKS Pod使用独立的IP地址段的架构图

如上文描述,本文使用方式2,也就是为VPC扩展IP地址并配置EKS Pod使用独立的IP地址段。架构图如下。

下面开始描述配置过程。注:文档描述的IP地址段与上边的架构图地址段有出入,以实际配置为准。

二、为VPC添加第二IP地址段

1、为VPC添加IP地址

首先查看当前VPC的IP范围,并查看AWS官方文档描述的可扩充IP范围的限制。

首先进入VPC界面,选择要添加IP地址的VPC,点击右上角的操作,选择修改CIDR。如下截图。

进入添加IP地址段界面,添加上第二个地址段,例如100.64.0.0/16,然后点击右侧的分配按钮,再点击下方的保存。如下截图。

2、为第二IP地址段创建子网

进入创建子网界面,选择对应的VPC,创建新的子网,并使用刚才新添加的IP地址段。例如本例中100.64.0.0/16被添加到VPC中,那么子网可采用100.64.1.0/24100.64.2.0/24100.64.3.0/24分别对应三个AZ。

如此分别为3个AZ都创建好对应的Pod使用的子网。如下截图。

操作完成。

3、为新增加的子网配置路由表

新创建好的子网会绑定到VPC默认路由表,因此还需要将新创建的子网绑定到和Node节点同一个路由表。进入路由表界面,查看Node所在的private subnet的路由表,可以看到当前只关联了三个Node子网。点击编辑按钮。如下截图。

将新创建的Pod子网关联到Node所在的Private子网的路由表上。如下截图。

添加子网完成后,确认下Node所在的子网和Pod所在的子网,所对应的路由表的下一跳是NAT Gateway。这是因为这两个子网都是私有子网,没有Elastic IP,因此默认网关下一跳都必须是NAT Gateway。如下截图。

备注:如果您使用了Gateway Load Balancer做的集中网络流量检测方案,那么这里的默认网关下一跳应该是TGW。如果您没有使用Gateway Load Balancer,默认下一跳都是NAT Gateway。

4、确认VPC和Subnet带有EKS的ELB所需要的标签

请确保本子网已经设置了正确的路由表,且VPC内包含NAT Gateway可以提供外网访问能力。然后接下来为其打标签。

找到当前的VPC,找到有EIP和NAT Gateway的Public Subnet,为其添加标签:

标签名称:kubernetes.io/role/elb,值:1
标签名称:kubernetes.io/cluster/eksworkshop,值:shared
接下来进入Private subnet,为其添加标签:

标签名称:kubernetes.io/role/internal-elb,值:1
标签名称:kubernetes.io/cluster/eksworkshop,值:shared
接下来请重复以上工作,三个AZ的子网都实施相同的配置,注意第一项标签值都是1。

至此VPC配置完毕。

三、配置EKS集群

1、创建一个不包含Node节点的空白EKS集群(设置Node所在子网)

首先构建配置文件,替换其中的子网ID为Node所在的子网ID。

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: eksworkshop
  region: cn-northwest-1
  version: "1.22"

vpc:
  clusterEndpoints:
    publicAccess:  true
    privateAccess: true
  subnets:
    private:
      cn-northwest-1a: { id: subnet-0af2e9fc3c3ab08b4 }
      cn-northwest-1b: { id: subnet-0bb5aa110443670a1 }
      cn-northwest-1c: { id: subnet-008bcabf73bea7e58 }

kubernetesNetworkConfig:
  serviceIPv4CIDR: 10.50.0.0/24

cloudWatch:
  clusterLogging:
    enableTypes: ["api", "audit", "authenticator", "controllerManager", "scheduler"]
    logRetentionInDays: 30

将以上内容保存为eks-without-nodegroup.yaml,然后运行如下命令启动集群。

eksctl create cluster -f eks-without-nodegroup.yaml

2、调整aws-vpc-cni的参数(设置Pod所在子网)

允许EKS自定义CNI网络插件的参数,执行如下命令:

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

进入AWS控制台,从子网界面查看子网信息,确定Pod所在子网,获得可用区ID和子网ID。将三个Pod子网的信息分别复制下来。如下截图。

用文本编辑器编辑如下文件,替换其中的可用区ID和子网ID为Pod所在子网的ID,然后保存为eniconfig.yaml文件。

apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata: 
  name: cn-northwest-1c
spec: 
  subnet: subnet-045930b2b272266a0
---
apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata: 
  name: cn-northwest-1b
spec: 
  subnet: subnet-0e1b4e449662b8829
---
apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata: 
  name: cn-northwest-1a
spec: 
  subnet: subnet-0a6e4899eb92cb204

将以上配置文件保存为eniconfig.yaml文件。然后执行如下命令:

kubectl apply -f eniconfig.yaml

接下来为EKS设置标签,允许Node使用对应子网。执行如下命令:

kubectl set env daemonset aws-node -n kube-system ENI_CONFIG_LABEL_DEF=topology.kubernetes.io/zone

修改完毕后,使用如下命令可输出所有网络参数,然后对比可检查下是否生效。

kubectl set env daemonset aws-node -n kube-system

3、创建新的Nodegroup节点组

注意:本实验采用的是创建全新集群,并修改网络配置,然后创建节点组。如果是先有集群,修改网络配置后也要重新创建Node才可以生效。

构建如下内容,保存为newnodegroup.yaml文件。

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: eksworkshop
  region: cn-northwest-1
  version: "1.22"

managedNodeGroups:
  - name: ng1
    labels:
      Name: ng1
    instanceType: m5.2xlarge
    minSize: 3
    desiredCapacity: 3
    maxSize: 6
    privateNetworking: true
    volumeType: gp3
    volumeSize: 100
    tags:
      nodegroup-name: ng1
    iam:
      withAddonPolicies:
        imageBuilder: true
        autoScaler: true
        certManager: true
        efs: true
        albIngress: true
        xRay: true
        cloudWatch: true

保存配置完毕后,执行如下命令生效:

eksctl create nodegroup -f newnodegroup.yaml

四、部署AWS Load Balancer Controller

在EKS 1.21以上版本,由于API的变化,创建NLB和ALB Ingress都被整合到了Load Balancer Controller中。所以如果不部署Load Balancer Controller,NLB也是无法创建成功的。在部署Load Balancer Controller完成后,可以根据需要只部署NLB或者只部署ALB Ingress,或者同时部署两种负载均衡。

分别执行以下命令,完成AWS Load Balancer Controller的部署。

eksctl utils associate-iam-oidc-provider --region cn-northwest-1 --cluster eksworkshop --approve
eksctl create iamserviceaccount --cluster=eksworkshop --namespace=kube-system --name=aws-load-balancer-controller --attach-policy-arn=arn:aws-cn:iam::420029960748:policy/AWSLoadBalancerControllerIAMPolicy --override-existing-serviceaccounts --approve
kubectl apply --validate=false -f https://blogimg.bitipcman.com/workshop/eks101/cert-manager_v1.8.1.yaml
kubectl apply -f https://blogimg.bitipcman.com/workshop/eks101/crds.yaml
kubectl apply -f https://blogimg.bitipcman.com/workshop/eks101/v2_4_1_full-zhy.yaml

五、在公有子网创建NLB并使用NodePort方式暴露应用

如果需求方式是使用Node节点的高位端口暴露应用,那么可不使用ALB Ingress,只是使用简单的NodePort方式暴露应用。前文在创建子网部分已经描述了如何在Subnet上打上EKS的tag,由此EKS会自动找到对应子网。

构建如下测试应用(Nginx):

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: public.ecr.aws/nginx/nginx:1.21.6-alpine
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: "service-nginx"
  annotations:
        service.beta.kubernetes.io/aws-load-balancer-type: nlb
spec:
  selector:
    app: nginx
  type: LoadBalancer
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80

将以上配置文件保存为nginx-nlb.yaml,然后执行如下命令启动:

kubectl apply -f nginx-nlb.yaml

查看NLB入口。

kubectl get service service-nginx -o wide 

启动完成后,查看所有pod的IP,可发现除默认负责网络转发的kube-proxy和aws-node(VPC CNI)还运行在Node所在的Subnet上之外,新创建的应用都会运行在新的子网和IP地址段上。如下截图。

六、在私有子网部署内网的NLB

在某些模式下,我们只需要对VPC内网或者其他VPC、专线等另一侧暴露内网NLB。因此这时候就不需要构建基于Internet-facing的公网NLB了。这种场景下,构建如下一段配置:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: public.ecr.aws/nginx/nginx:1.21.6-alpine
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: "service-nginx"
  annotations:
        service.beta.kubernetes.io/aws-load-balancer-type: nlb-ip
        service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
        service.beta.kubernetes.io/aws-load-balancer-scheme: internal
        service.beta.kubernetes.io/aws-load-balancer-attributes: load_balancing.cross_zone.enabled=true
spec:
  selector:
    app: nginx
  type: LoadBalancer
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80

七、部署CloudWatch Container Insight

部署CloudWatch Container Insight的方法与此前方法相同。可参考这篇文档。

八、参考文档

Github上的AWS VPC CNI代码和文档:

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

使用CNI自定义网络:

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

EKS的NLB参数说明:

https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.4/guide/service/annotations/#lb-scheme