스터디/AEWS

[AEWS] 2주차 EKS VPC CNI 알아보기

안녕유지 2025. 2. 15. 21:21
Cloudnet AWES 2주차 스터디를 진행하며 정리한 글입니다.

 

 

이번 포스팅에서 EKS 파드 간 통신에 사용되는 VPC CNI에 대해 알아보도록 하겠습니다.

 

CNI(Container Network Interface)란

CNI는 쿠버네티스에서 컨테이너 간 네트워크를 설정하고 관리하는 표준 인터페이스입니다.

쿠버네티스에서 Pod는 서로 통신해야 하며, 각 Pod는 고유한 IP 주소를 가져야 하는데, CNI는 네트워크 인터페이스 생성, IP 할당, 라우팅 및 네트워크 정책 적용을 담당하는 플러그인으로, Pod 간 네트워크를 설정하는 역할을 합니다.

쿠버네티스에서 보편적으로 사용되는 CNI는 다음과 같습니다.

  • Calico : BGP 및 VXLAN 기반 네트워킹
  • Flannel : 간단한 VXLAN 기반 네트워킹
  • Cilium : eBPF 기반 고성능 네트워크, 보안 및 정책 강화

https://kubernetes.io/docs/concepts/cluster-administration/addons/#networking-and-network-policy

 

여기서 쿠버네티스 네트워크에서 헷갈렸던 점!

 

kube-proxy는 쿠버네티스 Service를 통한 트래픽 라우팅 및 로드밸런싱을 담당하는 역할을 합니다.

1. 클러스터 내부에서 서비스로 접근 가능하도록 설정

- 서비스 단위 트래픽은 kube-proxy가 담당, 즉 kube-proxy로 들어온 요청을 특정 서비스의 Pod로 라운드로빈 분배

2. 외부 서비스 트래픽 처리 (NodePort, LoadBalancer)

- 외부에서 들어오는 요청이 특정 Pod으로 가도록 설정

3. iptables, IPVS, nftables 설정

 

하지만 이때 CNIPod간 네트워크 연결을 설정하고 IP를 할당하는 역할을 합니다.

1. Pod가 생성될 때 IP 할당

- Pod의 고유한 IP를 생성하고 할당

2. Pod간 네트워크 설정

- Pod간 트래픽 흐를 수 있도록 인터페이스 및 라우팅 설정

- Overlay 네트워크(VXLAN) 또는 Direct Routing(BGP) 방식으로 통신

3. 네트워크 정책 적용

- 특정 Pod간 통신을 허용하거나 차단(NetworkPolicy)

 

 

VPC CNI란

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

 

Amazon VPC CNI - Amazon EKS

The most frequently used fields such as WARM_ENI_TARGET, WARM_IP_TARGET, and MINIMUM_IP_TARGET are not managed and will not be reconciled. The changes to these fields will be preserved upon updating of the add-on.

docs.aws.amazon.com

 

AWS EKS에서는 기본적으로 VPC CNI를 사용합니다. VPC CNI는 VPC의 네트워크 기능을 활용할 수 있도록 고안된 CNI입니다.

 

VPC CNI의 동작 방식

1) ENI 및 IP 할당

- EC2 인스턴스가 생성되면, 기본적으로 하나의 primary ENI가 생성

- 이 ENI에는 Primary IP가 있고, 해당 노드에서 hostNetwork를 사용하는 Pod는 이 IP를 공유

- Pod가 생성될 때 VPC CNI는 ENI의 Secondary IP를 Pod에 할당

 

2) ENI 추가 및 Warm Pool 할당

- Pod 수가 증가하면 새로운 IP가 필요하므로, 새로운 ENI가 추가

- VPC CNI는 Pod의 배포 속도를 높이기 위해 Warm Pool을 유지하며, 미리 IP 주소 확보

- ENI 개수와 할당 가능한 IP 개수는 EC2 인스턴스 타입에 따라 결정

 

 

Calico CNI VS AWS VPC CNI 파드 간 통신 방식

일반적으로 사용하는 CNI인 Calico CNI와 (사내에서도 IDC K8s에 Calico CNI를 사용하고 있습니다.) AWS VPC CNI가 각각 통신 과정에서 어떤 차이가 있는지 비교해보겠습니다.

  Calico CNI AWS VPC CNI
Pod IP 할당 쿠버네티스 내부 서브넷 (BGP, VXLAN) AWS VPC 서브넷의 실제 IP (ENI)
Pod간 통신 방식 BGP 또는 VXLAN 기반 라우팅 AWS VPC 네트워크 라우팅
Overlay 네트워크 사용 여부 O (VXLAN 기능) X (VPC 네트워크 그대로 사용)
네트워크 성능 Overlay 사용시 약간의 성능 오버헤드 AWS VPC 네이티브 네트워크라 성능 최적화
외부 트래픽 (인터넷 -> Pod)
(확인해봐야함)
별도 설정 필요 (kube-proxy 등) AWS 보안 그룹, NACL 설정 사용 가능
멀티 클러스터 지원 BGP 활용하여 가능 AWS VPC Peering / Transit Gateway 필요
확장성 ENI 제한 없음, IP 자유롭게 할당 가능 ENI 갯수 제한 있음 (인스턴스 타입에 따라)

 

 

 

출처 - gasidaseo 노션

 

 

즉, Calico는 오버레이 네트워크 방식을 사용하여, 기존 네트워크 위에 가상 네트워크를 추가로 구성하여 통식하는 방식입니다.

물리적인 네트워크와는 별도로 가상의 터널을 만들어서 데이터를 주고 받는 구조로, Pod 네트워크 대역(10.1.1.0/24)과 Node 네트워크 대역(192.168.1.0/24)이 다릅니다.

 

VPC CNI는 네이티브 VPC 네트워크를 사용하여, Pod에 AWS VPC의 실제 ENI와 IP가 할당되는 방식입니다.

네트워크 터널링 없이 L3 네트워크를 통해 직접 통신이 가능하고, Pod 네트워크 대역과 Node 네트워크 대역(10.10.1.0/24)가 같습니다.

 

출처 - gasidaseo 노션

 

Calico CNI를 사용했을 때 다른 노드에서 Pod 간 통신 구조입니다.

  1. Pod1 (10.1.1.1)이 Pod2 (10.1.1.2)로 패킷을 보냅니다.
  2. 패킷이 Virtual Router(가상 라우터)를 통해 노드의 물리 인터페이스(eth0)로 이동합니다.
  3. Overlay 네트워크(VXLAN 또는 IP-in-IP)를 사용하여 외부 패킷(Outer Packet)을 생성하여 전송합니다.
  4. 목적지 노드에서 Overlay 패킷을 디캡슐화(Encapsulation 해제)한 후 Pod2로 전달합니다.

AWS VPC CNI를 사용했을 때 다른 노드에서 Pod 간 통신 구조입니다.

 

  1. Pod1 (192.168.1.3)이 Pod2 (192.168.1.4)로 패킷을 보냅니다.
  2. 패킷이 AWS VPC 네트워크를 통해 직접 전달됩니다.
  3. Overlay 없이 원본 패킷 그대로 목적지 노드의 Pod로 전달합니다.
    AWS 네이티브 라우팅을 그대로 사용하므로 별도의 캡슐화 과정이 없습니다.

 

VPC CNI에서 Pod IP를 할당하는 과정

출처 - gasidaseo 노션

 

1. Secondary IPv4 addresses 

- ENI의 Secondary IP를 하나씩 Pod에 할당

- 인스턴스 유형에 최대 ENI 갯수와 할당 가능 IP 수를 조합하여 선정

- ex) t3.medium 타입 사용 시 : 17 * 3대 IP 사용 가능 (단, aws-node, kube-proxy 등 제외)

 

2. IPv4 Prefix Delegration

- 하나의 ENI에 IP범위(/28 프리픽스)를 할당하고, 해당 범위 내에서 Pod IP를 동적으로 할당

- IPv4 28비트 서브넷을 위임하여 할당 가능한 IP수와 인스턴스 유형에 권장하는 최대 갯수로 선정

- ex) c5.large 타입 사용 시 : 47 * 3개 IP 사용 가능 (단, aws-node, kube-proxy 등 제외)

 

노드 당 최대 파드 배치 수를 알고 싶다면?
https://hellouz818.tistory.com/21

 

 

2주차 실습 cloudformation으로 myeks EKS 클러스터 생성 후 aws-node 설정을 확인했을 때, VPC CNI가 ENALBE_PREFIX_DELEGATION 설정을 사용하지 않는다는 것을 알 수 있었습니다.

❯ kubectl describe  daemonset aws-node -n kube-system  | grep PREFIX

      AWS_VPC_K8S_CNI_VETHPREFIX:             eni
      ENABLE_PREFIX_DELEGATION:               false
      WARM_PREFIX_TARGET:                     1

 

 

실제 EC2에 붙어있는 ENI 주소는 다음과 같습니다.

❯ aws ec2 describe-network-interfaces --filters "Name=attachment.instance-id,Values=i-0ec4a14ee9c062ffc" --query 'NetworkInterfaces[*].PrivateIpAddresses[*].PrivateIpAddress'

[
    [
        "192.168.3.34",
        "192.168.3.108",
        "192.168.3.33",
        "192.168.3.225",
        "192.168.3.50",
        "192.168.3.133"
    ],
    [
        "192.168.3.88",
        "192.168.3.59",
        "192.168.3.191",
        "192.168.3.16",
        "192.168.3.68",
        "192.168.3.247"
    ]
]q

❯ aws ec2 describe-network-interfaces --filters "Name=attachment.instance-id,Values=i-04bf487c0f9092917" --query 'NetworkInterfaces[*].PrivateIpAddresses[*].PrivateIpAddress'

[
    [
        "192.168.1.44",
        "192.168.1.248",
        "192.168.1.58",
        "192.168.1.66",
        "192.168.1.131",
        "192.168.1.150"
    ]
]

❯ aws ec2 describe-network-interfaces --filters "Name=attachment.instance-id,Values=i-082ea60f7ad3d7fda" --query 'NetworkInterfaces[*].PrivateIpAddresses[*].PrivateIpAddress'

[
    [
        "192.168.2.109",
        "192.168.2.145",
        "192.168.2.33",
        "192.168.2.147",
        "192.168.2.35",
        "192.168.2.4"
    ]
]

 

이 때! 첫 번째 노드를 예시로 들자면, 왜 커맨드로 private IP address를 호출했을때는 한 쌍 (5개 묶음)씩 나오지만, 웹 콘솔에서는 2개만 나올까요? 

그것은 바로, 웹 콘솔에서는 기본 Primary 프라이빗 IP만 표시되고, 추가 Secondary IP는 콘솔에서 기본적으로 표시되지 않기 때문입니다. 하지만, 네트워크 인터페이스를 확인하면, 각 Primary 프라이빗 IP에 붙은 Secondary IP들도 확인할 수 있습니다.

 

 

 

 

이 때 ENI에 Secondary IPv4 Address 방식으로 IP가 할당되어 /28 IPv4 Prefix 블록이 할당되지 않았음을 알 수 있습니다.

❯ aws ec2 describe-instances --query 'Reservations[*].Instances[].{InstanceId: InstanceId, Prefixes: NetworkInterfaces[].Ipv4Prefixes[]}'
[
    {
        "InstanceId": "i-0ec4a14ee9c062ffc",
        "Prefixes": []
    },
    {
        "InstanceId": "i-066fa01415e5a6ddc",
        "Prefixes": []
    },
    {
        "InstanceId": "i-04bf487c0f9092917",
        "Prefixes": []
    },
    {
        "InstanceId": "i-082ea60f7ad3d7fda",
        "Prefixes": []
    }
]

 

 

테스트용 파드 50개를 만들어보았습니다.

❯ kubectl create deployment nginx-test --image=nginx --replicas=50

❯ kubectl get pod -n default | grep Pending                               
nginx-test-5b77bfd686-44v6q   0/1     Pending             0          15s
nginx-test-5b77bfd686-6n6sr   0/1     Pending             0          16s
nginx-test-5b77bfd686-8txmx   0/1     Pending             0          15s
nginx-test-5b77bfd686-pghnk   0/1     Pending             0          15s
nginx-test-5b77bfd686-pqhnb   0/1     Pending             0          15s
nginx-test-5b77bfd686-r4bql   0/1     Pending             0          15s
nginx-test-5b77bfd686-rzfhc   0/1     Pending             0          15s
nginx-test-5b77bfd686-vfgt4   0/1     Pending             0          15s
nginx-test-5b77bfd686-w2r9f   0/1     Pending             0          16s

❯ kubectl get pod -n default | grep Pending | wc -l                       
       9

 

IP 부족으로 인한 Pending 상태의 Pod는 9개입니다. 

(1번 노드 kube-proxy, aws-node 파드 2개 제외하여 15개 스케줄, 2번 노드 kube-proxy, aws-node 파드  2개 제외하여 15개 스케줄, 3번 노드 kube-proxy, aws-node, coredns*2, metrics-server*2  6개 제외하여 11개 스케줄 -> 총 41개 스케줄 / 9개 Pending)

 

 

이 때 ENABLE_PREFIX_DELEGATION=true 값으로 변경한 후 다시 Node에 연결된 ENI 값을 확인해보겠습니다.

❯ kubectl set env daemonset aws-node -n kube-system ENABLE_PREFIX_DELEGATION=true
daemonset.apps/aws-node env updated

❯ kubectl describe  daemonset aws-node -n kube-system  | grep PREFIX      

      ENABLE_PREFIX_DELEGATION:  true
      AWS_VPC_K8S_CNI_VETHPREFIX:             eni
      ENABLE_PREFIX_DELEGATION:               true
      WARM_PREFIX_TARGET:                     1
      ENABLE_PREFIX_DELEGATION:  true

 

 

++ 또 다른 트러블 슈팅 - 설정 변경에도 즉시 적용되지 않는 이슈)

아니 그런데 여전히 아까 스케줄 되지 못한 파드들의 펜딩이 해소되지 않는다,,?! 

 

그것은 AWS VPC CNI(aws-node)는 Pod에 IP를 할당하는 역할을 수행하는 DaemonSet인데, VPC CNI가 재시작되었다고 해서 즉시 kubelet이 새로운 IP 풀을 반영하는 것은 아닙니다.!

기존 노드 (kubelet)가 새로 할당된 IPv4 Prefix를 자동으로 반영하지 않았기 때문입니다. kubelet은 기존 ENI IP 정보를 캐싱하고 있으므로, 새로 추가된 /28 블록을 즉시 반영하지 않았을 가능성이 높습니다.!

이 때 노드 재시작을 하면서 파드들의 Pending이 해소 되었는데, 이 때 kubelet이 새로 초기화되어 기존 IP 할당 캐시가 사라지고 aws-node가 새롭게 /28 블록을 반영하여 kubelet에게 전달하여 인식할 수 있기 때문입니다.

 

❯ aws ec2 describe-network-interfaces --filters "Name=attachment.instance-id,Values=i-09b7fd7dc2493f5ba" --query 'NetworkInterfaces[*].PrivateIpAddresses[*].PrivateIpAddress'
[
    [
        "192.168.1.100"
    ]
]

❯ aws ec2 describe-network-interfaces --filters "Name=attachment.instance-id,Values=i-098063ca39e4f6b22" --query 'NetworkInterfaces[*].PrivateIpAddresses[*].PrivateIpAddress'
[
    [
        "192.168.2.26"
    ]
]

❯ aws ec2 describe-network-interfaces --filters "Name=attachment.instance-id,Values=i-0a08a9165a30c968e" --query 'NetworkInterfaces[*].PrivateIpAddresses[*].PrivateIpAddress'
[
    [
        "192.168.3.19",
        "192.168.3.108",
        "192.168.3.78",
        "192.168.3.130",
        "192.168.3.99"
    ]
]

 

aws ec2 describe-network-interfaces \
  --filters "Name=attachment.instance-id,Values=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)" \
  --query 'NetworkInterfaces[*].PrefixDelegation[*]'

[]

# [] (빈 배열) → IPv4 Prefix Delegation이 적용되지 않음
# [{ "Prefix": "192.168.x.x/28" }, { "Prefix": "192.168.y.y/28" }] → IPv4 Prefix Delegation 정상 적용

 

하지만, 노드 재시작하는 과정에서도 문제가 발생했는데요.. OTL

 

++ 또 다른 트러블 슈팅 - 노드 삭제편)

그래서 Node를 지웠더니 Node가 사라졌습니다.. 아무래도 당연한 얘기인것일까요? 노드를 삭제하면 노드가 롤링으로 뜰 것이라고 생각한 제가 어리석었네요,, 

'kubectl delete node' 명령을 수행 Kubernetes API server 노드 삭제 API 요청하고 Kubernetes 클러스터 내에서 해당 노드가 이상 사용되지 않게 제거되지만, AWS 서비스에서는 EC2 인스턴스에 대해 Terminate 요청을 받지 않았기 때문에 인스턴스는 계속 구동중인 상태를 유지한다고 합니다. (delete외에도 cordon, drain 등 모두 동일)

따라서, 이럴 경우에는 인스턴스를 강제로 Terminate하는 것을 권장한다고 합니다.

Autoscaling Group이 적용되어있으면 새로 생성된다고 하는데 이건 추후 확인해보도록 하겠습니다.

 

 

NodeGroup을 지우고 다시 만들었습니다. 이 때, 새로 Node가 생성되었는데, 기존 배포된 리소스들은 그대로 있고, Node만 새로 뜨는 것으로 보입니다. 

이 때 기존에 노드에는 모두 파드가 각각 17개씩만 떴는데, 지금은 각 노드에 27개, 17개, 16개의 파드가 할당 되어있고, 실제로 Pending된 Pod도 없는 것을 확인할 수 있습니다.

 

❯ kubectl get pod -n default | grep Pending
❯

 

 

이 때 ENI에 IPv4 Prefix Delegation 방식으로 IP가 할당되어 /28 IPv4 Prefix 블록이 할당되었음을 알 수 있습니다.

❯ aws ec2 describe-instances --query 'Reservations[*].Instances[].{InstanceId: InstanceId, Prefixes: NetworkInterfaces[].Ipv4Prefixes[]}'
[
    {
        "InstanceId": "i-0ec4a14ee9c062ffc",
        "Prefixes": [
            {
                "Ipv4Prefix": "192.168.3.16/28"
            }
        ]
    },
    {
        "InstanceId": "i-066fa01415e5a6ddc",
        "Prefixes": []
    },
    {
        "InstanceId": "i-04bf487c0f9092917",
        "Prefixes": [
            {
                "Ipv4Prefix": "192.168.1.48/28"
            }
        ]
    },
    {
        "InstanceId": "i-082ea60f7ad3d7fda",
        "Prefixes": [
            {
                "Ipv4Prefix": "192.168.2.208/28"
            }
        ]
    }
]
            
            
            
❯ aws ec2 describe-network-interfaces --filters "Name=attachment.instance-id,Values=i-0ec4a14ee9c062ffc" --query 'NetworkInterfaces[*].PrivateIpAddresses[*].PrivateIpAddress'

[
    [
        "192.168.3.34",
        "192.168.3.108",
        "192.168.3.33",
        "192.168.3.225",
        "192.168.3.50"
    ]
]

❯ aws ec2 describe-network-interfaces --filters "Name=attachment.instance-id,Values=i-04bf487c0f9092917" --query 'NetworkInterfaces[*].PrivateIpAddresses[*].PrivateIpAddress'
[
    [
        "192.168.1.44"
    ]
]

❯ aws ec2 describe-network-interfaces --filters "Name=attachment.instance-id,Values=i-082ea60f7ad3d7fda" --query 'NetworkInterfaces[*].PrivateIpAddresses[*].PrivateIpAddress'
[
    [
        "192.168.2.109"
    ]
]


https://docs.aws.amazon.com/ko_kr/AWSEC2/latest/UserGuide/ec2-prefix-eni.html

 

Amazon EC2 네트워크 인터페이스에 대한 접두사 위임 - Amazon Elastic Compute Cloud

Amazon EC2 네트워크 인터페이스에 대한 접두사 위임 프라이빗 IPv4 또는 IPv6 CIDR 범위를 자동 또는 수동으로 네트워크 인터페이스에 할당할 수 있습니다. 접두사를 할당하면 인스턴스에 여러 IP 주

docs.aws.amazon.com

 

 

 

실제로, AWS VPC CNI가 새로운 Pod에 IP를 할당하는 과정은 다음과 같은 흐름으로 이루어집니다.

 

AWS VPC CNI가 새로운 Pod에 IP를 할당하는 과정

 

 

1. 새로운 슬롯(Slot)이 필요한 상황

Pod가 생성되면 해당 Pod에 IP 주소를 할당해야 합니다. 이를 위해 VPC CNI는 기존 ENI에서 남은 IP가 있는지 확인하고, 없으면 추가적인 ENI를 할당합니다.

 

2. 슬롯 할당 프로세스

(1) Primary ENI에 슬롯(IP)이 남아 있는가?

  • YES → 기존 Primary ENI에서 IP 슬롯을 Pod에 할당 → SUCCESS
  • NO → 다음 단계로 이동

(2) Secondary ENI에 남은 슬롯(IP)이 있는가?

  • YES → 기존 Secondary ENI에서 IP 슬롯을 Pod에 할당 → SUCCESS
  • NO → 다음 단계로 이동

(3) 인스턴스가 새로운 ENI를 추가할 수 있는가?

  • YES → 새로운 ENI를 인스턴스에 추가하고, ENI에 할당된 IP 중 하나를 Pod에 할당 → SUCCESS
  • NO → Pod에 IP를 할당할 수 없으므로 FAILURE

 

 

 

++ 위의 실습에 대한 내용과 비슷한 블로그가 있어서 참고하겠습니다.

https://aws.amazon.com/ko/blogs/containers/amazon-vpc-cni-increases-pods-per-node-limits/

 

Amazon VPC CNI plugin increases pods per node limits | Amazon Web Services

As of August 2021, Amazon VPC Container Networking Interface (CNI) Plugin supports “prefix assignment mode”, enabling you to run more pods per node on AWS Nitro based EC2 instance types. To achieve higher pod density, the VPC CNI plugin leverages a new

aws.amazon.com