
들어가며
이번 주차에는 EKS Networking에 대해서 학습했다. 이전 PKOS 스터디에서도 네트워크 부분이 많이 어려웠는데, 실습과 과제를 통해서 개념을 한번더 다잡아 보는 시간이 되길 바라며 포스팅을 시작한다.
실습환경 세팅
2주차부터는 Amazon EKS를 CloudFormation을 이용하여 배포한다.
스터디 그룹장인 가시다님께서 작성하신 CloudFormation 파일은 1.24버전의 Kubernetes를 사용하는데, 실습의 애플리케이션과 addon의 호환성을 고려한 것이라고 한다.
또한 애플리케이션이 3개의 카피로 동기화 되는 경우가 있기 때문에 AZ가 3개로 구성하여 프로덕션 환경에 맞춰서 온디맨드 노드로 구성했다고 한다. kube-proxy, coredns, aws vpc-cni와 같은 addon 역시 기본적으로 설치해 놓으셔서 수월하게 실습을 진행할 수 있었다.
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/eks-oneclick.yaml
(CloudFormation 스크립트는 제대로 한번 더 분석해서 향후 실무 적용시 커스텀 해보려고 한다.)
설치가 잘되고 나면 CloudFormation이 잘 되었는지 확인해볼 수 있다.
# 클러스터 정보 조회
kubectl cluster-info
eksctl get cluster
eksctl get nodegroup --cluster $CLUSTER_NAME
# CloudFormation으로 설정된 환경변수 정보 확인
# (할당되는 정보는 설치 스크립트에서 변수 형태로 자동으로 가져온다.)
export | egrep 'ACCOUNT|AWS_|CLUSTER|KUBERNETES|VPC|Subnet' | egrep -v 'SECRET|KEY'

# 노드 정보 확인
kubectl get node --label-columns=node.kubernetes.io/instance-type,eks.amazonaws.com/capacityType,topology.kubernetes.io/zone
eksctl get iamidentitymapping --cluster myeks
# krew 플러그인 확인
kubectl krew list

# 노드 IP 확인 및 PrivateIP 변수 지정
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
N1=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2a -o jsonpath={.items[0].status.addresses[0].address})
N2=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2b -o jsonpath={.items[0].status.addresses[0].address})
N3=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2c -o jsonpath={.items[0].status.addresses[0].address})
echo "export N1=$N1" >> /etc/profile
echo "export N2=$N2" >> /etc/profile
echo "export N3=$N3" >> /etc/profile
echo $N1, $N2, $N3
# 보안그룹 ID와 보안그룹 이름 확인
aws ec2 describe-security-groups --query 'SecurityGroups[*].[GroupId, GroupName]' --output text
y "SecurityGroups[*].[GroupId]" --output text
# 노드 보안 그룹에 eksctl-host에서 노드(파드)에 접속 가능하게 룰 설정
NGSGID=$(aws ec2 describe-security-groups --filters Name=group-name,Values=*ng1* --query "SecurityGroups[*].[GroupId]" --output text)
echo $NGSGID
aws ec2 authorize-security-group-ingress --group-id $NGSGID --protocol '-1' --cidr 192.168.1.100/32
# 핑 테스트 및 각각의 워커노드에 접속되는지 확인
ping -c 2 $N1
ping -c 2 $N2
ping -c 2 $N3
ssh -o ec2-user@$N1 hostname
ssh -o ec2-user@$N2 hostname
ssh -o ec2-user@$N3 hostname



클러스터가 정상적으로 설치되었음을 확인한 후 AWS Console에서 설치됨 Addon 정보를 확인할 수 있다. 배포 스크립트가 addon 형태로 kube-proxy, CoreDNS, Amazon VPC CNI를 설치하기 때문이다. 버전들이 잘 설치되어있는지 확인도 할 수 있다.

다음의 명령어를 통해 쿠버네티스 버전에 따라 지원하는 플러그인 정보도 확인할 수 있는데, 가시다님 말씀대로 kubernetes version1.24에서 지원하는 플러그인이 최근 버전에 비해 훨씬 많은 것도 확인해 볼 수 있었다.

AWS VPC CNI
이제부터가 본격적인 세미나 내용이다. 세미나 내용을 이해하기 위해서는 두개의 키워드를 알아야 한다.
- k8s CNI(Container Network Interface)
Kubernetes에서 컨테이너 네트워크를 관리하기 위한 표준 인터페이스로써 인터페이스에 맞는 여러가지 플러그인들이 있다. 그중 스터디에 다룬 플러그인은 AWS에서 kubernetes CNI 규격에 맞게 제공하는 AWS VPC CNI이다. - AWS VPC CNI(Amazon Web Service Virtual Private Cluster Container Network Interface)
AWS VPC CNI는 Kubernetes 컨테이너 네트워크에 대한 보안, 관리, 성능 등을 고려하여 설계된 CNI 플러그인이다.
이전 PKOS 스터디에서 학습 했던 내용처럼 AWS VPC CNI는 클러스터 내의 모든 Pod와 통신할 때 오버레이 네트워크를 거치지 않고 직접 통신이 가능하다. 이는 VPC ENI에 미리 할당된 IP를 파드에서 사용할 수 있기 때문이다.
VPC CNI 플러그인은 노드에서 ENI를 관리한다. 노드가 프로비저닝 될 때 CNI 플러그인은 노드의 서브넷에서 기본 ENI로 슬롯 풀을 자동으로 할당한다. 이 크기는 Node의 인스턴스 타입에 따라 다르다. (관련 내용은 아래에서 설명을 하도록 한다.)
기본 ENI 슬롯이 할당되면 CNI는 추가 ENI를 노드에 연결할 수 있는데, 이 떄 추가되는 ENI는 보조 ENI라고한다. 이 ENI 역시 인스턴스 유형에 따라 특정 수의 슬롯(로컬 IP 할당 개수)이 있다.
CNI가 동작하는 매커니즘은 아래의 플로우 차트와 같다.

플로우 차트를 간략히 설명하면 CNI는 Pod에 로컬 private IP를 할당할 때 Primary ENI에서 할당된 슬롯을 사용할 수 없으면 Secondary ENI 에서 사용할 수 있는 IP를 찾는다. Secondary ENI에도 사용할 수 있는 IP가 없으면 가용범위 내에서 새로운 ENI를 생성하여 IP를 할당한다.
AWS VPC CNI Plugin의 공식 설명에 보면 각 kubernetes 노드(ec2 인스턴스)에 대해 기본 ENI와 보조 ENI를 생성하고 보조 IP 주소를 할당한다는 내용이 나온다. 그리고 이 보조 IP 주소는 쿠버네티스 node IP 대역과 동일하게 설정되어 있다.
이것을 관리하는 것은 L-IPAM이라는 로컬 IP 주소 관리자다. L-IPAM은 노드마다 DaemonSet으로 실행되어있고, kubelet이 ADD Pod 요청을 수신할 때 CNI 플러그인을 통해 즉시 웜풀에서 사용가능한 보조 UP 주소를 하나 가져와 Pod에 할당한다.

하지만 EC2 인스턴스 종류 별로 ENI 할당 개수가 정해져 있기 때문에 Pod를 무한정 생성할 수 없다.
(다음의 주소에서 인스턴스 유형별 ENI 최대 개수와 Private IP 주소를 확인할 수 있다.)
참고: https://docs.aws.amazon.com/ko_kr/AWSEC2/latest/UserGuide/using-eni.html
탄력적 네트워크 인터페이스 - Amazon Elastic Compute Cloud
이 페이지에 작업이 필요하다는 점을 알려 주셔서 감사합니다. 실망시켜 드려 죄송합니다. 잠깐 시간을 내어 설명서를 향상시킬 수 있는 방법에 대해 말씀해 주십시오.
docs.aws.amazon.com
Follow Node 별로 위에서 언급한 제한없이, Private IP 개수를 늘려 운영 가능한 Pod 수를 늘이기 위해서는 아래의 세가지 방법을 사용할 수 있다.
첫 번째 방법은 ENI 최대 개수가 많은 인스턴스 유형을 사용하는 것이다. ENI 갯수와 IP 슬롯 수를 조합해서 더 많은 Pod를 이용할 수 있다. 최대 Pod 생성 개수는 다음과 같이 산정된다.
(ENI 갯수 * (ENI에서 제공하는 Private IP Address 개수 - 1)) + 2개

두 번째 방법은 IPv4 Prefix Delegation을 이용하는 방법으로 IPv4 28bit 서브넷을 위임하여 할당 가능한 IP 수와 인스턴스 유형에 권장하는 최대 갯수로 선정하는 방법이다. 워크샵에서 언급된 Prefix Deletegation 모드를 활성화 하는 방법은 kOps기반 Kubernetes Cluster에서 적용했던 방식과 비슷하게, aws-node의 환경설정을 변경하여 세팅 가능하다.
kubectl set env daemonset aws-node -n kube-system ENABLE_PREFIX_DELEGATION=true
참고: https://trans.yonghochoi.com/translations/aws_vpc_cni_increase_pods_per_node_limits.ko
Amazon VPC CNI 플러그인으로 노드당 파드수 제한 늘리기
When a node is started in their cluster, IPAMD will allocate 2 prefixes (32 IP address) to the primary ENI (this user is not using CNI custom networking, so only the primary ENI is used in this example) to satisfy the MINIMUM_IP_TARGET of 25. Now 25 pods g
trans.yonghochoi.com


세 번째 방법은 AWS VPC CNI Custom Networking을 이용한 벙법으로 노드와 파드의 대역을 분리하고, 파드에 별도 서브넷을 부여한 후 사용하는 방법이다.
지기님께서 블로그에서 설명해 주신 것처럼 EKS Cluster 생성 시 aws-node DaemonSet의 AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG를 활성화 해주고, Pod 할당용 서브넷을 생성한 다음, 이를 ENIConfig Custom 리소스로 등록해 주는 것이다.
실습 1 - 기본 네트워크 정보 확인
# CNI 정보 확인
kubectl describe daemonset aws-node --namespace kube-system | grep Image | cut -d "/" -f 2
amazon-k8s-cni-init:v1.12.6-eksbuild.1
amazon-k8s-cni:v1.12.6-eksbuild.1
# kube-proxy가 iptables 사용하는지 확인
kubectl describe cm -n kube-system kube-proxy-config | grep mode
...
mode: "iptables"
...
# 노드 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 | wc -l
# 파드 이름 확인
kubectl get pod -A -o name

실습 2 - Node의 네트워크 정보 확인
# 노드에 툴 설치
ssh ec2-user@$N1 sudo yum install links tree jq tcpdump -y
ssh ec2-user@$N2 sudo yum install links tree jq tcpdump -y
ssh ec2-user@$N3 sudo yum install links tree jq tcpdump -y
# CNI 정보 확인
ssh ec2-user@$N1 tree /var/log/aws-routed-eni
ssh ec2-user@$N1 cat /var/log/aws-routed-eni/plugin.log | jq
ssh ec2-user@$N1 cat /var/log/aws-routed-eni/ipamd.log | jq
ssh ec2-user@$N1 cat /var/log/aws-routed-eni/egress-v4-plugin.log | jq
# 네트워크 정보 확인 : eniY는 pod network 네임스페이스와 veth pair
# 워커 노드 1번의 정보 확인
ssh ec2-user@$N1 sudo ip -br -c addr
ssh ec2-user@$N1 sudo ip -c addr
# 워커 노드의 IP 정보 확인
ssh ec2-user@$N1 sudo ip -c route
# IP tables 룰 확인
ssh ec2-user@$N1 sudo iptables -t nat -S
ssh ec2-user@$N1 sudo iptables -t nat -L -n -v

/var/log/aws-routed-eni/ipamd.log 를 확인하면 IPs/Prefixes 정보와 할당된 IP가 1개 Cooldown IP가 0개, IP 슬롯 개수가 5개 등의 정보를 확인할 수 있다.

워커 노드 1번 정보를 확인해 보면 네트워크 인터페이스 2개가 있고, Pod가 IP를 할당 받은 것도 확인할 수 있다.
실습 3 - 보조 IPv4 주소를 파드가 사용하는지 확인하는지 확인
kubectl get pod -n kube-system -l k8s-app=kube-dns -owide


웜풀의 보조 프라이빗 IP를 사용하는 것으로 확인된다. 보조 IP가 10개 인것은 ENI가 2개만 할당되어 있기 때문이다.
# coredns 파드 IP 정보 확인
kubectl get pod -n kube-system -l k8s-app=kube-dns -owide
# 노드의 라우팅 정보 확인
ssh ec2-user@$N1 sudo ip -c route
ssh ec2-user@$N2 sudo ip -c route
ssh ec2-user@$N3 sudo ip -c route
Node의 라우팅 정보와 coredns 파드의 보조 Private IP 주소를 보면 동일하게 설정되어 있는것을 알 수 있다.

실습 4 - 테스트용 파드가 생성될 때 ENI가 자동으로 생성되는지 확인
워커노드 2번에는 Pod가 생성되지 않아 ENI가 하나밖에 없는 상황이다.

이 상태에서 테스트용 Pod를 만들게 되면 ENI가 생기게 된다. 다음과 같이 테스트 Pod를 만들고, 자동으로 ENI가 추가되는지 확인해 볼 수 있다.
다음과 같이 netshoot Pod를 배포한다.
cat <<EOF | kubectl create -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

진짜 2번 노드에 ENI가 새로 생겼다. 2번 Node로 접속하여 Node의 파드 정보를 확인해 보면 다음과 같이 Node의 IP와 동일한 IP인 192.168.2.106으로 할당된 것을 확인할 수 있다.
# 마지막 생성된 네임스페이스 net PID 정보 출력 -t net(네트워크 타입)를 변수 지정
MyPID=$(sudo lsns -o PID,COMMAND -t net | awk 'NR>2 {print $1}' | tail -n 1)
sudo nsenter -t $MyPID -n ip -c addr

# Node2의 Pod2에 접속
PODNAME2=$(kubectl get pod -l app=netshoot-pod -o jsonpath={.items[1].metadata.name})
kubectl exec -it $PODNAME2 -- zsh
# Node2의 Pod의 네트워크 정보 확인
ip -c addr
# Ping test
ping -c 1 192.168.1.26 # Node1의 IP!!
Node 2의 Pod에서 Node 1과 통신이 잘되는지 ping을 통해 확인해 보았다.

실습 5 - 서로 다른 Node에 배포된 Pod간 통신 (오버헤드 없이 통신이 된다!!!)
AWS VPC CNI 플러그인으로 서로 다른 Node에 배포된 Pod간 통신을 할때는 아래와 같은 형태로 통신하게 된다. 이 때 오버레이 통신 기술 없이 파드간 직접 통신을 할 수 있다. 이를 확인하기 위해 각각의 ENI에서 TCP Dump를 이용하여 패킷을 캡쳐해서 실제로 VPC Native하게 통신하는지 ping을 날려 확인해 보는 실습을 진행했다.

이 실습을 진행할 때 패킷이 전달되는 동작은 아래의 시퀀스 다이어그램과 같다.

간단히 말하자면 ping 패킷이 전달되는 순서는 pod의 eth0 → node 1의 veth-1 → node 1의 eth0(외부로 빠져나가는) → Node 2의 eth0(외부에서 내부로 들어오는) → Node 2의 veth-1 → pod의 Eth0 순서다.
이를 확인하기 위해 아래처럼 실습을 진행했다.
# 파드 이름 변수 지정
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[1].status.podIP})
# 파드1 Shell 에서 파드2로 ping 테스트
kubectl exec -it $PODNAME1 -- ping -c 2 $PODIP2
# 노드 1, 노드 2 tcp 덤프 확인
sudo tcpdump -i any -nn icmp

Pod의 IP로 통신된 것을 확인할 수 있다. Calico나 다른 CNI Plugin들 과 같이 노드의 IP를 가지고 캡슐화를 통한 오버헤드가 발생하지 않는다.
다음으로는 Node 1의 Pod1 에서 Node2의 Pod 2로 통신이 잘 되는지 확인해본다.
# 파드1 Shell 에서 파드2로 ping 테스트
kubectl exec -it $PODNAME1 -- ping -c 2 $PODIP2
# iptables 의 라우팅 정책 확인
ip route show table main
# defalut ENI에서 패킷을 확인
sudo tcpdump -i eth0 -nn icmp


라우팅 테이블의 정책에 따라 Pod의 defalut ENI를 통해 통신하는 것 역시 확인했다.
(실습에서는 잘되는데..세미나에서는 eth1으로 통신이 되었다.. 이유가 확인하고 다시 업데이트 하겠다.. 여기까지도 너무 어렵네요 ㅠ..)
실습 6 - Pod에서 외부 통신
VPC CNI의 External source network address translation (SNAT) 설정에 따라 외부 통신 시 SNAT 또는 SNAT 없이 통신이 가능하다.
Pod에서 외부로 통신할때는 Masquerading 되어서 나간다. 하지만 SNAT을 빼서 통신할 수 도 있다.
# 작업용 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
# 작업용 EC2 : pod-1 Shell 에서 외부 접속 확인 - 공인IP는 어떤 주소인가?
## The right way to check the weather - 링크
kubectl exec -it $PODNAME1 -- curl -s ipinfo.io/ip ; echo
구글에 Ping을 날리면 node IP로 Masquerading 되어 통신이 되는 것을 확인할 수 있다.

ipinfo.io/ip를 통해 외부 주소를 확인해보면 현재 Node와 동일한 Public IP를 사용하는 것을 알 수 있다. Masquerading이 된 것이다.


세미나에서 잠시 동작하지 않았던 날씨 정보 사이트도 복구가 되었는지 잘 통신이 되어 출력된다. ^-^

이렇게 통신이 되는 이유는 다음과 같은 라우팅 정책 때문인데, 다음과 같이 노드의 라우팅 정보를 확인해보면 알 수 있다.
# SNAT-CHAIN 라우팅 룰 확인
sudo iptables -t nat -S | grep 'A AWS-SNAT-CHAIN'

클러스터 내부에서 통신이 될 경우에는 AWS-SNAT-CHAIN을 거치지 않고, 이 대역이 아니라면 SNAT CHAIN을 통해 Node의 IP를 이용하게 되는 것이다.
즉 Pod간 통신할 때는 SNAT을 거치지 않기 때문에 Pod의 IP로 통신이 되었던 것이다.
실습 7 - 노드에 파드 생성 갯수 제한
원래는 위에서 언급했던 ENI 제한 때문에 t3.mid에서 최대 할당 가능 IP가 15개라 Pod를 15개 밖에 배치할 수 없다.
다음과 같이 t3 타입의 MaxENI를 확인 할 수 있다.
# t3 타입의 정보(필터) 확인
aws ec2 describe-instance-types --filters Name=instance-type,Values=t3.* \
--query "InstanceTypes[].{Type: InstanceType, MaxENI: NetworkInfo.MaximumNetworkInterfaces, IPv4addr: NetworkInfo.Ipv4AddressesPerInterface}" \
--output table

예를들어, t3mid에서 pod를 50개 생성하면 IP가 모자라게 되고, Pod가 Pending상태가 된다.
# 작업용 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
# 파드 증가 테스트 >> 파드 정상 생성 확인, 워커 노드에서 eth, eni 갯수 확인 >> 어떤일이 벌어졌는가?
kubectl scale deployment nginx-deployment --replicas=50
# 디플로이먼트 삭제
kubectl delete deploy nginx-deployment


필자는 Prefix Delegation 을 사용하여 → Pod 갯수제한을 풀어보려다 실패했다.
# 플러그인 설정
kubectl set env daemonset aws-node -n kube-system ENABLE_PREFIX_DELEGATION=true
# 환경변수 설정 확인
kubectl describe daemonset -n kube-system aws-node | grep ENABLE_PREFIX_DELEGATION
#Kublet의 설정에서 maxPods를 200개로 설정
{
"kind": "KubeletConfiguration",
"maxPods": 200,
"apiVersion": "kubelet.c"..
}
# default Resource Limit을 최소화
apiVersion: v1
kind: LimitRange
metadata:
name: limit-range-1
namespace: default
spec:
limits:
- type: Pod
max:
cpu: 40m
memory: 24Mi
min:
cpu: 40m
memory: 24Mi
- type: Container
max:
cpu: 40m
memory: 24Mi
min:
cpu: 40m
memory: 24Mi
# Deployment 갯수 확인
watch 'kubectl get pods --field-selector=status.phase=Running | grep -c Running'


이전 kOps 클러스터에서 시도한 방법들과 Resouce Limit 설정까지 해봤지만 최대 43개 Pod만 생성되는 상황이다..
(추후 해결해서 내용을 업데이트하겠다ㅠ.. ㅂㄷㅂㄷ)
Kubernetes Service 와 AWS LoadBalancer Controller 개념
Kubernetes Service에 AWS LoadBalancer Controller를 적용하는 방법에 대해서도 배웠다. PKOS 스터디와 마찬가지로 ClusterIP, NodePort, Loadbalancer를 적용했을 때 각각에 대해서 학습했다.
ClusterIP
ClusterIP는 클러스터 내부 통신을 위한 서비스 유형으로, 클러스터 내부에서만 접근 가능한 IP주소를 할당한다. 클러스터 내부에서 다른 파드들이 특정 서비스에 액세스하려면 이 IP를 사용하여 통신한다.

NodePort
NodePort 서비스는 외부에서 클러스터 내의 서비스에 Port를 통해 접근할 수 있도록 방법을 제공한다. 각 노드에 할당된 고유한 포트 번호를 사용하여 서비스에 접근할 수 있다. NodePort 서비스는 내부적으로 앞서 설명한 Cluster IP 서비스를 기반으로 동작한다.
NodePort가 설정되면 Kubernetes는 먼저 기본 ClusterIP 서비스를 생성하고, 추가적으로 각 노드에서 외부 트래픽을 수신하기 위한 포트를 할당한다.
NodePort 서비스에서 트래픽이 노드에 도착하면 그림과 같이 iptables의 분산룰에 따라서 해당 Pod로 리다이렉트 시킨다.

LoadBalancer
로드밸런서 서비스는 클라우드 공급자의 로드 밸런서를 사용하여 서비스를 자동으로 노출한다. 이 서비스는 외부에서 클러스터 내의 서비스에 접근할 수 있는 로드밸런스된 IP 주소를 제공한다. 로드 밸런서는 들어오는 트래픽을 적절한 파드로 분산시킨다.
C/NLB등의 로드벨런서를 사용하게 되면 Node Port를 타고 해당 노드에 분산된 후 iptables의 분산룰에 따라 Pod로 통신한다.

AWS LoadBalancer Controller와 VPC CNI와 NLB
AWS LoadBalancer Controller와 VPC CNI와 NLB를 같이쓰면 외부의 트래픽을 Pod로 바로 전달할 수 있다.
Pod의 IP가 EC2 인스턴스처럼 동일한 대역에 있으므로, VPC CNI와 연결된 Pod로 연결을 시켜버린다. Node Port 서비스를 바로 통과함으로써 연산과 같은 부수적인 리소스를 사용하지 않기 때문에 빠르게 접근이 가능해진다. 장애 처리, 통신도 효율적이다.
Pod로 연결을 바로 할 수 있는 이유는 아래 그림과 같이 Pod가 할당한 IP 정보를 Kubernetes Load Balancer Controller 파드가 NLB로 Pod의 IP 정보를 제공하기 때문이다.

실습 7 - AWS LoadBalancer Controller 배포 with IRSA
Kubernetes가 AWS와 인증체계가 연결되어야한다. 인증체계를 위해 OIDC를 사용한다. 기본적으로 OIDC Provider 셋팅이 되있다.
(OIDC에 대한 자세한 내용은 추후 세미나 후반부에서 다시 확인해보도록 한다.)
# 설치된 OIDC 확인
aws eks describe-cluster --name $CLUSTER_NAME --query "cluster.identity.oidc.issuer" --output text

ALB를 LoadBalancer Controller가 제어할 수 있는 권한을 IAM에 추가해야한다.
# IAM Policy (AWSLoadBalancerControllerIAMPolicy) 생성
curl -o iam_policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.4.7/docs/install/iam_policy.json
aws iam create-policy --policy-name AWSLoadBalancerControllerIAMPolicy --policy-document file://iam_policy.json
정책이 생성되면 다음과 같이 AWS IAM Console 에서 확인이 가능하다.

다음으로는 AWS Load Balancer Controller를 위한 ServiceAccount를 생성한다. 자동으로 매칭되는 IAM Role을 CloudFormation으로 생성된다.
eksctl create iamserviceaccount --cluster=$CLUSTER_NAME --namespace=kube-system --name=aws-load-balancer-controller \
--attach-policy-arn=arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --override-existing-serviceaccounts --approve


SA가 생성되면 다음의 명령으로 확인할 수 있다.
kubectl get serviceaccounts -n kube-system aws-load-balancer-controller -o yaml | yh

다음으로는 헬름차트로 LoadBalancerController를 생성한다.
# 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

설치가 완료되면 다음과 같이 crd가 생성되고, aws 서비스를 kubernetes의 리소스처럼 관리할 수 있게 된다.
# 설치 확인
kubectl get crd
kubectl describe deploy -n kube-system aws-load-balancer-controller


권한을 확인해보면 pod의 상태나, ingress, nodes, pods등을 관리할 수 있도록 되어 있다.
이제 서비스를 배포하며 로드 밸런서가 정말 자동으로 생성되는지 확인해본다.
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/2/echo-service-nlb.yaml
cat echo-service-nlb.yaml | yh
kubectl apply -f echo-service-nlb.yaml

생성된 LoadBalancer 서비스를 확인해 보면 EXTERNAL-IP가 ~~~.elb.~~ 형태로 되어 있는것을 확인할 수 있다.

Console에서 로드 밸런서를 확인해보면 다음과 같이 로드밸런서가 생성되어 있는것을 확인할 수 있다.
여기서 Forward to target group를 확인해보면 두개의 IP가 등록된 것을 확인할 수 있다.

이것이 바로 로드밸런서에 Pod의 IP가 등록이 되어 있다는 것이다.

로드밸런서 접속 주소를 확인하여 접속하면 접속이 된다.
# 웹 접속 주소 확인
kubectl get svc svc-nlb-ip-type -o jsonpath={.status.loadBalancer.ingress[0].hostname} | awk '{ print "Pod Web URL = http://"$1 }'

또한 생성한 Pod의 replica를 조정하면 로드밸런서에 등록된 타겟 대상도 계속 변경되는 것을 확인할 수 있다.
Ingress + LoadBancer Controller
Ingress는 Web Proxy 형태로 HTTP/HTTPS만 지원한다. 다른 서비스들 처럼 Load Balancer Controller와 함께 사용하면 Port로 바로 통신 할 수 있다.

# 게임 파드와 Service, Ingress 배포
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/3/ingress1.yaml
cat ingress1.yaml | yh
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
NAME SERVICE-NAME SERVICE-PORT TARGET-TYPE AGE
k8s-game2048-service2-e48050abac service-2048 80 ip 87s
# Ingress 확인
kubectl describe ingress -n game-2048 ingress-2048
# 게임 접속 : 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


Ingress 역시 마찬가지로 Kubernetes의 deployment의 replica 갯수를 조정하면 자동으로 ALB 타겟 그룹에 등록되는 Pod 갯수가 조정된다.
마치며
이번 포스팅에서는 AWS VPC기반 EKS 클러스터에서 오버헤드없이 로드밸런서에서 Kubernetes cluster에 배포된 Pod에 Bypass 형태로 접속하는 방법에 대해서 배웠다. 또한 LoadBalancer Controller를 이용하여 EKS 서비스들을 Kubernetes의 자원처럼 사용하는 방법에 대해서 학습했다.
아무래도 저번 PKOS 스터디와 마찬가지로 네트워크 쪽의 용어들이 익숙하지 않아서 세미나 내용을 이해하는 것이 힘들었다. 하지만 처음 들었을 때보다 훨씬 이해할 수 있는 부분이 많아졌고, kOps때의 환경을 EKS에 옮기는 듯한 느낌을 받아서 수월했다.
또한 IPv4 Prefix Delegation을 EKS 클러스터에서 적용하는 도전과제를 해결하지 못한 것이 너무 아쉽다. 가시다 님께서 구축한 클러스터에는 Limitrange도 별도로 설정되어 있지 않았고, ENABLE_PRIFIX_DELEGATION 역시 활성화 시킨데다가, kubelet의 maxPods 설정도 200으로 늘렸음에도 불구하고 클러스터 3대로 50개의 Pod를 배포하지 못했다.
계속 시간을 할애하면 과제를 모두 수행하지 못할 것 같아서 우선 접었지만, 사례들을 좀 더 찾아보고 꼭 해결하고싶다.
'클라우드 컴퓨팅 & NoSQL > [AEWS] Amazon EKS 워크숍 스터디' 카테고리의 다른 글
[6주차] AEWS Amazon EKS 워크숍 스터디 (23.05.28) (3) | 2023.06.02 |
---|---|
[5주차] AEWS Amazon EKS 워크숍 스터디 (23.05.21) (0) | 2023.05.27 |
[4주차] AEWS Amazon EKS 워크숍 스터디 (23.05.14) (0) | 2023.05.20 |
[3주차] AEWS Amazon EKS 워크숍 스터디 (23.05.07) (2) | 2023.05.14 |
[1주차] AEWS Amazon EKS 워크숍 스터디 (23.04.23) (2) | 2023.04.30 |
- 들어가며
- 실습환경 세팅
- AWS VPC CNI
- 실습 1 - 기본 네트워크 정보 확인
- 실습 2 - Node의 네트워크 정보 확인
- 실습 3 - 보조 IPv4 주소를 파드가 사용하는지 확인하는지 확인
- 실습 4 - 테스트용 파드가 생성될 때 ENI가 자동으로 생성되는지 확인
- 실습 5 - 서로 다른 Node에 배포된 Pod간 통신 (오버헤드 없이 통신이 된다!!!)
- 실습 6 - Pod에서 외부 통신
- Kubernetes Service 와 AWS LoadBalancer Controller 개념
- 실습 7 - AWS LoadBalancer Controller 배포 with IRSA
- Ingress + LoadBancer Controller
- 마치며