클라우드의 쿠버네티스 네트워크 - AWS
November 2024 (2286 Words, 13 Minutes)
About AWS VPC CNI
EKS에서는 AWS에서 자체적으로 개발한 VPC CNI를 이용합니다.
CNI란?
Container Network Interface 는 k8s 네트워크 환경을 구성해줍니다.
이렇게 만들어진 컨테이너 네트워크 표준은, 제각기 다른 플러그인들을 통해 구현하거나 모듈형태로 교체가 가능합니다.
다양한 플러그인이 존재 - networking-and-network-policy
AWS VPC CNI
그중에서 살펴볼것은 EKS를 사용할때 흔하게 사용하는 CNI인 AWS VPC CNI 입니다.
이 CNI는 VPC 대역의 IP를 파드의 IP로 할당합니다.
파드의 IP 네트워크 대역과 노드(워커)의 IP 대역이 같아 직접 통신이 가능합니다
Amazon EKS는 클러스터 네트워킹을 Amazon VPC Container Network Interface(VPC CNI) 플러그인을 통해 구현합니다.
EKS의 모든 Pod가 VPC 네트워크에서 고유한 IP 주소를 가지며, Pod 내부의 모든 컨테이너는 동일한 네트워크를 공유하여 로컬 포트를 통해 서로 통신이 가능합니다.
- CNI 바이너리: Pod 간 네트워크 연결을 설정하여 Pod 간 통신을 가능하게 합니다. CNI 바이너리는 각 노드의 루트 파일 시스템에서 실행되며, 새로운 Pod가 추가되거나 기존 Pod가 노드에서 제거될 때 kubelet에 의해 호출됩니다.
- ipamd (IPAM 데몬): 노드에 상주하며 실행되는 IP 주소 관리 데몬으로, 다음 역할을 수행합니다.
- 노드의 ENI 관리
- 사용할 수 있는 IP 주소 또는 프리픽스의 워밍 풀 관리
EC2 인스턴스가 생성되면, EC2는 인스턴스에 기본 서브넷에 연결된 주 ENI를 생성하여 연결합니다. 이 기본 서브넷은 퍼블릭 또는 프라이빗일 수 있습니다. hostNetwork 모드에서 실행되는 Pod는 노드 주 ENI에 할당된 주 IP 주소를 사용하며, 노드와 동일한 네트워크 네임스페이스를 공유합니다.
CNI 플러그인은 노드의 Elastic Network Interfaces (ENI)
를 관리합니다. 노드가 프로비저닝되면 CNI 플러그인은 노드의 서브넷에서 주 ENI로부터 슬롯(IP 주소 또는 프리픽스)을 자동으로 할당하여 워밍 풀을 생성합니다. 이 워밍 풀의 크기는 노드의 인스턴스 유형에 따라 결정됩니다.
ENI에 있는 슬롯이 할당되면 CNI는 노드에 추가적인 ENI를 연결하고 슬롯을 추가합니다.
- supports native VPC networking with the Amazon VPC Container Network Interface (CNI) plugin for Kubernetes.
- VPC 와 통합 : VPC Flow logs , VPC 라우팅 정책, 보안 그룹(Security group) 을 사용 가능함
- This plugin assigns an IP address from your VPC to each pod.
- 파드의 빠른 시작을 위해 VPC ENI 에 미리 할당된 IP(=Local-IPAM Warm IP Pool)를 파드에서 사용가능
- L-IPAM 소개 - 링크
Calico CNI vs AWS VPC CNI
두 CNI 간의 차이점을 살펴봅니다.
네트워크 통신의 최적화(성능, 지연)를 위해 노드와 파드의 네트워크 대역을 동일하게 설정
일반 K8S CNI 플러그인(Calico) 와 AWS VPC CNI 의 노드와 파드 네트워크 대역 비교
- 파드간 통신 시 일반적으로
K8S CNI
는 오버레이(VXLAN, IP-IP 등) 통신을 하고,AWS VPC CNI
는 동일 대역으로 직접 통신 합니다.
Node Default Network Configuration
워커 노드
기본 네트워크 구성 :
- Network 네임스페이스는 호스트(Root)와 파드 별(Per Pod)로 구분된다
-
특정한 파드(kube-proxy, aws-node)는 호스트(Root)의 IP를 그대로 사용한다 ⇒ 파드의 Host Network 옵션
- t3.medium 의 경우 ENI 마다 최대 6개의 IP를 가질 수 있다
- ENI0, ENI1 으로 2개의 ENI는 자신의 IP 이외에 추가적으로 5개의 보조 프라이빗 IP를 가질수 있다
- coredns 파드는 veth 으로 호스트에는 eniY@ifN 인터페이스와 파드에 eth0 과 연결되어 있다
워커 노드1
인스턴스의 네트워크 정보 확인 : 프라이빗 IP와 보조 프라이빗 IP 확인
- 네트워크인터페이스(ENI)에 설명 내용 확인해보자 : 주ENI와 추가ENI의 설명 차이점 확인
[실습] 보조 IPv4 주소를 파드가 사용하는지 확인
# coredns 파드 IP 정보 확인
kubectl get pod -n kube-system -l k8s-app=kube-dns -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
coredns-6777fcd775-57k77 1/1 Running 0 70m 192.168.1.142 ip-192-168-1-251.ap-northeast-2.compute.internal <none> <none>
coredns-6777fcd775-cvqsb 1/1 Running 0 70m 192.168.2.75 ip-192-168-2-34.ap-northeast-2.compute.internal <none> <none>
# 노드의 라우팅 정보 확인 >> EC2 네트워크 정보의 '보조 프라이빗 IPv4 주소'와 비교해보자
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo ip -c route; echo; done
[실습] 테스트용 파드 생성 - nicolaka/netshoot
# [터미널1~3] 노드 모니터링
ssh ec2-user@$N1
watch -d "ip link | egrep 'eth|eni' ;echo;echo "[ROUTE TABLE]"; route -n | grep eni"
ssh ec2-user@$N2
watch -d "ip link | egrep 'eth|eni' ;echo;echo "[ROUTE TABLE]"; route -n | grep eni"
ssh ec2-user@$N3
watch -d "ip link | egrep 'eth|eni' ;echo;echo "[ROUTE TABLE]"; route -n | grep eni"
# 테스트용 파드 netshoot-pod 생성
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
# 파드 이름 변수 지정
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})
# 파드 확인
kubectl get pod -o wide
kubectl get pod -o=custom-columns=NAME:.metadata.name,IP:.status.podIP
# 노드에 라우팅 정보 확인
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i sudo ip -c route; echo; done
- 파드가 생성되면, 워커 노드에 eniY@ifN 추가되고 라우팅 테이블에도 정보가 추가된다
- 테스트용 파드 eniY 정보 확인 - 워커 노드 EC2
# **노드3**에서 네트워크 인터페이스 정보 확인
ssh ec2-user@$N3
----------------
ip -br -c addr show
ip -c link
ip -c addr
ip route # 혹은 route -n
# 마지막 생성된 네임스페이스 정보 출력 -t net(네트워크 타입)
sudo lsns -o PID,COMMAND -t net | awk 'NR>2 {print $1}' | tail -n 1
# 마지막 생성된 네임스페이스 net PID 정보 출력 -t net(네트워크 타입)를 변수 지정
MyPID=$(sudo lsns -o PID,COMMAND -t net | awk 'NR>2 {print $1}' | tail -n 1)
# PID 정보로 파드 정보 확인
sudo nsenter -t $MyPID -n ip -c addr
sudo nsenter -t $MyPID -n ip -c route
exit
----------------
- 테스트용 파드 접속(exec) 후 확인
# 테스트용 파드 접속(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
Inter Node Network by Pods
파드간 통신 시 tcpdump 내용으로 통신 과정을 알아본다
통신 흐름 :
AWS VPC CNI 기준으로 기타 CNI와 다르게, VPC 내부에서 직접 파드간 통신이 가능합니다.
- 파드간 통신 시 과정 참고
https://github.com/aws/amazon-vpc-cni-k8s/blob/master/docs/cni-proposal.md
TCP DUMP를 이용해 AWS VPC CNI의 파드간 통신을 확인합니다.
# 파드 IP 변수 지정
POD1=$(kubectl get pod pod-1 -o jsonpath={.status.podIP})
POD2=$(kubectl get pod pod-2 -o jsonpath={.status.podIP})
# 파드1 Shell 에서 파드2로 ping 테스트
kubectl exec -it pod-1 -- ping -c 2 $POD2
# 파드2 Shell 에서 파드1로 ping 테스트
kubectl exec -it pod-2 -- ping -c 2 $POD1
POD 1 ↔ POD2 ping 을 수행합니다.
# 워커 노드 EC2 : TCPDUMP 확인
sudo tcpdump -i any -nn icmp
sudo tcpdump -i ens5 -nn icmp
sudo tcpdump -i ens6 -nn icmp
각 pod들이 할당되어있는 노드(node1 / node2) 에서
tcpdump 명령어들로 ping을 시도했을때 오가는 패킷의 정보들을 확인 할 수 있습니다.
Pod External Network
파드는 iptable 에 SNAT 과정을 거쳐 노드의 eth0 IP로 변경되어 외부로 통신합니다
https://github.com/aws/amazon-vpc-cni-k8s/blob/master/docs/cni-proposal.md
- VPC CNI 의 External source network address translation (
SNAT
) 설정에 따라, 외부(인터넷) 통신 시 SNAT 하거나 혹은 SNAT 없이 통신을 할 수 있다 - 링크
[실습] 파드에서 외부 통신 테스트 및 확인
- 파드 shell 실행 후 외부로 ping 테스트 & 워커 노드에서 tcpdump 및 iptables 정보 확인
# 작업용 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 - [링크](https://github.com/chubin/wttr.in)
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](https://ssup2.github.io/issue/Linux_TCP_SYN_Packet_Drop_SNAT_Port_Race_Condition/) [링크2](https://ssup2.github.io/issue/Kubernetes_TCP_Connection_Delay_VXLAN_CNI_Plugin/)
sudo iptables -t nat -S | grep 'A AWS-SNAT-CHAIN'
- 다음 실습을 위해서 파드 삭제:
kubectl delete deploy netshoot-pod
Node’s MAX Pod
생성되는 자원 확인을 위한 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"}'
- Secondary IPv4 addresses (기본값) : 인스턴스 유형에 최대 ENI 갯수와 할당 가능 IP 수를 조합하여 선정
워커 노드의 인스턴스 타입 별 파드 생성 갯수 제한
- 인스턴스 타입 별 ENI 최대 갯수와 할당 가능한 최대 IP 갯수에 따라서 파드 배치 갯수가 결정됨
- 단, aws-node 와 kube-proxy 파드는 호스트의 IP를 사용함으로 최대 갯수에서 제외함
최대 파드 생성 갯수 : ENI 갯수(인스턴스 고유) × (ENI당 address제한 -1 ) + 2
계산식은 공식 doc에서 노드에 할당 가능한 pod 갯수를 계산해보도록 하겠습니다
https://docs.aws.amazon.com/ko_kr/AWSEC2/latest/UserGuide/using-eni.html
인스턴스 유형별 최대 ENI 갯수, ENI 별 할당 가능한 ip 갯수가 명시되어있습니다
AWS CLI에서도 확인이 가능합니다.
aws ec2 describe-instance-types --filters Name=instance-type,Values=t3.* \
--query "InstanceTypes[].{Type: InstanceType, MaxENI: NetworkInfo.MaximumNetworkInterfaces, IPv4addr: NetworkInfo.Ipv4AddressesPerInterface}" \
--output table
output
--------------------------------------
| DescribeInstanceTypes |
+----------+----------+--------------+
| IPv4addr | MaxENI | Type |
+----------+----------+--------------+
| 15 | 4 | t3.2xlarge |
| 6 | 3 | t3.medium |
| 12 | 3 | t3.large |
| 15 | 4 | t3.xlarge |
| 2 | 2 | t3.micro |
| 2 | 2 | t3.nano |
| 4 | 3 | t3.small |
+----------+----------+--------------+
t3.medium 기준으로 3 ENI per Node, 6 IP per ENI 이기때문에
3*(6-1) +2 =17
총 17개의 pod ip 가 할당이 가능해집니다.
최대 파드 생성 및 확인
그럼 일반적인 onpremise의 노드의 pod 갯수제한이 아닌, vpc cni를 이용할때의 기본 pod 제한의 차이가 크기때문에, 이러한 제한을 풀어보는 방법을 알아보겠습니다.
물론 이 방법이 production에서 아주 쉬이 권장되는 형태의 아키텍처는 아니라고 생각합니다.
EKS Workshop
- Prefix Delegation : https://www.eksworkshop.com/docs/networking/vpc-cni/prefix/
- Custom Networking : https://www.eksworkshop.com/docs/networking/vpc-cni/custom-networking/
- Security Groups for Pods : https://www.eksworkshop.com/docs/networking/vpc-cni/security-groups-for-pods/
- Network Policies : https://www.eksworkshop.com/docs/networking/vpc-cni/network-policies/
- Amazon VPC Lattice : https://www.eksworkshop.com/docs/networking/vpc-lattice/
https://aws.amazon.com/ko/blogs/containers/amazon-vpc-cni-increases-pods-per-node-limits/
https://aws.amazon.com/ko/blogs/containers/amazon-vpc-cni-increases-pods-per-node-limits/
해결방안으로는 몇가지 Keyword들이 있습니다.
- Prefix Delegation
- Warm & Min IP/Prefix Targets
- Custom Network
다음 자료들을 보면 CSP에서 권장하는 설정에 대한 비교들이 가능합니다.
CSP 별 Node 에서 생성 가능한 pod 갯수제한을 걸어둔 비교자료입니다.
해당 인스턴스 타입에서는 노드당 pod의 갯수가 제한된다.
AWS VPC CNI prefix assignment mode를 통해 노드당 파드제한증가가 가능하다
https://aws.amazon.com/ko/blogs/containers/amazon-vpc-cni-increases-pods-per-node-limits/
따라서 Nitro Based 인스턴스를 이용하여 MAX POD갯수를 증가시키는 실습을 진행한다.
우선 KOPS 프로비저닝할때 C5.large로 생성하거나,
AWS 콘솔에서 AutoScaling그룹을 수정하거나,
방법은 다양하게 존재합니다.
ENABLE_PREFIX_DELEGATION 제한을 해제합니다.
kubectl patch daemonset aws-node -n kube-system \
--type=json \
-p='[{"op": "replace", "path": "/spec/template/spec/containers/0/env/0", "value": {"name": "WARM_PREFIX_TARGET", "value": "1"}}, {"op": "replace", "path": "/spec/template/spec/containers/0/env/1", "value": {"name": "ENABLE_PREFIX_DELEGATION", "value": "true"}}]'
Nitro 계열의 NodeGroup을 새로 생성합니다.
eksctl create nodegroup \
--cluster myeks \
--region ap-northeast-2 \
--name ng1-replacement \
--node-type c5.large \
--nodes 3 \
--nodes-min 1 \
--nodes-max 4 \
--managed
기존의 NG에 노드 3대
새로 추가한 NG에 노드 3대
ENI 갯수 확인을 위해 5개의 노드에 Cordon을 걸고 테스트를 진행해봅니다.
kubectl scale deployment nginx-deployment --replicas=110
노드의 MAX pods가 110이지만, kube-system의 파드 두개때문에 실제로 생성 가능한 nginx pod는 108개가 최대입니다.