EKS AWS VPC CNI

주진성·2024년 3월 16일
1

AWS

목록 보기
3/3
post-thumbnail
  • CloudNet@ 팀 gasida님의 AEWS(AWS EKS Workshop Study) 스터디를 진행하며 작성한 글 입니다.

1. AWS VPC CNI

Kubernetes CNI는 Kubernetes Worker Node끼리 통신하기 위해 사용되며, 대표적으로 Calico, Flannel, Weave Net 등이 있습니다.

AWS EKS에선 각 노드들의 통신을 위해서 AWS VPC CNI를 사용합니다.

VPC CNI 는 EKS에 addon 형태로 배포되게 됩니다.

AWS VPC CNI는 VPC와 통합되어 Security Group, VPC Flow Logs, VPC 라우팅 정책을 사용하는것이 가능하며, 해당 Plugin을 통해 각 Pod에게 IP주소를 부여해 줍니다.

AWS ENI를 파드별로 할당해 줍니다!

  • 이말은 Pod에 직접 접근할 수 있다는 의미
  • 개수에 한계가 있다.

1.1 AWS VPC CNI vs Kubernetes Calico

위 사진처럼 Calico는 Pod Network CIDR와 Node Network CIDR가 다릅니다. 이는 파드간 통신 네트워크를 격리하여 보안적 이점과 IP 대역의 충돌을 막습니다.

그러나 AWS VPC CNI는 노드 네트워크 CIDR와 Pod Network CIDR가 같습니다.

이는 Kubernetes Kube-proxy 통신의 복잡성을 줄일 수 있고, 이는 넘어가는 Hop의 수를 줄일 수 있기 때문에 통신이 단순명확해지고 속도가 빨라진다는 이점을 가집니다.

# AWS VPC CNI를 사용하는 환경입니다.
## 노드 IP 확인
aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,PrivateIPAdd:PrivateIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output table
----------------------------------------------------------------------
|                         DescribeInstances                          |
+-------------------+-----------------+-------------------+----------+
|   InstanceName    |  PrivateIPAdd   |   PublicIPAdd     | Status   |
+-------------------+-----------------+-------------------+----------+
|  myeks-ng1-Node   |  192.168.3.92   |  ---.---.---.---  |  running |
|  myeks-bastion-EC2|  192.168.1.100  |  ---.---.---.---  |  running |
|  myeks-ng1-Node   |  192.168.1.223  |  ---.---.---.---  |  running |
|  myeks-ng1-Node   |  192.168.2.31   |  ---.---.---.---  |  running |
+-------------------+-----------------+-------------------+----------+


## 파드 IP 확인
kubectl get pod -n kube-system -o=custom-columns=NAME:.metadata.name,IP:.status.podIP,STATUS:.status.phase
NAME                       IP              STATUS
aws-node-7l2zr             192.168.1.223   Running
aws-node-94mxb             192.168.3.92    Running
aws-node-hxzcf             192.168.2.31    Running
coredns-55474bf7b9-shclw   192.168.1.151   Running
coredns-55474bf7b9-zgf7w   192.168.2.123   Running
kube-proxy-cxgxz           192.168.2.31    Running
kube-proxy-fmjh6           192.168.3.92    Running
kube-proxy-r52t7           192.168.1.223   Running

파드간 통신에서도 기존 CNI와 AWS VPC CNI는 상이합니다.

Kuberntes CNI는 일반적으로 오버레이(VXLAN, IP-IP 등) 통신으로 연결되게 됩니다. 따라서 원본 패킷에 다른 헤더가 추가되어 패킷이 다른노드의 파드로 전송되게 됩니다.

그러나 AWS VPC CNI는 각각의 Pod가 ENI를 가지기 때문에 패킷이 추가되거나 하는 등의 변조가 일어나지 않고 원본 패킷이 전달되게 됩니다.

1.2 AWS VPC CNI의 생성가능 파드 개수

AWS VPC CNI는 파드에 AWS ENI가 할당됩니다.

AWS Instance는 Type에 따라서 CPU 스팩 차이가 있기에 가질 수 있는 ENI 개수가 한정되어 있습니다. 또한 ENI별로 Secondary IPV4를 몇개나 가질 수 있는지에 차이가 있습니다.

  • 예를들어서 t3.medium 은 ENI가 3개를 가지는데, Secondary IPV4는 각 ENI별로 5개를 가질 수 있습니다. 만약 t3.medium worker Node가 3대라면, 15개의 파드가 생성될 수 있다는것을 의미합니다.

이러한 한계를 극복하기 위하여 IPv4 Prefix 위임 등의 많은 방법들이 있습니다.

AWSVPC_CNI플러그인으로노드당파드수_제한늘리기

2. Node 기본 네트워크 정보 확인

기본적으로 Kubernetes 에서는 Network Namespace 는 호스트(Root) 와 파드 별(Per Pod) 로 구분됩니다.

Kubernetes Pod들은 타 노드에 배포된 파드와 통신하기 위해 veth 인터페이스라는 가상 이더넷 인터페이스와 연결되어 노드간 연결된 CNI와 통신함으로써 타 노드로 통신이 가능하게 됩니다.

그러나 특정 파드들은 HostNetwork 옵션을 True 로 두면서 Host의 Namespace에 인터페이스와 직접 연결됩니다.

  • 예를 들어 CNI인 aws-node(AWS VPC CNI) , Kube-Proxy

또한 AWS VPC CNI를 사용했을 경우 파드가 생성되면 , 각 노드에 eniY@ifN 이더넷이 생성됩니다.

eniY는 호스트 시스템에 있는 특정 ENI를 가리키며, @ifN은 이 ENI와 연결된 내부 또는 가상 인터페이스를 나타냅니다. 

이 구성을 통해 파드는 호스트 시스템의 네트워크 인터페이스를 통해 VPC의 다른 리소스나 인터넷과 통신할 수 있습니다.

eniY@ifN 이더넷이 있기 때문에, AWS ENI와 파드가 직접 연결될 수 있으며, 파드간 통신이 진행될 때 ENI를 통해 직접 통신이 가능하게 됩니다.

2.1 eniY@ifN 확인

실제로 노드 라우팅테이블을 모니터링하여 eniY@ifN 가 생성되는지 확인해 봅니다.

# 워커노드 1번 모니터링
ssh ec2-user@$N1
watch -d "ip link | egrep 'eth|eni' ;echo;echo "[ROUTE TABLE]"; route -n | grep eni"
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP mo
de DEFAULT group default qlen 1000
    link/ether 02:e0:53:4b:62:89 brd ff:ff:ff:ff:ff:ff
3: enicb22935b2f6@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc
noqueue state UP mode DEFAULT group default
    link/ether be:b9:69:13:09:77 brd ff:ff:ff:ff:ff:ff link-netns cni-62
75d2d1-259f-8188-46f4-6697f14cb4b2
4: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP mo
de DEFAULT group default qlen 1000
    link/ether 02:c4:08:00:10:d7 brd ff:ff:ff:ff:ff:ff

[ROUTE TABLE]
192.168.1.151   0.0.0.0         255.255.255.255 UH    0      0        0
enicb22935b2f6


# 워커노드 2번 모니터링
ssh ec2-user@$N2
watch -d "ip link | egrep 'eth|eni' ;echo;echo "[ROUTE TABLE]"; route -n | grep eni"
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP mo
de DEFAULT group default qlen 1000
    link/ether 06:e0:77:a1:c3:95 brd ff:ff:ff:ff:ff:ff
3: enie7b667f3e46@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc
noqueue state UP mode DEFAULT group default
    link/ether 66:1e:55:90:25:45 brd ff:ff:ff:ff:ff:ff link-netns cni-71
5466e8-f161-91b2-f9eb-602f40ab364e
4: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP mo
de DEFAULT group default qlen 1000
    link/ether 06:c5:93:88:65:2d brd ff:ff:ff:ff:ff:ff

[ROUTE TABLE]
192.168.2.123   0.0.0.0         255.255.255.255 UH    0      0        0
enie7b667f3e46


# 워커노드 3번 모니터링
ssh ec2-user@$N3
watch -d "ip link | egrep 'eth|eni' ;echo;echo "[ROUTE TABLE]"; route -n | grep eni"
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP mo
de DEFAULT group default qlen 1000
    link/ether 0a:c4:f0:43:1b:b5 brd ff:ff:ff:ff:ff:ff

[ROUTE TABLE]


# 테스트용 파드 netshoot-pod 생성
cat <<EOF | kubectl create -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

netshoot Pod를 생성했을 때 , 각 노드 라우팅 테이블이 아래와 같이 eniY@ifN 이더넷이 생성된것을 확인할 수 있으며, 이는 AWS ENI 와 연결됫다는것을 반증합니다. !

# 생성한 Pod Network 정보
$ kubectl get pods -o wide
NAME                            READY   STATUS    RESTARTS   AGE    IP              NODE                                               NOMINATED NODE   READINESS GATES
netshoot-pod-79b47d6c48-4xqkh   1/1     Running   0          112s   192.168.3.88    ip-192-168-3-92.ap-northeast-2.compute.internal    <none>           <none>
netshoot-pod-79b47d6c48-c4nxr   1/1     Running   0          112s   192.168.2.232   ip-192-168-2-31.ap-northeast-2.compute.internal    <none>           <none>
netshoot-pod-79b47d6c48-wwzpq   1/1     Running   0          112s   192.168.1.85    ip-192-168-1-223.ap-northeast-2.compute.internal   <none>           <none>

# 워커노드 1번 라우팅 정보
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP mo
de DEFAULT group default qlen 1000
    link/ether 02:e0:53:4b:62:89 brd ff:ff:ff:ff:ff:ff
3: enicb22935b2f6@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc
noqueue state UP mode DEFAULT group default
    link/ether be:b9:69:13:09:77 brd ff:ff:ff:ff:ff:ff link-netns cni-62
75d2d1-259f-8188-46f4-6697f14cb4b2
4: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP mo
de DEFAULT group default qlen 1000
    link/ether 02:c4:08:00:10:d7 brd ff:ff:ff:ff:ff:ff
6: enif6a2040d438@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc
noqueue state UP mode DEFAULT group default
    link/ether 2e:18:a4:a7:38:a1 brd ff:ff:ff:ff:ff:ff link-netns cni-45
844378-40f9-3834-45df-5adb244bb7e7

[ROUTE TABLE]
192.168.1.85    0.0.0.0         255.255.255.255 UH    0      0        0
enif6a2040d438
192.168.1.151   0.0.0.0         255.255.255.255 UH    0      0        0
enicb22935b2f6


# 워커노드 2번 라우팅 정보
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP mo
de DEFAULT group default qlen 1000
    link/ether 06:e0:77:a1:c3:95 brd ff:ff:ff:ff:ff:ff
3: enie7b667f3e46@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc
noqueue state UP mode DEFAULT group default
    link/ether 66:1e:55:90:25:45 brd ff:ff:ff:ff:ff:ff link-netns cni-71
5466e8-f161-91b2-f9eb-602f40ab364e
4: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP mo
de DEFAULT group default qlen 1000
    link/ether 06:c5:93:88:65:2d brd ff:ff:ff:ff:ff:ff
6: enib67b2fece8f@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc
noqueue state UP mode DEFAULT group default
    link/ether aa:83:64:3d:cc:a6 brd ff:ff:ff:ff:ff:ff link-netns cni-a5
28197e-0503-a9a9-7487-4b2547d80e19

[ROUTE TABLE]
192.168.2.123   0.0.0.0         255.255.255.255 UH    0      0        0
enie7b667f3e46
192.168.2.232   0.0.0.0         255.255.255.255 UH    0      0        0
enib67b2fece8f


# 워커노드 1번 라우팅 정보
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP mo
de DEFAULT group default qlen 1000
    link/ether 0a:c4:f0:43:1b:b5 brd ff:ff:ff:ff:ff:ff
8: eni017542ca75f@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc
noqueue state UP mode DEFAULT group default
    link/ether 5e:35:3e:7b:2e:a0 brd ff:ff:ff:ff:ff:ff link-netns cni-03
2c4e03-3236-ad20-19e8-adab269e38ca
9: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP mo
de DEFAULT group default qlen 1000
    link/ether 0a:d0:c4:9d:70:d9 brd ff:ff:ff:ff:ff:ff

[ROUTE TABLE]
192.168.3.88    0.0.0.0         255.255.255.255 UH    0      0        0
eni017542ca75f

실제로 netshoot 파드에 접속해서, ip 정보를 확인해볼 수 있습니다.

# 파드 이름 변수 지정
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})

# 테스트용 파드 접속(exec) 후 Shell 실행
kubectl exec -it $PODNAME1 -- zsh

# 아래부터는 pod-1 Shell 에서 실행 : 네트워크 정보 확인
----------------------------
ip -c addr
ip -c route
route -n
ping -c 1 <pod-2 IP>
ps
cat /etc/resolv.conf
exit
----------------------------

# 파드2 Shell 실행
kubectl exec -it $PODNAME2 -- ip -c addr

# 파드3 Shell 실행
kubectl exec -it $PODNAME3 -- ip -br -c addr

3. 노드 간 파드 통신

AWS VPC CNI 를 사용하면, 파드에 ENI가 직접 할당되기에 노드간 통신이 이루어질 때 파드에 직접 접근할 수 있습니다.

이는 기존 Kube-Proxy 를 통한다면 NAT 되기때문에 Hop이 있으며 통신이 복잡한데, 이러한 단점을 모두 해결하게 됩니다.

이를 반증하는 테스트로, Pod에서 Pod로 Ping을 보냈을 경우 tcpdump 내용을 확인해보면 볼 수 있습니다.

3.1 노드간 파드 통신 실습

실제로 AWS VPC CNI를 사용한 환경에서 파드 Ping Test를 통해 위 정보를 확인할 수 있습니다.

먼저 netshoot deployment를 생성합니다.

# 테스트용 파드 netshoot-pod 생성
cat <<EOF | kubectl create -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

Ping Test를 진행합니다.

# 파드 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})

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

# 파드2 Shell 에서 파드3로 ping 테스트
kubectl exec -it $PODNAME2 -- ping -c 2 $PODIP3

# 파드3 Shell 에서 파드1로 ping 테스트
kubectl exec -it $PODNAME3 -- ping -c 2 $PODIP1

# 워커 노드 EC2 : TCPDUMP 확인
sudo tcpdump -i any -nn icmp
sudo tcpdump -i eth1 -nn icmp
sudo tcpdump -i eth0 -nn icmp
sudo tcpdump -i eniYYYYYYYY -nn icmp

[워커 노드1]
# routing policy database management 확인
ip rule

# routing table management 확인
ip route show table local

# 디폴트 네트워크 정보를 eth0 을 통해서 빠져나간다
ip route show table main
default via 192.168.1.1 dev eth0
...

4. Pod에서 외부 통신

VPC CNI의 External source network address translation 설정에 따라서, 외부(인터넷) 통신 시 SNAT를 설정 하거나 혹은 SNAT 없이 통신이 가능합니다.

  • 이는 Pod가 AWS ENI를 갖기 때문에 가능한것.
# 작업용 EC2 : pod-1 Shell 에서 외부로 ping
kubectl exec -it $PODNAME1 -- ping -c 1 www.google.com
kubectl exec -it $PODNAME1 -- ping -i 0.1 www.google.com

# 워커 노드 EC2 : TCPDUMP 확인
sudo tcpdump -i any -nn icmp
sudo tcpdump -i eth0 -nn icmp

# 워커 노드 EC2 : 퍼블릭IP 확인
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i curl -s ipinfo.io/ip; echo; echo; done

# 작업용 EC2 : pod-1 Shell 에서 외부 접속 확인 - 공인IP는 어떤 주소인가?
## The right way to check the weather - 링크
for i in $PODNAME1 $PODNAME2 $PODNAME3; do echo ">> Pod : $i <<"; kubectl exec -it $i -- curl -s ipinfo.io/ip; echo; echo; done
kubectl exec -it $PODNAME1 -- curl -s wttr.in/seoul
kubectl exec -it $PODNAME1 -- curl -s wttr.in/seoul?format=3
kubectl exec -it $PODNAME1 -- curl -s wttr.in/Moon
kubectl exec -it $PODNAME1 -- curl -s wttr.in/:help

# 워커 노드 EC2
## 출력된 결과를 보고 어떻게 빠져나가는지 고민해보자!
ip rule
ip route show table main
sudo iptables -L -n -v -t nat
sudo iptables -t nat -S

# 파드가 외부와 통신시에는 아래 처럼 'AWS-SNAT-CHAIN-0' 룰(rule)에 의해서 SNAT 되어서 외부와 통신!
# 참고로 뒤 IP는 eth0(ENI 첫번째)의 IP 주소이다
# --random-fully 동작 - 링크1  링크2
sudo 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.1.251 --random-fully

## 아래 'mark 0x4000/0x4000' 매칭되지 않아서 RETURN 됨!
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --set-xmark 0x4000/0x0
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE --random-fully
...

# 카운트 확인 시 AWS-SNAT-CHAIN-0에 매칭되어, 목적지가 192.168.0.0/16 아니고 외부 빠져나갈때 SNAT 192.168.1.251(EC2 노드1 IP) 변경되어 나간다!
sudo iptables -t filter --zero; sudo iptables -t nat --zero; sudo iptables -t mangle --zero; sudo iptables -t raw --zero
watch -d 'sudo iptables -v --numeric --table nat --list AWS-SNAT-CHAIN-0; echo ; sudo iptables -v --numeric --table nat --list KUBE-POSTROUTING; echo ; sudo iptables -v --numeric --table nat --list POSTROUTING'

# conntrack 확인
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo conntrack -L -n |grep -v '169.254.169'; echo; done
conntrack v1.4.5 (conntrack-tools): 
icmp     1 28 src=172.30.66.58 dst=8.8.8.8 type=8 code=0 id=34392 src=8.8.8.8 dst=172.30.85.242 type=0 code=0 id=50705 mark=128 use=1
tcp      6 23 TIME_WAIT src=172.30.66.58 dst=34.117.59.81 sport=58144 dport=80 src=34.117.59.81 dst=172.30.85.242 sport=80 dport=44768 [ASSURED] mark=128 use=1

5. 노드 파드 생성갯수 제한

AWS VPC CNI를 사용했을 때 , 노드의 Instance Type에 따라서 파드 생성 개수가 제한됩니다.

기본적으로 Secondary IPv4 addresses 를 사용하게 됩니다. 이는 인스턴스 Type의 최대 ENI 개수와 할당 가능 IP 수를 조합해서 파드 개수를 계산할 수 있습니다.

  • 단 aws-node 와 kube-proxy 파드는 Host의 Namespace를 그대로 사용하기 떄문에, 이 둘은 최대 개수에서 제외합니다.

최대 파드 생성 개수 계산식 : 

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

아래 명령어로 Instance Type별로 IPV4 addr 개수와 가질 수 있는 최대 ENI 개수를 확인할 수 있습니다.

 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      |
+----------+----------+--------------+
|  6       |  3       |  t3.medium   |
|  15      |  4       |  t3.xlarge   |
|  15      |  4       |  t3.2xlarge  |
|  12      |  3       |  t3.large    |
|  2       |  2       |  t3.micro    |
|  2       |  2       |  t3.nano     |
|  4       |  3       |  t3.small    |
+----------+----------+--------------+

c 계열 Instance 일 경우에 ENI 개수와 IPV4 addr 개수는 다음과 같습니다.

aws ec2 describe-instance-types --filters Name=instance-type,Values=c5*.* \
>  --query "InstanceTypes[].{Type: InstanceType, MaxENI: NetworkInfo.MaximumNetworkInterfaces, IPv4addr: NetworkInfo.Ipv4AddressesPerInterface}" \
>  --output table
----------------------------------------
|         DescribeInstanceTypes        |
+----------+----------+----------------+
| IPv4addr | MaxENI   |     Type       |
+----------+----------+----------------+
|  10      |  3       |  c5d.large     |
|  30      |  8       |  c5.4xlarge    |
|  10      |  3       |  c5n.large     |
|  15      |  4       |  c5n.2xlarge   |
|  30      |  8       |  c5d.12xlarge  |
|  50      |  15      |  c5d.24xlarge  |
|  15      |  4       |  c5a.xlarge    |
|  15      |  4       |  c5.xlarge     |
|  30      |  8       |  c5a.4xlarge   |
|  30      |  8       |  c5a.12xlarge  |
|  30      |  8       |  c5.12xlarge   |
|  30      |  8       |  c5n.9xlarge   |
|  15      |  4       |  c5d.2xlarge   |
|  30      |  8       |  c5n.4xlarge   |
|  50      |  15      |  c5a.24xlarge  |
|  50      |  15      |  c5.24xlarge   |
|  50      |  15      |  c5.metal      |
|  30      |  8       |  c5d.4xlarge   |
|  50      |  15      |  c5a.16xlarge  |
|  50      |  15      |  c5n.18xlarge  |
|  30      |  8       |  c5.9xlarge    |
|  50      |  15      |  c5d.18xlarge  |
|  15      |  4       |  c5a.2xlarge   |
|  15      |  4       |  c5d.xlarge    |
|  15      |  4       |  c5.2xlarge    |
|  10      |  3       |  c5.large      |
|  50      |  15      |  c5d.metal     |
|  30      |  8       |  c5a.8xlarge   |
|  50      |  15      |  c5n.metal     |
|  15      |  4       |  c5n.xlarge    |
|  50      |  15      |  c5.18xlarge   |
|  30      |  8       |  c5d.9xlarge   |
|  10      |  3       |  c5a.large     |
+----------+----------+----------------+

t3.medium이며 노드가 3개일 경우, 파드 개수는 다음과 같습니다.

# 파드 사용 가능 계산 예시 : aws-node 와 kube-proxy 파드는 host-networking 사용으로 IP 2개 남음
((MaxENI * (IPv4addr-1)) + 2)
t3.medium 경우 : ((3 * (6 - 1) + 2 ) = 17>> aws-node 와 kube-proxy 2개 제외하면 15

워커 노드의 상세정보를 확인해보면, Allocatable.pods 의 value로 파드 개수가 출력되는것을 확인할 수 있습니다.

$ kubectl describe node | grep Allocatable: -A6
Allocatable:
  cpu:                1930m
  ephemeral-storage:  27905944324
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             3388352Ki
  pods:               17
--
Allocatable:
  cpu:                1930m
  ephemeral-storage:  27905944324
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             3388344Ki
  pods:               17
--
Allocatable:
  cpu:                1930m
  ephemeral-storage:  27905944324
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             3388344Ki
  pods:               17

17개 이상의 파드가 생성됐을 경우에, 파드가 Pending 으로 변하는것을 확인할 수 있습니다.

# 워커 노드 EC2 - 모니터링
while true; do ip -br -c addr show && echo "--------------" ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; done

# 작업용 EC2 - 터미널1
watch -d 'kubectl get pods -o wide'

# 작업용 EC2 - 터미널2
# 디플로이먼트 생성
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/2/nginx-dp.yaml
kubectl apply -f nginx-dp.yaml

# 파드 확인
kubectl get pod -o wide
kubectl get pod -o=custom-columns=NAME:.metadata.name,IP:.status.podIP

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

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

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

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

# 파드 생성 실패!
kubectl get pods | grep Pending
nginx-deployment-f7f5c78c5-dh99d   0/1     Pending   0          3s
nginx-deployment-f7f5c78c5-4tj8d   0/1     Pending   0          17s
nginx-deployment-f7f5c78c5-6z8dr   0/1     Pending   0          17s
...

kubectl describe pod nginx-deployment-f7f5c78c5-6z8dr | grep Events: -A5
Events:
  Type     Reason            Age   From               Message
  ----     ------            ----  ----               -------
  Warning  FailedScheduling  32s   default-scheduler  0/3 nodes are available: 3 Too many pods. preemption: 0/3 nodes are available: 3 No preemption victims found for incoming pod..

# 디플로이먼트 삭제
kubectl delete deploy nginx-deployment

5.1 파드 생성갯수 제한 해결 방안

파드 생성갯수 제한을 해결하는 방언으로 다양한 방법들이 제안됩니다.

  1. Prefix Delegation
  2. WARM & MIN IP/Prefix Targets
  3. Custom Network

해당 문서에서는 Prefix Delegation 방안에 대해 기술합니다.

5.1.0 방법들의 대한 EKS Workshop

EKS Workshop

5.1.1 Prefix Delegation

AWS에서 제공되는 방법으로, ENI에 IP 주소가 할당되는 대신에 CIDR 블록을 할당하는 방법입니다.

개별 IP주소를 ENI에 제공하는 대신에 , 더 큰 네트워크 범위 (/28) 를 ENI에 할당할 수 있습니다. 이를 통해서 파드 개수를 늘릴 수 있습니다.

6. AWS VPC CNI with Kubernetes LoadBalancer Service

기존 Kubernetes LoadBalancer를 사용하면 , 각 노드의 NodePort 와 LoadBalancer가 연결되게 됩니다.

AWS VPC CNI를 사용하면, AWS LoadBalancer를 사용했을. 때, EKS Cluster의 Pod IP로 바로 통신하게 됩니다.

이때 AWS LB의 서비스 타입은 NLB 입니다.

  • TCP/UDP 프로토콜

Service가 L4계층 서비스기 때문에 NLB를 프로비저닝합니다.

7. AWS VPC with Kubernetes Ingress

Kubernetes Ingress를 통하기 위해 AWS ALB를 배포시킨 뒤 접근한다면, Pod ENI를 통해 Pod에 직접 접근하게 됩니다.

  • HTTP/HTTPS 프로토콜

Ingress가 L7계층 서비스기 때문에 ALB를 프로비저닝합니다.

0개의 댓글