AEWS 2회차는 EKS Networking에 대한 내용을 다룬다.
지난 kOps Study 때 학습, 실습한 내용들과 중복되는 내용들이 꽤 많았어서 이해하기가 수월했다. (물론 실습은 별개의 난이도지만…ㅎㅎ)
이번 실습부터는 가시다님이 제공해주시는 one click 배포 스크립트를 활용해서 진행할 예정이다.
0. 환경 구성
앞서 밝힌 것과 같이 이번 실습부터는 가시다님이 제공해주신 One Click 배포 스크립트를 활용해서 환경 구성을 진행한다.
아래 스크립트를 참고하면 여러분들도 충분히 배포할 수 있다. 물론 배포 이전에 keypair는 생성해둬야 한다.
# YAML 파일 다운로드
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/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
Cloudformation이 배포되는 시간 동안 해당 yaml 파일이 어떤 내용을 담고있는지 확인해보도록 한다.
1개의 VPC, 각각 3개의 Public/Private Subnet, IGW의 네트워크 자원이 생성된다.
그리고 EKS Cluster가 생성되고 밑에 Node Group 1개와 3개의 Worker Node가 생성된다. Control Plane은 지난 회차에서 설명한 것처럼 별도로 EC2로 생성되지 않기 때문에 Worker Node 3개만 생성 된다. 거기에 추가로 작업을 위한 용도로 Bastion EC2가 생성된다.
Bastion EC2에는 kubectl, helm, eksctl, awscli, krew 등이 설치된다.
일정 시간이 지나면 아래와 같이 Cloudformation 배포가 완료되고 Node 3개가 올라온 것도 확인할 수 있다.
문제 없이 배포가 완료되었다면 아래 스크립트를 참고해서 Bastion EC2에 SSH 접속을 하고 설치 정보를 확인한다.
# CloudFormation 스택 배포 완료 후 작업용 EC2 IP 출력
aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text
# 마스터노드 SSH 접속
ssh -i ~/.ssh/<My SSH Keyname>.pem ec2-user@$(aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text)
1. AWS VPC CNI?
k8s Network에 대한 기본적인 내용은 지난 스터디였던 kOps 스터디 때의 Network 내용과 유사한 점이 많아 kOps 스터디 때 정리했던 내용으로 대체한다.
2. Pod 생성 수량 변경 (진행 중)
kOps에서도 Pod 수량 변경은 진행했었으나 EKS에서 한 번 더 진행해보도록 한다.
우선 Pod 최대 수량 계산식을 사용해 사용 가능 수량을 계산한다.
Worker Node의 EC2 Instance Type 별로 Pods 생성 수량은 제한 된다. Pods 생성 수량 기준은 인스턴스에 연결 가능한 ENI 숫자와 할당 가능한 IP 수에 따라 결정되게 된다.
aws-node, kube-proxy Pods는 Host의 IP을 같이 사용하기 때문에 배포 가능 최대 수량에서 제외 된다. (IP 조건에서 제외되기 때문에)
Pods 최대 생성 수량 계산 : {Instance에 연결 가능한 최대 ENI 수량 x (ENI에 할당 가능한 IP 수량 – 1)} + 2
위 식을 계산하기 위해서는 내가 사용하는 Node Instance Type의 ENI 및 IP 할당 제한을 확인할 필요가 있는데 그럴 때는 아래와 같이 확인 할 수 있다.
Values=t3.* 부분을 확인하고 싶은 Instance Type으로 변경하면 된다.
aws ec2 describe-instance-types --filters Name=instance-type,Values=t3.* \
--query "InstanceTypes[].{Type: InstanceType, MaxENI: NetworkInfo.MaximumNetworkInterfaces, IPv4addr: NetworkInfo.Ipv4AddressesPerInterface}" \
--output table
내가 사용하는 Instance Type은 t3.medium이고 해당 Instance의 경우 연결 가능한 ENI는 3개이고 ENI당 할당 가능한 IP는 6개이다. 위 계산식에 대입하면 {3 x (6-1)} + 2가 되고 aws-node, kube-proxy을 제외하면 총 15개의 Pods를 생성할 수 있게 된다.
Pods 생성 수량(Replicas)을 늘리면서 Worker Node의 ENI 정보와 IP 주소에 어떤 변화가 있는지 확인해보겠다.
우선 어떠한 Pods도 배포하지 않았을 때 Worker Node 1의 ENI/IP 정보와 pods 정보 화면이다.
eth0으로 192.168.1.x/24만 표현되는 것을 알 수 있다.
테스트용 Pod을 배포했다. 해당 배포는 Pod Replicas가 2개로 설정되어 있다.
Worker Node 1에 eth1이 새로 생기고 eni가 1개 더 추가된 것을 확인할 수 있다. 그리고 Pod 정보에 2개의 Pod가 각각 Worker Node에서 사용하는 IP 대역을 활용하여 배포된 것을 볼 수 있다.
여기서 Replicas를 8개로 추가해보았다. Worker Node 1에 eni가 2개 더 추가됐고 pod 정보에 8개의 Pod가 올라온 것을 확인할 수 있다. 여기서는 Replicas가 8개라 기존 2개일 때는 Worker Node 1, 2에 각각 1개씩 Pod가 배포됐지만 지금은 Worker Node 1~3에 골고루 배포가 된 것을 알 수 있다.
30개 까지로 올렸을 때도 문제 없이 Pod가 잘 올라오는 것을 확인할 수 있다. 그리고 eth2가 추가된 것도 확인할 수 있었다.
여기서 Replicas를 50개로 늘려보았다. 7개의 Pod가 배포되지 않고 Pending 상태로 머물러 있는 것을 확인할 수 있었다. 배포 되지 않은 오류 원인을 확인해보면 Too many Pods라고 나온다. 위에서 설명한 EC2에서 받아들일 수 있는 최대 Pod 수를 초과했기 때문이다.
여기서 의문이 생긴다. 위에서 계산한 공식에 따르면 1대의 EC2 Worker Node에서는 17개의 Pod 생성이 가능하다. 그런데 왜 43개밖에 생성이 되지 않았을까? 15*3은 45개이기 때문에 45개가 배포되어야 하는데 말이다.
이 의문을 확인하기 위해 Worker Node에서 내가 방금 배포한 Pod 말고 사용 중인 Pod가 있는지 확인해보도록 한다.
kube-proxy, aws-node이외에도 coredns를 2개의 Node에서 사용하고 있는 것을 확인할 수 있다. 그렇기 때문에 총 45-2=43개의 Pod가 배포 된 것이다.
그럼 이 제한 수량을 초과하는 Pod는 배포할 수 없을까? 아니다 배포가 가능하다.
Prefix Delegation 방식을 사용해서 MAX IP 제한 수를 해제할 예정이다.
현재 설정이 어떻게 되어있는지 확인하기 위해 aws-node의 정보를 yaml로 출력해 spec.containers.env를 확인해봤다. ENABLE_PREFIX_DELEGATION 항목이 false로 되어있는 것을 확인할 수 있다. 이 부분을 true로 바꿔주면 PREFIX DELEGATION을 활성화할 수 있다.
ENABLE_PREFIX_DELEGATION 활성화를 위해 kubectl set env 명령어를 사용해서 true로 바꿔주고 rollout을 해준다. 그 뒤에 다시 aws-node containers 값을 확인했을 때 true로 변경된 것을 확인할 수 있었다.
kubectl set env daemonset aws-node -n kube-system ENABLE_PREFIX_DELEGATION=true
kubectl rollout restart ds aws-node -n kube-system
ENABLE_PREFIX_DELEGATION이 true로 잘 설정됐다면 IPv4 Prefix 정보가 /28로 나뉘어져 있는 2개의 Prefix 있는 것을 확인 할 수 있다.
여기까지 진행하면 Pod 배포 수량 제한이 해제되어야 하는데 해제가 되지 않는 상황이다. 여기저기 확인해봐도 확인 방법을 알 수가 없어서 이 부분은 차후에 다시 진행할 예정이다.
3. AWS LoadBalancer Controller
Kubernetes의 Network Service에는 ClusterIP, NodePort, Loadbalancer Type이 있다.
ClusterIP : Control Plane의 iptables을 이용하여 내부에서만 접근 가능한 방식으로 외부에서의 접근은 불가능하다.
NodePort : 위 ClusterIP는 외부에서 접근이 불가능하기 때문에 외부에서 접근 가능하게 하기 위해서 Worker Node의 Port을 맵핑하여 통신할 수 있도록 한다.
Loadbalancer : NodePort 방식과 마찬가지로 외부에서 접근할 수 있는 방식이다. 다만, Worker Node의 Port을 통해 접근하는 방식이 아닌 앞단에 위치한 LoadBalancer을 통해 접근하게 된다.
Loadbalancer Controller : Public Cloud을 사용할 경우 각 CSP에서 제공하는 Loadbalancer을 사용하기 위해 설치하는 Controller이다. 이를 통해 CSP의 LB을 제어할 수 있게 된다.
AWS Loadbalancer Controller을 사용하기 위해 AWS LB Controller 배포를 진행한다.
# 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 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
# 생성된 IAM Policy Arn 확인
aws iam list-policies --scope Local
aws iam get-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy
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 \
--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
## 설치 확인
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
Loadbalancer Service Account가 제대로 생성된 것을 확인할 수 있다. 이후 nlb 테스트 용 pod을 배포해서 부하분산이 되는지 테스트를 진행해본다.
# 모니터링
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 | yh
kubectl apply -f echo-service-nlb.yaml
# 확인
kubectl get deploy,pod
kubectl get svc,ep,ingressclassparams,targetgroupbindings
kubectl get targetgroupbindings -o json | jq
# AWS ELB(NLB) 정보 확인
aws elbv2 describe-load-balancers | jq
aws elbv2 describe-load-balancers --query 'LoadBalancers[*].State.Code' --output text
# 웹 접속 주소 확인
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
부하분산 테스트를 진행해보니 54:46으로 비교적 정교하게 부하분산이 된 것을 확인할 수 있었다.
Replicas를 3개로 변경한 뒤 테스트를 진행해보니 완전 균등은 아니여도 적당하게 부하분산이 되는 것을 알 수 있다. 아마 보다 많은 트래픽을 넣다보면 좀 더 정교하게 부하분산이 되지 않을까 생각 된다.
4. Ingress
Ingress는 위에 Network Service에서 소개 한 ClusterIP, NodePort, LB을 HTTP/HTTPS 방식으로 외부에 노출하는 Web Proxy 역할을 말한다.
위 3번에서 설치한 Load Balancer Controller와 ALB의 조합으로 실습을 진행할 수 있다.
예전 스터디 때부터 자주 쓰던 배포 샘플인 game-2048을 이용해서 배포를 진행한다.
# 게임 파드와 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
배포가 잘 된 것을 확인했으니 ALB URL을 확보하여 테스트를 진행해본다.
이전 스터디 때 익숙하게 봐왔던 화면이 보이는 것을 확인할 수 있다. Ingress는 결국 어려운 개념이 아니라 뒷단에 있는 pod들의 서비스를 ALB에서 HTTP/HTTPS로 사용자에게 보여주는 방식이라고 보면 될 것 같다.
5. 정리
이번 실습 중에서 Pod 제한을 해제하는 건 현재 해결하지 못했다.
뭔가 어떤 부분에서 틀어막혀 있는 것 같은데… 이 부분은 여기저기 좀 물어봐서 해결을 해야할 것 같다. 그리고 External DNS의 경우에는 현재 도메인을 관리하는 Route53이 다른 Account에 있기 때문에 이 R53을 현재 실습 중인 Account에서 컨트롤 할 수 있는 방안을 찾아서 다음 실습에 배포되는 서비스에 적용하여 사용할 예정이다.