참조: https://velog.io/@eljoelee/AEWS-STUDY-2%EC%A3%BC%EC%B0%A8-Network1
1. AWS VPC CNI란?
EKS 클러스터는 AWS VPC 내부에 구현되므로 네트워크 구성을 AWS VPC 기준으로 구성한다. 따라서, 파드는 클러스터가 구현된 VPC 내부 서브넷의 CIDR 대역에 해당하는 IP 주소를 할당받는다.
- 이러한 AWS VPC 네트워크와 EKS 네트워크 간 인터페이스 역할을 수행하는 Network Addon을 AWS VPC CNI(Container Networking Interface)라 한다.
1.1 AWS VPC CNI
- Pod의 IP를 할당
- Pod의 IP 대역과 Worker Node의 IP 대역이 동일해서 직접 통신이 가능
- K8S 컨테이너 IP 대역은 기본 VPC 네트워킹을 지원
- VPC와 통합: VPC Flow logs, VPC 라우팅 정책, SG(Security Group) 사용 가능
- VPC ENI에 미리 할당된 IP(=Local-IPAM Warm IP Pool)를 파드에서 사용 가능
ㆍAWS VPC CNI는 Pod를 위해 기본적으로 ENI를 미리 확보해 둠
ㆍ--max-pods를 변경하면 kubelet이 미리 확보해둔 IP를 더 많이 활용하도록
ㆍ
1.2 K8S Calico CNI vs AWS VPC CNI
Calico CNI
ㆍ일반적인 K8S CNI 플러그인
ㆍ파드와 노드가 사용하는 네트워크 대역이 다름
ㆍ그래서 오버레이(VXLAN, IP-IP 등) 통신 함.
오버레이
ㆍ기존 네트워크 위에 가상 네트워크를 만들어 통신하는 방식
ㆍ파드와 노드가 사용하는 네트워크 대역이 달라 직접 통신이 어려워 변환 과정 수행
AWS VPC CNI
ㆍAWS VPC CNI는 AWS VPC 네트워크를 그대로 활용하는 CNI 플러그인
ㆍ파드가 AWS VPC 서브넷의 실제 ENI IP를 직접 할당받아 사용
ㆍ파드와 노드가 사용하는 네트워크 대역이 동일함
ㆍ그래서 직접 통신 함.
ㆍAWS의 SG, VPC 네트워크 정책, 라우팅 등 쉽게 연동 가능
1.3 Worker Node에 생성 가능한 최대 Pod 갯수
Worker Node는 Primary IPv4를 사용하고 그 안에 뜨는 Pod는 보조 IPv4 주소를 사용합니다.
그래서 최대 할당 가능한 Secondary IPv4 주소를 확인은 중요합니다.
1.3.1 Secondary IPv4 주소 할당
인스턴스 유형별 최대 ENI 개수와 ENI당 Secondary IPv4 주소 최대 개수를 조합하여 선정
- ex) m5.large 기준, ENI 최대 개수:3 (primary ENI 포함) 과 ENI당 Secondary 주소 최대 개수: 10를 조합
노드 자체에서 1개 사용하여, (3*10) -1 = 29개 사용가능 - 단점: 서브넷에 할당 가능한 IP 주소가 제한적이므로 Pod 개수가 많아지면 IP 부족
1.3.2 IPv4 Prefix Delegation (접두사 위임)
한 번에 여러 개의 IP 블록(/28 or /27)을 할당하는 방식
IPv4 28bit 서브넷(prefix)를 위임하여 할당 가능 IP 수와 인스턴스 유형에 권장하는 최대 갯수로 선정
- 각 ENI에 IPv4 개별 주소가 아닌 IPv4 Prefix 리스트를 할당하여 한 번에 더 많은 Pod에 IP를 제공 가능
- EKS Node에 생성할 수 있는 Pod의 개수는 Node에 할당된 ENI 개수와 ENI에 할당된 IP 주소 수를 기준
ex) m5.large 기준, ENI 최대 개수:3 과 ENI당 할당 가능한 Prefix 블록: /28 (16개 IP)
노드 자체에서 1개 사용하여, (3*16) -1 = 47개 사용 가능 - 단점: IPv4 Prefix를 지원하는 서브넷 및 인스턴스 타입이어야 가능 (현재 일부 인스턴스에만 지원)
인스턴스 유형에 따라 할당 가능한 접두사(prefix)가 정해져 있으며, ENI 최대 개수와 할당 가능한 Prefix 기준으로 최대 할당 가능한 Pod IP 개수가 늘어납니다.
1.3.2.1 IPv4 Prefix Delegation (접두사 위임) 조건
모든 EKS Node가 Prefix Delegation을 사용할 수 있는 것은 아니고 다음의 조건을 지켜야 합니다.
a. Amazon VPC CNI 1.9.0 버전 이상
kubectl describe daemonset aws-node --namespace kube-system | grep Image | cut -d "/" -f 2
b. Worker Node 가 AWS Nitro 기반 EC2 인스턴스로 구성
c. Worker Node 의 max-pod 값 설정
kubectl get node -o yaml | grep allocatable -A7
1.3.2.2 IPv4 Prefix Delegation (접두사 위임) 고려사항
a. /28 접두사를 사용하기에 충분하지 않은 Subnet IP 범위를 가지고 있을 때
b. Security Groups Per Pods 기능을 사용할 때
Prefix Delegation 은 해당 노드의 모든 파드가 동일한 보안 그룹을 사용합니다.
1.3.2.3 IPv4 Prefix Delegation (접두사 위임) 관련 기능 용어 정리
Prefix Delegation 활성화 기능은 AWS VPC CNI에 존재합니다.
VPC CNI는 각 노드에 aws-node 라는 DaemonSets 형태의 Pod로 존재합니다.
kubectl get pods -n kube-system -o wide
VPC CNI에 어떤 기능이 있는지 확인해봅시다.
kubectl describe daemonsets.apps aws-node -n kube-system | grep ADDITIONAL_ENI_TAGS: -B1 -A26
EKS IP 할당 용어 | 동작 방식 |
WARM_IP_TARGET | Node ENI 전체를 Warm Pool로 준비하지 않고, 설정한 IP 개수만큼만 확보 |
WARM_PREFIX_TARGET | /28 Prefix를 할당한 ENI를 확보해놓을 개수 (ENI 1개당 IP 16개, 4개면 IP 64개 확보) |
WARM_ENI_TARGET | EKS Node에서 신속한 Pod 배포를 위해 미리 부착해둔 ENI 개수 |
MINIMUM_IP_TARGET | EKS Node에서 항상 보유할 최소 IP 개수 |
1.3.2.4 IPv4 Prefix Delegation (접두사 위임) 활성화
# Prefix Delegation(접두사 위임) 활성화
kubectl set env daemonset aws-node -n kube-system ENABLE_PREFIX_DELEGATION=true
1.3.3 [실습] 네트워크 기본 정보 확인
# CNI 정보 확인
kubectl describe daemonset aws-node --namespace kube-system | grep Image | cut -d "/" -f 2
# kube-proxy config 확인 : 모드 iptables 사용 >> ipvs 모드 사용하지 않는 이유???
kubectl describe cm -n kube-system kube-proxy-config | grep mode
# 노드 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
1.3.4 노드에서 기본 네트워크 정보 확인
- Namespace는 호스트(Root)와 파드 별(per Pod)로 구분
- 특정 Pod(kube-proxy, aws-node)는 호스트(Root)의 IP를 그대로 사용
=> Pod의 Host Network 옵션 - ENI0, ENI1 으로 2개의 ENI는 자신의 IP 이외에 추가적으로 5개의 보조 프라이빗 IP를 가질수 있음
- coredns 파드는 veth 으로 호스트에는 eniY@ifN 인터페이스와 파드에 eth0 과 연결되어 있음
1.3.5 인스턴스의 네트워크 정보 확인: 프라이빗 IP와 보조 프라이빗 IP 확인
EC2 콘솔로는 보조 프라이빗 IP가 확인되지 않아, Instance ID 기반으로 Network Interface 콘솔 창에서 확인하였습니다.
프라이빗 IP와 보조 프라이빗 IP를 확인할 수 있습니다.
1.3.6 [실습] 보조 IPv4 주소를 파드가 사용하는지 확인
Worker Node의 Route 확인
Worker Node에 접속하여 보조 프라이빗 IPv4 주소 5개 중 하나를 확인할 수 있었습니다.
route 테이블은 특정 네트워크 대역(서브넷)과 인터페이스 간의 현재 연결 정보만 기록하므로 다른 연결되지 않은 4개의 IP 정보는 보이지 않습니다.
Worker Node 접속 (bastion 서버에 등록된 Key를 활용해 접속)
$ ssh -i ~/.ssh/id_rsa ec2-user@<Worker Node의 Internal IP>
[root@myeks-bastion-EC2 ~]# kubectl get pods -A -o wide
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
kube-system coredns-86f5954566-5f8v4 1/1 Running 0 116m 192.168.1.168 ip-192-168-1-22.ap-northeast-2.compute.internal <none> <none>
kube-system coredns-86f5954566-8p4qd 1/1 Running 0 116m 192.168.2.200 ip-192-168-2-252.ap-northeast-2.compute.internal <none> <none>
kube-system kube-proxy-2tksl 1/1 Running 0 112m 192.168.2.252 ip-192-168-2-252.ap-northeast-2.compute.internal <none> <none>
kube-system kube-proxy-xnmgn 1/1 Running 0 112m 192.168.1.22 ip-192-168-1-22.ap-northeast-2.compute.internal <none>
...생략...
현재 core-dns Pod가 192.168.1.168 보조 IPv4 주소를 사용하고 있어, 확인이 되는 것입니다.
kube-proxy와 aws-node는 Node의 IP와 동일하지만 coredns의 IP는 보조 프라이빗 IP인 이유
kube-proxy와 aws-node는 노드의 네트워크를 다루므로 host IP(Node의 IP)를 사용하지만
coredns는 Secondary IP를 사용합니다.
kube-proxy
ㆍ각 노드에서 iptables(또는 ipvs)를 사용해 Service의 가상 IP를 실제 Pod의 IP로 라우팅
ㆍ노드에서 설치 & 실행되면서, 노드의 네트워크 설정을 조작하므로 노드의 Primary IP를 사용
aws-node
ㆍPod에 ENI와 Secondary IP를 할당
ㆍ노드에서 네트워크 설정을 변경하고 관리하는 역할
ㆍ노드 네트워크와 직접 상호작용하므로 Primary IP 사용
1.3.7 [실습] 테스트용 파드 생성
1. 네트워크 인터페이스 모니터링
watch -d "ip link | egrep 'eth|eni' ;echo;echo "[ROUTE TABLE]"; route -n | grep eni"
Linux 시스템에서 네트워크 인터페이스와 라우팅 테이블을 실시간으로 모니터링하는 명령어 입니다.
- eth와 eni 인터페이스들의 상태와 eni 관련 라우팅 테이블을 실시간으로 모니터링하며, 그 변경 사항을 하이라이트(강조)합니다.
1.3.8 netshoot-pod 생성하여 스트레스 생성
cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: netshoot-pod
namespace: test2
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
Bastion-ec2에서 위의 yaml 파일을 배포하여 netshoot-pod를 생성합니다.
기타 옵션 지정(선택)
# 파드 이름 변수 지정
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
1.3.9 Worker Node의 Route 확인
다시 Worker Node에 ssh 접속하여 Route 테이블을 확인해봅니다.
192.168.1.220 보조 IPv4 주소를 추가 확인할 수 있습니다.
[root@myeks-bastion-EC2 ~]# kubectl get pods -A -o wide
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
kube-system coredns-86f5954566-5f8v4 1/1 Running 0 116m 192.168.1.168 ip-192-168-1-22.ap-northeast-2.compute.internal <none> <none>
kube-system coredns-86f5954566-8p4qd 1/1 Running 0 116m 192.168.2.200 ip-192-168-2-252.ap-northeast-2.compute.internal <none> <none>
kube-system kube-proxy-2tksl 1/1 Running 0 112m 192.168.2.252 ip-192-168-2-252.ap-northeast-2.compute.internal <none> <none>
kube-system kube-proxy-xnmgn 1/1 Running 0 112m 192.168.1.22 ip-192-168-1-22.ap-northeast-2.compute.internal <none>
...생략...
test2 netshoot-pod-744bd84b46-fnvsj 1/1 Running 0 7m17s 192.168.1.220 ip-192-168-1-22.ap-northeast-2.compute.internal <none> <none>
netshoot-pod를 배포하면서 보조 IPv4 주소가 할당되었음을 확인할 수 있습니다.
1.4 노드 간 파드 통신
목표: 파드간 통신 시 tcpdump 내용을 확인하고 통신 과정을 알아본다
파드 간 통신 흐름
AWS VPC CNI 경우 별도의 오버레이(Overlay) 통신 기술 없이, VPC Native 하게 파드간 직접 통신이 가능합니다
[실습] 파드 간 통신 테스트
별도의 NAT 동작 없이 통신이 가능함을 확인할 수 있습니다.
# 파드 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
1.5 파드에서 외부 통신
파드에서 외부 통신 흐름
iptable 에 SNAT 을 통하여 노드의 eth0 IP로 변경되어서 외부와 통신됨
- VPC CNI 의 External source network address translation (SNAT) 설정에 따라, 외부(인터넷) 통신 시 SNAT 하거나 혹은 SNAT 없이 통신을 할 수 있다
SNAT(Source Network Address Translation)
NAT 기술 중 하나로, 출발지 IP 주소를 변환하는 방식
주로 내부 네트워크에서 외부로 나가는 트래픽의 출발지 주소를 변경할 때 사용
ex) 파드에서 외부로 트래픽 나갈 때 (외부 네트워크는 파드의 IP를 직접 알 수 없으므로 노드의 외부 IP로 변경 필요)
파드에서 외부 통신 테스트 및 확인
파드 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 - 링크
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
다음 실습을 위해 파드 삭제가 필요
$ kubectl delete deploy netshoot-pod
1.6 노드에 파드 생성 갯수 제한
1.6.1 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 env.TZ="Asia/Seoul" --namespace kube-system
kubectl patch svc -n kube-system kube-ops-view -p '{"spec":{"type":"LoadBalancer"}}'
# 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"}'
삭제: helm uninstall kube-ops-view -n kube-system
- Secondary IPv4 addresses (기본값)
인스턴스 유형에 최대 ENI 갯수와 할당 가능 IP 수를 조합하여 선정 - 워커 노드의 인스턴스 타입 별 파드 생성 갯수 제한
인스턴스 타입 별 ENI 최대 갯수와 할당 가능한 최대 IP 갯수에 따라서 파드 배치 갯수가 결정됨
ex) m5.large는 최대 ENI 개수가 3개, ENI당 할당 가능한 최대 IP 개수는 10개이므로 총 최대 30개 IP 사용 가능 - 단, aws-node 와 kube-proxy 파드는 호스트의 IP(Node의 IP)를 사용함으로 최대 갯수에서 제외
kube-proxy와 aws-node는 Node의 IP와 동일하지만 coredns의 IP는 보조 프라이빗 IP인 이유
kube-proxy와 aws-node는 노드의 네트워크를 다루므로 host IP(Node의 IP)를 사용하지만
coredns는 Secondary IP를 사용합니다.
kube-proxy
ㆍ각 노드에서 iptables(또는 ipvs)를 사용해 Service의 가상 IP를 실제 Pod의 IP로 라우팅
ㆍ노드에서 설치 & 실행되면서, 노드의 네트워크 설정을 조작하므로 노드의 Primary IP를 사용
aws-node
ㆍPod에 ENI와 Secondary IP를 할당
ㆍ노드에서 네트워크 설정을 변경하고 관리하는 역할
ㆍ노드 네트워크와 직접 상호작용하므로 Primary IP 사용
1.6.2 워커 노드의 인스턴스 정보 확인 : t3.medium 사용 시
# 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.micro |
| 2 | 2 | t3.nano |
| 4 | 3 | t3.small |
+----------+----------+--------------+
# c5 타입의 정보(필터) 확인
aws ec2 describe-instance-types --filters Name=instance-type,Values=c5*.* \
--query "InstanceTypes[].{Type: InstanceType, MaxENI: NetworkInfo.MaximumNetworkInterfaces, IPv4addr: NetworkInfo.Ipv4AddressesPerInterface}" \
--output table
# 파드 사용 가능 계산 예시 : 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 에 17개 정보 확인
kubectl describe node | grep Allocatable: -A6
Allocatable:
cpu: 1930m
ephemeral-storage: 27905944324
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 3388360Ki
pods: 17
1.6.3 워커 노드의 파드 증가 테스트
# 워커 노드 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-7fb7fd49b4-d4bk9 0/1 Pending 0 3m37s
nginx-deployment-7fb7fd49b4-qpqbm 0/1 Pending 0 3m37s
...
kubectl describe pod <Pending 파드> | grep Events: -A5
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 45s default-scheduler 0/3 nodes are available: 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }, 2 Too many pods. preemption: 0/3 nodes are available: 1 Preemption is not helpful for scheduling, 2 No preemption victims found for incoming pod.
# 디플로이먼트 삭제
kubectl delete deploy nginx-deployment
1.7 해결 방안 : Prefix Delegation, WARM & MIN IP/Prefix Targets, Custom Network
참고: https://docs.aws.amazon.com/eks/latest/best-practices/custom-networking.html
Custom Networking 사용시 고려사항
ㆍIPv4 고갈 및 IPv6 사용 불가
ㆍRFC6598(100.64.0.0/10) 지원 → 기존 RFC1918 사용시 우려된 IP 고갈 문제 없이 더 많은 Pod를 실행
ㆍENIConfig 에 정의된 Subnet에 보조 ENI를 생성
ㆍVPC CNI는 ENIConfig CRD에 정의된 CIDR 범위에서 Pod에 IP 주소 할당
ㆍOn-Premise 연결에 이상적인 옵션
ㆍENIconfig 파일을 배포하면 새 Worker Nodes를 생성하고 현 Node들은 drain이 필요
ㆍ현 Worker Nodes 및 Pods는 영향받지 않
1.7.1 Prefix Delegation (접두사 위임)
참조: https://www.eksworkshop.com/docs/networking/vpc-cni/prefix/
ENI에 연결된 Secondary IP가 /32 대역으로 n개씩이 아닌 /28과 같은 Prefix 블록 대역으로 지정할 수 있습니다.
1.7.2 Custom Networking
참조: https://www.eksworkshop.com/docs/networking/vpc-cni/custom-networking/
네트워크 대역이 부족할 떄마다 Subnet을 추가 연결합니다.
1.7.3 Security Groups Per Pods
참조: https://www.eksworkshop.com/docs/networking/vpc-cni/security-groups-for-pods/
Pod마다 다른 서비스를 사용하는 경우도 있습니다.
그러나 Node(EC2) 위에 배포된 Pod는 기본적으로 모두 같은 SG(보안그룹)을 공유하는 방식이었고,
이는 보안 규정에 위배되는 등의 번거로운 상황을 야기했습니다.
그래서 Pod 별(서비스 별)로 SG를 설정하기 위해 나온 것이 Security Groups Per Pod 입니다.
SG Per Pod는 기존 인스턴스별 SG가 아닌 Pod별 SG를 설정할 수 있어 보다 유연하게 SG를 적용할 수 있게 되었습니다.
EKS에서 Pod는 기본적으로 Worker Node의 ENI에서 Secondary IP를 할당받았습니다.Security Groups for Pods(SGP) 사용 시, Pod마다 개별적으로 ENI를 생성할 수 있습니다.즉, Pod가 ENI에서 IP를 가져오는 기존 방식이 아니라, 새로운 ENI를 추가하면서 Secondary IP를 확장할 수 있게 되었습니다.
SG Per Pod 제약 조건
a. Windows Node 에서는 사용 불가
b. T 시리즈 EC2 인스턴스는 사용 불가
c. VPC CNI 버전 1.7.7 이상
kubectl describe daemonset aws-node --namespace kube-system | grep amazon-k8s-cni: | cut -d : -f 3
d. EKS Cluster Role에 AmazonEKSVPCResourceController 정책 부여
SGP를 적용하면?
1. Pod마다 별도의 ENI 생성 가능
2. Pod별로 다른 보안 그룹 적용 가능
3. ENI가 증가하면서, 사용 가능한 Secondary IP도 함께 증가
1.7.4 Networking Policies
참조: https://www.eksworkshop.com/docs/networking/vpc-cni/network-policies/
Network Policies
Pod 간 네트워크 트래픽을 제한하여 불필요한 연결을 막음
보안 및 네트워크 리소스 효율화를 위해 특정 트래픽만 허용
불필요한 연결이 줄어들면서, IP 사용도 절약됨
1.7.5 Amazon VPC Lattice
참조: https://www.eksworkshop.com/docs/networking/vpc-lattice/
1. Pod가 직접 ENI를 사용하지 않아도 됨
Lattice는 네트워크 트래픽을 관리하므로, Pod가 직접 ENI에서 IP를 받아야 하는 부담이 줄어듦
즉, Pod가 더 적은 ENI로도 충분한 IP를 가질 수 있어, 더 많은 Pod 배포 가능
✅ 2. VPC Lattice는 서비스 단위의 네트워킹을 제공
기존 Kubernetes에서 Pod 간 네트워크 연결을 위해선 각 Pod가 개별 IP를 가져야 했지만, Lattice는 서비스 단위(Service-Based Routing)로 트래픽을 관리
즉, 각 Pod가 개별적으로 IP를 할당받을 필요 없이, VPC Lattice가 대신 네트워크 트래픽을 라우팅
결과적으로, Pod가 사용하는 IP 개수가 줄어들고, 기존보다 더 많은 Pod를 배포 가능
3. 멀티 VPC & 크로스 서브넷 지원 → Pod의 IP 확장성 증가
VPC Lattice는 멀티 VPC 및 크로스 서브넷을 지원하여, 하나의 VPC에 IP가 부족해도 다른 VPC의 서브넷을 활용 가능
반면, 기존 EKS에서는 Pod가 할당받을 수 있는 IP가 VPC 및 ENI의 제한을 받음
✅ 4. Lattice는 Kubernetes 네트워크 정책과도 통합 가능
네트워크 정책을 활용해 불필요한 트래픽을 줄여서, 기존 IP를 효율적으로 사용하도록 최적화 가능
1.7 Service & AWS LoadBalancer Controller
서비스 종류
1.7.1 ClusterIP 타입
- 클러스터 내부에서만 접근 가능한 서비스 타입
- 외부에서는 접근 불가. 클러스터 내부의 서비스(Pod, Service 등)만 이 IP를 통해 접근 가능
- Pod의 Private IP는 언제든 변동될 수 있으므로 EIP와 같은 IP 고정 역할 수행
1.7.2 NodePort 타입
- 클러스터의 노드에서 특정 포트(30000~32767)를 열어서 외부에서 접근 가능
- 외부 사용자는 NodeIP:NodePort 형태로 접근 가능
- 보통 단독보다는 LoadBalancer나 Ingress 뒤에 사용됨
1.7.3 LoadBalancer 타입
- CSP에서 제공하는 외부 로드밸런서를 생성하여 외부 접근 가능케 하는 서비스 타입
- 내부 라우팅 흐름
1. LB에서 NodePort를 통해 각 Worker Node에 트래픽 전달
2. 각 Worker Node는 iptables를 참고하여 알맞은 Pod로 트래픽 전달 - 기본 NLB 기반이며, 포트 라우팅은 NodePort 서비스 기반으로 동작. (Ingress Controller 설치해야 ALB 제공)
- LoadBalancer 타입을 사용할 때 Pod별 ClusterIP 설정 필요 없음.
Pod의 변경사항을 감지하고 트래픽을 새로운 Pod로 연결. 서비스는 항상 최신 Pod의 IP를 알고 있음 (Label 기반 구분)
1.7.4 Service (LoadBalancer Controller) 타입
- LoadBalancer 서비스는 CSP의 Native LoadBalacner를 사용하나, LoadBalancer Controller(Nginx Ingress Controller, MetalLB 등)는 내부적으로 K8S 리소스를 활용해서 로드밸런싱을 수행
- 내부 라우팅 흐름
1. LB Controller Pod가 LB에게 파드 정보 제공
2. LB는 알맞은 Pod의 IP로 직접 트래픽 전달 - 별도 설치 필요
- ALB 기능 지원
- 참고: https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/aws-load-balancer-controller.html
AWS LB Controller는 클러스터 Service or Ingress 리소스를 가리키는 AWS LB를 프로비저닝함
K8S Ingress 생성 시 → AWS ALB 생성
Loadbalancer 유형의 K8S 서비스 → NLB 생성
구분 | LoadBalancer Service(클라우드 네이티브) | LoadBalancer Controller (온프렘 & Ingress) |
설치 필요 여부 | X | O |
지원 환경 | 클라우드 (AWS, GCP, Azure) | 온프렘, 베어메탈, 클라우드 |
트래픽 라우팅 | L4 (IP 기반 라우팅) | L7 (도메인 기반 라우팅) |
고급 기능 지원 | 도메인 기반 라우팅, TLS 종료 등 지원 X | 도메인 기반 라우팅, TLS 종료 등 지원 O |
사용 기술 | 클라우드 네이티브 로드밸런서 (ELB) | Nginx Ingress Controller, MetalLB 등 |
트래픽 라우팅 방식 | 특정 서비스로 직접 전달 | 여러 서비스로 라우팅 가능 (Ingress 활용) |
NodePort 사용 여부 | 내부적으로 NodePort 사용 | 필요할 수도 있음 |
1.8 NLB 모드 전체 정리
클라이언트 IP 주소 보존 (Client IP preservation) 활성화
- 외부에서는 NLB의 퍼블릭도메인(공인IP)로 접속하면, 대상(EC2)의 IP로 DNAT(목적지 IP 변환)하여 전달
- 클라이언트 IP 주소 보존 활성화 상태이므로 외부 클라이언트의 IP 주소를 대상(EC2)에서 확인 가능
클라이언트 IP 주소 보존 (Client IP preservation) 비활성화
- NLB에서 대상(EC2)로 전달 시, 출발지 IP 주소를 NLB의 내부 ENI(IP)로 SNAT(출발지 IP 변환)하여 전달
- 클라이언트 IP 주소 보존 비활성화 상태이므로, 외부 클라이언트의 IP 주소를 대상(EC2)에서 확인 불가능
Proxy Protocol v2(PPv2) 사용 시 (클라이언트 IP 보존 비활성화)
- NLB 에서 PPv2 사용 시, 외부 Client IP 정보를 PPv2 헤더에 담아 TCP 데이터에 추가하여 대상(EC2)에게 전달
- NLB 에서 PPv2 사용 시에는 대상(타켓 EC2)의 애플리케이션이 추가된 PPv2 헤더를 인식할수 있게 설정이 반드시 필요!
- 결과적으로 NLB PPv2 사용 시, 외부 클라이언트의 IP 주소를 대상(타켓 EC2)에서 확인 가능
NLB 대상 유형
https://gasidaseo.notion.site/AWS-NLB-Client-IP-Proxy-protocol-57827e2c83fc474992b37e65db81f669
1.8.1 인스턴스 유형
- externalTrafficPolicy: ClusterIP
2번 분산 및 SNAT으로 Client IP 확인 불가
LoadBalacner 타입(기본모드) 동작 - externalTrafficPolicy: Local
1번 분산 및 Client IP 유지, 워커 노드의 iptables 사용
ㆍ로드밸런서가 트래픽을 노드에게 전달, 노드의 iptables 룰에 의해 트래픽을 파드에게 전달 (externalTrafficPolicy: local)
ㆍDNAT 2번 동작: 첫번째(로드밸런서에서 나갈 때), 두번째(노드의 iptables 룰에 파드 IP 전달
ㆍ외부 클라이언트 IP 보존(유지): AWS NLB는 타겟이 인스턴스일 경우 클라이언트 IP를 유지, iptables 룰 경우도 externalTrafficPolicy로 클라이언트 IP를 보존
1.8.3.2 IP 유형
!! 반드시 AWS LoadBalancer Controller 파드 및 정책 설정이 필요 !!
ㆍ로드밸런서가 트래픽을 노드에게 전달, 노드의 iptables 룰에 의해 트래픽을 파드에게 전달 (externalTrafficPolicy: local)
ㆍDNAT 2번 동작: 첫번째(로드밸런서에서 나갈 때), 두번째(노드의 iptables 룰에 파드 IP 전달
ㆍ외부 클라이언트 IP 보존(유지): AWS NLB는 타겟이 인스턴스일 경우 클라이언트 IP를 유지, iptables 룰 경우도 externalTrafficPolicy로 클라이언트 IP를 보존
1.8.2 AWS LoadBalancer Controller 배포 with IRSA
# OIDC 확인
aws eks describe-cluster --name $CLUSTER_NAME --query "cluster.identity.oidc.issuer" --output text
aws iam list-open-id-connect-providers | jq
# IAM Policy (AWSLoadBalancerControllerIAMPolicy) 생성
curl -O https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.5.4/docs/install/iam_policy.json
aws iam create-policy --policy-name AWSLoadBalancerControllerIAMPolicy --policy-document file://iam_policy.json
# 혹시 이미 IAM 정책이 있지만 예전 정책일 경우 아래 처럼 최신 업데이트 할 것
# aws iam update-policy ~~~
# 생성된 IAM Policy Arn 확인
aws iam list-policies --scope Local | jq
aws iam get-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy | jq
aws iam get-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --query 'Policy.Arn'
# AWS Load Balancer Controller를 위한 ServiceAccount를 생성 >> 자동으로 매칭되는 IAM Role 을 CloudFormation 으로 생성됨!
# IAM 역할 생성. AWS Load Balancer Controller의 kube-system 네임스페이스에 aws-load-balancer-controller라는 Kubernetes 서비스 계정을 생성하고 IAM 역할의 이름으로 Kubernetes 서비스 계정에 주석을 답니다
eksctl create iamserviceaccount --cluster=$CLUSTER_NAME --namespace=kube-system --name=aws-load-balancer-controller --role-name AmazonEKSLoadBalancerControllerRole \
--attach-policy-arn=arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --override-existing-serviceaccounts --approve
## IRSA 정보 확인
eksctl get iamserviceaccount --cluster $CLUSTER_NAME
## 서비스 어카운트 확인
kubectl get serviceaccounts -n kube-system aws-load-balancer-controller -o yaml | yh
# Helm Chart 설치
helm repo add eks https://aws.github.io/eks-charts
helm repo update
helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=$CLUSTER_NAME \
--set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller
## 설치 확인 : aws-load-balancer-controller:v2.7.1
kubectl get crd
kubectl get deployment -n kube-system aws-load-balancer-controller
kubectl describe deploy -n kube-system aws-load-balancer-controller
kubectl describe deploy -n kube-system aws-load-balancer-controller | grep 'Service Account'
Service Account: aws-load-balancer-controller
# 클러스터롤, 롤 확인
kubectl describe clusterrolebindings.rbac.authorization.k8s.io aws-load-balancer-controller-rolebinding
kubectl describe clusterroles.rbac.authorization.k8s.io aws-load-balancer-controller-role
...
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
targetgroupbindings.elbv2.k8s.aws [] [] [create delete get list patch update watch]
events [] [] [create patch]
ingresses [] [] [get list patch update watch]
services [] [] [get list patch update watch]
ingresses.extensions [] [] [get list patch update watch]
services.extensions [] [] [get list patch update watch]
ingresses.networking.k8s.io [] [] [get list patch update watch]
services.networking.k8s.io [] [] [get list patch update watch]
endpoints [] [] [get list watch]
namespaces [] [] [get list watch]
nodes [] [] [get list watch]
pods [] [] [get list watch]
endpointslices.discovery.k8s.io [] [] [get list watch]
ingressclassparams.elbv2.k8s.aws [] [] [get list watch]
ingressclasses.networking.k8s.io [] [] [get list watch]
ingresses/status [] [] [update patch]
pods/status [] [] [update patch]
services/status [] [] [update patch]
targetgroupbindings/status [] [] [update patch]
ingresses.elbv2.k8s.aws/status [] [] [update patch]
pods.elbv2.k8s.aws/status [] [] [update patch]
services.elbv2.k8s.aws/status [] [] [update patch]
targetgroupbindings.elbv2.k8s.aws/status [] [] [update patch]
ingresses.extensions/status [] [] [update patch]
pods.extensions/status [] [] [update patch]
services.extensions/status [] [] [update patch]
targetgroupbindings.extensions/status [] [] [update patch]
ingresses.networking.k8s.io/status [] [] [update patch]
pods.networking.k8s.io/status [] [] [update patch]
services.networking.k8s.io/status [] [] [update patch]
targetgroupbindings.networking.k8s.io/status [] [] [update patch]
ㅁ
'세미나 & 교육 & Tech' 카테고리의 다른 글
[Amazon EKS STUDY] #2주차 (도전과제1~7) (0) | 2025.02.18 |
---|---|
[Amazon EKS STUDY] #2주차 (이론) AWS VPC CNI (2) (0) | 2025.02.18 |
[Amazon EKS STUDY] #1주차 (도전과제6~9) (0) | 2025.02.10 |
[Amazon EKS STUDY] #1주차 (도전과제1~5) EKS 원클릭 배포 가이드 (0) | 2025.02.10 |
[Amazon EKS STUDY] #1주차 (실습) eksctl를 활용한 EKS 배포 (0) | 2025.02.04 |