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

EKS动手实验合集请参考这里

EKS 1.30 版本 @2024-07 AWS Global区域测试通过

一、背景及网络场景选择

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)方案二、更换Kubenetus社区的CNI并配置EKS Pod使用非VPC IP地址

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

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

VPC和EKS都支持使用扩展地址段。在此方案下,继续使用EKS默认的VPC CNI,首先为现有VPC扩展IP地址,并配置EKS使用扩展IP地址。本方案影响较小,过度平滑,不需要额外创建VPC,也不需要重新部署EKS网络CNI。

要添加的IP,通常是VPC的CIDR扩展,或者是100.64的保留网段。AWS云上100.64是定义为保留网段使用。

需要注意的是,扩展IP地址存在范围限制,并不是任意IP都可以添加到VPC的扩展范围内,请注意参考这里文档描述的限制范围。如果此IP段不可接受,则因考虑其他方案。

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

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

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

在这张图内,以AZ1的网络为例进行讲解,分成几个层面:

  • VPC的CIDR是172.31.0.0/16,因此现有的子网都在这个范围内
  • 部署NAT Gateway的公有子网,分配了是172.31.0.0/20的子网
  • 部署EKS的Nodegroup的节点组是在私有子网,分配了172.31.48.0/20的子网
  • 为了模拟VPC扩容,在VPC上新增了100.64.0.0/16的网段,并且分配了一个Pod专用子网100.64.0.0/20,且这个子网也是私有子网,对互联网的交互是依赖NAT Gateway的

下面开始描述配置过程。

二、为现有VPC扩展地址段

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、为要使用ELB的子网打标签

(1)使用Internet-facing ELB,面向公网提供服务

找到当前的VPC,找到有EIP和NAT Gateway的Public Subnet,为其添加标签(多个AZ需要同时添加):

  • 标签名称:kubernetes.io/role/elb,值:1

如果之前标签已经存在,请跳过这一步。

(2)使用Internal ELB,面向Private内部子网提供服务

在创建私有ELB时候,可选的任意子网创建,可以选择一个独立的私有子网部署ELB,也可以选择Node所在子网,也可以选择Pod所在子网。

本文以使用Node所在子网为例。在VPC界面上,找到Node使用的私有子网,为其添加标签(多个AZ需要同时添加):

  • 标签名称:kubernetes.io/role/internal-elb,值:1

接下来请重复以上工作,每个AZ的子网都实施相同的配置,注意第一项标签值都是1。至此VPC配置完毕。

三、配置并配置EKS集群

1、创建一个默认EKS集群

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


apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: eksworkshop
  region: ap-southeast-1
  version: "1.30"

vpc:
  clusterEndpoints:
    publicAccess:  true
    privateAccess: true
  subnets:
    private:
      ap-southeast-1a: { id: subnet-04a7c6e7e1589c953 }
      ap-southeast-1b: { id: subnet-031022a6aab9b9e70 }
      ap-southeast-1c: { id: subnet-0eaf9054aa6daa68e }

kubernetesNetworkConfig:
  serviceIPv4CIDR: 10.50.0.0/24

managedNodeGroups:
  - name: managed-ng
    labels:
      Name: managed-ng
    instanceType: t3.2xlarge
    minSize: 3
    desiredCapacity: 3
    maxSize: 6
    privateNetworking: true
    subnets:
      - subnet-04a7c6e7e1589c953
      - subnet-031022a6aab9b9e70
      - subnet-0eaf9054aa6daa68e
    volumeType: gp3
    volumeSize: 100
    tags:
      nodegroup-name: managed-ng
    iam:
      withAddonPolicies:
        imageBuilder: true
        autoScaler: true
        externalDNS: true
        certManager: true
        efs: true
        ebs: true
        fsx: true
        albIngress: true
        awsLoadBalancerController: true
        xRay: true
        cloudWatch: true

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

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

eksctl create cluster -f eks-in-private-subnet.yaml

2、部署AWS Load Balancer Controller

有关详细部署Load Balancer Controllerd的说明请参考前文的实验

3、部署CloudWatch Container Insight

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

四、修改EKS的网络参数为Pod指定单独子网

注意:在修改本参数之后,必须重新创建新的Nodegroup才可以生效。

1、调整aws-vpc-cni的参数分别设置Node子网和Pod子网

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

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

返回如下信息表示配置成功。

daemonset.apps/aws-node env updated

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

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

apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata: 
  name: ap-southeast-1a
spec: 
  subnet: subnet-0691037d70aac39da
---
apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata: 
  name: ap-southeast-1b
spec: 
  subnet: subnet-096d7481a653e3f47
---
apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata: 
  name: ap-southeast-1c
spec: 
  subnet: subnet-0db55d7fb02249825

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

kubectl apply -f eniconfig.yaml

执行命令kubectl get ENIConfigs验证配置是否成功。返回结果如下则表示设置成功。

NAME              AGE
ap-southeast-1a   91s
ap-southeast-1b   91s
ap-southeast-1c   90s

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

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

返回如下结果表示设置成功。

daemonset.apps/aws-node env updated

为了查询上述配置是否生效,可以自行如下命令:

kubectl describe daemonset aws-node -n kube-system | grep ENI_CONFIG_LABEL_DEF

返回如下结果表示设置成功。

      ENI_CONFIG_LABEL_DEF:                topology.kubernetes.io/zone
      ENI_CONFIG_LABEL_DEF:                topology.kubernetes.io/zone
      ENI_CONFIG_LABEL_DEF:                topology.kubernetes.io/zone

2、使用Node子网创建新的Nodegroup节点组

注意:修改了EKS网络参数后,必须重新创建新的Nodegroup节点组,原先的节点组不会发生过变化、原先的Pod也不会自动迁移到新分配的子网。另外如果EKS版本低于1.28版本,那么建议执行如下命令确认插件为最新。如果已经1.28版本可以暂时不用升级。

升级命令如下(会对现有pod造成短暂网络影响)。

构建如下内容,保存为new-subnet-for-pod文件。注意这里建议使用相同处理器架构的Nodegroup,这样便于Pod可以自动漂移过去。

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: eksworkshop
  region: ap-southeast-1
  version: "1.30"

managedNodeGroups:
  - name: newng
    labels:
      Name: newng
    instanceType: t3.2xlarge
    minSize: 3
    desiredCapacity: 3
    maxSize: 6
    volumeType: gp3
    volumeSize: 100
    volumeIOPS: 3000
    volumeThroughput: 125
    tags:
      nodegroup-name: new-subnet-for-pod
    iam:
      withAddonPolicies:
        imageBuilder: true
        autoScaler: true
        externalDNS: true
        certManager: true
        efs: true
        ebs: true
        fsx: true
        albIngress: true
        awsLoadBalancerController: true
        xRay: true
        cloudWatch: true

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

eksctl create nodegroup -f new-subnet-for-pod.yaml

3、把旧的Nodegroup删除

如果新创建的Nodegroup是采用相同处理器架构的EC2,那么删除旧的Nodegroup时候,原有的Pod会自动漂移到新的Nodegroup上。反之,则要看本应用对应的镜像仓库上是否有分别提供X86_64版本和ARM版本的容器镜像,如果有对应版本的话原有的Pod会自动漂移到新的Nodegroup上,如果没有的话应用Pod会启动失败。

执行如下命令:

eksctl delete nodegroup --name managed-ng --cluster eksworkshop 

删除完毕后,即可在新的节点上用新的网络配置启动应用,这时候应用Pod网段将会与Node网段独立开。

五、测试多种ELB部署场景

1、在公有子网部署ALB Ingress并测试从互联网访问

(1)部署应用

构建应用配置文件。

---
apiVersion: v1
kind: Namespace
metadata:
  name: public-alb
---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: public-alb
  name: nginx
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: nginx
  replicas: 3
  template:
    metadata:
      labels:
        app.kubernetes.io/name: nginx
    spec:
      containers:
      - image: public.ecr.aws/nginx/nginx:1.27-alpine-slim
        imagePullPolicy: Always
        name: nginx
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  namespace: public-alb
  name: nginx
spec:
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
  type: NodePort
  selector:
    app.kubernetes.io/name: nginx
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  namespace: public-alb
  name: ingress-for-nginx-app
  labels:
    app: ingress-for-nginx-app
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
spec:
  ingressClassName: alb
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: nginx
            port:
              number: 80

将上述配置文件保存为public-alb.yaml。然后执行如下命令启动:

kubectl apply -f public-alb.yaml

返回结果:

namespace/public-alb created
deployment.apps/nginx created
service/nginx created
ingress.networking.k8s.io/ingress-for-nginx-app created

(2)查看ALB Ingress入口地址并测试

执行如下命令可查看:

kubectl get ingress -n public-alb

返回结果:

NAME                    CLASS   HOSTS   ADDRESS                                                                        PORTS   AGE
ingress-for-nginx-app   alb     *       k8s-publical-ingressf-e3bf1572ab-1992535472.ap-southeast-1.elb.amazonaws.com   80      14s

(3)测试ALB访问

使用浏览器访问上一步获得的ALB的地址,即可看到应用部署成功。

2、在公有子网创建NLB并通过互联网访问

如果需求方式是网络流量发布而非HTTP请求发布,那么可不使用ALB Ingress,而是使用NLB发布四层端口。前文在创建子网部分已经描述了如何在Subnet上打上EKS的tag,由此EKS会自动找到对应子网。

(1)部署测试应用

构建如下测试应用:

---
apiVersion: v1
kind: Namespace
metadata:
  name: public-nlb
---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: public-nlb
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: nginx
  replicas: 3
  template:
    metadata:
      labels:
        app.kubernetes.io/name: nginx
    spec:
      containers:
      - image: public.ecr.aws/nginx/nginx:1.27-alpine-slim
        imagePullPolicy: Always
        name: nginx
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  namespace: public-nlb
  name: "service-nginx"
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
spec:
  loadBalancerClass: service.k8s.aws/nlb
  selector:
    app.kubernetes.io/name: nginx
  type: LoadBalancer
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80

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

kubectl apply -f public-nlb.yaml

返回结果:

namespace/public-nlb created
deployment.apps/nginx-deployment created
service/service-nginx created

(2)查看Public NLB的入口地址

查看NLB入口。

kubectl get service service-nginx -n public-nlb -o wide 

返回结果如下。

NAME            TYPE           CLUSTER-IP    EXTERNAL-IP                                                                          PORT(S)        AGE    SELECTOR
service-nginx   LoadBalancer   10.50.0.224   k8s-publicnl-servicen-112bd18a54-a628f86f0217bffa.elb.ap-southeast-1.amazonaws.com   80:31011/TCP   118s   app.kubernetes.io/name=nginx

即可获得NLB的入口地址。

(3)测试公有NLB

从互联网访问上一步查询出来的NLB入口,可看到访问正常。

3、在私有子网部署只能从内网访问的私有NLB

(1)构建应用和私有NLB配置

在某些模式下,我们只需要对VPC内网或者其他VPC、专线等另一侧暴露内网NLB。因此这时候就不需要构建基于Internet-facing的公网NLB了,而是将NLB配置为私有NLB,其访问入口也只能通过VPC访问。构建如下一段配置:

---
apiVersion: v1
kind: Namespace
metadata:
  name: private-nlb-fixed-ip
---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: private-nlb-fixed-ip
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: nginx
  replicas: 3
  template:
    metadata:
      labels:
        app.kubernetes.io/name: nginx
    spec:
      containers:
      - image: public.ecr.aws/nginx/nginx:1.27-alpine-slim
        imagePullPolicy: Always
        name: nginx
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  namespace: private-nlb-fixed-ip
  name: "service-nginx"
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
    service.beta.kubernetes.io/aws-load-balancer-name: myphpdemo
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold: "2"
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold: "2"
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval: "10"
    service.beta.kubernetes.io/aws-load-balancer-attributes: load_balancing.cross_zone.enabled=true
    service.beta.kubernetes.io/aws-load-balancer-private-ipv4-addresses: 172.31.48.254, 172.31.64.254, 172.31.80.254
spec:
  loadBalancerClass: service.k8s.aws/nlb
  selector:
    app.kubernetes.io/name: nginx
  type: LoadBalancer
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80

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

kubectl apply -f private-nlb.yaml

返回结果:

namespace/private-nlb-fixed-ip created
deployment.apps/nginx-deployment created
service/service-nginx created

(2)查看Private NLB入口地址并测试

查看NLB入口。

kubectl get service service-nginx -n private-nlb -o wide
NAME            TYPE           CLUSTER-IP   EXTERNAL-IP                                                                          PORT(S)        AGE    SELECTOR
service-nginx   LoadBalancer   10.50.0.34   k8s-privaten-servicen-3fe387f3ed-3b1e7aef54125420.elb.ap-southeast-1.amazonaws.com   80:32631/TCP   118s   app.kubernetes.io/name=nginx

这个地址将会解析为内网IP。

(3)测试从VPC内访问私有NLB

从VPC内访问以上的NLB入口地址,即可看到应用加载正常。

六、确认以上Pod运行在和Node相互独立的网段

上述几个场景的实验完整后,EKS集群上分别有了可从外网访问的ALB Ingress、Public NLB和Private NLB,以及他们背后的应用pod。

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

七、删除Pod环境(不删除集群和Node)

执行如下命令:

kubectl delete -f private-nlb.yaml
kubectl delete -f public-alb.yaml
kubectl delete -f public-nlb.yaml

八、参考文档

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

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

使用CNI自定义网络:

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

EKS的NLB参数说明:

https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.5/guide/service/nlb