이번 포스트에서는 EKS Networking이라는 주제로 이어가 보겠습니다.
내용이 길어질 것으로 보아 파트를 나눠서 작성할 예정이며, EKS Networking Part1에서는 EKS의 VPC CNI와 기본 환경을 검토해보고, 파드와 노드 통신을 확인해보겠습니다. 이후 EKS Networking Part2에서 Loadbalancer 유형의 서비스의 구성과 Ingress에 대해서 확인해 보겠습니다.
목차
- AWS VPC CNI 개요
- 파드 생성 갯수 제한
- 실습 환경 배포
- 노드 정보 확인
- 파드 통신 확인
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의 동작은 간략히 아래와 같이 이뤄집니다.
- Kubelet이 Container Runtime에 컨테이너 생성을 요청
- Container Runtime이 컨테이너의 Network Namespace를 생성
- Container Runtime이 CNI 설정과 환경변수를 표준 입력으로 CNI Plugin 호출
- CNI Plugin이 컨테이너의 네트워크 인터페이스를 구성하고, IP를 할당하고, 호스트 네트워크 간의 veth pair 를 생성
- 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
- 파드가 스케줄링 됩니다.
- kubelet은 Add 요청을 VPC CNI로 보냅니다.
- VPC CNI는 L-IPAM에 파드 IP를 요청합니다.
- L-IPAM에서 파드 IP를 반환합니다.
- VPC CNI는 네트워크 네임스페이스를 구성합니다.
- VPC CNI는 파드 IP를 kubelet으로 반환합니다.
- 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 |