AWS VPC CNI
CNI는 Container Network Interface로 컨테이너 간의 소통에 반드시 필요한 플러그인이다. 쿠버네티스가 컨테이너 오케스트레이션 툴이기 때문에 사실상 CNI가 없다면 쿠버네티스 네트워크가 없다는 것과 동일한 말이다.
그 중 오늘은 AWS VPC CNI에 대해서 작성해본다. AWS에서 제공하는 플러그인으로 CNI인데 컨테이너가 VPC 내부로 편입할 수 있도록 네트워킹을 구성해주는 플러그인이다. 이를 이용하면 파드와 노드의 IP 대역을 같도록 맞춰준다. 즉 같은 네트워크 대역대에서 통신하므로 원본 패킷을 그대로 유지하고 네트워크 홉을 줄일 수 있다는 장점이 있다.
공식문서에서도 확인해보기 바란다.
여기에서는 VPC CNI의 두 가지 컴포넌트에 대해서 이야기한다.
1) CNI Binary
가장 기본적인 기능으로 pod-to-pod 통신을 가능하도록 하며, 노드 루트 파일 시스템에서 실행되어 새 pod가 추가되거나 제거되면 kubelet에서 호출된다.
2) ipamd
IPAM(long-running node-local IP Address Management)라고 하는 데몬이다. 노드에서 ENI를 관리하고 warm-pool로 사용가능한 IP 주소와 prefix를 관리한다.
L-IPAM 이란
각 노드에서 사용 가능한 보조 IP 주소의 warm-pool을 유지하는데 사용한다. warm-pool이라는 이름 답게 사용 가능한 IP 대역을 미리 할당받아 warm-pool에 보관하고 있다가 kubelet에서 pod가 새로 추가되게 되면 warm-pool에서 즉시 IP를 할당 받아 사용한다.
L-IPAM은 노드의 메타데이터를 활용해 사용 가능한 ENI와 보조 IP 주소를 파악해, DaemonSet이 재시작될때마다 kubelet을 통해 파드 이름, 네임스페이스, IP 주소 등 현재 실행 중인 파드 정보를 가져와 warm-pool을 구축한다.
실제로 확인해보자. 현재 내가 가지고 있는 노드의 콘솔상의 네트워크 정보를 확인해보았다.
프라이머리 IP로 2개가 있으며, 보조 IP로 10개가 존재하는 것을 확인할 수 있다.
L-IPAM의 동작원리
kube-proxy나 aws-node를 제외한 파드가 배포되면 L-IPAM은 Secondary ENI를 생성한다. L-IPAM은 Secondary ENI의 보조 private IP를 warm-pool에서 가져와 즉시 사용 가능한 IP로 가지고 파드가 생성되며 즉시 IP를 할당한다.
CNI 정보 확인
기본적으로 EKS를 배포하면 kube-system 네임스페이스에서 aws-node와 kube-proxy를 확인할 수 있다. 이 둘은 CNI를 구성하고 네트워킹을 설정하는 중요한 파드로 역할을 한다.
aws-node
aws-node라는 파드가 기본적으로 배포되는데 다음과 같은 역할을 한다.
1) vpc cni 역할의 container
2) networkpolicy(네트워크 정책 제어)
다음 명령어를 통해서 aws-node에서 사용하는 cni 정보를 확인할 수 있다.
kubectl describe daemonset aws-node --namespace kube-system | grep Image | cut -d "/" -f 2
kube-proxy 모드
기본적으로 iptables 를 사용하고, IPVS로 변경할 수 있다.
다음 명령어로 kube-proxy의 configMap을 확인하면 어떤 모드를 사용하는 지 확인할 수 있다.
kubectl describe cm -n kube-system kube-proxy-config
CNI 구성정보를 조금 더 자세히 파악하고 싶다면 /var/log에서 aws-routed-eni 항목에서 제공하는 로그들을 살펴보고 확인할 수 있다. network에 대한 로그들을 이쪽에서 기록하게 된다.
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i tree /var/log/aws-routed-eni; echo; done
ssh ec2-user@$N1 sudo cat /var/log/aws-routed-eni/plugin.log | jq
ssh ec2-user@$N1 sudo cat /var/log/aws-routed-eni/ipamd.log | jq
ssh ec2-user@$N1 sudo cat /var/log/aws-routed-eni/egress-v6-plugin.log | jq
ssh ec2-user@$N1 sudo cat /var/log/aws-routed-eni/ebpf-sdk.log | jq
ssh ec2-user@$N1 sudo cat /var/log/aws-routed-eni/network-policy-agent.log | jq
노드에 파드 생성 갯수 제한
EKS에서는 Secondary IPv4 addresses라는 IP Warm pool을 사용하고 있다. 인스턴스 타입별로 ENI에 할당할 수 있는 가상 IP 풀을 만드는 것인데, 사전에 풀을 만들어 빠르게 매핑할 수 있다는 장점이 있다.
aws-node와 kube-proxy 파드의 경우 호스트의 IP를 사용해 최대 갯수에서 제외된다.
그리고 파드를 죽이게 되면 다시 ENI랑 IP pool이 없어진다.
따라서 (인스턴스 타입별 ENI의 수 x (네트워크 인터페이스에 할당될 수 있는 IP의 수 -1)) +2 의 공식이 나온다.
-1을 빼는 이유는 node에서 기본적으로 가져가는 IP도 있기 때문이다.
할당 가능한 IP를 찾기 위해서는 ec2 describe-instance 명령어에 필터를 주고, MaxENI 쪽으로 보면된다.
aws ec2 describe-instance-types --filters Name=instance-type,Values=t3.* \
--query "InstanceTypes[].{Type: InstanceType, MaxENI: NetworkInfo.MaximumNetworkInterfaces, IPv4addr: NetworkInfo.Ipv4AddressesPerInterface}" \
--output table
그럼 이렇게 표로 나오게 할 수 있다.
예를 들어 당신이 가지고 있는 노드의 인스턴스 타입이 t3.medium라면, (3*(6-1))+2 = 17개가 된다.
이 중 +2로 된 aws-node와 kube-proxy는 제외해야 한다. 즉 t3.medium 노드는 1개당 총 15개만 가용가능하다.
실제 최대 파드 생성 확인
while true; do ip -br -c addr show && echo "--------------" ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; done
15개 까지는 전혀 문제가 없다.
테스트에서는 t3.medium 노드가 3개이기 때문에 총 45개의 IP를 가져갈 수 있어 pod의 갯수를 매우 늘려보겠다.
갯수를 50개로 늘리니까 7개의 pod가 pending 상태임을 확인할 수 있다.
kubectl scale deployment nginx-deployment --replicas=50
describe 명령어를 통해 Pending 상태의 이유를 살펴보았다.
kubectl describe pod nginx-deployment-6f999cfffb-5xkt4 | grep Events: -A5
too many pods 오류가 발생하는 것을 확인할 수 있다.
VPC CNI를 통한 네트워킹 흐름
AWS에서는 VPC CNI를 통해 별도의 오버레이(Overlay) 통신 기술 없이, VPC Native 하게 파드간 직접 통신이 가능하다. 즉 VPC CNI를 쓰게되면 파드의 IP 네트워크 대역과 워커 노드의 IP 네트워크 대역이 같아 직접 통신이 가능하다.
파드간 통신 흐름
pod에서 pod로 이동하게 된다면 과연 어떤 식으로 이동하게 될까?
기본적으로는 노드까지 타고 가는 것으로 생각되지만 vpc cni를 사용하면 바로 직접 통신이 가능해진다.
# 파드 IP 변수 지정
PODIP1=$(kubectl get pod -l app=netshoot-pod -o jsonpath={.items[0].status.podIP})
PODIP3=$(kubectl get pod -l app=netshoot-pod -o jsonpath={.items[2].status.podIP})
# 파드1 Shell 에서 파드3로 ping 테스트
kubectl exec -it $PODNAME1 -- ping -c 2 $PODIP3
# 워커 노드 EC2 : TCPDUMP 확인
sudo tcpdump -i any -nn icmp
실제로 노드 1과 노드3에서 tcpdump를 확인해보면 pod1의 ip에서 pod3의 ip로 간다는 것을 확인할 수 있었다.
IP가 직접적으로 찍힌다.
라우트 테이블을 node 1에서 확인해보면 디폴트 네트워크는 eth0 을 통해서 빠져나간다는 것을 확인할 수 있다.
ip route show table main
실제로 eth1과 eth0을 tcpdump 해보게 되면 eth1은 트래픽이 없고, eth0에서 트래픽이 발생하는 것을 확인할 수 있다.
sudo tcpdump -i eth1 -nn icmp
sudo tcpdump -i eth0 -nn icmp
파드와 외부 통신
이번에는 외부로 트래픽이 나가는 통신 흐름을 확인해본다. iptable에 SNAT를 통해 노드의 eth0 IP로 변경되서 외부와 통신하게 된다.
Node1의 pod1에서 www.google.com으로 ping을 날려보았다.
kubectl exec -it $PODNAME1 -- ping -c 1 www.google.com
192.168.1.23이 pod의 ip이며, 192.168.1.44가 node의 ip이다.
192.168.1.44로 SNAT이 된것을 확인할 수 있다.
다음 명령어를 통해서 pod가 외부와 통신할 때 어떤 IP로 받는지 ipinfo 사이트에 접근시켜서 확인해보자.
for i in $PODNAME1 $PODNAME2 $PODNAME3; do echo ">> Pod : $i <<"; kubectl exec -it $i -- curl -s ipinfo.io/ip; echo; echo; done
각 노드의 공인 IP로 SNAT 된다는 것을 확인할 수 있다.
실제로 따라가 보겠다.
노드 1에서 iptables에서 확인해보면 AWS-SNAT-CHAIN-0 규칙을 통해서 SNAT이 된다. 뒤에 있는 192.168.1.44는 노드 1의 eth0 주소이다.
sudo iptables -t nat -S | grep 'A AWS-SNAT-CHAIN'
그리고 mark 0x4000/0x4000은 매칭되지 않아서 RETURN 되고, MASQUERADE 되는 것을 확인할 수 있다.
해당 명령어로 모니터링을 걸어놓고 외부로 ping을 해보면
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'
SNAT으로 계속 바이트가 올라가는 것을 확인할 수 있었다.
이상으로 VPC CNI에 대해서 포스팅을 마친다.
'프로젝트&&스터디 > KANS2기' 카테고리의 다른 글
[KANS-9주차] Ingress와 Gateway API (0) | 2024.10.31 |
---|---|
[KANS-9주차] AWS의 로드밸런서 모드 정리 (0) | 2024.10.30 |
[KANS-3기] 여러기능 (Network Policy/Bandwidth Manager/L2 Announcements) (3) | 2024.10.27 |
[KANS-3기] Cilium CNI - 배포 / Hubble / 통신과정확인 (3) | 2024.10.26 |
[KANS-7주차] Istio의 Egress Gateway (0) | 2024.10.20 |