AWS VPC CNI란?
EKS에서 사용하는 CNI로 아래와 같은 특징을 지닌다.
- POD와 NODE가 같은 네트워크 대역을 가진다.
- POD간 통신이 직접 이루어진다.
- EC2 타입 별 생성할 수 있는 POD 개수 제한이 존재한다.
그 외 상세한 특징이나 정보는 이전에 공부한 자료 링크에 설명되어있다.
AWS VPC CNI 특징 : https://ersia.tistory.com/28
기존에 공부한 VPC CNI 기능이나 이번 스터디 시간에 테스트한 내용을 또 해보긴 애매해서, NLB의 IP Mode와 Instance Mode 간의 차이를 알아보는 테스트를 수행했다.
EKS의 NLB IP Mode란?
AWS Network Load Balancer(NLB)의 IP mode는 대상 그룹(Target Group)을 구성할 때 사용할 수 있는 옵션 중 하나이다.
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type specifies the target type to configure for NLB.
You can choose between instance and ip.
- instance mode will route traffic to all EC2 instances within cluster on the NodePort opened for your service.
* service must be of type NodePort or LoadBalancer for instance targets
- ip mode will route traffic directly to the pod IP.
* network plugin must use native AWS VPC networking configuration for pod IP, for example Amazon VPC CNI plugin.
두개의 모드간 주요 차이점은 아래와 같다.
- 트래픽 전달 경로
- IP mode : 트래픽이 NLB에서 직접 Pod의 IP로 라우팅된다.
- Instance mode : 트래픽이 Woker Node의 NodePort로 먼저 전달된 후 kube-proxy(iptables)에 의해 Pod로 라우팅된다. (Woker Node 1개 홉을 더 거치게 됨)
- 대상(Target) 등록
- IP mode : 개별 Pod의 IP 주소가 NLB 대상 그룹에 등록된다. (이로 인해 Fargate도 사용 가능함)
- Instance mode : EC2 인스턴스가 대상 그룹에 등록된다.
- 상태 체크
- IP mode : NLB가 직접 Pod의 상태를 확인한다.
- Instance mode : Worker Node의 NodePort를 통하기 때문에 Node 레벨에서 상태 체크가 수행된다.
EKS 환경의 NLB의 NLB(IP Mode) 테스트 구성
기존 EKS 환경 구성
스터디하면서 공유받은 기본 EKS 환경이다.
해당 yaml 파일을 Cloudformation을 통해 배포한다. 배포가 완료되기까지 약 20분 가량 소요된다.
(test_admin@myeks:default) [root@myeks-bastion ~]# eksctl get cluster
NAME REGION EKSCTL CREATED
myeks ap-northeast-2 True
(test_admin@myeks:default) [root@myeks-bastion ~]# kubectl get node -o wide -A
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
ip-192-168-1-118.ap-northeast-2.compute.internal Ready <none> 10m v1.30.4-eks-a737599 192.168.1.118 43.201.61.154 Amazon Linux 2 5.10.226-214.880.amzn2.x86_64 containerd://1.7.22
ip-192-168-2-146.ap-northeast-2.compute.internal Ready <none> 10m v1.30.4-eks-a737599 192.168.2.146 3.35.157.124 Amazon Linux 2 5.10.226-214.880.amzn2.x86_64 containerd://1.7.22
ip-192-168-3-137.ap-northeast-2.compute.internal Ready <none> 10m v1.30.4-eks-a737599 192.168.3.137 15.164.210.40 Amazon Linux 2 5.10.226-214.880.amzn2.x86_64 containerd://1.7.22
(test_admin@myeks:default) [root@myeks-bastion ~]# kubectl get pod -o wide -A
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
kube-system aws-node-mwktx 2/2 Running 0 10m 192.168.1.118 ip-192-168-1-118.ap-northeast-2.compute.internal <none> <none>
kube-system aws-node-tgjrb 2/2 Running 0 10m 192.168.2.146 ip-192-168-2-146.ap-northeast-2.compute.internal <none> <none>
kube-system aws-node-tl76m 2/2 Running 0 10m 192.168.3.137 ip-192-168-3-137.ap-northeast-2.compute.internal <none> <none>
kube-system coredns-699d8c5988-mtpc4 1/1 Running 0 14m 192.168.3.62 ip-192-168-3-137.ap-northeast-2.compute.internal <none> <none>
kube-system coredns-699d8c5988-t42hz 1/1 Running 0 14m 192.168.3.202 ip-192-168-3-137.ap-northeast-2.compute.internal <none> <none>
kube-system kube-proxy-d9qds 1/1 Running 0 10m 192.168.3.137 ip-192-168-3-137.ap-northeast-2.compute.internal <none> <none>
kube-system kube-proxy-m2bxm 1/1 Running 0 10m 192.168.1.118 ip-192-168-1-118.ap-northeast-2.compute.internal <none> <none>
kube-system kube-proxy-r68rf 1/1 Running 0 10m 192.168.2.146 ip-192-168-2-146.ap-northeast-2.compute.internal <none> <none>
(test_admin@myeks:default) [root@myeks-bastion ~]# kubectl get svc -A
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
default kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 18m
kube-system eks-extension-metrics-api ClusterIP 10.100.110.81 <none> 443/TCP 18m
kube-system kube-dns ClusterIP 10.100.0.10 <none> 53/UDP,53/TCP,9153/TCP 15m
테스트 서비스 및 NLB (IP Mode) 배포
배포 완료를 확인하고 bastion 서버에 접속해 아래의 테스트용 서비스와 NLB를 IP mode로 배포한다.
# AWS LoadBalancer Controller 배포
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=myeks
# 테스트 서비스 및 NLB 배포
cat <<EOF > nlb_ip_mode.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.21
ports:
- containerPort: 7777
command: ["/bin/sh"]
args: ["-c", "sed -i 's/listen .*/listen 7777;/' /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"]
---
apiVersion: v1
kind: Service
metadata:
name: nginx-nlb-service
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "external"
# target-type 부분이 instance에서 ip로 변경되었다.
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip"
service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"
spec:
type: LoadBalancer
ports:
- port: 7777
targetPort: 7777
selector:
app: nginx
EOF
kubectl apply -f nlb_ip_mode.yaml
(test_admin@myeks:default) [root@myeks-bastion ~]# kubectl apply -f nlb_ip_mode.yaml
deployment.apps/nginx-deployment created
service/nginx-nlb-service created
NLB 프로비저닝에 시간이 조금 걸린다. 배포 후 NLB를 확인하면 아래와 같이 대상 그룹이 EC2 인스턴스가 아닌 IP로 지정된 것을 볼 수 있다.
임의의 Worker node로 접속해 tcpdump로 패킷을 떠보자.
# 임의의 Worker Node에서 패킷 캡쳐 수행
# 여기선 2번 Worker Node에서 수행
(test_admin@myeks:default) [root@myeks-bastion ~]# ssh ec2-user@3.35.157.124
...
...
[root@ip-192-168-2-146 ~]# tcpdump -i any -nn 'port 7777' -w nlb_traffic.pcap
tcpdump: listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
# Bastion 서버에서 서비스 호출 수행
NLB_DNS=$(kubectl get svc nginx-nlb-service -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
# 로드밸런싱되므로 패킷 캡쳐하는 Worker Node의 Pod에 접근하기 위해 여러번 호출
for i in {1..10}; do curl -s $NLB_DNS; done
패킷을 뜨면 굉장히 많은 패킷이 보이는데, 우리가 원하는건 nginx로의 curl 호출 패킷이므로 http로 필터링을 해주면 아래와 같은 패킷 흐름을 볼 수 있다.
192.168.2.199는 당연하겠지만 nginx pod의 IP 이다.
(test_admin@myeks:default) [root@myeks-bastion ~]# kubectl get pod -o wide -A | grep 192.168.2.199
default nginx-deployment-67dd5f77ff-mhsp9 1/1 Running 0 11m 192.168.2.199 ip-192-168-2-146.ap-northeast-2.compute.internal <none> <none>
그럼 192.168.2.198은 누구의 IP일까? AWS 콘솔의 네트워크 인터페이스에서 검색해보면 쉽게 알 수 있다.
NLB의 Private IP라는 것을 알 수 있다.
위의 패킷 흐름을 보면 사전에 알아봤듯이 NLB가 Pod와 직접 통신을 하게 된다.
NLB의 Instance Mode 변경 후 통신 확인
그럼 Instance mode일 때는 어떨까? 배포한 NLB와 서비스 pod를 제거하고 target_type 부분만 instance로 변경해 다시 배포해 보자.
cat <<EOF > nlb_instance_mode.yaml
...
apiVersion: v1
kind: Service
metadata:
name: nginx-nlb-service
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "external"
# target-type 부분이 ip에서 instance로 변경되었다.
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "instance"
service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"
...
EOF
kubectl apply -f nlb_instance_mode.yaml
배포 후에 대상 그룹을 확인해보면 아래와 같이 인스턴스로 지정된 것을 볼 수 있다.
32392포트가 생소할 수 있는데, LoadBalancer Service Port이다.
(test_admin@myeks:default) [root@myeks-bastion ~]# kubectl get svc -A | grep 32392
default nginx-nlb-service LoadBalancer 10.100.30.185 k8s-default-nginxnlb-4295c357fc-1463d4c43f23f992.elb.ap-northeast-2.amazonaws.com 7777:32392/TCP 5m17s
그럼 아까와 동일한 조건으로 패킷을 캡쳐해보자.
# 임의의 Worker Node에서 패킷 캡쳐 수행
# 여기선 2번 Worker Node에서 수행
(test_admin@myeks:default) [root@myeks-bastion ~]# ssh ec2-user@3.35.157.124
...
...
[root@ip-192-168-2-146 ~]# tcpdump -i any -nn 'port 7777' -w nlb_traffic.pcap
tcpdump: listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
# Bastion 서버에서 서비스 호출 수행
NLB_DNS=$(kubectl get svc nginx-nlb-service -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
# 로드밸런싱되므로 패킷 캡쳐하는 Worker Node의 Pod에 접근하기 위해 여러번 호출
for i in {1..10}; do curl -s $NLB_DNS; done
http프로토콜로 필터링하면 아래와 같은 패킷 플로우를 확인할 수 있다.
그냥 보기만 해도 굉장히 어지럽고 뭔가 이상할 수 있는데, Woker Node에서 iptables를 통해 Pod로 전해지기 때문에 발생하는 패킷 흐름이다.
위의 표를 참고해서 아래의 흐름을 이해해보자.
- 가장 먼저 Bastion 서버(192.168.1.100)에서 NLB로 curl 호출을 수행한다.
- NLB는 3개의 Private IP(192.168.1.51, 192.168.2.149 , 192.168.3.171)를 가지고 있는데 여기서 로드밸런싱이 이루어진다.
- Worker Node 1번(192.168.1.118)로 전달되었을 때
- Node의 iptables 정책에 의해 1번 Pod(192.168.1.248)가 선택
- Node의 iptables 정책에 의해 2번 Pod(192.168.2.206)가 선택(2번 Worker Node에서 패킷 캡처)
- Node의 iptables 정책에 의해 3번 Pod(192.168.3.111)가 선택
- Worker Node 2번(192.168.2.146)로 전달되었을 때
- Node의 iptables 정책에 의해 1번 Pod(192.168.1.248)가 선택(2번 Worker Node에서 패킷 캡처)
- Node의 iptables 정책에 의해 2번 Pod(192.168.2.206)가 선택(2번 Worker Node에서 패킷 캡처)
- Node의 iptables 정책에 의해 3번 Pod(192.168.3.111)가 선택(2번 Worker Node에서 패킷 캡처)
- Worker Node 3번(192.168.3.137)로 전달되었을 때
- Node의 iptables 정책에 의해 1번 Pod(192.168.1.248)가 선택
- Node의 iptables 정책에 의해 2번 Pod(192.168.2.206)가 선택(2번 Worker Node에서 패킷 캡처)
- Node의 iptables 정책에 의해 3번 Pod(192.168.3.111)가 선택
- Worker Node 1번(192.168.1.118)로 전달되었을 때
우리는 2번 Worker Node(192.168.2.146)에서 패킷을 캡쳐했기 때문에
wireshark 패킷 흐름에서 위의 5가지 케이스가 모두 캡쳐된 것이다.
#### 임의의 Worker Node에서 iptables -t nat -L -n -v 수행 후 분석 ####
...
# nginx-nlb-service에 대한 트래픽을 KUBE-SVC-VDXCGHLC7LJVQSZ7 체인으로 전달
Chain KUBE-SERVICES (2 references)
0 0 KUBE-SVC-VDXCGHLC7LJVQSZ7 tcp -- * * 0.0.0.0/0 10.100.30.185 /* default/nginx-nlb-service cluster IP */ tcp dpt:7777
...
# 로드밸런싱 규칙
Chain KUBE-SVC-VDXCGHLC7LJVQSZ7 (2 references)
125 7500 KUBE-SEP-LHC45K453FK42NFG all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-nlb-service -> 192.168.1.248:7777 */ statistic mode random probability 0.33333333349
132 7920 KUBE-SEP-KZTINFDZQREWAZ5M all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-nlb-service -> 192.168.2.206:7777 */ statistic mode random probability 0.50000000000
154 9240 KUBE-SEP-62QXZKLRNZ4Q2ZG5 all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-nlb-service -> 192.168.3.111:7777 */
...
# 개별 Pod로 전달
Chain KUBE-SEP-LHC45K453FK42NFG (1 references)
0 0 KUBE-MARK-MASQ all -- * * 192.168.1.248 0.0.0.0/0 /* default/nginx-nlb-service */
125 7500 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-nlb-service */ tcp to:192.168.1.248:7777
Chain KUBE-SEP-KZTINFDZQREWAZ5M (1 references)
0 0 KUBE-MARK-MASQ all -- * * 192.168.2.206 0.0.0.0/0 /* default/nginx-nlb-service */
132 7920 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-nlb-service */ tcp to:192.168.2.206:7777
Chain KUBE-SEP-62QXZKLRNZ4Q2ZG5 (1 references)
0 0 KUBE-MARK-MASQ all -- * * 192.168.3.111 0.0.0.0/0 /* default/nginx-nlb-service */
154 9240 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-nlb-service */ tcp to:192.168.3.111:7777
따라서 패킷 캡쳐 결과를 보면 IP mode와는 다르게 NLB -> Worker Node -> Pod 순으로 Worker Node 홉(hop) 1개를 더 거치는 것을 확인할 수 있다.
참고자료
- IP mode : https://www.eksworkshop.com/docs/fundamentals/exposing/loadbalancer/ip-mode/
- NLB IP mode : https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.1/guide/service/nlb_ip_mode/
- Choosing Load Balancer Target-Type : https://aws.github.io/aws-eks-best-practices/networking/loadbalancing/loadbalancing/#choosing-load-balancer-target-type