AWS VPC CNI(Container Network Interface)는 Amazon EKS에서 Pod들이 AWS VPC 내의 네이티브 IP 주소를 직접 사용하도록 지원하는 네트워크 플러그인입니다. 이를 통해 파드(Pod)들은 VPC 서브넷에서 직접 IP를 할당받아 통신하며, AWS의 보안 그룹, 네트워크 ACL, 라우팅 정책 등 VPC 네트워크의 모든 기능을 그대로 활용할 수 있습니다.
기존 Kubernetes 네트워크 플러그인(Flannel, Weave, Calico 등)과 달리, VPC CNI는 오버레이 네트워크 없이 파드에 VPC의 실제 IP 주소를 직접 할당하여 NAT 없이도 빠르고 안정적인 통신이 가능합니다. 또한, 파드들이 VPC의 한 구성원처럼 동작하므로 AWS의 보안 정책과 완벽하게 연계되어, 더욱 안전하고 효율적인 네트워크 환경을 제공합니다.
파드에 새로운 IP를 할당하는 과정
출처: https://docs.aws.amazon.com/eks/latest/best-practices/vpc-cni.html
AWS VPC CNI는 네트워크 통신의 최적화(성능, 지연)를 위해서 노드와 파드의 네트워크 대역을 동일하게 설정합니다.
파드간 통신 시 일반적으로 Calico CNI는 오버레이(VXLAN, IP-IP 등) 통신을 하고, AWS VPC CNI는 동일 대역으로 직접 통신한다.
AWS VPC CNI는 기본적으로 보조(Secondary) IP 모드를 사용하는데, 이 모도는 각 ENI에 여러 개의 Secondary IP를 할당하고, 이를 파드에 부여하는 방식입니다.
이 방식에서는 각 ENI에 할당된 IP 개수에 제한이 있으며, Pod 수가 많아질 경우 ENI를 추가해야 하는 상황이 발생할 수 있습니다.
더 많은 파드를 실행해야 하는 경우에는 Prefix 모드를 활성화할 수도 있습니다.
Prefix 모드는 각 ENI가 개별 IP가 아니라 /28 CIDR 블록(16개 IP)을 할당받아, 더 많은 파드를 배포할 수 있도록 하는 방식입니다.
이 방식은 ENI의 IP 할당을 더 효율적으로 수행할 수 있도록 하며, 파드 밀도를 증가시키는 데 유리합니다.
Prefix 모드를 활성화하면 ENI 수를 줄이면서도 더 많은 IP를 사용할 수 있어 네트워크 확장성이 더욱 높아집니다.
이외에도 Custom Networking 기능으로 노드와 파드의 네트워크를 분리하여 보안 강화 및 IP 관리 효율성을 높일 수 있습니다.
노드는 기존 서브넷을 사용하고 100.64.0.0/16 또는 198.19.0.0/16와 같이 파드는 파드 전용 서브넷에서 IP를 할당받아 사용할 수 있습니다.(별도로 기능 활성해주어야 함.)
앞에서 아래와 같이 실습환경을 구축하였습니다.
해당 환경을 바탕으로 실습을 진행해 보도록 하겠습니다.
CNI 정보 확인
kubectl describe daemonset aws-node --namespace kube-system | grep Image | cut -d "/" -f 2
kube-proxy 설정에 경우 여기선 iptables 모드가 기본 설정이지만, 파드와 서비스 간의 트래픽이 많아지면 성능 이슈가 발생할 수 있기 때문에 성능이 좋은 ipvs모드로 변경하였습니다.
#kube-proxy config 확인
kubectl describe cm -n kube-system kube-proxy-config
#kube-proxy 설정 변경
kubectl edit cm -n kube-system kube-proxy-config
이 후 설정 적용을 위해 kube-proxy 파드를 재시작해주어야 합니다.
kubectl delete pod -n kube-system -l k8s-app=kube-proxy
참고: kube-proxy 파드를 삭제하면 자동으로 새로운 kube-proxy 파드를 생성하면서 변경된 ConfigMap을 반영합니다.
추가로 아래 명령어들을 통해 노드에 네트워크 정보 확인이 가능합니다.
#노드 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
#파드 IP 확인
kubectl get pod -n kube-system -o=custom-columns=NAME:.metadata.name,IP:.status.podIP,STATUS:.status.phase
#파드 이름 확인
kubectl get pod -A -o name
#파드 개수 확인
kubectl get pod -A -o name | wc -l
노드에 네트워크 정보 확인
# CNI 정보 확인
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
# 네트워크 정보 확인 : eniY는 pod network 네임스페이스와 veth pair
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo ip -br -c addr; echo; done
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo ip -c addr; echo; done
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo ip -c route; echo; done
ssh ec2-user@$N1 sudo iptables -t nat -S
ssh ec2-user@$N1 sudo iptables -t nat -L -n -v
Amazon EKS에서는 네트워크 네임스페이스가 두 가지로 구분됩니다.
노드에서 실행되는 Root Net Namespace와 개발 파드에서 실행되는 Per Pod Net Namespace입니다.
Root Net Namespace는 EKS 클러스터 내의 EC2(워커 노드)에서 기본적으로 실행되는데 노드 내부에서 동작하는 시스템 파드(kube-proxy, aws-node 등)는 별도의 네트워크 네임스페이스를 사용하지 않고, 노드의 기본 네트워크 IP를 그대로 사용합니다. 이렇게 특정 파드가 노드의 네트워크를 그대로 사용할 수 있도록 설정하는 것을 Host Network 옵션이라고 합니다.
Per Pod Net Namespace는 각 파드가 노드와 직접적으로 네트워크를 공유하지 않고 ENI에서 제공하는 보조 IP를 할당 받아 개별적으로 네트워크를 구성하여 ENI에 직접 연결되지 않으며 veth 인터페이스를 통해 ENI와 연결됩니다. 이 방식은 파드가 네트워크적으로 격리되어 보안이 강화될 수 있습니다.
아래 예시에서 먼저 coredns 파드의 IP 정보를 확인해보고 파드가 보조 IP를 할당 받아 사용하는지 확인해보겠습니다.
#coredns 파드 IP 정보 확인
kubectl get pod -n kube-system -l k8s-app=kube-dns -owide
이 후 워커 노드의 라우팅 테이블을 조회하여 coredns 파드가 어느 노드의 ENI에서 IP를 할당 받는지 확인합니다.
#워커 노드의 라우팅 정보 확인
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo ip -c route; echo; done
AWS 콘솔에서도 EC2(워커 노드)의 네트워크 정보를 확인해볼 수 있습니다.
다음은 테스트용 deployment를 생성하여 확인해보겠습니다.
우선 ENI 목록을 실시간으로 확인 하기 위해 워커 노드에 SSH 접속 후 아래 명령어를 실행해 줍니다.
(라우팅 테이블에서 eni가 포함된 경로 확인)
watch -d "ip link | egrep 'ens|eni' ;echo;echo "[ROUTE TABLE]"; route -n | grep eni"
이 후 테스트용 deployment를 생성해줍니다.
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
파드 확인 후 각 워커 노드의 라우팅 테이블을 확인해봅니다.
#파드 정보 확인
kubectl get pod -o wide
#Name과 IP만 확인
kubectl get pod -o=custom-columns=NAME:.metadata.name,IP:.status.podIP
생성한 파드간의 통신 테스트 및 확인을 진행해보겠습니다.
워커 노드에서 tcpdump로 트래픽이 어떤 ENI에서 나가는지도 확인합니다.
(아래는 워커 노드 1번을 예시로 확인하였습니다.)
라우팅 정책 데이터베이스 확인
로컬 트래픽이 어떻게 처리되는지 확인
워커 노드가 외부 네트워크와 통신할 때 어떤 경로인지 확인
최종적으로 워커 노드는 ens5를 통해 VPC 밖으로 나가는 것을 확인하였습니다.
파드가 외부 통신할때 노드의 iptables 규칙에 따라 SNAT가 적용되어 노드의 ens5 IP로 변경되어 통신합니다.
아래와 같이 테스트 시 확인할 수 있습니다.
파드와 EC2 서버간 통신
파드에서 EC2 서버로 통신할 때, AWS에서 자동으로 파드의 IP를 노드의 프라이빗 IP로 변경(SNAT)하여 보내기 때문에 통신이 가능합니다.
VPC CNI 정보를 확인해보면 외부 SNAT가 비활성화되어 있는 것을 확인할 수 있습니다.
#vpc cni env 정보 확인
kubectl get ds aws-node -n kube-system -o json | jq '.spec.template.spec.containers[0].env'
아래 간단한 테스트로 확인이 가능합니다.
SNAT 없이 통신 가능하게 설정
EC2가 있는 172.20.0.0/16 대역과 파드가 통신할 때 SNAT를 제외하기 위해선 VPC CNI 설정을 변경해줘야 합니다.
아래 명령어로 aws-node DaemonSet의 환경 변수를 변경해줍니다.
kubectl set env daemonset aws-node -n kube-system AWS_VPC_K8S_CNI_EXCLUDE_SNAT_CIDRS=172.20.0.0/16
설정 변경을 확인해줍니다.
kubectl get ds aws-node -n kube-system -o json | jq '.spec.template.spec.containers[0].env'
NAT 정책 적용이 되어 있는지 확인합니다.
ping 테스트를 통해 적용이 되었는지 확인 가능합니다.
#파드 1에서 EC2 서버로 ping
kubectl exec -it netshoot-pod-744bd84b46-h5w5h -- ping -c 1 172.20.1.100
적용 전에는 파드 IP(192.168.1.154)가 워커 노드의 IP(192.168.1.251)로 변환
적용 후에는 파드 IP(192.168.1.154)가 그대로 유지
EC2 서버와 파드가 직접 통신할 경우 SNAT를 거치지 않아 처리 속도가 빨라지고 latency가 감소하게 됩니다. 또한 EC2에서 파드의 원래 IP를 확인하고 직접 트래픽을 제어할 수 있으며, NAT Gateway 비용을 절감할 수 있습니다.
하지만 EC2의 라우팅 테이블과 보안 그룹 설정이 필요하며, IAM 역할 및 네트워크 ACL을 적절히 설정해줘야 합니다. 또한 파드가 재배치될 때마다 IP가 변경될 수 있어 네트워크 정책 관리가 어려워질 수 있는 단점이 있습니다.
이를 고려하여 적절하게 사용이 필요합니다.
추가로 kubectl set env로 설정한 환경 변수는 기본적으로 aws-node Daemonset 환경 변수에만 적용되며, EKS 클러스터가 재시작되거나 업그레이드 될 경우 설정이 초기화 될 수 있습니다.
따라서 환경 변수를 영구 적용이 필요할 경우 아래와 같은 방법을 통해 적용해 주시면 됩니다.
eksctl을 이용하여 aws-node Addon의 환경 변수를 변경할 수 있습니다.
eksctl utils update-aws-node --set-env AWS_VPC_K8S_CNI_EXCLUDE_SNAT_CIDRS=172.20.0.0/16
AWS 콘솔에서도 직접 변경이 가능합니다.
[EKS > 클러스터 > myeks > 추가 기능 > vpc-cni > 추가 기능 편집]
EKS를 운영할 때, 노드 하나에 얼마나 많은 파드를 배포할 수 있는지 아는 것은 중요합니다. 이는 AWS에서 제공하는 ENI(Elastic Network Interface)와 IP 주소 제한에 따라 결정됩니다.
사전 준비
kube-ops-view 설치: 노드별 파드 배포 상태를 한눈에 보기 위해 필요
# kube-ops-view
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=LoadBalancer --set env.TZ="Asia/Seoul" --namespace kube-system
# kube-ops-view 접속 URL 확인 (1.5 배율)
kubectl get svc -n kube-system kube-ops-view -o jsonpath='{.status.loadBalancer.ingress[0].hostname}' | awk '{ print "KUBE-OPS-VIEW URL = http://"$1":8080/#scale=1.5"}'
워커 노드에서 생성할 수 있는 파드의 최대 개수는 해당 노드의 인스턴스 타입에 따라 달라집니다. 이는 각 인스턴스 타입별로 ENI(Elastic Network Interface)의 최대 개수와 각 ENI에서 할당할 수 있는 최대 IP 개수에 의해 결정됩니다. 즉, 한 노드에서 사용할 수 있는 네트워크 인터페이스와 IP 주소의 개수가 많을수록 더 많은 파드를 배치할 수 있습니다.
다만, aws-node와 kube-proxy 파드는 노드 자체의 IP 주소를 사용하기 때문에, 이 두 파드는 최대 파드 개수 계산에서 제외됩니다. 따라서 실제로 배포 가능한 파드의 수는 인스턴스의 네트워크 설정에 따라 변동될 수 있습니다.
참고:
최대 파드 개수=(인스턴스 타입의 네트워크 인터페이스 개수×(각 인터페이스의 IP 개수−1))+2
ENI 자체가 하나의 IP주소를 사용하기 때문에 -1
aws-node와 kube-proxy에서 노드의 기본 IP 주소를 사용하기 때문에 2개 파드를 포함
특정 인스턴스 타입에 대해 네트워크 인터페이스 및 IP 주소 할당 정보를 조회할 수 있습니다.
여기서는 t3 타입에 대해 확인해보겠습니다.
#t3 타입의 정보 확인
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개 >> aws-node와 kube-proxy 2개를 제외하면 15개
현재 워커 노드의 상세 정보 확인
kubectl describe node | grep Allocatable: -A6
현재 워커 노드에 파드를 생성해보고 최대 개수를 초과하면 어떻게 되는지 확인해보도록 하겠습니다.
#Deployment 생성
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
EOF
파드 확인
kubectl get pod -o wide
kubectl get pod -o=custom-columns=NAME:.metadata.name,IP:.status.podIP
이 상태에서 파드를 8개로 배포해보면 기존 ENI 내에서 할당 가능한 IP가 없어 새로운 ENI가 자동으로 추가되며 파드가 정상 배포된 것을 확인하였습니다.
15개로 늘리게 되면 파드가 정상 배포 되지만 기존 ENI가 모두 사용되면서 각 워커 노드에서 6~7개까지 ENI가 증가한 것으로 보입니다.
30개로 늘렸을 경우 각 워커 노드에서 ENI가 11~12개까지 증가하여 한계에 가까워진 것을 확인하였습니다.
50개로 늘렸을 경우 ENI 개수가 최대(15개)까지 증가하면서 더 이상 ENI를 추가살 수 없으므로 일부 파드가 Pending 상태로 남은 것을 확인하였습니다.
이 내용은 아래 명령어을 통해 Pending 상태의 파드를 확인해보면 알 수 있었습니다.
kubectl describe pod nginx-deployment-6c8cb99bb9-8rccr
0/3 nodes are available: 3 Too many pods.
모든 노드에서 이미 최대 한도의 파드를 배포하고 있어 추가 배포 불가로 확인
추가 조치 없이는 더 이상 배포가 불가능하기 몇가지 조치를 통해 이 문제를 해결 할 수 있습니다.
4주차 Study - EKS Observability (0) | 2025.03.01 |
---|---|
3주차 Study - EKS Storage, Managed Node Groups (0) | 2025.02.21 |
2주차 Study - EKS Networking (2) (0) | 2025.02.15 |
2주차 Study - EKS Networking (실습 환경 구축) (0) | 2025.02.15 |
1주차 Study - EKS 클러스터 배포 (0) | 2025.02.07 |