AEWS Study #2 – EKS Networking

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 스터디 때 정리했던 내용으로 대체한다.

https://bs-yang.com/42

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에서 컨트롤 할 수 있는 방안을 찾아서 다음 실습에 배포되는 서비스에 적용하여 사용할 예정이다.

PKOS Study #2-2 – Kubernetes Network&Storage

PKOS Study #2-1에서 Kubernetes Network에 대한 기본적인 내용을 정리했다.

이번 2-2에서는 지난 문서에서 정리하지 못한 Loadbalancer에 대한 내용과 Storage 파트를 정리할 계획이다.

1. Pods 생성 수량 제한

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 포함하여 총 17개의 Pods를 생성할 수 있게 된다.

Pods 생성 수량(Replicas)을 늘리면서 Worker Node의 ENI 정보와 IP 주소에 어떤 변화가 있는지 확인해보겠다.

우선 어떠한 Pods도 배포하지 않았을 때 Worker Node 1, 2의 ENI 상태 화면이다. (위가 1, 밑이 2)

Replicas 수량이 2개인 Deployment을 생성했을 때 ENI와 IP 모습이다.

Pods가 2개 생성되어 각각 IP을 할당 받은 것을 확인 할 수 있다. 각 Pods는 서로 다른 Node에 배포 되었다는 것을 IP을 통해 확인 할 수 있다.

이번엔 Replicas을 8로 증가하여 보았다.

Pods가 8개로 증가하며 ENI 정보와 IP가 각각 증가한 것을 알 수 있다. 현재 Worker Node는 t3.medium으로 앞서 계산한 공식에 따르면 Pods을 15개까지 생성할 수 있다. Worker Node가 2개이니 30개까지 생성이 가능한지 확인해봤다.

30개로 증가하는 명령어 이후 Running 중인 Pods 수량을 확인하면 21개만 생성 된 것을 알 수 있다. 왜 30개가 아닌 21개인지 궁금하여 현재 Node에서 실행 중인 Pods들을 모두 조회하고 해당 Pods들이 사용 중인 IP들을 확인해보았다.

kubectl get pod --all-namespaces -o wide

현재 Deployment가 생성 된 namespace가 아닌 kube-system namespace에서 사용 중인 Pods들까지 확인할 수 있었다.

Node 1은 이미 5개의 IP을 할당 받아 사용 중이었고 Node 2는 4개의 IP을 할당 받아 사용 중이었다. 총 할당 가능한 15개의 IP 중 이미 4, 5개의 IP을 기존 시스템에서 사용 중인 상태라 30개의 Pods 중 9개가 부족한 21개의 Pods만 정상적으로 배포가 된 것을 확인할 수 있었다.

그럼 EC2 Instance Type에 종속되는 Pods 배포 수량을 극복할 수 있는 방안은 없을까? 아니다 Prefix Delegation 방식을 사용하면 Instance Type에 한정되지 않고 Pods을 배포할 수 있다.

자세한 내용은 아래 링크를 참고하면 된다.Amazon EC2 노드에 사용 가능한 IP 주소의 양 늘리기 – Amazon EKS관리형 노드 그룹은 maxPods의 값에 최대 수를 적용합니다. vCPU가 30개 미만인 인스턴스의 경우 최대 수는 110이고 다른 모든 인스턴스의 경우 최대 수는 250입니다. 이 최대 수는 접두사 위임의 활성docs.aws.amazon.com

나는 t3.medium Type을 Worker Node에 사용했고 해당 Node의 Allocatable을 확인하는 명령어를 통해 가용 가능 Pods을 확인해보았다. 앞서 계산한 바와 같이 17개임을 확인 할 수 있다.

50개의 Pods을 배포하는 명령어를 실행했을 때 21개만 배포가 되어있는 것을 확인할 수 있다. 이는 앞서 배포했을 때 확인할 수 있었던 수치와 동일하다.

설정값을 바꾸기 위해 kops edit cluster을 통해 내용을 수정하고 Rolling Update을 실행했다.

kops edit cluster
<em>#maxPods 수정</em>
  kubelet:
    anonymousAuth: false
    maxPods: 50
<em>#ENABLE_PREFIX_DELEGATION 수정</em>
  networking:
    amazonvpc:
      env:
      - name: ENABLE_PREFIX_DELEGATION
        value: "true"

<em>#Rolling Update 진행, 해당 과정에서 Node들의 IP 변경 발생하고 약 15~20분 정도 소요 된다.</em>
kops update cluster --yes && echo && sleep 5 && kops rolling-update cluster --yes

Rolling Update 이후 배포 가능 Pods 확인을 진행하였다.

그 후 다시 Replicas을 50으로 수정하여 배포를 진행하였다.

50개가 모두 실행 중인 것을 확인할 수 있었다. ENI 정보와 IP 정보를 조회했을 때도 해당 수량이 증가한 것을 알 수 있었다.

나는 실습 전에 CPU Limits을 제거하고 진행했기 때문에 원할하게 Pods들이 증가했으나 Network 제한만 푼다고 해서 Pods들이 많이 생성되지 않을 수 있다. 그럴 때는 아래와 같은 방법으로 CPU Limits을 해제하면 된다.

kubectl describe limitranges <em># LimitRanges 기본 정책 확인 : 컨테이너는 기본적으로 0.1CPU(=100m vcpu)를 최소 보장(개런티)</em>
kubectl delete limitranges limits
kubectl get limitranges

위 방법으로 CPU 제한을 해제하고 배포를 진행해보면 더 많은 수의 Pods가 배포되는 것을 확인할 수 있다.

2. LoadBalancer Controller with NLB

Kubernetes의 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을 제어할 수 있게 된다.

나는 one-click kOps yaml을 사용하여 실습을 진행했기 때문에 그 과정에 IAM Policy 생성 및 Role Attach까지 같이 마무리를 하였다. 혹시 그 내용이 누락되어 있다면 해당 내용을 추가로 진행해야 한다. (특히 Rolling Update 등으로 IAM Profile이 빠져있을 수 있다.)

NLB가 같이 포함 된 yaml을 사용하여 Deployment 생성을 진행했다.

그 후 정보를 확인하여 NLB 주소를 확인할 수 있었다.

NLB 주소를 Client에서 접속 시도하여 정상적으로 페이지가 호출되는 것까지 확인하였고 해당 NLB 주소는 내 도메인에 연결 및 테스트까지 완료하였다.

내 도메인 레코드와 NLB을 통해 정상적으로 분산 되는지를 테스트하였고 이상 없이 분산 되는 것을 확인했다.

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

3. Kubernetes Storage

Kubernetes Storage : hostPath, emptyDir, PV/PVC 등이 있고 이번 실습에는 PV/PVC을 진행할 예정이다.

hostPath : Pods가 배포 된 Worker Node(Host)의 Directory Path을 Pod에 Mount하여 사용하는 방법으로 Host Directory Path에 Data을 저장하기 때문에 Pods가 삭제되더라도 Data는 유지시킬 수 있다.

emptyDir : Pod 내부에 존재하고 휘발성을 뛰고 있다. hostPath와 다르게 Pod을 삭제하면 Data도 삭제되기 때문에 이 점은 유의해야 한다.

PV/PVC : hostPath와 언뜻 비슷해보이지만 hostPath는 Pods가 배포 된 Worker Node의 공간을 사용한다고 봤을 때 PV/PVC는 공유 공간이라고 보는 게 좋다. 동일한 Worker Node가 아니더라도 공간을 공유할 수 있으며 이를 위해 AWS에서는 EBS, EFS 등을 사용한다.

EBS을 생성하고 추가 된 EBS 볼륨을 확인하는 내용으로 PV/PVC 실습을 진행한다.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ebs-claim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 4Gi
apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  terminationGracePeriodSeconds: 3
  containers:
  - name: app
    image: centos
    command: ["/bin/sh"]
    args: ["-c", "while true; do echo $(date -u) >> /data/out.txt; sleep 5; done"]
    volumeMounts:
    - name: persistent-storage
      mountPath: /data
  volumes:
  - name: persistent-storage
    persistentVolumeClaim:
      claimName: ebs-claim

위 내용으로 PVC 및 Pods을 생성한다. 4GB의 용량에 RW Access가 설정 된 EBS을 생성한 후 Pods에 해당 EBS을 Mount하는 내용이다. 생성 된 Pods의 /data 경로를 보면 해당 EBS가 마운트되어 있어야 한다. 아래 방법으로 해당 내용을 확인한다.

/data/out.txt 파일을 호출하고 app Pod의 Filesystem 정보를 조회한 내용이다. 제대로 Mount되었음을 확인할 수 있다.

4. 마무리

지난 실습 때 Pods 생성 제한이나 NLB 등을 제대로 이해하지 못해 추가로 진행하였는데 늦게라도 해보길 잘했다는 생각이 들었다. Storage 부분은 특이한 내용은 없었고 기존 컨테이너를 공부할 때 배웠던 내용들이 같이 들어있어서 이해하는데 도움이 됐다.

Network 부분이 계속 어려웠었는데 이번 기회가 기술 성숙도를 올릴 수 있는 기회가 되길 바란다.

PKOS Study #2-1 – Kubernetes Network

PKOS Study #1 kOps에 이어 이번 #2에는 Kubernetes Network에 대해 정리할 예정이다.

사실 eks나 kOps나 배포 자체는 어렵지 않고 Docker Image을 통해 Container을 배포하는 것도 조금만 찾아보면 쉽게 따라 할 수 있다.

하지만 Network는 이해하는 영역이나 컨트롤 하는 부분이 어렵고 이 부분에 대해 자세히 학습할 수 있는 기회가 많지 않다.

이번 스터디 때는 그 부분에 대해 상세하게 설명을 들을 수 있었고 실습을 통해 이해의 영역이 조금 더 넓어진 것 같다.

0. 환경 구성

기존에 직접 kOps EC2를 배포해서 설정한 뒤에 kOps 배포를 진행했지만 이번 실습부터는 간편하게 설정 된 one-click kOps 배포 yaml을 사용하여 진행할 예정이다.

one-click yaml로 환경 배포 후 IAM 정책을 생성하고 EC2 Instance Profile에 Attach 한다.

IAM Policy은 https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.4.5/docs/install/iam_policy.json 파일을 통해 진행했다.

1. AWS VPC CNI?

CNI : Container Network Interface로 컨테이너 간의 네트워킹 제어 플러그인을 위한 표준이다. k8s에서는 Pod간의 네트워킹을 위해 CNI을 사용한다. kubenet이라는 자체 k8s CNI 플러그인이 있지만 기능이 부족하여 Calico, Weave 등을 사용하기도 한다.

Amazon VPC CNI : Node에 VPC IP Address을 할당하고 각 Node의 Pods에 대한 필수 네트워킹을 구성하는 역할을 한다.

특징으로는 Node와 Pods의 IP Range가 동일하게 Node<->Pods 통신이 가능하고 VPC Flow logs, Routing Table, Security Group 등을 지원한다.

VPC 연계되는 리소스들을 지원하기 때문에 AWS 인프라를 관리하는 것과 비슷한 방식으로 네트워크 환경을 관리할 수 있는 이점이 있다.

Node와 Pods의 IP을 확인하는 명령어를 입력하면 아래와 같이 Node와 Pods의 IP가 동일한 대역(172.30.*.*)을 사용하는 것을 확인할 수 있다.

# 노드 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

2. Pods 간 통신 확인

Master Node와 Worker Node의 보조 IPv4을 확인해보면 아래와 같이 확인 할 수 있다.

Worker Node #1과 #2에 각각 Pod을 생성 후 두 Pods의 통신을 확인해볼 계획이다.

우선 테스트 Pods을 생성한 뒤 생성 된 Pods의 IP을 확인한다.

# 테스트용 파드 netshoot-pod 생성
cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: netshoot-pod
spec:
  replicas: 2
  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

# 파드 이름 변수 지정
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})

# 파드 확인
kubectl get pod -o wide
kubectl get pod -o=custom-columns=NAME:.metadata.name,IP:.status.podIP

Pod #1에서 Pod #2로 Ping을 시도했을 때 정상적으로 Ping이 가는 것을 확인 할 수 있다.

만약 Ping이 제대로 가지 않는다면 Routing Table 정보가 업데이트 되었는지 확인해봐야 한다. (기본적으로는 자동으로 업데이트가 된다.)

Worker Node에서 ip route 정보를 확인해보면 Pods 간 통신시에는 Overlay을 사용하지 않고 Pods간 직접 통신을 하는 것을 확인 할 수 있다. 여기서 Overlay을 사용한다는 뜻은 가상의 네트워크를 만들어서 해당 네트워크를 통해 통신하게 한다는 의미이다. Amazon VPC CNI을 사용하면 해당 기술을 사용하지 않고도 직접적으로 Pods간 통신이 가능하게 된다.

3. Pods -> 외부 통신 확인

2번에서 Pods간 통신을 확인했다면 이번에는 Pods -> 외부 통신을 확인하려 한다.

외부 통신을 할 때에는 Pods 간 통신과는 다르게 Node의 IP(eth0)로 변경되어 외부로 통신을 나가게 된다.

위 흐름에 따라 통신이 된다면 Pod에서 외부 통신을 수행할 경우 소속 된 Worker Node의 Public IP을 통해 통신을 하게 된다.

아래 켑쳐 화면을 보면 Worker Node의 Public IP인 3.36.113.87을 통해 Pod가 외부 통신을 하는 것을 확인할 수 있다.

물론 이는 기본적인 Amazon VPC CNI의 SNAT Rule에 따른 것이고 SNAT Rule을 변경하게 되면 이 부분은 변경 될 수 있다.

SNAT Rule을 확인한 화면은 아래와 같다. 

Worker Node에 접속하여 확인한 화면이고 Source NAT IP을 172.30.53.166을 할당한다는 뜻이다. 해당 IP에 Attach된 Public IP는 외부 통신 확인하는 사진에서 볼 수 있듯이 172.30.53.166->3.36.113.87임을 알 수 있다. 내부에서 보기에는 Public IP을 바라보는 것이 아닌 Private IP인 172.30 대역을 바라보고 이후 Attach 된 Public IP(3.36.*.*)으로 외부 통신 됨을 확인할 수 있다.

4. 마무리

Node와 Pods 배포 및 내/외부 통신을 확인하는 과정을 진행하였다.

본 과제에는 정리하지 못하였으나 Loadbalancer와 Ingress 도 실습을 진행해보았다. 다만, 내용을 아직 이해하지 못했고 단순 따라하기 정도에 그친 부분이 있어 이 부분은 조금 더 실습을 진행하고 내가 이해한 부분을 정리할 수 있도록 해야겠다.

CNI에 대해 완벽하게 내용을 이해하지는 못하였지만 Pods간 통신과 Pods->외부 통신에 대한 원리를 이해할 수 있었던 게 이번 과정에서 제일 의미 있던 부분이라고 생각 된다.