[KANS] 3기 9주 – AWS EKS : VPC CNI

CloudNet@팀에서 진행하는 쿠버네티스 네트워크 스터디 3기 참가글입니다.

‘쿠버네티스’ 네트워크에 대해서 직접 실습을 통해 장애 시 해결 능력을 갖추고, 학습 내용을 전파 및 공유합니다!


1. AWS VPC CNI

K8S CNI

  • Container Network Interface 는 k8s 네트워크 환경을 구성해준다 – 링크
  • 다양한 플러그인이 존재 – 링크

AWS VPC CNI

  • 파드의 IP를 할당해준다
  • 파드의 IP 네트워크 대역과 노드(워커)의 IP 대역이 같아서 직접 통신이 가능하다
  • 구성요소 (2개)
    • CNI binary
      • Pod-to-Pod 통신을 위한 네트워크 설정
      • node의 root file system에서 실행
      • Pod 생성 및 제거 시 kubelet에 의해 작동
    • ipamd
      • node의 local IP Address Management (IPAM) daemon
      • 노드의 ENI 관리
      • 사용가능한 IP 주소대역 또는 prefix의 warm pool 관리
  • 새로운 EC2 인스턴스 시작 시, primary ENI가 생성되어 primary subnet에 부착
    • Pod는 hostNetwork 모드로 실행되어 노드의 primary ENI 사용 및 같은 Network namespace 공유
  • AWS CNI 플러그인은 노드의 ENI를 관리
    • 노드 생성시, CNI플러그인은 노드의 primary ENI가 할당된 primary subnet에서 사용가능한 대역(IP 또는 prefix)범위를 구성 = warm pool
    • 해당 warm pool은 노드의 Secondary ENI로 부착
    • 사전 생성된 warm pool을 통해 더 빠른 pod 시작을 가능하게함
  • EC2 type에 따라 최대 Network Interface 개수가 다름


실습환경 구성 (EKS on AWS)

  • 전체 구성도 : VPC 1개(퍼블릭 서브넷 3개, 프라이빗 서브넷 3개), EKS 클러스터(Control Plane), 관리형 노드 그룹(EC2 3대), Add-on
# YAML 파일 다운로드
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/kans/eks-oneclick.yaml

# CloudFormation 스택 배포
# aws cloudformation deploy --template-file eks-oneclick.yaml --stack-name myeks --parameter-overrides KeyName=<My SSH Keyname> SgIngressSshCidr=<My Home Public IP Address>/32 MyIamUserAccessKeyID=<IAM User의 액세스키> MyIamUserSecretAccessKey=<IAM User의 시크릿 키> ClusterBaseName='<eks 이름>' --region ap-northeast-2
예시) aws cloudformation deploy --template-file eks-oneclick.yaml --stack-name myeks --parameter-overrides KeyName=kp-gasida SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32  MyIamUserAccessKeyID=AKIA5... MyIamUserSecretAccessKey='CVNa2...' ClusterBaseName=myeks --region ap-northeast-2

## Tip. 워커노드 인스턴스 타입 변경 : WorkerNodeInstanceType=t3.xlarge
예시) aws cloudformation deploy --template-file eks-oneclick.yaml --stack-name myeks --parameter-overrides KeyName=kp-gasida SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32  MyIamUserAccessKeyID=AKIA5... MyIamUserSecretAccessKey='CVNa2...' ClusterBaseName=myeks --region ap-northeast-2 WorkerNodeInstanceType=t3.xlarge 

# CloudFormation 스택 배포 완료 후 작업용 EC2 IP 출력
aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text

# 작업용 EC2 SSH 접속
ssh -i ~/.ssh/kp-gasida.pem ec2-user@$(aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text)

2. Node의 기본 네트워크 정보 확인

워커 노드1 기본 네트워크 구성

  • 워커 노드2 는 구성이 유사함
  • 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 과 연결되어 있다

보조 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

결과

  • coreDNS pod의 IP가 해당노드의 eni로 붙어있음 확인

3. 노드간 파드 통신

목표:

  • 파드간 통신 시
  • tcpdump 내용을 확인하고 통신 과정을 알아본다.

파드간 통신 흐름

  • AWS VPC CNI 경우 별도의 오버레이(Overlay) 통신 기술 없이,
  • VPC Native 하게 파드간 직접 통신이 가능하다

  • 파드간 통신 시 과정 참고
Life of a Pod to Pod Ping Packet, AWS CNI Proposal

[실습] 파드간 통신 테스트 및 확인 : 별도의 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 확인
## For Pod to external (outside VPC) traffic, we will program iptables to SNAT using Primary IP address on the Primary ENI.
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
...

결과

  • eth1이 아닌 eth0, eni*를 통해 통신함 확인
    • PodName3 > PodIP1 통신시
      • 192.168.2.185 > 192.168.1.40
    • PodName1 > PodIP3 통신시
      • 192.168.1.40 > 192.168.3.107
  • worker node 1 정보

4. 파드에서 외부 통신

파드에서 외부 통신 흐름

  • iptable 에 SNAT 을 통하여
  • 노드의 eth0 IP로 변경되어서 외부와 통신됨
Life of a Pod to External Packet, AWS CNI Proposal
  • 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 - 링크
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

결과

  • 외부 ping 확인
  • pod가 외부네트워크에 요청을 보낼시, 위치하는 노드의 공인IP를 사용한다.
  • iptables 규칙 확인
    • –dst-type LOCAL(192.168.0.0./16)을 제외한 다른 모든 트래픽은 노드로 SNAT 대상
  • conntrack 확인

5. 노드에 파드 생성 갯수 제한

Secondary IPv4 addresses (기본값)

  • 인스턴스 유형에 최대 ENI 갯수와 할당 가능 IP 수를 조합하여 선정
  • 워커 노드의 인스턴스 타입 별 파드 생성 갯수 제한
    • 인스턴스 타입 별 ENI 최대 갯수와 할당 가능한 최대 IP 갯수에 따라서 파드 배치 갯수가 결정됨
    • 단, aws-node 와 kube-proxy 파드는 호스트의 IP를 사용함으로 최대 갯수에서 제외함

최대 파드 생성 갯수 : (Number of network interfaces for the instance type × (the number of IP addressess per network interface – 1)) + 2


해결 방안 : Prefix Delegation, WARM & MIN IP/Prefix Targets, Custom Network

워커 노드의 인스턴스 정보 확인 > t3.medium 사용시 17개

# 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

최대 파드 생성 및 확인

  • 사전 준비 : 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"}'
  • 최대 파드 생성 및 확인
# 워커 노드 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

결과

  • t3 medium 정보 확인 > 실제 노드 Allocatable pods 17
  • 파드 증가 테스트
  • replicas15: 개별노드당 5개, eth1에 전체 할당 후 eth2 대기
  • replicas30: 개별노드당 10개, eth1/eth2에 각5씩 전체 10개 생성
  • replicas 50: 개별노드당 약 17개
    • 최대 생성 이후 일부 pod는 Pending 상태로 대기

ㅇㅇ

6. Service & AWS LoadBalancer Controller

서비스 종류

  • Cluster IP 타입

  • NodePort 타입

  • LoadBalancer 타입(기본 모드): NLB 인스턴스 유형

  • Service (LoadBalancer Controller) : AWS Load Balancer Controller + NLB IP 모드 동작 with AWS VPC CNI

NLB 모드 전체 정리

1. 인스턴스 유형

Deploying AWS Load Balancer Controller on Amazon EKS
  1. externalTrafficPolicy : ClusterIP ⇒ 2번 분산 및 SNAT으로 Client IP 확인 불가능 ← LoadBalancer 타입 (기본 모드) 동작
  2. externalTrafficPolicy : Local ⇒ 1번 분산 및 ClientIP 유지, 워커 노드의 iptables 사용함

2. IP 유형 ⇒ 반드시 AWS LoadBalancer 컨트롤러 파드 및 정책 설정이 필요함!

Deploying AWS Load Balancer Controller on Amazon EKS
  1. Proxy Protocol v2 비활성화 ⇒ NLB에서 바로 파드로 인입, 단 ClientIP가 NLB로 SNAT 되어 Client IP 확인 불가능
  2. Proxy Protocol v2 활성화 ⇒ NLB에서 바로 파드로 인입 및 ClientIP 확인 가능(→ 단 PPv2 를 애플리케이션이 인지할 수 있게 설정 필요)


AWS LoadBalancer Controller 배포

# 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


## 설치 확인
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]

서비스/파드 배포 테스트 with NLB

# 모니터링
watch -d kubectl get pod,svc,ep

# 작업용 EC2 - 디플로이먼트 & 서비스 생성
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/2/echo-service-nlb.yaml
cat echo-service-nlb.yaml
kubectl apply -f echo-service-nlb.yaml

# 확인
kubectl get deploy,pod
kubectl get svc,ep,ingressclassparams,targetgroupbindings
kubectl get targetgroupbindings -o json | jq

# (옵션) 빠른 실습을 위해서 등록 취소 지연(드레이닝 간격) 수정 : 기본값 300초
vi echo-service-nlb.yaml
..
apiVersion: v1
kind: Service
metadata:
  name: svc-nlb-ip-type
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
    service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-port: "8080"
    service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
    service.beta.kubernetes.io/aws-load-balancer-target-group-attributes: deregistration_delay.timeout_seconds=60
...
:wq!
kubectl apply -f echo-service-nlb.yaml

# AWS ELB(NLB) 정보 확인
aws elbv2 describe-load-balancers | jq
aws elbv2 describe-load-balancers --query 'LoadBalancers[*].State.Code' --output text
ALB_ARN=$(aws elbv2 describe-load-balancers --query 'LoadBalancers[?contains(LoadBalancerName, `k8s-default-svcnlbip`) == `true`].LoadBalancerArn' | jq -r '.[0]')
aws elbv2 describe-target-groups --load-balancer-arn $ALB_ARN | jq
TARGET_GROUP_ARN=$(aws elbv2 describe-target-groups --load-balancer-arn $ALB_ARN | jq -r '.TargetGroups[0].TargetGroupArn')
aws elbv2 describe-target-health --target-group-arn $TARGET_GROUP_ARN | jq
{
  "TargetHealthDescriptions": [
    {
      "Target": {
        "Id": "192.168.2.153",
        "Port": 8080,
        "AvailabilityZone": "ap-northeast-2b"
      },
      "HealthCheckPort": "8080",
      "TargetHealth": {
        "State": "initial",
        "Reason": "Elb.RegistrationInProgress",
        "Description": "Target registration is in progress"
      }
    },
...

# 웹 접속 주소 확인
kubectl get svc svc-nlb-ip-type -o jsonpath={.status.loadBalancer.ingress[0].hostname} | awk '{ print "Pod Web URL = http://"$1 }'

# 파드 로깅 모니터링
kubectl logs -l app=deploy-websrv -f

# 분산 접속 확인
NLB=$(kubectl get svc svc-nlb-ip-type -o jsonpath={.status.loadBalancer.ingress[0].hostname})
curl -s $NLB
for i in {1..100}; do curl -s $NLB | grep Hostname ; done | sort | uniq -c | sort -nr
  52 Hostname: deploy-echo-55456fc798-2w65p
  48 Hostname: deploy-echo-55456fc798-cxl7z

# 지속적인 접속 시도 : 아래 상세 동작 확인 시 유용(패킷 덤프 등)
while true; do curl -s --connect-timeout 1 $NLB | egrep 'Hostname|client_address'; echo "----------" ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; done
  • AWS NLB의 대상 그룹 확인 : IP를 확인해보자
  • 파드 2개 → 1개 → 3개 설정 시 동작 : auto discovery ← 어떻게 가능할까?
# (신규 터미널) 모니터링
while true; do aws elbv2 describe-target-health --target-group-arn $TARGET_GROUP_ARN --output text; echo; done

# 작업용 EC2 - 파드 1개 설정 
kubectl scale deployment deploy-echo --replicas=1

# 확인
kubectl get deploy,pod,svc,ep
curl -s $NLB
for i in {1..100}; do curl -s --connect-timeout 1 $NLB | grep Hostname ; done | sort | uniq -c | sort -nr

# 작업용 EC2 - 파드 3개 설정 
kubectl scale deployment deploy-echo --replicas=3

# 확인 : NLB 대상 타켓이 아직 initial 일 때 100번 반복 접속 시 어떻게 되는지 확인해보자!
kubectl get deploy,pod,svc,ep
curl -s $NLB
for i in {1..100}; do curl -s --connect-timeout 1 $NLB | grep Hostname ; done | sort | uniq -c | sort -nr

# 
kubectl describe deploy -n kube-system aws-load-balancer-controller | grep -i 'Service Account'
  Service Account:  aws-load-balancer-controller

# [AWS LB Ctrl] 클러스터 롤 바인딩 정보 확인
kubectl describe clusterrolebindings.rbac.authorization.k8s.io aws-load-balancer-controller-rolebinding

# [AWS LB Ctrl] 클러스터롤 확인 
kubectl describe clusterroles.rbac.authorization.k8s.io aws-load-balancer-controller-role

결과

  • 최초 배포 이후 작동 확인
  • IP모드로, NodePort가 아닌 Pod 직접 연결 확인
  • Draining 도중 > 작동중인 1개의 Pod만 응답
  • initial 도중
    • 작동중인 target에만 전달
    • AWS LB Controller에서 reconcile 완료 후 전체 전달

7. Ingress

인그레스 소개 : 클러스터 내부의 서비스(ClusterIP, NodePort, Loadbalancer)를 외부로 노출(HTTP/HTTPS) – Web Proxy 역할


서비스/파드 배포 테스트 with Ingress(ALB)

# 게임 파드와 Service, Ingress 배포
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/3/ingress1.yaml
cat ingress1.yaml
kubectl apply -f ingress1.yaml

# 모니터링
watch -d kubectl get pod,ingress,svc,ep -n game-2048

# 생성 확인
kubectl get-all -n game-2048
kubectl get ingress,svc,ep,pod -n game-2048
kubectl get targetgroupbindings -n game-2048

# ALB 생성 확인
aws elbv2 describe-load-balancers --query 'LoadBalancers[?contains(LoadBalancerName, `k8s-game2048`) == `true`]' | jq
ALB_ARN=$(aws elbv2 describe-load-balancers --query 'LoadBalancers[?contains(LoadBalancerName, `k8s-game2048`) == `true`].LoadBalancerArn' | jq -r '.[0]')
aws elbv2 describe-target-groups --load-balancer-arn $ALB_ARN
TARGET_GROUP_ARN=$(aws elbv2 describe-target-groups --load-balancer-arn $ALB_ARN | jq -r '.TargetGroups[0].TargetGroupArn')
aws elbv2 describe-target-health --target-group-arn $TARGET_GROUP_ARN | jq

# Ingress 확인
kubectl describe ingress -n game-2048 ingress-2048
kubectl get ingress -n game-2048 ingress-2048 -o jsonpath="{.status.loadBalancer.ingress[*].hostname}{'\n'}"

# 게임 접속 : ALB 주소로 웹 접속
kubectl get ingress -n game-2048 ingress-2048 -o jsonpath={.status.loadBalancer.ingress[0].hostname} | awk '{ print "Game URL = http://"$1 }'

# 파드 IP 확인
kubectl get pod -n game-2048 -owide

결과

  • Web 접근 확인
  • AWS 관리 콘솔 확인
    • ALB에서 Pod IP로 직접 전달!

8. ExternalDNS

소개 : K8S 서비스/인그레스 생성 시 도메인을 설정하면, AWS(Route 53), Azure(DNS), GCP(Cloud DNS) 에 A 레코드(TXT 레코드)로 자동 생성/삭제

A Self-hosted external DNS resolver for Kubernetes.
  • ExternalDNS CTRL 권한 주는 방법 3가지 : Node IAM Role, Static credentials, IRSA

실습시 Public 도메인 소유 필요

  • AWS Route 53 정보 확인 & 변수 지정
# 자신의 도메인 변수 지정 : 소유하고 있는 자신의 도메인을 입력하시면 됩니다
MyDomain=<자신의 도메인>
MyDomain=gasida.link
echo "export MyDomain=gasida.link" >> /etc/profile

# 자신의 Route 53 도메인 ID 조회 및 변수 지정
aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." | jq
aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Name"
aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Id" --output text
MyDnzHostedZoneId=`aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Id" --output text`
echo $MyDnzHostedZoneId

# (옵션) NS 레코드 타입 첫번째 조회
aws route53 list-resource-record-sets --output json --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'NS']" | jq -r '.[0].ResourceRecords[].Value'
# (옵션) A 레코드 타입 모두 조회
aws route53 list-resource-record-sets --output json --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A']"

# A 레코드 타입 조회
aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A']" | jq
aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A'].Name" | jq
aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A'].Name" --output text

# A 레코드 값 반복 조회
while true; do aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A']" | jq ; date ; echo ; sleep 1; done
# EKS 배포 시 Node IAM Role 설정되어 있음
# eksctl create cluster ... --external-dns-access ...

# 
MyDomain=<자신의 도메인>
MyDomain=gasida.link

# 자신의 Route 53 도메인 ID 조회 및 변수 지정
MyDnzHostedZoneId=$(aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Id" --output text)

# 변수 확인
echo $MyDomain, $MyDnzHostedZoneId

# ExternalDNS 배포
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/aews/externaldns.yaml
cat externaldns.yaml
MyDomain=$MyDomain MyDnzHostedZoneId=$MyDnzHostedZoneId envsubst < externaldns.yaml | kubectl apply -f -

# 확인 및 로그 모니터링
kubectl get pod -l app.kubernetes.io/name=external-dns -n kube-system
kubectl logs deploy/external-dns -n kube-system -f

Service(NLB) + 도메인 연동(ExternalDNS)

# 터미널1 (모니터링)
watch -d 'kubectl get pod,svc'
kubectl logs deploy/external-dns -n kube-system -f

# 테트리스 디플로이먼트 배포
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tetris
  labels:
    app: tetris
spec:
  replicas: 1
  selector:
    matchLabels:
      app: tetris
  template:
    metadata:
      labels:
        app: tetris
    spec:
      containers:
      - name: tetris
        image: bsord/tetris
---
apiVersion: v1
kind: Service
metadata:
  name: tetris
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
    service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
    service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
    service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "http"
    #service.beta.kubernetes.io/aws-load-balancer-healthcheck-port: "80"
spec:
  selector:
    app: tetris
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  type: LoadBalancer
  loadBalancerClass: service.k8s.aws/nlb
EOF

# 배포 확인
kubectl get deploy,svc,ep tetris

# NLB에 ExternanDNS 로 도메인 연결
kubectl annotate service tetris "external-dns.alpha.kubernetes.io/hostname=tetris.$MyDomain"
while true; do aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A']" | jq ; date ; echo ; sleep 1; done

# Route53에 A레코드 확인
aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A']" | jq
aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A'].Name" | jq .[]

# 확인
dig +short tetris.$MyDomain @8.8.8.8
dig +short tetris.$MyDomain

# 도메인 체크
echo -e "My Domain Checker = https://www.whatsmydns.net/#A/tetris.$MyDomain"

# 웹 접속 주소 확인 및 접속
echo -e "Tetris Game URL = http://tetris.$MyDomain"

9. CoreDNS

The life of a DNS query in Kubernetes

Recent changes to the CoreDNS add-on – 링크

  • EKS CoreDNS 애드온 구성 스키마에 topologySpreadConstraints 설정 추가
# CoreDNS 기본 정보 확인 : volumes - configMap - Corefile - coredns
kubectl get deploy coredns -n kube-system -o yaml | kubectl neat 
kubectl get cm -n kube-system coredns -o yaml | kubectl neat
data: 
  Corefile: |
    .:53 {
        errors
        health {
            lameduck 5s
          }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
          pods insecure
          fallthrough in-addr.arpa ip6.arpa
        }
        prometheus :9153
        forward . /etc/resolv.conf
        cache 30
        loop
        reload
        loadbalance
    }

# JSON 구성 스키마에 topologySpreadConstraints 매개변수를 추가
aws eks describe-addon-configuration --addon-name coredns --addon-version v1.10.1-eksbuild.2 --query 'configurationSchema' --output text | jq .
aws eks describe-addon-configuration --addon-name coredns --addon-version v1.10.1-eksbuild.2 --query 'configurationSchema' --output text | jq . | grep -A3 topologySpreadConstraints

# check coredns deployment - it returns an empty output because it is not set by default 
kubectl get deploy -n kube-system coredns -o yaml | grep topologySpreadConstraints -A8

#
aws eks describe-addon --cluster-name $CLUSTER_NAME --addon-name coredns | jq
{
  "addon": {
    "addonName": "coredns",
    "clusterName": "myeks",
    "status": "ACTIVE",
    "addonVersion": "v1.10.1-eksbuild.7",
    "health": {
      "issues": []
    },
    "addonArn": "arn:aws:eks:ap-northeast-2:911283464785:addon/myeks/coredns/4ec712ea-54eb-05df-5d57-7b52ef1efc6f",
    "createdAt": "2024-03-10T09:47:58.100000+09:00",
    "modifiedAt": "2024-03-10T09:48:06.073000+09:00",
    "tags": {}
  }
}

# add-on configuration YAML blob
cat << EOT > topologySpreadConstraints.yaml
"topologySpreadConstraints":
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: ScheduleAnyway
    labelSelector:
      matchLabels:
        k8s-app: kube-dns
EOT

# apply change to add-on
aws eks update-addon --cluster-name $CLUSTER_NAME --addon-name coredns --configuration-values 'file://topologySpreadConstraints.yaml'

# check add-on configuration to see if it is in ACTIVE status
aws eks describe-addon --cluster-name $CLUSTER_NAME --addon-name coredns | jq
kubectl get deploy coredns -n kube-system -o yaml | kubectl neat
...
    topologySpreadConstraints: 
    - labelSelector: 
        matchLabels: 
          k8s-app: kube-dns
      maxSkew: 1
      topologyKey: topology.kubernetes.io/zone
      whenUnsatisfiable: ScheduleAnyway
...
  • coredns 애드온에 PDB(Pod Disruption Budget) 추가
    • PDB는 두 개의 복제본 Pod가 있는 coredns와 같이 노드 종료 중에 동시에 작동이 중지되는 복제된 애플리케이션의 Pod 수를 제한
# "coredns" add-on v1.9.3-eksbuid.5 and v1.9.3-eksbuid.6
kubectl get pdb -n kube-system coredns
NAME    MIN AVAILABLE   MAX UNAVAILABLE  ALLOWED DISRUPTIONS   AGE
coredns 1               N/A              1                    13h

# "coredns" add-on v1.10.1-eksbuild.2 and v1.10.1-eksbuild.3
kubectl get pdb -n kube-system coredns
NAME    MIN AVAILABLE   MAX UNAVAILABLE  ALLOWED DISRUPTIONS   AGE
coredns N/A             1                1                     27h
  • DNS 확인 실패를 최소화하기 위해 기본적으로 coreDNS 플러그인에 lameduck 옵션을 추가
    • lameduck은 상태 엔드포인트가 여전히 200으로 응답하는 동안 DURATION초 동안 종료를 지연합니다.
    • 플러그인에 lameduck을 추가하면 CoreDNS 포드 다시 시작(예: 상태 문제, 노드 종료 등으로 인해) 또는 배포 롤아웃 중에 DNS 확인 실패가 최소화됨
# CoreDNS 기본 정보 확인
kubectl get cm -n kube-system coredns -o yaml | kubectl neat
data: 
  Corefile: |
    .:53 {
        errors
        health {
            lameduck 5s
          }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
          pods insecure
          fallthrough in-addr.arpa ip6.arpa
        }
        prometheus :9153
        forward . /etc/resolv.conf
        cache 30
        loop
        reload
        loadbalance
    }
  • CoreDNS의 readinessProbe 에서 /health 대신 /ready를 사용
#
kubectl get deploy coredns -n kube-system -o yaml | kubectl neat 
...
      readinessProbe: 
        failureThreshold: 3
        httpGet: 
          path: /ready
          port: 8181
          scheme: HTTP
        periodSeconds: 10
        successThreshold: 1
        timeoutSeconds: 1
...

# The “ready” plugin is already part of the “coredns” ConfigMap:
kubectl get cm -n kube-system coredns -o yaml | kubectl neat
data: 
  Corefile: |
    .:53 {
        errors
        health {
            lameduck 5s
          }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
          pods insecure
          fallthrough in-addr.arpa ip6.arpa
        }
        prometheus :9153
        forward . /etc/resolv.conf
        cache 30
        loop
        reload
        loadbalance
    }
  • EKS 관리형 Add-On Pod에 대한 라벨 설정
# 파드 Labels 확인
kubectl get deploy coredns -n kube-system -o yaml | kubectl neat 

# pod labels
aws eks describe-addon-configuration --addon-name coredns --addon-version v1.10.1-eksbuild.3 \
  --query 'configurationSchema' --output text | jq . | grep -A4 '\"podLabels\"'
        "podLabels": {
          "properties": {},
          "title": "The podLabels Schema",
          "type": "object"
        },

# YAML configuration blob
cat << EOT > podLabels.yaml
podLabels:
  foo: bar
EOT
  
# apply changes : 적용 시 coredns 파드 재시작됨
aws eks update-addon --cluster-name $CLUSTER_NAME --addon-name coredns --configuration-values 'file://podLabels.yaml'
watch -d kubectl get pod -n kube-system

# wait a while until the add-on is ACTIVE again
aws eks describe-addon --cluster-name $CLUSTER_NAME --addon-name coredns

# 파드 Labels 확인
kubectl get deploy coredns -n kube-system -o yaml | kubectl neat
kubectl get pod -n kube-system -l foo=bar
kubectl get po -n kube-system -l=k8s-app=kube-dns -o custom-columns="POD-NAME":.metadata.name,"POD-LABELS":.metadata.labels

10. Topology Aware Routing


Topology Aware Routing은 네트워크 트래픽을 발생한 영역(zone) 내에 유지하도록 돕는 메커니즘을 제공합니다. 클러스터 내에서 동일한 영역의 파드 간 트래픽을 우선시함으로써 신뢰성, 성능(네트워크 지연 시간 및 처리량), 또는 비용 측면에서 도움이 될 수 있습니다.

Topology Aware Routing provides a mechanism to help keep network traffic within the zone where it originated. Preferring same-zone traffic between Pods in your cluster can help with reliability, performance (network latency and throughput), or cost.

Topology Aware Routing, k8s Doc


AWS의 서비스를 물리적 위치를 기준으로 분류하면 Region 종속적인 서비스와 AZ 종속적인 서비스로 나눌 수 있다. Network 관점에서, VPC는 Region 단위로 설정하고, 그 내부에 Subnet을 각 AZ(가용영역)단위로 설정하여, 워크로드를 여러 AZ간에 분산해 워크로드의 고가용성을 확보할 수 있다.


그러나 통신 비용에 있어, AZ 내부의 통신은 비용은 발생하지 않지만 AZ간 통신은 비용이 발생한다. k8s(EKS) 구축시 많은 경우 워커노드를 AZ간 분산하여 고가용성을 얻지만 클러스터내 워크로드(pod)들이 상호 통신 하는 과정에서 Cross-AZ 트래픽이 발생해 비용이 청구될 수 있다.

이럴때 Topology Aware Routing(구 Topology Aware Hint)를 사용하여, 가능한 같은 AZ내의 통신은 가용영역 내의 대상으로 전달하도록하여 불필요한 통신비용을 줄일 수 있다.

테스트를 위한 디플로이먼트와 서비스 배포

# 현재 노드 AZ 배포 확인
kubectl get node --label-columns=topology.kubernetes.io/zone
NAME                                               STATUS   ROLES    AGE   VERSION                ZONE
ip-192-168-1-225.ap-northeast-2.compute.internal   Ready    <none>   70m   v1.24.11-eks-a59e1f0   ap-northeast-2a
ip-192-168-2-248.ap-northeast-2.compute.internal   Ready    <none>   70m   v1.24.11-eks-a59e1f0   ap-northeast-2b
ip-192-168-3-228.ap-northeast-2.compute.internal   Ready    <none>   70m   v1.24.11-eks-a59e1f0   ap-northeast-2c

# 테스트를 위한 디플로이먼트와 서비스 배포
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy-echo
spec:
  replicas: 3
  selector:
    matchLabels:
      app: deploy-websrv
  template:
    metadata:
      labels:
        app: deploy-websrv
    spec:
      terminationGracePeriodSeconds: 0
      containers:
      - name: websrv
        image: registry.k8s.io/echoserver:1.5
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: svc-clusterip
spec:
  ports:
    - name: svc-webport
      port: 80
      targetPort: 8080
  selector:
    app: deploy-websrv
  type: ClusterIP
EOF

# 확인
kubectl get deploy,svc,ep,endpointslices
kubectl get pod -owide
kubectl get svc,ep svc-clusterip
kubectl get endpointslices -l kubernetes.io/service-name=svc-clusterip
kubectl get endpointslices -l kubernetes.io/service-name=svc-clusterip -o yaml

# 접속 테스트를 수행할 클라이언트 파드 배포
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: netshoot-pod
spec:
  containers:
  - name: netshoot-pod
    image: nicolaka/netshoot
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0
EOF

# 확인
kubectl get pod -owide

테스트 파드(netshoot-pod)에서 ClusterIP 접속 시 부하분산 확인
> AZ(zone) 상관없이 랜덤 확률 부하분산 동작

# 디플로이먼트 파드가 배포된 AZ(zone) 확인
kubectl get pod -l app=deploy-websrv -owide

# 테스트 파드(netshoot-pod)에서 ClusterIP 접속 시 부하분산 확인
kubectl exec -it netshoot-pod -- curl svc-clusterip | grep Hostname
Hostname: deploy-echo-7f67d598dc-h9vst

kubectl exec -it netshoot-pod -- curl svc-clusterip | grep Hostname
Hostname: deploy-echo-7f67d598dc-45trg

# 100번 반복 접속 : 3개의 파드로 AZ(zone) 상관없이 랜덤 확률 부하분산 동작
kubectl exec -it netshoot-pod -- zsh -c "for i in {1..100}; do curl -s svc-clusterip | grep Hostname; done | sort | uniq -c | sort -nr"
  35 Hostname: deploy-echo-7f67d598dc-45trg
  33 Hostname: deploy-echo-7f67d598dc-hg995
  32 Hostname: deploy-echo-7f67d598dc-h9vst

Topology Mode(구 Aware Hint) 설정 후 테스트 파드(netshoot-pod)에서 ClusterIP 접속 시 부하분산 확인
> 같은 AZ(zone)의 목적지 파드로만 접속

  • svc의 endpoint(slice)에 Zone에 대한 hint 등록 > node의 zone label

# Topology Aware Routing 설정 : 서비스에 annotate에 아래처럼 추가
kubectl annotate service svc-clusterip "service.kubernetes.io/topology-mode=auto"

# 100번 반복 접속 : 테스트 파드(netshoot-pod)와 같은 AZ(zone)의 목적지 파드로만 접속
kubectl exec -it netshoot-pod -- zsh -c "for i in {1..100}; do curl -s svc-clusterip | grep Hostname; done | sort | uniq -c | sort -nr"
  100 Hostname: deploy-echo-7f67d598dc-45trg

# endpointslices 확인 시, 기존에 없던 hints 가 추가되어 있음 >> 참고로 describe로는 hints 정보가 출력되지 않음
kubectl get endpointslices -l kubernetes.io/service-name=svc-clusterip -o yaml
apiVersion: v1
items:
- addressType: IPv4
  apiVersion: discovery.k8s.io/v1
  endpoints:
  - addresses:
    - 192.168.3.13
    conditions:
      ready: true
      serving: true
      terminating: false
    hints:
      forZones:
      - name: ap-northeast-2c
    nodeName: ip-192-168-3-228.ap-northeast-2.compute.internal
    targetRef:
      kind: Pod
      name: deploy-echo-7f67d598dc-hg995
      namespace: default
      uid: c1ce0e9c-14e7-417d-a1b9-2dfd54da8d4a
    zone: ap-northeast-2c
  - addresses:
    - 192.168.2.65
    conditions:
      ready: true
      serving: true
      terminating: false
    hints:
      forZones:
      - name: ap-northeast-2b
    nodeName: ip-192-168-2-248.ap-northeast-2.compute.internal
    targetRef:
      kind: Pod
      name: deploy-echo-7f67d598dc-h9vst
      namespace: default
      uid: 77af6a1b-c600-456c-96f3-e1af621be2af
    zone: ap-northeast-2b
  - addresses:
    - 192.168.1.240
    conditions:
      ready: true
      serving: true
      terminating: false
    hints:
      forZones:
      - name: ap-northeast-2a
    nodeName: ip-192-168-1-225.ap-northeast-2.compute.internal
    targetRef:
      kind: Pod
      name: deploy-echo-7f67d598dc-45trg
      namespace: default
      uid: 53ca3ac7-b9fb-4d98-a3f5-c312e60b1e67
    zone: ap-northeast-2a
  kind: EndpointSlice
...
  • 만약 파드 갯수를 1개로 줄여서 같은 AZ에 목적지 파드가 없을 경우?
# 파드 갯수를 1개로 줄이기
kubectl scale deployment deploy-echo --replicas 1
# 동일 AZ일 경우 0 -> 1 시도
kubectl scale deployment deploy-echo --replicas 0
kubectl scale deployment deploy-echo --replicas 1

# 파드 AZ 확인 : 아래 처럼 현재 다른 AZ에 배포
kubectl get pod -owide
NAME                           READY   STATUS    RESTARTS   AGE   IP              NODE                                               NOMINATED NODE   READINESS GATES
deploy-echo-7f67d598dc-h9vst   1/1     Running   0          18m   192.168.2.65    ip-192-168-2-248.ap-northeast-2.compute.internal   <none>           <none>
netshoot-pod                   1/1     Running   0          66m   192.168.1.137   ip-192-168-1-225.ap-northeast-2.compute.internal   <none>           <none>

# 100번 반복 접속 : 다른 AZ이지만 목적지파드로 접속됨!
kubectl exec -it netshoot-pod -- zsh -c "for i in {1..100}; do curl -s svc-clusterip | grep Hostname; done | sort | uniq -c | sort -nr"
  100 Hostname: deploy-echo-7f67d598dc-h9vst


ssh ec2-user@$N1 sudo iptables -v --numeric --table nat --list KUBE-SERVICES

# 아래 3개 노드 모두 SVC에 1개의 SEP 정책 존재
ssh ec2-user@$N1 sudo iptables -v --numeric --table nat --list KUBE-SVC-KBDEBIL6IU6WL7RF
Chain KUBE-SVC-KBDEBIL6IU6WL7RF (1 references)
 pkts bytes target     prot opt in     out     source               destination
  100  6000 KUBE-SEP-XFCOE5ZRIDUONHHN  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* default/svc-clusterip:svc-webport -> 192.168.2.65:8080 */

ssh ec2-user@$N2 sudo iptables -v --numeric --table nat --list KUBE-SVC-KBDEBIL6IU6WL7RF
Chain KUBE-SVC-KBDEBIL6IU6WL7RF (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 KUBE-SEP-XFCOE5ZRIDUONHHN  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* default/svc-clusterip:svc-webport -> 192.168.2.65:8080 */

ssh ec2-user@$N3 sudo iptables -v --numeric --table nat --list KUBE-SVC-KBDEBIL6IU6WL7RF
Chain KUBE-SVC-KBDEBIL6IU6WL7RF (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 KUBE-SEP-XFCOE5ZRIDUONHHN  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* default/svc-clusterip:svc-webport -> 192.168.2.65:8080 */

# endpointslices 확인 : hint 정보 없음
kubectl get endpointslices -l kubernetes.io/service-name=svc-clusterip -o yaml

결과

  • Topology Aware Routing 설정 없음
    • 무작위 AZ간 부하분산
  • Topology Aware Routing 설정 있음 / 대상 목적지 존재
    • 동일한 AZ의 노드로만 요청 전달
    • endpointslice에 zone hint 추가
  • Topology Aware Routing 설정 있음 / 대상 목적지 없음
    • 타 AZ로 요청 전달
    • endpointslice에 zone hint 없음

11. 스터디 종료 소회

정말로 매웠던 약 2달간의 k8s 네트워크 스터디(매운맛)과정이 끝났습니다.

업무상으로 k8s를 사용하다보면 주 관심사는 k8s 위의 작동이였습니다. k8s위에 어떻게 워크로드를 올릴까, 외부/내부 연결 및 제한은 어떤식으로 할까, 모니터링 및 로깅은 어떤식으로 할까, 등등등..

그러다보니 k8s 아래의 작동은 자주 살펴볼일이 없었습니다. 기본적으로 잘 작동하기도하고, 아랫단에서 문제가 있었던적은 드물었기 때문이죠.

다만 언젠가는 알아둬야한다고 항상 마음의 짐이였는데, 이번 스터디를 통해서 그 일부를 달성하여 매우 뿌듯합니다.

  • k8s namespace와는 다른 노드(linux)의 namespace 확인 및 제어
  • 노드수준의 iptables 규칙 확인과 NIC별 tcpdump 체크 실습
  • 다양한 CNI(Flannel, Calico, Cilium, AWS VPC CNI 등)에 대한 구체적인 동작확인
    • 항상 ‘Flannel은 간단하고, Cilium은 eBPF 기반이라 보이지않는다’는 말을 실감을 못했는데 이제는 최소한 어떤느낌인지 이해는 할 수 있습니다


다만 처음 마음가짐과 달리 중간부터, 많이 친숙하지않은 Cilium과 istio 부터는 스터디 과제하기만으로도 급급해서.. 좀더 많은 공부가 필요합니다. 그래도 무엇을 어떻게 공부할지 방향성은 잡은것 같아서 매우 뿌듯합니다.

스터디를 이끌어주신 가시다님과 조력자분들, 그리고 경험공유해주신 발표자분들에게 큰 감사를 드리면서 마무리하겠습니다. 감사합니다!

Leave a Reply