본문 바로가기

DevOps

[AWS EKS] VPC CNI와 Loadbalancer

CloudNet@팀의 EKS 스터디 AEWS 2기에 작성된 자료를 토대로 작성합니다.

 

2주차 과제의 도전과제를 위주로 포스팅을 하려 한다. 

물론 과제를 위해 필요한 개념들을 먼저 다루도록 하겠다.

AWS VPC CNI와 Loadbalancer 사용방법에 대한 이야기 위주로 포스팅 하겠다.

 

✅   Amazon VPC CNI                                                                                                                                     

  •  K8s에서는 POD간의 통신을 확장하는 규약을 CNI(Container Network Interface)로 정의 하고 있다. 번역이라 말이 어려운데 쉽게 말해서 CNI  → 네트워크 환경을 구성해준다고 생각하자
  • 네트워크 환경을 구성한다 = IP 할당, 라우팅 테이블 관리,
  • AWS VPC CNI 도 마찬가지로 CNI 인데, 차이점이라고 하면 파드의 IP 네트워크 대역과 노드(워커)의 IP 대역이 같아서 직접 통신이 가능한 점이다.

☑️ Amazon VPC CNI 특징

  • supports native VPC networking with the Amazon VPC Container Network Interface (CNI) plugin for Kubernetes.
  • VPC 와 통합 : VPC Flow logs , VPC 라우팅 정책, 보안 그룹(Security group) 을 사용 가능함
  • This plugin assigns an IP address from your VPC to each pod. 기본적으로 CNI의 가장 큰 역할은 pod의 ip를 할당 하는것이라고 할수 있다. 물론 ip도 할당하고 내부에 ip link도 맺고, 각 pod와 노드에 라우팅 테이블도 고지고 많은 역할을 하지만..
  • VPC ENI 에 미리 할당된 IP(=Local-IPAM Warm IP Pool)를 파드에서 사용할 수 있음 -> Secondary IP
더보기

1. 워커 노드 한개당 몇개의 보조 IP를 받을수 있을까? 

2. 제한된 보조 IP를 사용하지 않고 무제한으로 POD를 배포하는 방법은 없을까? 

☑️ IPv4 Prefix 위임

  • 해당 모드를 사용하면, 인스턴스 유형 마다 가지고 있는 ENI의 맥시멈 개수는  동일하게 유지 하지만, VPC CNI가 IP를 할당 하는 범위를 정해서 IPv4 28bit 서브넷(prefix)를 위임하여 할당 가능 IP 수와 인스턴스 유형에 권장하는 최대 갯수로 선정
  • 그냥 쉽게 말하면 원래 ec2 인스턴스 유형에 따라 pod가 가져갈수 있는 IP가 굉장히 한정적인데, 그부분을 prefix 위임해서 훨씬 많이 쓸수 있는 그런 개념이다.

✅   도전과제 1번                                                                                                                                         

[도전과제1] EKS Max pod 개수 증가 - Prefix Delegation + WARM & MIN IP/Prefix Targets : EKS에 직접 설정 후 파드 150대 생성

  • nitro EC2 인스턴스 유형만 적용 가능함 ( c5,m5,r5,a1 등 )
  • /28(IP 주소 16개) IPv4 주소 접두사를 할당하도록 Amazon VPC CNI를 구성할 수 있습니다
  • 버전 1.9.0 이상부터 사용 가능함
  •  WARM_PREFIX_TARGET= 1은 기존 접두사가 단 하나의 파드에 사용되었을지라도 하나의 완전한 (/28) 접두사를 할당한다.
  • 하지만, WARM & MIN IP/Prefix Target 기능은  포드 실행 시간에 약간의 성능 저하가 허용될 경우 사용하면 chunk prefix를 최소화 할수 있다. 
예시를 들어보자 ,, !!
인스턴스에 pod가 25개 올라간다면 접두사 위임은 하나의 ENI를 사용하면 28비트로 총 16개의 ip 청크를 할당 받는다. 25개의 파드를 배포해야 하기 때문에 2개의 청크 즉 32개의 IP를 받아야 한다.이 때  WARM_IP_TARGET을 5로 설정하면 WARM_IP_TARGET의 요구사항인 5를 만족하는 7개의 미사용 IP 주소가 존재하기 때문에 IPAMD는 해당 설정에 만족 할 것이다. 해당 설정에 트래픽이 증가하여 만약 12개의 pod를 더 배포해야 한다면 37개의 IP는 2개의 청크로 부족하기 때문에 ENI에 할당된 접두사를 한개 더 요청할것이다. 

1. 버전 확인 및 VPC CNI install 상태 확인

$ kubectl get pods --selector=k8s-app=aws-node -n kube-system

# aws-node는 VPC CNI를 뜻한다.
# 각 노드마다 1개 씩 배포가 되있음을 확인 할수 있다.

$ kubectl describe daemonset aws-node --namespace kube-system | grep Image | cut -d "/" -f 2

# Confirm the CNI version. The CNI version must be 1.9.0 or later.
# CNI 버전이 1.9.0보다 높아야 prefix 기능을 이용할수 있다.

$ kubectl get ds aws-node -o yaml -n kube-system | yq '.spec.template.spec.containers[].env'

# 현재 Prefix Delegation 옵션이 무엇으로 되어 있는지 확인
# 확인 결과 false 
- name: ENABLE_PREFIX_DELEGATION
  value: "false"
#


$ aws ec2 describe-instances --filters "Name=tag-key,Values=eks:cluster-name" \
  "Name=tag-value,Values=myeks" \
  --query 'Reservations[*].Instances[].{InstanceId: InstanceId, Prefixes: NetworkInterfaces[].Ipv4Prefixes[]}'

# prefix 설정이 되어있는지 확인
[
    {
        "InstanceId": "i-0c6182e3f21515b54",
        "Prefixes": []
    },
    {
        "InstanceId": "i-01623a8bda355f3c9",
        "Prefixes": []
    },
    {
        "InstanceId": "i-0742989209715218e",
        "Prefixes": []
    }
]

 

2. set Prefix Delegation

  • aws-node의 데몬셋의 환경변수가 변경 되었지만 node의 Allocate 가능한 pod수는 변경이 되지 않는다.
  • t3.medium이 nitro instance가 아니어서 그런가 싶어서 m5.large로 초기 구성을 다시 했지만 동일하다.
  • 하지만 새로운 노드그룹을 배포하였을땐 정상 동작하는것을 확인 하였기 때문에 이부분을  
kubectl describe node
...
Allocatable:
  cpu:                1930m
  ephemeral-storage:  27905944324
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             7220152Ki
  pods:               29
...
  
$ kubectl set env daemonset aws-node -n kube-system ENABLE_PREFIX_DELEGATION=true
$ kubectl set env ds aws-node -n kube-system WARM_PREFIX_TARGET=1

kubectl describe node
...
Allocatable:
  cpu:                1930m
  ephemeral-storage:  27905944324
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             7220152Ki
  pods:               29
...

3. nitro instance 추가 배포

# Nitro 인스턴스 타입의 Managed 노드 그룹 생성 : 워커 노드 1대
SSHKEYNAME=koo-seoul
eksctl create nodegroup --cluster $CLUSTER_NAME --region $AWS_DEFAULT_REGION --name nitro-ng --node-type c5.large --nodes 1 --ssh-access --ssh-public-key $SSHKEYNAME

# 워커노드의 최대 파드 가능 갯수 확인
kubectl describe nodes ip-192-168-2-201.ap-northeast-2.compute.internal | grep Allocatable: -A 7
Allocatable:
  attachable-volumes-aws-ebs:  25
  cpu:                         1930m
  ephemeral-storage:           76224326324
  hugepages-1Gi:               0
  hugepages-2Mi:               0
  memory:                      3093420Ki
  pods:                        110
  
  
  
# deployment 배포 후 확인
(koo@myeks:N/A) [root@myeks-bastion-EC2 ~]# k get deploy -n other

NAME                READY     UP-TO-DATE   AVAILABLE   AGE
pause-pods-prefix   150/150   150          150         21m

 


✅   Service & AWS LoadBalancer Controller                                                                                                 

☑️ NLB vs ALB vs CLB

  • 지난시간에 EKS에서 AWS 책임영역에서 사용되는 로드벨런서들에 NLB를 사용하는 이유에 대해서 정리해보자
  • 가장 큰 차이점 : HTTP를 패킷 처리할수 있는가? 
  • 성능 및 가용성 차원에서도 NLB가 효율적이다 -> Hyperplane 아키텍처
  • private-link도 NLB만 지원함 

 

 

☑️ 로드벨런서 컨트롤러 만들기 전 사전작업 

  • EKS에서 LB를 사용하기 위해서는 로드벨런서 컨트롤러의 IRSA를  먼저 eksctl로 배포 해야한다.
  •  위 동작이 시작되면 IAM 정책 (policy)을 먼저 배포한 뒤 --attach-policy-arn 으로 정책을 assume한다.
  • 위 동작이 완료되어 IRSA를 만들어지면 AWS Load Balancer Controller를 위한 ServiceAccount를 쿠버네티스에 생성
  • AWS Load Balancer Controller라는 이름의 kube-system 네임스페이스에 aws-load-balancer-controller라는 Kubernetes 서비스 계정을 생성하고 IAM 역할을 assume 받은 Kubernetes 서비스 어카운트에 annotaion 생성
### 
# 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 정보 확인

(koo@myeks:N/A) [root@myeks-bastion-EC2 ~]# eksctl get iamserviceaccount --cluster $CLUSTER_NAME

NAMESPACE	NAME				ROLE ARN
kube-system	aws-load-balancer-controller	arn:aws:iam::XXX:role/eksctl-myeks-addon-iamserviceaccount-kube-sys-Role1-XXX

☑️ Service Account 생성 확인

  • Annotation에 권한 위임이 되있는것을 확인 할수 있음
(koo@myeks:N/A) [root@myeks-bastion-EC2 ~]# kubectl get serviceaccounts -n kube-system aws-load-balancer-controller -o yaml | yh

apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::466593096201:role/eksctl-myeks-addon-iamserviceaccount-kube-sys-Role1-H7cl4MmTW0EF
  creationTimestamp: "2024-03-10T03:34:16Z"
  labels:
    app.kubernetes.io/managed-by: eksctl
  name: aws-load-balancer-controller
  namespace: kube-system
  resourceVersion: "153262"
  uid: a25f5fba-d76b-4c98-922e-339483dabe2d

 

☑️ Helm 으로 로드벨런서 컨트롤러 설치 

  • 위에서 만든 Service Account 적용 
# 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

☑️ Service spec > LoadBalancerClass: service.k8s.aws/nlb에서 확인


✅   INGRESS                                                                                                                                                      

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

☑️ ingress 배포시 spec > ingressClassName: alb로 확인됨 


✅   도전과제 4                                                                                                                                      

[도전과제4]   게임서버의 트래픽(UDP)를 서비스(NLB)를 통해 인입 설정 - 링크

  • UDP 기반 LoadBalancer Kubernetes Service 유형의 구성은 TCP 기반과 유사하지만 Network Load Balancer 상태 확인 구성과 같이 명심해야 할 몇 가지 사항이 있습니다 . 이 블로그에서는 UDP 기반 게임 서버를 배포하는 방법과 UDP 기반이 아닌 상태 확인을 활성화하는 패턴을 보여줍니다.
  • UDP 트래픽 및 고정성이 설정된 대상 그룹에는 수동 상태 확인이 지원되지 않습니다. 따라서 이 블로그 게시물에 언급된 샘플 게임 서버는 포트 80에서 수신 대기하는 사이드카로 NGINX를 실행합니다. 게임 서버 상태 확인의 정확성을 높이려면 liveness Probe 구성하는 것이 좋습니다
  • 대상 그룹에서 비정상 임계값 수에 도달하면 요청이 다른 정상 Pod로 리디렉션됩니다. 활성 프로브 사이의 간격은 Network Load Balancer 상태 확인 사이의 간격보다 작아야 합니다. 기본 로드벨런서의 헬스체크의 비정상 임계값은 3회로 지정되있습니다.

 

1. 로드벨런서 컨트롤러 배포 

# 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 https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.5.4/docs/install/iam_policy.json
aws iam create-policy --policy-name AWSLoadBalancerControllerIAMPolicy --policy-document file://iam_policy.json

# 혹시 이미 IAM 정책이 있지만 예전 정책일 경우 아래 처럼 최신 업데이트 할 것
# aws iam update-policy ~~~

# 생성된 IAM Policy Arn 확인
aws iam list-policies --scope Local | jq
aws iam get-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy | jq
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 --role-name AmazonEKSLoadBalancerControllerRole \
--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

## 설치 확인 : aws-load-balancer-controller:v2.7.1
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

 

2.  cluster 배포 

# git clone https://github.com/aws-samples/containerized-game-servers.git

cd containerized-game-servers/udp-nlb-sample

# 
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
export AWS_REGION=us-west-2


# 
cd containerized-game-servers/udp-nlb-sample
eksctl create cluster -f eks-arm64-cluster-spec.yaml

 

3.  supertuxkart 게임 이미지 배포

# stk 배포
cd containerized-game-servers/udp-nlb-sample/stk
sh ecr-repos.sh
sh build.sh

# 어플리케이션 sidecar 배포
cd containerized-game-servers/udp-nlb-sample/nginx-static-sidecar/
sh ecr-repos.sh
sh build.sh

#nlb-sidecar 배포
cd containerized-game-servers/nginx-static-sidecar/
cat stknlb-static-tcphealth-sidecar.yaml | envsubst | kubectl apply -f -

 

 

  • 해당 블로그에서 제공하는 stk 게임 이미지가 현재는 제공되지 않는것 같음, 2년전 이미지 
  • 따라서 테스트는 여기까지만 진행 하였음. 그렇지만 UDP를 NLB에서  이렇게 간단하게 사용할수 있다는 정도만 알아두면 좋을것 같음
  • 또한 해당 클러스터는 전 시간에 배웠던 Gravition 타입의 노드그룹을 생성함으로 해당 타입의 인스턴스가 게임을 돌리는데 유리하구나 정도로 이해하면 될듯.
(koo@arm-us-west-2:N/A) [root@myeks-bastion-EC2 stk]# sh build.sh
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded
Sending build context to Docker daemon  19.97kB
Step 1/2 : FROM public.ecr.aws/p9d8y1e7/supertuxkart:arm10
repository public.ecr.aws/p9d8y1e7/supertuxkart not found: name unknown: The repository with name 'supertuxkart' does not exist in the registry with id 'p9d8y1e7'
Error response from daemon: No such image: stk:latest
The push refers to repository [466593096201.dkr.ecr.us-west-2.amazonaws.com/stk]
tag does not exist: 466593096201.dkr.ecr.us-west-2.amazonaws.com/stk:arm0.11.0

✅   도전과제 5                                                                                                                                             

[도전과제5] Multiple Ingress pattern : 여러 Ingress 를 하나의 ALB에서 처리 할 수 있게 설정 - 링크

  • 기본적으로 각 Ingress는 별도의 ALB를 생성
  • 하지만 여러 Ingress 리소스를 함께 그룹화할 수 있는 IngressGroup 기능을 활용할 수 있습니다
  • 실습 전에 AWS-loadbalancer-controller 배포 필요
  • ingress 배포 -> 확인

1. AWS Loadbalancer 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 https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.5.4/docs/install/iam_policy.json
aws iam create-policy --policy-name AWSLoadBalancerControllerIAMPolicy --policy-document file://iam_policy.json

# 혹시 이미 IAM 정책이 있지만 예전 정책일 경우 아래 처럼 최신 업데이트 할 것
# aws iam update-policy ~~~

# 생성된 IAM Policy Arn 확인
aws iam list-policies --scope Local | jq
aws iam get-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy | jq
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 --role-name AmazonEKSLoadBalancerControllerRole \
--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

## 설치 확인 : aws-load-balancer-controller:v2.7.1
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'

 

2. 인그레스 배포

- ui 네임스페이스 생성

- catalog 네임스페이스 생성

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ui
  namespace: ui
  labels:
    app.kubernetes.io/created-by: eks-workshop
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/healthcheck-path: /actuator/health/liveness
    alb.ingress.kubernetes.io/group.name: retail-app-group
spec:
  ingressClassName: alb
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: ui
            port:
              number: 80
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: catalog
  namespace: catalog
  labels:
    app.kubernetes.io/created-by: eks-workshop
  annotations:
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/healthcheck-path: /health
    alb.ingress.kubernetes.io/group.name: retail-app-group
spec:
  ingressClassName: alb
  rules:
  - http:
      paths:
      - path: /catalogue
        pathType: Prefix
        backend:
          service:
            name: catalog
            port:
              number: 80

 

3. 인그레스 배포 후 확인

# 인그레스 배포
(koo@myeks:N/A) [root@myeks-bastion-EC2 ~]# k apply -f ingress-catalog.yaml

# 인그레스 배포 2
(koo@myeks:N/A) [root@myeks-bastion-EC2 ~]# k apply -f ingress-ui.yaml

  • 1개 로드벨런서가 생성된것을 확인할수 있음
  • Path prefix 가 있는 요청은 /catalogue 카탈로그 서비스의 대상 그룹으로 전송됩니다.
  • 이외의 다른 요청은  UI 서비스의 대상 그룹으로 전송됩니다.
  • 에러가 발생하는 모든 요청에는 404