이번 포스트에서는 EKS Networking이라는 주제로 이어가 보겠습니다.

내용이 길어질 것으로 보아 파트를 나눠서 작성할 예정이며, EKS Networking Part1에서는 EKS의 VPC CNI와 기본 환경을 검토해보고, 파드와 노드 통신을 확인해보겠습니다. 이후 EKS Networking Part2에서 Loadbalancer 유형의 서비스의 구성과 Ingress에 대해서 확인해 보겠습니다.

 

 

목차

  1. AWS VPC CNI 개요
  2. 파드 생성 갯수 제한
  3. 실습 환경 배포
  4. 노드 정보 확인
  5. 파드 통신 확인

 

1. AWS VPC CNI 개요

CNI란?

CNI(Container Network Interface)는 CNCF(Cloud Native Computing Foundation)의 프로젝트로 Specification과 리눅스 컨테이너의 네트워크 인터페이스를 구성하기 위한 plugin을 작성하기 위한 라이브러리로 구성됩니다.

CNI는 컨테이너의 네트워크 연결성과 컨테이너가 삭제되었을 때 할당된 리소스를 제거하는 역할에 집중합니다.

참고: https://github.com/containernetworking/cni

 

보통 Kubernetes에 어떤 CNI를 쓰느냐라고 얘기를 하면 의미가 통하기는 하지만, 실제로 calico, cilium, flannel 등은 CNI plugin이라고 할 수 있습니다.

Kubernetes 에서 CNI Plugin의 동작은 간략히 아래와 같이 이뤄집니다.

  1. Kubelet이 Container Runtime에 컨테이너 생성을 요청
  2. Container Runtime이 컨테이너의 Network Namespace를 생성
  3. Container Runtime이 CNI 설정과 환경변수를 표준 입력으로 CNI Plugin 호출
  4. CNI Plugin이 컨테이너의 네트워크 인터페이스를 구성하고, IP를 할당하고, 호스트 네트워크 간의 veth pair 를 생성
  5. CNI Plugin이 호스트 네트워크 네임스페이스와 컨테이너 네트워크 네임스페이스에 라우팅을 구성

 

관련하여 CNI plugin이 실제로 어떤 방식으로 호출되고 동작하는지에 대해서 재미난 실습이 있어 별도의 포스트를 작성하였으니 아래를 확인해보시기 바랍니다.

https://a-person.tistory.com/32

 

 

AWS VPC CNI 소개

오픈소스 CNI plugin들은 파드 네트워크를 VxLAN이나 IPIP와 같은 프로토콜을 이용해 overlay 네트워크로 구성하는 경우가 많습니다. 이것은 노드가 위치한 네트워크가 물리적으로 구성되어 있거나 하는 이유로 CNI가 직접 IP를 관리하기 어려운 이유도 있을 수 있고, 한편으로는 어떤 네트워크 환경에서든 유연하게 사용 가능하게 하기 위한 목적일 수도 있습니다.

 

반면 AWS VPC CNI는 AWS 환경에 최적화되어 VPC 네트워크와 동일한 IP 주소를 파드에 할당합니다. 또 AWS VPC CNI는 eBPF 기반의 Network Policy를 기본으로 제공하고 있습니다. (앞서 노드에 배포된 파드를 확인하면서 aws-node의 컨테이너가 각 VPC CNI, Network Policy Agent 인것을 확인했습니다)

 

AWS VPC CNI의 컴포넌트는 CNI 바이너리와 ipamd로 이뤄져 있습니다.

  • CNI binary: 파드 네트워크를 구성
  • ipamd: IP Address Management(IPAM) 데몬으로, ENI를 관리하고 사용가능한 IP 혹은 prefix를 warm-pool로 관리합니다.

앞서 실습을 통해 EKS를 생성한 이후 노드에 ENI가 2개 생성된 경우가 확인했는데 이것은 VPC CNI의 동작과도 관련이 있습니다.

 

먼저 인스턴스가 생성되면 EC2는 첫번째 ENI를 생성해 연결하고, VPC CNI가 hostNetwork 모드로 Primary IP로 실행됩니다. 노드가 프로비저닝 되면, CNI 플러그인 Primary ENI에 IP 혹은 Prefix의 slots를 가져와 IP Pool에 할당합니다. 이러한 IP Pool을 warm pool 이라고 하며 warm pool의 사이즈는 노드의 인스턴스 타입에 결정됩니다.

각 ENI는 인스턴스는 타입에 따라 제한된 slot 개수를 지원하기 때문에, 추가로 필요한 slot에 따라 CNI는 인스턴스에 추가 ENI를 할당 요청 합니다.

 

요약하면, 인스턴스 타입에 따라 IP Pool의 개수가 정해져 있고, 이때 slots 단위로 IP를 warm pool에 저장해두고, IP가 필요하면 warm pool에서 파드에 할당합니다. 인스턴스 타입에 따라 ENI가 가질 수 있는 IP 개수도 제한되기 때문에 warm pool이 증가할 때 추가 ENI가 필요할 수 있습니다.

 

아래 VPC CNI 문서의 그림을 살펴보면 flow를 확인할 수 있습니다.

새 slot이 필요할 때, Primary ENI에 여유 slot이 있으면 파드에 이 slot을 할당합니다. Primary ENI에 여유 slot이 없으면, Secondary ENI가 있는지 확인하고, 다시 Secondary ENI에서 여유 slot을 확인해 파드에 slot을 할당합니다.

Secondary ENI가 없는 경우, 인스턴스에 새로운 ENI 가용을 확인하여, 가능하다면 새로운 ENI를 할당합니다.

참고: https://docs.aws.amazon.com/eks/latest/best-practices/vpc-cni.html

 

이러한 절차에서 복잡한 느낌을 지울 수 없습니다.

 

AKS의 Azure CNI는 AWS VPC CNI와 유사하게 Virtual Network(EKS의 VPC)에서 Pod IP를 가져와 사용합니다. 이때 하나의 NIC에서 할당 가능한 IP개수의 제한은 없으므로, Azure CNI은 단순히 정의된 max-pods 개수 만큼 secondary IP로 배정합니다.

 

물론 VPC CNI는 slots 만큼 동적 할당을 하기 때문에 불필요한 IP를 static 하게 할당받지 않는 장점이 있을 수 있습니다.

참고로 AKS는 다양한 CNI plugin을 제공하고 있습니다.

먼저 VPC CNI와 유사하게 Virtual Network의 IP를 사용하는 Azure CNI가 있고, Azure CNI에서 동적으로 파드 IP할당하는 방식인 Azure CNI with dynamic IP allocation 이 있습니다. 또한 overlay network 사용하는 kubenet, Azure CNI overlay가 있습니다.

이때, Azure CNI overlay와 Azure CNI with dynamic IP allocation의 경우 dataplane을 cilium으로 오프로딩하는 Azure CNI powered by cilium 를 사용할 수 있습니다.

참고: https://learn.microsoft.com/en-us/azure/aks/concepts-network-cni-overview#choosing-a-cni

 

 

VPC CNI의 동작 과정은 아래 그림에서 살펴보실 수 있습니다.

출처: https://docs.aws.amazon.com/eks/latest/best-practices/vpc-cni.html

  1. 파드가 스케줄링 됩니다.
  2. kubelet은 Add 요청을 VPC CNI로 보냅니다.
  3. VPC CNI는 L-IPAM에 파드 IP를 요청합니다.
  4. L-IPAM에서 파드 IP를 반환합니다.
  5. VPC CNI는 네트워크 네임스페이스를 구성합니다.
  6. VPC CNI는 파드 IP를 kubelet으로 반환합니다.
  7. kubelet은 파드 IP를 파드에 할당합니다.

 

이 절차가 사실 일반적인 CNI plugin의 동작 과정과 조금 다르게 묘사되어 있습니다. kubelet이 IP를 할당한다라.. 쉽게 설명하려고 한건지 정확하진 않네요.

이 그림에서 VPC CNI의 구성요소와 전반적인 흐름을 이해하는 정도로 넘어가는게 좋을 것 같습니다.

 

추가로 해당 문서를 보면 L-IPAM에서 iptables의 NAT rules를 업데이트 한다고 표시도 있습니다. 아마도 VPC IP에 대한 정보를 알고 있으므로, 이를 바탕으로 VPC를 제외한 네트워크에 대해 SNAT 정책을 업데이트 하는 것으로 이해됩니다.

 

 

2. 파드 생성 갯수 제한

AWS는 인스턴스 타입별로 최대 ENI 개수와 ENI별 할당가능한 최대 IP 개수가 정해져 있습니다.

아래와 같은 명령으로 t3 시리즈에 해당하는 값을 확인할 수 있습니다.

$ aws ec2 describe-instance-types --filters Name=instance-type,Values=t3.\* \
 --query "InstanceTypes[].{Type: InstanceType, MaxENI: NetworkInfo.MaximumNetworkInterfaces, IPv4addr: NetworkInfo.Ipv4AddressesPerInterface}" \
 --output table
 --------------------------------------
|        DescribeInstanceTypes       |
+----------+----------+--------------+
| IPv4addr | MaxENI   |    Type      |
+----------+----------+--------------+
|  15      |  4       |  t3.2xlarge  |
|  6       |  3       |  t3.medium   |
|  12      |  3       |  t3.large    |
|  15      |  4       |  t3.xlarge   |
|  2       |  2       |  t3.nano     |
|  2       |  2       |  t3.micro    |
|  4       |  3       |  t3.small    |
+----------+----------+--------------+

 

예를 들어, t3.medium은 최대 3개의 ENI를 연결할 수 있고, ENI별 6개의 IP가 할당 가능 합니다.

총 3*6=18개 IP가 사용가능합니다만, 각 ENI에 할당되는 IP는 파드에서 사용할 수 없게 됩니다. 또한 VPC CNI와 kube-proxy가 hostNetwork를 사용하므로 이들은 IP가 필요 없습니다.

 

결론적으로, EKS에서 최대 파드 개수는 인스턴스 타입에 따라 아래와 같은 공식으로 계산됩니다.

(Number of network interfaces for the instance type * (the number of IP addresses per network interface - 1)) + 2

즉, t3.medium은 3*(6-1)+2= 17개 파드가 실행될 수 있습니다.

 

이는 인스턴스 유형마다 다를 수 있지만 최대 파드 개수에는 limit이 있습니다. vCPU 30개 미만의 EC2 인스턴스 유형에서는 최대 파드 개수가 110개로 제한되고, vCPU 30이상의 EC2 인스턴스 유형에서는 250개로 제한을 권고 합니다.

VPC CNI 절에서 설명한 ENI와 파드 생성 갯수 제한에 대한 테스트는 이후 노드 정보를 확인하는 과정에서 다시 살펴보겠습니다.

 

 

참고로 AKS는 노드에 생성 가능한 파드 갯수의 제한은 인터페이스와 관련이 없이 250으로 지정되어 있으며 또한 --max-pods 옵션으로 정의할 수 있습니다. 다만 --max-pods 옵션을 지정하지 않은 경우 CNI별로 일부 차이가 있으므로 이는 문서를 확인하시기 바랍니다.

https://learn.microsoft.com/en-us/azure/aks/quotas-skus-regions#service-quotas-and-limits

 

 

EKS에서는 인스턴스 타입과 ENI 제약으로 인해 최대 파드 개수가 제한되는 독특한 문제를 가지고 있습니다. 이를 해소하기 위해 ENI에 특정 IP prefix를 할당하는 Prefix Delegation(https://www.eksworkshop.com/docs/networking/vpc-cni/prefix/)와 같은 방식을 제공하고 있습니다.

 

 

AWS VPC CNI의 컨셉을 살펴보면 Azure CNI와 비슷한 문제점이 있고, 또한 해결을 위한 유사한 접근 방법을 가지고 있습니다.

첫번째 문제는 다수의 파드/노드가 생성되는 경우 VPC 자체의 IP가 고갈되는 문제입니다.

근본적으로 더 큰 VPC를 가진 클러스터를 생성해야 합니다. 또한 EKS와 AKS는 동일하게 VPC, Virtual Network에 Address Space를 추가하는 방식을 제공하고 있습니다.

추가로 AKS에서는 이러한 제약사항을 극복하기 위해서 overlay network를 사용하는 CNI(kubenet, Azure CNI overlay)를 선택할 수 있다는 점에서 차이점이 있습니다.

 

두번째 문제는 Pre-IP allocation으로 이미 할당은 되었지만 미사용되는 IP로 인한 낭비입니다. 이런 warm pool이 다수의 노드에 존재한다면 IP가 상당히 낭비될 수 있습니다.

AWS VPC CNI에서는 Warm Pool 에 관련된 설정을 변경하는 방식으로 할당되는 IP를 최소화 할 수 있습니다. 이때 WARM_IP_TARGET, MINIMUM_IP_TARGET 과 같은 값을 조정할 수 있습니다.

AKS에서는 Azure CNI with Dynamic Pod IP Allocation 방식을 통해 Pod가 사용하는 서브넷을 지정할 수 있습니다. 이 방식은 Azure CNI와 다르게, batch 사이즈 만큼의 IP를 할당하고 지정된 퍼센티지 이상 사용하는 경우 batch 사이즈 만큼을 추가로 할당하는 방식을 사용합니다. 이 방식은 EKS의 VPC CNI의 worm pool 방식과 Custom Networking(https://www.eksworkshop.com/docs/networking/vpc-cni/custom-networking/)을 섞은 것 같기도 합니다.

 

 

3. 실습 환경 배포

먼저 아래를 따라 기본적인 실습 환경을 구성합니다.

아래 CloudFormation을 배포하면서 Key를 인자로 전달하기 때문에 사전에 key pair가 있는지 확인하시고, 없으면 생성을 해야 합니다. (EC2>Key pairs>Create key pair>.pem 형식으로 생성)

# yaml 파일 다운로드
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/myeks-2week.yaml

# 배포
# aws cloudformation deploy --template-file myeks-1week.yaml --stack-name mykops --parameter-overrides KeyName=<My SSH Keyname> SgIngressSshCidr=<My Home Public IP Address>/32 --region <리전>
예시) aws cloudformation deploy --template-file ./myeks-2week.yaml \
     --stack-name myeks --parameter-overrides KeyName=ekskey SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 --region ap-northeast-2

# CloudFormation 스택 배포 완료 후 운영서버 EC2 IP 확인
ec2ip=$(aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[*].OutputValue' --output text)

# 운영서버 EC2 에 SSH 접속
ssh -i <ssh 키파일> ec2-user@$ec2ip

 

CloudFormation으로 배포되는 실습 환경은 아래와 같습니다.

 

myeks-vpc와 operator-vpc를 각각 만들고 VPC Peering으로 연결합니다.

operator-vpc에는 CloudFormation으로 미리 operator-host 라는 서버를 배포했습니다.

myeks-vpc에는 각 가용 영역에 PublicSubnet과 PrivateSubnet을 구성했고, PublicSubnet에 EKS를 배포할 예정입니다.

 

이제 EKS를 배포하겠습니다.

# 환경 변수 지정
export CLUSTER_NAME=myeks

# myeks-VPC/Subnet 정보 확인 및 변수 지정
export VPCID=$(aws ec2 describe-vpcs --filters "Name=tag:Name,Values=$CLUSTER_NAME-VPC" --query 'Vpcs[*].VpcId' --output text)
echo $VPCID

export PubSubnet1=$(aws ec2 describe-subnets --filters Name=tag:Name,Values="$CLUSTER_NAME-Vpc1PublicSubnet1" --query "Subnets[0].[SubnetId]" --output text)
export PubSubnet2=$(aws ec2 describe-subnets --filters Name=tag:Name,Values="$CLUSTER_NAME-Vpc1PublicSubnet2" --query "Subnets[0].[SubnetId]" --output text)
export PubSubnet3=$(aws ec2 describe-subnets --filters Name=tag:Name,Values="$CLUSTER_NAME-Vpc1PublicSubnet3" --query "Subnets[0].[SubnetId]" --output text)
echo $PubSubnet1 $PubSubnet2 $PubSubnet3

# 출력된 내용 참고해 아래 yaml에서 vpc/subnet id, ssh key 경로 수정
eksctl create cluster --name $CLUSTER_NAME --region=ap-northeast-2 --nodegroup-name=ng1 --node-type=t3.medium --nodes 3 --node-volume-size=30 --vpc-public-subnets "$PubSubnet1","$PubSubnet2","$PubSubnet3" --version 1.31 --with-oidc --external-dns-access --full-ecr-access --alb-ingress-access --node-ami-family AmazonLinux2023 --ssh-access --dry-run > myeks.yaml

 

이후 생성된 myeks.yaml 에서 # 각자 환경 정보로 수정로 커맨트된 열을 확인하고 정보가 다른 경우 수정합니다.

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
  name: myeks
  region: ap-northeast-2
  version: "1.31"

kubernetesNetworkConfig:
  ipFamily: IPv4

iam:
  vpcResourceControllerPolicy: true
  withOIDC: true

accessConfig:
  authenticationMode: API_AND_CONFIG_MAP

vpc:
  autoAllocateIPv6: false
  cidr: 192.168.0.0/16
  clusterEndpoints:
    privateAccess: true # if you only want to allow private access to the cluster
    publicAccess: true # if you want to allow public access to the cluster
  id: vpc-0ab40d2acbda845d8  # 각자 환경 정보로 수정
  manageSharedNodeSecurityGroupRules: true # if you want to manage the rules of the shared node security group
  nat:
    gateway: Disable
  subnets:
    public:
      ap-northeast-2a:
        az: ap-northeast-2a
        cidr: 192.168.1.0/24
        id: subnet-014dc12ab7042f604  # 각자 환경 정보로 수정
      ap-northeast-2b:
        az: ap-northeast-2b
        cidr: 192.168.2.0/24
        id: subnet-01ba554d3b16a15a7  # 각자 환경 정보로 수정
      ap-northeast-2c:
        az: ap-northeast-2c
        cidr: 192.168.3.0/24
        id: subnet-0868f7093cbb17c34  # 각자 환경 정보로 수정

addons:
  - name: vpc-cni # no version is specified so it deploys the default version
    version: latest # auto discovers the latest available
    attachPolicyARNs: # attach IAM policies to the add-on's service account
      - arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
    configurationValues: |-
      enableNetworkPolicy: "true"

  - name: kube-proxy
    version: latest

  - name: coredns
    version: latest

  - name: metrics-server
    version: latest

privateCluster:
  enabled: false
  skipEndpointCreation: false

managedNodeGroups:
- amiFamily: AmazonLinux2023
  desiredCapacity: 3
  disableIMDSv1: true
  disablePodIMDS: false
  iam:
    withAddonPolicies:
      albIngress: false # Disable ALB Ingress Controller
      appMesh: false
      appMeshPreview: false
      autoScaler: false
      awsLoadBalancerController: true # Enable AWS Load Balancer Controller
      certManager: true # Enable cert-manager
      cloudWatch: false
      ebs: false
      efs: false
      externalDNS: true # Enable ExternalDNS
      fsx: false
      imageBuilder: true
      xRay: false
  instanceSelector: {}
  instanceType: t3.medium
  preBootstrapCommands:
    # install additional packages
    - "dnf install nvme-cli links tree tcpdump sysstat ipvsadm ipset bind-utils htop -y"
    # disable hyperthreading
    - "for n in $(cat /sys/devices/system/cpu/cpu*/topology/thread_siblings_list | cut -s -d, -f2- | tr ',' '\n' | sort -un); do echo 0 > /sys/devices/system/cpu/cpu${n}/online; done"
  labels:
    alpha.eksctl.io/cluster-name: myeks
    alpha.eksctl.io/nodegroup-name: ng1
  maxSize: 3
  minSize: 3
  name: ng1
  privateNetworking: false
  releaseVersion: ""
  securityGroups:
    withLocal: null
    withShared: null
  ssh:
    allow: true
    publicKeyName: mykeyname  # 각자 환경 정보로 수정
  tags:
    alpha.eksctl.io/nodegroup-name: ng1
    alpha.eksctl.io/nodegroup-type: managed
  volumeIOPS: 3000
  volumeSize: 30
  volumeThroughput: 125
  volumeType: gp3

 

 

마지막으로 생성된 myeks.yaml을 바탕으로 EKS를 배포합니다.

# kubeconfig 파일 경로 위치 지정 : 
export KUBECONFIG=$HOME/.kube/config

# 배포
eksctl create cluster -f myeks.yaml --verbose 4

 

생성이 완료되면 클러스터 정보를 확인합니다.

# 클러스터 확인
$ kubectl cluster-info
Kubernetes control plane is running at https://F141CFF9E7E8776AF6826A7D1341FBEA.yl4.ap-northeast-2.eks.amazonaws.com
CoreDNS is running at https://F141CFF9E7E8776AF6826A7D1341FBEA.yl4.ap-northeast-2.eks.amazonaws.com/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
$ eksctl get cluster
NAME    REGION          EKSCTL CREATED
myeks   ap-northeast-2  True

# 노드 확인
$ kubectl get node --label-columns=node.kubernetes.io/instance-type,eks.amazonaws.com/capacityType,topology.kubernetes.io/zone
NAME                                               STATUS   ROLES    AGE     VERSION               INSTANCE-TYPE   CAPACITYTYPE   ZONE
ip-192-168-1-203.ap-northeast-2.compute.internal   Ready    <none>   2m37s   v1.31.4-eks-aeac579   t3.medium       ON_DEMAND      ap-northeast-2a
ip-192-168-2-77.ap-northeast-2.compute.internal    Ready    <none>   2m35s   v1.31.4-eks-aeac579   t3.medium       ON_DEMAND      ap-northeast-2b
ip-192-168-3-141.ap-northeast-2.compute.internal   Ready    <none>   2m35s   v1.31.4-eks-aeac579   t3.medium       ON_DEMAND      ap-northeast-2c

# 생성 노드 확인
$ aws ec2 describe-instances --query "Reservations[*].Instances[*].{InstanceID:InstanceId, PublicIPAdd:PublicIpAddress, PrivateIPAdd:PrivateIpAddress, InstanceName:Tags[?Key=='Name']|[0].Value, Status:State.Name}" --filters Name=instance-state-name,Values=running --output table
-----------------------------------------------------------------------------------------
|                                   DescribeInstances                                   |
+----------------------+-----------------+----------------+-----------------+-----------+
|      InstanceID      |  InstanceName   | PrivateIPAdd   |   PublicIPAdd   |  Status   |
+----------------------+-----------------+----------------+-----------------+-----------+
|  i-095eb2faccd652e06 |  myeks-ng1-Node |  192.168.3.141 |  43.201.9.71    |  running  |
|  i-035d1900144a7ba5f |  operator-host  |  172.20.1.100  |  3.34.186.167   |  running  |
|  i-0b35dccaffe41de41 |  myeks-ng1-Node |  192.168.1.203 |  43.202.40.202  |  running  |
|  i-0543c6996984a0290 |  myeks-ng1-Node |  192.168.2.77  |  15.164.237.196 |  running  |
+----------------------+-----------------+----------------+-----------------+-----------+

# 관리형 노드 그룹 확인
$ eksctl get nodegroup --cluster $CLUSTER_NAME
CLUSTER NODEGROUP       STATUS  CREATED                 MIN SIZE        MAX SIZE        DESIRED CAPACITY        INSTANCE TYPE   IMAGE ID                ASG NAMETYPE
myeks   ng1             ACTIVE  2025-02-14T13:28:22Z    3               3               3                       t3.medium       AL2023_x86_64_STANDARD  eks-ng1-7cca8252-ac30-3af4-6c5e-a3b2d91981c3     managed

 

AWS는 서브넷이 각 가용 영역에 배치되는 리소스입니다. Azure에서는 서브넷을 생성해도 이는 가용 영역을 걸쳐서 생성되는 리전 리소스입니다. 이러한 이유로 EKS는 생성 시 가용 영역별로 서브넷을 제공하면 노드들은 자동으로 가용 영역에 배치됩니다.

 

AKS는 서브넷을 지정해도 가용 영역의 구분이 없기 때문에 생성 시점에 가용 영역 여부를 별도로 지정해야 합니다. 아래는 포탈에서 AKS를 생성하며 가용 영역을 지정하는 화면입니다.

 

아래로 AKS의 availability zone 문서를 참고하실 수 있습니다.

https://learn.microsoft.com/en-us/azure/aks/availability-zones-overview#zone-spanning

 

 

파드 정보도 확인해봅니다.

# 파드 확인
$ kubectl get pod -A
NAMESPACE     NAME                              READY   STATUS    RESTARTS   AGE
kube-system   aws-node-5skww                    2/2     Running   0          13m
kube-system   aws-node-qmmn8                    2/2     Running   0          13m
kube-system   aws-node-x5rxg                    2/2     Running   0          13m
kube-system   coredns-9b5bc9468-8prkc           1/1     Running   0          18m
kube-system   coredns-9b5bc9468-qp9mj           1/1     Running   0          18m
kube-system   kube-proxy-d7l2t                  1/1     Running   0          13m
kube-system   kube-proxy-fp99v                  1/1     Running   0          13m
kube-system   kube-proxy-sxq5k                  1/1     Running   0          13m
kube-system   metrics-server-86bbfd75bb-smjpb   1/1     Running   0          18m
kube-system   metrics-server-86bbfd75bb-xm8ds   1/1     Running   0          18m

$ kubectl get ds -A
NAMESPACE     NAME         DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
kube-system   aws-node     3         3         3       3            3           <none>          18m
kube-system   kube-proxy   3         3         3       3            3           <none>          18m

# eks addon 확인
$ eksctl get addon --cluster $CLUSTER_NAME
2025-02-14 22:43:26 [ℹ]  Kubernetes version "1.31" in use by cluster "myeks"
2025-02-14 22:43:26 [ℹ]  getting all addons
2025-02-14 22:43:27 [ℹ]  to see issues for an addon run `eksctl get addon --name <addon-name> --cluster <cluster-name>`
NAME            VERSION                 STATUS  ISSUES  IAMROLE                                                                         UPDATE AVAILABLE        CONFIGURATION VALUES     POD IDENTITY ASSOCIATION ROLES
coredns         v1.11.3-eksbuild.1      ACTIVE  0                                                                                       v1.11.4-eksbuild.2,v1.11.4-eksbuild.1,v1.11.3-eksbuild.2
kube-proxy      v1.31.2-eksbuild.3      ACTIVE  0                                                                                       v1.31.3-eksbuild.2
metrics-server  v0.7.2-eksbuild.1       ACTIVE  0
vpc-cni         v1.19.0-eksbuild.1      ACTIVE  0       arn:aws:iam::430118812536:role/eksctl-myeks-addon-vpc-cni-Role1-fdAOfLzN8tNl    v1.19.2-eksbuild.5,v1.19.2-eksbuild.1

 

앞서 설명드린 바와 같이 EKS의 노드에는 aws-node 가 daemonset 으로 실행되고, VPC CNI와 Network Policy Agent로 멀티 컨테이너로 구성되어 있습니다.

 

Azure CNI에서는 azure-vnet과 azure-vnet-ipam가 binary로 구현되어 있고 별도의 파드가 생성되지 않습니다. 다만 azure CNI overlay과 같은 유형의 CNI에서는 IPAM으로 azure-cns 데몬셋이 사용됩니다.

 

 

한편 EKS의 VPC CNI는 L-IPAM 에서 NAT rules를 iptables로 반영합니다.

AKS의 Azure CNI도 Virtual Network 이 외의 네트워크에 대해서 NAT rules을 생성하는데, ip-masq-agent를 daemonset으로 사용하는 차이가 있습니다.

# Virtual Network에 대해서 nonMasqeuradeCIDRs 로 정의되어 있음
$ kubectl get cm azure-ip-masq-agent-config-reconciled -n kube-system -oyaml
apiVersion: v1
data:
  ip-masq-agent-reconciled: |-
    nonMasqueradeCIDRs:
      - 10.244.0.0/16
    masqLinkLocal: true
kind: ConfigMap
...
  labels:
    addonmanager.kubernetes.io/mode: Reconcile
    component: ip-masq-agent
    kubernetes.io/cluster-service: "true"
  name: azure-ip-masq-agent-config-reconciled
  namespace: kube-system

# 노드의 iptables 정보
root@aks-nodepool1-76251328-vmss000000:/# iptables -t nat -S |grep ip-masq-agent
-A POSTROUTING -m comment --comment "\"ip-masq-agent: ensure nat POSTROUTING directs all non-LOCAL destination traffic to our custom IP-MASQ-AGENT chain\"" -m addrtype ! --dst-type LOCAL -j IP-MASQ-AGENT
-A IP-MASQ-AGENT -d 10.244.0.0/16 -m comment --comment "ip-masq-agent: local traffic is not subject to MASQUERADE" -j RETURN
-A IP-MASQ-AGENT -m comment --comment "ip-masq-agent: outbound traffic is subject to MASQUERADE (must be last in chain)" -j MASQUERADE

 

 

4. 노드 정보 확인

앞서 살펴본 VPC CNI의 특성을 확인하기 위해서 노드 정보를 살펴보겠습니다.

 

먼저 웹 콘솔에서 노드를 살펴봅니다. t3.medium으로 생성되었습니다.

 

Networking 탭을 살펴보면 2개의 ENI에 해당하는 Private IP가 2개 확인되며, Secondary IP가 10개 확인됩니다.

 

아래를 살펴보면 2개의 ENI가 연결되었음을 알 수 있습니다.

 

노드에 할당된 IP를 확인해보면 hostNetwork를 제외하고 3개의 IP가 secondary IP에서 사용되고 있습니다.

$ kubectl get po -A -owide |grep ip-192-168-3-141.ap-northeast-2.compute.internal
kube-system   aws-node-x5rxg                    2/2     Running   0          70m   192.168.3.141   ip-192-168-3-141.ap-northeast-2.compute.internal   <none>           <none>
kube-system   coredns-9b5bc9468-8prkc           1/1     Running   0          75m   192.168.3.45    ip-192-168-3-141.ap-northeast-2.compute.internal   <none>           <none>
kube-system   coredns-9b5bc9468-qp9mj           1/1     Running   0          75m   192.168.3.48    ip-192-168-3-141.ap-northeast-2.compute.internal   <none>           <none>
kube-system   kube-proxy-sxq5k                  1/1     Running   0          70m   192.168.3.141   ip-192-168-3-141.ap-northeast-2.compute.internal   <none>           <none>
kube-system   metrics-server-86bbfd75bb-smjpb   1/1     Running   0          75m   192.168.3.69    ip-192-168-3-141.ap-northeast-2.compute.internal   <none>           <none>

 

물론 노드에는 2개의 ENI에 해당하는 ens5, ens6의 IP만 확인됩니다.

[root@ip-192-168-3-141 /]# ip -br -c a
lo               UNKNOWN        127.0.0.1/8 ::1/128
ens5             UP             192.168.3.141/24 metric 1024 fe80::8ef:19ff:fe5b:1dd3/64
eni9a92535a0a9@if3 UP             fe80::b428:33ff:feaa:6633/64
enib9bf070cdb3@if3 UP             fe80::b458:f9ff:fe9a:1a0b/64
eni54524c4f9d9@if3 UP             fe80::e8a1:acff:fe6f:6a57/64
enid0ded64c01f@if3 UP             fe80::9445:9ff:feaf:ad50/64
ens6             UP             192.168.3.172/24 fe80::832:3eff:fe60:eec5/64
eni98b6af8f1d6@if3 UP             fe80::54a1:b9ff:fe80:c915/64

 

앞서 EKS 노드에서 최대 파드 개수는 인스턴스 타입에 따라 아래와 같은 공식으로 계산되고, t3.medium에서는 3*(6-1)+2= 17개 파드가 실행될 수 있다고 알아봤습니다.

 

이 내용을 테스트를 위해서 테스트 디플로이먼트를 배포합니다.

$ cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: netshoot-pod
spec:
  replicas: 3
  selector:
    matchLabels:
      app: netshoot-pod
  template:
    metadata:
      labels:
        app: netshoot-pod
    spec:
      containers:
      - name: netshoot-pod
        image: nicolaka/netshoot
        command: ["tail"]
        args: ["-f", "/dev/null"]
      terminationGracePeriodSeconds: 0
EOF

 

아래와 같이 replicas를 늘여서 실제 변경되는 부분을 확인해 보겠습니다.

# 파드 확인 
$ kubectl get pod -o wide
NAME                            READY   STATUS    RESTARTS   AGE     IP              NODE                                               NOMINATED NODE   READINESS GATES
netshoot-pod-744bd84b46-g7hzp   1/1     Running   0          6m18s   192.168.3.223   ip-192-168-3-141.ap-northeast-2.compute.internal   <none>           <none>
netshoot-pod-744bd84b46-llqjz   1/1     Running   0          6m18s   192.168.1.220   ip-192-168-1-203.ap-northeast-2.compute.internal   <none>           <none>
netshoot-pod-744bd84b46-rwblp   1/1     Running   0          6m18s   192.168.2.140   ip-192-168-2-77.ap-northeast-2.compute.internal    <none>           <none>
nsenter-vg4pv8                  1/1     Running   0          7m1s    192.168.3.141   ip-192-168-3-141.ap-northeast-2.compute.internal   <none>           <none>

# 파드 증가 테스트 >> 파드 정상 생성 확인, 워커 노드에서 eth, eni 갯수 확인
kubectl scale deployment netshoot-pod --replicas=8

# 노드에 접속 후 확인 (사전)
[root@ip-192-168-3-141 /]# ip -br -c a
lo               UNKNOWN        127.0.0.1/8 ::1/128
ens5             UP             192.168.3.141/24 metric 1024 fe80::8ef:19ff:fe5b:1dd3/64
eni9a92535a0a9@if3 UP             fe80::b428:33ff:feaa:6633/64
enib9bf070cdb3@if3 UP             fe80::b458:f9ff:fe9a:1a0b/64
eni54524c4f9d9@if3 UP             fe80::e8a1:acff:fe6f:6a57/64
enid0ded64c01f@if3 UP             fe80::9445:9ff:feaf:ad50/64
ens6             UP             192.168.3.172/24 fe80::832:3eff:fe60:eec5/64
eni98b6af8f1d6@if3 UP             fe80::54a1:b9ff:fe80:c915/64

# 노드에 접속 후 확인 (사후)
[root@ip-192-168-3-141 /]# ip -br -c a
lo               UNKNOWN        127.0.0.1/8 ::1/128
ens5             UP             192.168.3.141/24 metric 1024 fe80::8ef:19ff:fe5b:1dd3/64
eni9a92535a0a9@if3 UP             fe80::b428:33ff:feaa:6633/64
enib9bf070cdb3@if3 UP             fe80::b458:f9ff:fe9a:1a0b/64
eni54524c4f9d9@if3 UP             fe80::e8a1:acff:fe6f:6a57/64
enid0ded64c01f@if3 UP             fe80::9445:9ff:feaf:ad50/64
ens6             UP             192.168.3.172/24 fe80::832:3eff:fe60:eec5/64
eni98b6af8f1d6@if3 UP             fe80::54a1:b9ff:fe80:c915/64
eni9c40e9afed0@if3 UP             fe80::58ea:7dff:fe10:23f/64
ens7             UP             192.168.3.104/24 fe80::883:eff:fe48:b1bf/64

 

확인해보면 파드 개수가 늘어나자 ens7이 신규로 생성된 것을 알 수 있습니다.

웹 콘솔에서도 Secondary IP가 15개로 증가한 것을 확인할 수 있습니다.

 

이때 Primary ENI가 아닌 ENI는 통신에 관여를 할지 한번 확인해보겠습니다.

kubectl get po -A -owide |grep ip-192-168-3-141.ap-northeast-2.compute.internal
default       netshoot-pod-744bd84b46-7ggdq     1/1     Running   0          7m10s   192.168.3.83    ip-192-168-3-141.ap-northeast-2.compute.internal   <none>           <none>
default       netshoot-pod-744bd84b46-g7hzp     1/1     Running   0          15m     192.168.3.223   ip-192-168-3-141.ap-northeast-2.compute.internal   <none>           <none>
default       nsenter-vg4pv8                    1/1     Running   0          15m     192.168.3.141   ip-192-168-3-141.ap-northeast-2.compute.internal   <none>           <none>
kube-system   aws-node-x5rxg                    2/2     Running   0          88m     192.168.3.141   ip-192-168-3-141.ap-northeast-2.compute.internal   <none>           <none>
kube-system   coredns-9b5bc9468-8prkc           1/1     Running   0          92m     192.168.3.45    ip-192-168-3-141.ap-northeast-2.compute.internal   <none>           <none>
kube-system   coredns-9b5bc9468-qp9mj           1/1     Running   0          92m     192.168.3.48    ip-192-168-3-141.ap-northeast-2.compute.internal   <none>           <none>
kube-system   kube-proxy-sxq5k                  1/1     Running   0          88m     192.168.3.141   ip-192-168-3-141.ap-northeast-2.compute.internal   <none>           <none>
kube-system   metrics-server-86bbfd75bb-smjpb   1/1     Running   0          93m     192.168.3.69    ip-192-168-3-141.ap-northeast-2.compute.internal   <none>           <none>
kube-system   metrics-server-86bbfd75bb-xm8ds   1/1     Running   0          93m     192.168.3.56    ip-192-168-3-141.ap-northeast-2.compute.internal   <none>           <none>

 

ENI 정보를 확인해보면 192.168.3.83만 두번째 ENI에 할당된 IP로 확인됩니다.

 

해당 파드에서 ping을 하고, tcpdump를 확인해봐도 Primary ENI에서만 패킷이 확인되는 것을 알 수 있습니다.

(정정) 이후 파드 통신 절에서 파드 통신 테스트를 한번 더 해보니 이건 ENI가 통신 관여를 하지 않는 건 아니고, 외부 통신은 Primary ENI에서 내부 통신은 해당 IP를 가진 Secondary ENI에서 통신이 이뤄졌습니다.

 

이번에는 replicas를 50개까지 증가시켜보겠습니다.

$ kubectl scale deployment netshoot-pod --replicas=50
deployment.apps/netshoot-pod scaled

$ kubectl get po |grep -v Running
NAME                            READY   STATUS    RESTARTS   AGE
netshoot-pod-744bd84b46-69r4x   0/1     Pending   0          23s
netshoot-pod-744bd84b46-bhprl   0/1     Pending   0          23s
netshoot-pod-744bd84b46-g6pp4   0/1     Pending   0          24s
netshoot-pod-744bd84b46-gs4dt   0/1     Pending   0          24s
netshoot-pod-744bd84b46-jpxbc   0/1     Pending   0          24s
netshoot-pod-744bd84b46-spdw6   0/1     Pending   0          24s
netshoot-pod-744bd84b46-v56h5   0/1     Pending   0          23s
netshoot-pod-744bd84b46-vhlzn   0/1     Pending   0          24s
netshoot-pod-744bd84b46-vngbr   0/1     Pending   0          23s
netshoot-pod-744bd84b46-wf58t   0/1     Pending   0          24s
$ kubectl get po -A |wc
     62     372    5026
$ kubectl get po |grep -v Running|wc
     11      55     725

 

t3.medium 에서는 17개의 파드가 실행 가능하고, 노드가 3대이므로 총 51개의 파드가 실행 가능합니다. 결국 11개의 파드는 unschedulable 한 상태로 빠집니다.

 

 

5. 파드 통신 확인

EKS의 VPC CNI는 파드에 VPC의 IP를 할당하는 방식으로 NAT 통신이 없이 파드간 통신이 이뤄집니다.

 

클러스터 내 파드 간 통신 확인

현재 파드는 3개로 각 노드에 배포되어 있습니다. 이때, 파드간 통신을 확인해보고, 노드에서 tcpdump로 확인합니다.

$ kubectl get po -A -owide
NAMESPACE     NAME                              READY   STATUS    RESTARTS   AGE    IP              NODE                                               NOMINATED NODE   READINESS GATES
default       netshoot-pod-744bd84b46-8mrkj     1/1     Running   0          15s    192.168.3.31    ip-192-168-3-141.ap-northeast-2.compute.internal   <none>           <none>
default       netshoot-pod-744bd84b46-lgkxf     1/1     Running   0          15s    192.168.2.140   ip-192-168-2-77.ap-northeast-2.compute.internal    <none>           <none>
default       netshoot-pod-744bd84b46-qzq29     1/1     Running   0          15s    192.168.1.126   ip-192-168-1-203.ap-northeast-2.compute.internal   <none>           <none>
..

# 파드 이름 변수 지정
PODNAME1=$(kubectl get pod -l app=netshoot-pod -o jsonpath='{.items[0].metadata.name}')
PODNAME2=$(kubectl get pod -l app=netshoot-pod -o jsonpath='{.items[1].metadata.name}')
PODNAME3=$(kubectl get pod -l app=netshoot-pod -o jsonpath='{.items[2].metadata.name}')

# 파드 IP 변수 지정
PODIP1=$(kubectl get pod -l app=netshoot-pod -o jsonpath='{.items[0].status.podIP}')
PODIP2=$(kubectl get pod -l app=netshoot-pod -o jsonpath='{.items[1].status.podIP}')
PODIP3=$(kubectl get pod -l app=netshoot-pod -o jsonpath='{.items[2].status.podIP}')

echo $PODIP1 $PODIP2 $PODIP3
192.168.3.31 192.168.2.140 192.168.1.126

# 파드1 Shell 에서 파드2로 ping 테스트, 8.8.8.8로 ping 테스트
kubectl exec -it $PODNAME1 -- ping -c 2 $PODIP2
 kubectl exec -it $PODNAME1 -- ping -c 2 8.8.8.8

# 워커 노드 EC2 : TCPDUMP 확인
## For Pod to external (outside VPC) traffic, we will program iptables to SNAT using Primary IP address on the Primary ENI.
[root@ip-192-168-3-141 /]# sudo tcpdump -i any -nn icmp
tcpdump: data link type LINUX_SLL2
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
15:31:58.525323 eni89548af8467 In  IP 192.168.3.31 > 192.168.2.140: ICMP echo request, id 7, seq 1, length 64
15:31:58.525366 ens6  Out IP 192.168.3.31 > 192.168.2.140: ICMP echo request, id 7, seq 1, length 64
15:31:58.528035 ens6  In  IP 192.168.2.140 > 192.168.3.31: ICMP echo reply, id 7, seq 1, length 64
15:31:58.528089 eni89548af8467 Out IP 192.168.2.140 > 192.168.3.31: ICMP echo reply, id 7, seq 1, length 64
...
15:35:38.173442 eni89548af8467 In  IP 192.168.3.31 > 8.8.8.8: ICMP echo request, id 13, seq 1, length 64
15:35:38.173494 ens5  Out IP 192.168.3.141 > 8.8.8.8: ICMP echo request, id 24172, seq 1, length 64
15:35:38.202228 ens5  In  IP 8.8.8.8 > 192.168.3.141: ICMP echo reply, id 24172, seq 1, length 64
15:35:38.202403 eni89548af8467 Out IP 8.8.8.8 > 192.168.3.31: ICMP echo reply, id 13, seq 1, length 64
..

 

파드 간 통신에서 출발지는 컨테이너의 veth 인터페이스이며, 이후 ens6를 통해 Pod IP에서 Pod IP로 직접 통신이 이뤄지는 것을 알 수 있습니다. 192.168.3.31은 두번째 EN에 있는 secondary IP입니다.

 

반면 파드의 외부 통신은 출발지는 컨테이너의 veth 인터페이스이며, 이후 ens5로 통신이 이뤄지는데 이 구간에서 노드 IP로의 SNAT이 이뤄지는 것을 확인할 수 있습니다. 독특하네요!

 

아래 CNI 제안 문서에서 제공하는 그림을 보시면 이해할 수 있습니다.

출처: https://github.com/aws/amazon-vpc-cni-k8s/blob/master/docs/cni-proposal.md

 

그렇다면 3번째 ENI의 secondary IP의 파드는 ens7을 사용할까요? 그렇습니다.

 

통신을 해보면 ens7로 나갑니다.

[root@ip-192-168-3-141 /]# ip -br -c a
lo               UNKNOWN        127.0.0.1/8 ::1/128
ens5             UP             192.168.3.141/24 metric 1024 fe80::8ef:19ff:fe5b:1dd3/64
eni9a92535a0a9@if3 UP             fe80::b428:33ff:feaa:6633/64
enib9bf070cdb3@if3 UP             fe80::b458:f9ff:fe9a:1a0b/64
eni54524c4f9d9@if3 UP             fe80::e8a1:acff:fe6f:6a57/64
enid0ded64c01f@if3 UP             fe80::9445:9ff:feaf:ad50/64
ens6             UP             192.168.3.172/24 fe80::832:3eff:fe60:eec5/64
eni89548af8467@if3 UP             fe80::c8:88ff:fea3:5679/64
enidefacbb986c@if3 UP             fe80::2825:89ff:fec5:d261/64
eni56e598ef751@if3 UP             fe80::6ce6:79ff:fe8e:7f2c/64
eni7e0515a0713@if3 UP             fe80::40f2:24ff:fedb:164a/64
eniea2b33504a4@if3 UP             fe80::6492:f6ff:fe5d:371a/64
eni4dfe955a07f@if3 UP             fe80::c435:82ff:feae:b50b/64
ens7             UP             192.168.3.123/24 fe80::880:71ff:feec:d51b/64
[root@ip-192-168-3-141 /]# sudo tcpdump -i any -nn icmp
tcpdump: data link type LINUX_SLL2
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
16:40:48.254174 eni9e794cfd960 In  IP 192.168.3.189 > 192.168.2.140: ICMP echo request, id 7, seq 1, length 64
16:40:48.254222 ens7  Out IP 192.168.3.189 > 192.168.2.140: ICMP echo request, id 7, seq 1, length 64
16:40:48.257570 ens7  In  IP 192.168.2.140 > 192.168.3.189: ICMP echo reply, id 7, seq 1, length 64
16:40:48.257619 eni9e794cfd960 Out IP 192.168.2.140 > 192.168.3.189: ICMP echo reply, id 7, seq 1, length 64

 

 

파드의 외부 통신 확인

두번째 테스트는 VPC Peering된 네트워크에 있는 서버와의 통신이 어떻게 이뤄지는지 테스트 해보겠습니다.

 

 

해당 서버의 IP는 172.20.1.100 입니다.

[root@operator-host ~]# ip -br -c a
lo               UNKNOWN        127.0.0.1/8 ::1/128
eth0             UP             172.20.1.100/24 fe80::3f:d8ff:fe93:8b1f/64
docker0          DOWN           172.17.0.1/16

 

EKS에서는 아래와 같은 SNAT rules에 따라 VPC Cidr(192.168.0.0/16)이 아닌 경우 SNAT을 수행합니다.

[root@ip-192-168-3-141 /]#  iptables -t nat -S | grep 'A AWS-SNAT-CHAIN'
-A AWS-SNAT-CHAIN-0 -d 192.168.0.0/16 -m comment --comment "AWS SNAT CHAIN" -j RETURN
-A AWS-SNAT-CHAIN-0 ! -o vlan+ -m comment --comment "AWS, SNAT" -m addrtype ! --dst-type LOCAL -j SNAT --to-source 192.168.3.141 --random-fully

 

이때 Pod1에서 서버로 Ping을 테스트 해보겠습니다.

# kubectl exec -it $PODNAME1 -- ping -c 1 172.20.1.100
PING 172.20.1.100 (172.20.1.100) 56(84) bytes of data.
64 bytes from 172.20.1.100: icmp_seq=1 ttl=254 time=1.73 ms

--- 172.20.1.100 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.732/1.732/1.732/0.000 ms

 

이 구간에서는 SNAT가 수행되는 것을 확인할 수 있습니다.

 

다만 Pod -> 서버로는 통신이 되지만 서버 -> pod로는 통신이 되지 않습니다.

 

 

만약 서버가 있는 네트워크가 DX/VPN/TGW로 연결된 확장된 네트워크라고 가정을 해보고, 해당 VPC Cidr에 대해서도 NAT rules를 제외하는방법을 알아 보겠습니다.

 

아래와 같이 aws-node에 AWS_VPC_K8S_CNI_EXCLUDE_SNAT_CIDRS를 환경 변수로 전달하는 방법을 사용할 수 있습니다. 이 경우 aws-node 파드들이 재시작 하는 것을 확인할 수 있습니다.

$ kubectl set env daemonset aws-node -n kube-system AWS_VPC_K8S_CNI_EXCLUDE_SNAT_CIDRS=172.20.0.0/16
daemonset.apps/aws-node env updated

# 파드 롤링 업데이트 수행
$ kubectl get po -A -w
NAMESPACE     NAME                              READY   STATUS            RESTARTS   AGE
default       netshoot-pod-744bd84b46-8mrkj     1/1     Running           0          36m
default       netshoot-pod-744bd84b46-lgkxf     1/1     Running           0          36m
default       netshoot-pod-744bd84b46-qzq29     1/1     Running           0          36m
default       nsenter-vg4pv8                    1/1     Running           0          83m
kube-system   aws-node-hg2mg                    0/2     PodInitializing   0          1s
kube-system   aws-node-nlqk9                    2/2     Running           0          5s
kube-system   aws-node-x5rxg                    2/2     Running           0          156m
kube-system   coredns-9b5bc9468-8prkc           1/1     Running           0          160m
kube-system   coredns-9b5bc9468-qp9mj           1/1     Running           0          160m
kube-system   kube-proxy-d7l2t                  1/1     Running           0          156m
kube-system   kube-proxy-fp99v                  1/1     Running           0          156m
kube-system   kube-proxy-sxq5k                  1/1     Running           0          156m
kube-system   metrics-server-86bbfd75bb-smjpb   1/1     Running           0          160m
kube-system   metrics-server-86bbfd75bb-xm8ds   1/1     Running           0          160m
kube-system   aws-node-hg2mg                    1/2     Running           0          2s
kube-system   aws-node-hg2mg                    2/2     Running           0          3s
kube-system   aws-node-x5rxg                    2/2     Terminating       0          156m
kube-system   aws-node-x5rxg                    0/2     Completed         0          156m
kube-system   aws-node-5qxln                    0/2     Pending           0          1s
kube-system   aws-node-5qxln                    0/2     Pending           0          1s


이제 노드에서도 확인해보면 172.20.0.0/16 에 대해서 AWS SNAT CHAIN EXCLUSION으로 추가된 것을 알 수 있습니다.

[root@ip-192-168-3-141 /]# iptables -t nat -S | grep 'A AWS-SNAT-CHAIN'
-A AWS-SNAT-CHAIN-0 -d 172.20.0.0/16 -m comment --comment "AWS SNAT CHAIN EXCLUSION" -j RETURN
-A AWS-SNAT-CHAIN-0 -d 192.168.0.0/16 -m comment --comment "AWS SNAT CHAIN" -j RETURN
-A AWS-SNAT-CHAIN-0 ! -o vlan+ -m comment --comment "AWS, SNAT" -m addrtype ! --dst-type LOCAL -j SNAT --to-source 192.168.3.141 --random-fully

 

다시 통신 테스트를 진행해보면 SNAT이 없이 Pod -> 서버 통신이 가능한 것으로 확인됩니다.

 

 

다만 kubectl set env daemonset aws-node -n kube-system으로 적용하는 방법은 영구적으로 적용되지 않기 때문에 아래와 같이 Addon 에 Configuration values를 추가해 줄 수 있습니다.

{
    "env": {
        "AWS_VPC_K8S_CNI_EXCLUDE_SNAT_CIDRS" : "172.20.0.0/16"
    }
}

 

 

웹 콘솔에서 EKS에서 클러스터를 선택하고, Add-ons탭에서 Amazon VPC CNI를 진입하고 Edit을 선택합니다.

Add-on configuration schema를 통해서 내용을 하고, json 구조에 맞춰 작성이 필요합니다.

 

적용을 하면 aws-node 파드들이 자동으로 재배포 됩니다.

kubectl get po -A -w
NAMESPACE     NAME                              READY   STATUS    RESTARTS   AGE
kube-system   aws-node-b7qj7                    2/2     Running   0          40s
kube-system   aws-node-j77kr                    2/2     Running   0          36s
kube-system   aws-node-qtchj                    2/2     Running   0          44s

 

확인해보면 AWS_VPC_K8S_CNI_EXCLUDE_SNAT_CIDRS 이 적용되어 있는 것을 확인할 수 있습니다.

kubectl get ds aws-node -n kube-system -o json | jq '.spec.template.spec.containers[0].env'
[
  {
    "name": "ADDITIONAL_ENI_TAGS",
    "value": "{}"
  },
..
  {
    "name": "AWS_VPC_K8S_CNI_EXCLUDE_SNAT_CIDRS",
    "value": "172.20.0.0/16"
  },
..

 

 

AKS에서 사용하는 ip-masq-agent의 기본 설정은 azure-ip-masq-agent-config-reconciled라는 configmap에 정보를 확인할 수 있습니다.

# Virtual Network에 대해서 nonMasqeuradeCIDRs 로 정의되어 있음
$ kubectl get cm azure-ip-masq-agent-config-reconciled -n kube-system -oyaml
apiVersion: v1
data:
  ip-masq-agent-reconciled: |-
    nonMasqueradeCIDRs:
      - 10.244.0.0/16
    masqLinkLocal: true
kind: ConfigMap
...
  labels:
    addonmanager.kubernetes.io/mode: Reconcile
    component: ip-masq-agent
    kubernetes.io/cluster-service: "true"
  name: azure-ip-masq-agent-config-reconciled
  namespace: kube-system

# 노드의 iptables 정보
root@aks-nodepool1-76251328-vmss000000:/# iptables -t nat -S |grep ip-masq-agent
-A POSTROUTING -m comment --comment "\"ip-masq-agent: ensure nat POSTROUTING directs all non-LOCAL destination traffic to our custom IP-MASQ-AGENT chain\"" -m addrtype ! --dst-type LOCAL -j IP-MASQ-AGENT
-A IP-MASQ-AGENT -d 10.244.0.0/16 -m comment --comment "ip-masq-agent: local traffic is not subject to MASQUERADE" -j RETURN
-A IP-MASQ-AGENT -m comment --comment "ip-masq-agent: outbound traffic is subject to MASQUERADE (must be last in chain)" -j MASQUERADE

 

 

다만 이 configmap은 addonmanager.kubernetes.io/mode: Reconcile으로 지정되어 addonManager에 의해서 관리되므로, 사용자가 임의로 변경해도 다시 Reconcile 됩니다.

 

아래와 같은 절차로 configmap을 생성하는 경우 iptables가 변경되는 것을 확인하실 수 있습니다.

$ cat config-custom.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: azure-ip-masq-agent-config
  namespace: kube-system
  labels:
    component: ip-masq-agent
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: EnsureExists
data:
  ip-masq-agent: |-
    nonMasqueradeCIDRs:
      - 172.31.1.0/24
    masqLinkLocal: false
    masqLinkLocalIPv6: true

$ kubectl apply -f config-custom.yaml
configmap/azure-ip-masq-agent-config created

# 노드의 iptables 정보 (172.31.1.0/24가 추가됨)
root@aks-nodepool1-76251328-vmss000000:/# iptables -t nat -S |grep ip-masq-agent
-A POSTROUTING -m comment --comment "\"ip-masq-agent: ensure nat POSTROUTING directs all non-LOCAL destination traffic to our custom IP-MASQ-AGENT chain\"" -m addrtype ! --dst-type LOCAL -j IP-MASQ-AGENT
-A IP-MASQ-AGENT -d 172.31.1.0/24 -m comment --comment "ip-masq-agent: local traffic is not subject to MASQUERADE" -j RETURN
-A IP-MASQ-AGENT -d 10.244.0.0/16 -m comment --comment "ip-masq-agent: local traffic is not subject to MASQUERADE" -j RETURN
-A IP-MASQ-AGENT -m comment --comment "ip-masq-agent: outbound traffic is subject to MASQUERADE (must be last in chain)" -j MASQUERADE

 

 

Azure의 공식 문서에서 가이드가 확인하기 어렵지만, 아래 깃헙 리파지터를 확인해보시면 daemonset과 동일한 네임스페이스에 configmap을 생성하면 되는 것을 알 수 있습니다.

https://github.com/Azure/ip-masq-agent-v2

 

 

마무리

EKS Networking이라는 주제로 CNI의 의미를 살펴보고 AWS VPC CNI의 특성과 EKS에서 인스턴스 타입에 따른 파드 생성 제한에 대해서 확인해 보았습니다.

이후 EKS 클러스터를 생성해보고, 앞서 확인한 정보를 바탕으로 노드와 파드 정보를 확인하는 실습을 다뤄봤습니다.

한편으로 EKS는 단일 VPC CNI만 제공하고 있는 반면 AKS에서는 다양한 CNI를 제공하는 차이점이 있었습니다.

 

다음 포스트에는 EKS Networking 두번째 주제로, Loadbalancer 유형의 서비스와 Ingress를 EKS에서 어떻게 구현하는 지 살펴보겠습니다.

'EKS' 카테고리의 다른 글

[4] EKS의 모니터링과 로깅  (0) 2025.03.01
[3-2] EKS 노드 그룹  (0) 2025.02.23
[3-1] EKS 스토리지 옵션  (0) 2025.02.23
[2-2] EKS Networking Part2 - LoadBalancer와 Ingress  (0) 2025.02.16
[1] EKS 생성과 리소스 살펴보기  (2) 2025.02.07

+ Recent posts