Work/개발 노트

[Kubernetes] Amazon EKS 운영 시 IP가 부족하다면?

★용호★ 2023. 11. 4. 20:48

Challenge

Amazon EKS 기반으로 서비스를 운영하다보면 Pod 수가 점점 늘어나면서 최초에 설계한 IP Range를 초과하는 경우가 있고, 마이크로서비스 기반 아키텍처가 인기를 얻으면서 더욱 빈번하게 발생합니다. 이 경우 서브넷의 IP 크기는 조정할 수 없기 때문에 새로운 서브넷을 생성하여 노드를 이전해야하고, VPC의 IP가 부족한 경우에는 서브넷을 확장할 수도 없게 됩니다.

 

Amazon EKS를 사용할 때 IP가 얼마나 필요할까?

  • 클러스터 생성 시 각 서브넷에는 Amazon EKS에서 사용할 IP 주소가 6개 이상 필요 (16개 이상 권장)
  • EKS는 클러스터 생성 중에 지정된 각 서브넷(클러스터 서브넷이라고도 함)에 X-ENI 생성
  • Kubernetes 버전의 클러스터를 업데이트하면 Amazon EKS에서는 클러스터가 생성한 원래 네트워크 인터페이스를 삭제하고 새로운 네트워크 인터페이스를 생성
    • 최대 5개의 IP가 추가로 필요하고, 업데이트 완료 시 기존 네트워크 인터페이스가 제거되므로 회수됨 (참고)
  • 만약 서브넷 설정을 변경하는 것으로 해소가 된다면 2023년 10월 24일 부터 클러스터의 서브넷과 보안그룹 수정 가능 (참고)

EC2 인스턴스 유형 별로 실행 가능한 Pod 수 확인

curl -o max-pods-calculator.sh https://raw.githubusercontent.com/awslabs/amazon-eks-ami/master/files/max-pods-calculator.sh
chmod +x max-pods-calculator.sh
./max-pods-calculator.sh --instance-type m5.large --cni-version 1.9.0 --cni-prefix-delegation-enabled

 

어떻게 IP 고갈 문제를 해결할 수 있을까?

1. 더 큰 IP Range를 가진 VPC에 신규 EKS 클러스터 생성

  • VPC CIDR의 최대 크기는 /16 (65k) → eksctl로 클러스터 생성 시 기본값
  • IPv6 기반으로 VPC 생성 시 /56(2^72)

 

2. VPC Secondary CIDR 추가 (Custom Networking)

기본적으로 Amazon VPC CNI는 지정한 서브넷의 IP Range 범위의 IP를 Pod에 할당합니다. 만약 CIDR 범위가 너무 작은 경우에 IP 고갈 문제로 인해 Pod에 할당할 충분한 Secondary IP를 확보하지 못할 수 있습니다.

Custom Networking은 IP 고갈 문제를 해결할 수 있는 방법 중에 하나이고, VPC의 Secondary CIDR 기능을 사용합니다.

 

 

Custom Networking으로 Secondary CIDR를 사용하게 되면 해당 서브넷에서 생성되는 Pod들은 기본 네트워크 인터페이스에 할당된 IP 주소가 Pod에 할당되는 것이 아니라 Secondary 네트워크 인터페이스의 IP 주소가 할당됩니다.

중요: 특정 상황에서 Amazon EKS는 클러스터가 생성된 후 VPC에 추가된 추가 CIDR 블록에서 서브넷에서 시작된 노드와 통신할 수 없습니다. 기존 클러스터에 CIDR 블록을 추가하여 발생하는 업데이트된 범위는 표시되는 데 5시간까지 걸릴 수 있습니다.

  • VPC에 Secondary CIDR 추가 시 적용되는 규칙
    • 허용되는 블록 크기는 /16 ~ /28
    • 기존 CIDR과 겹치지 않아야함
    • 최대 5개의 CIDR를 추가 할 수 있음
    • VPC Peering/DX 사용 시 CIDR 블록이 겹치지 않아야함

 

3. AWS VPC CNI의 Warm Pool 파라미터 최적화

AWS VPC CNI는 노드(EC2 인스턴스)에 Warm Pool 크기만큼 IP를 사전에 확보해 놓습니다. 이런 이유로 Pod가 실제 사용하지 않더라도 VPC IP를 점유하기 때문에 Subnet에 가용한 IP 수가 부족해질 수 있습니다.

 

풀의 ENI 수와 IP 주소는 WARM_ENI_TARGET, WARM_IP_TARGET, MINIMUM_IP_TARGET 환경 변수로 설정합니다. VPC CNI는 ENI가 부족하면 EC2에 API를 호출하여 ENI를 추가합니다.

  • WARM_ENI_TARGET : 기본 ENI 외 웜 대기 상태의 ENI 수. 웜 상태의 ENI의 IP가 Pod에 할당되면 더이상 웜상태가 아니기 때문에 새로운 ENI 생성
    • 만약 1로 설정할 경우 최초 노드에는 두개의 ENI가 attach됨 (기본값)
  • WARM_IP_TARGET : Pod에 할당되지 않고, 웜 대기 상태로 유지해야할 IP 개수
    • 실제 할당된 IP와 웜 상태의 IP수의 합이 ENI가 제공하는 IP 수를 초과할 때만 ENI를 추가
  • MINIMUM_IP_TARGET : 언제든 할당 할 수 있는 최소 IP 수
    • WARM_ENI나 WARM_IP 설정과 상관없이 최소로 지정된 IP 수를 맞추기 위해 ENI를 즉시 추가함

위 설정을 변경하려면 아래와 같이 AWS VPC CNI의 환경 변수 값을 수정해야합니다.

kubectl set env daemonset aws-node -n kube-system MINIMUM_IP_TARGET=5

 

 

실습 - EKS Custom Networking으로 추가 IP 확보하기

Secondary CIDR 추가하기

vpc_id=$(aws eks describe-cluster --name <클러스터명> --query "cluster.resourcesVpcConfig.vpcId" --output text)

aws ec2 describe-vpcs --vpc-ids $vpc_id \
    --query 'Vpcs[*].CidrBlockAssociationSet[*].{CIDRBlock: CidrBlock, State: CidrBlockState.State}' --out table

# Secondary CIDR 블록으로 100.64.0.0/16 추가
aws ec2 associate-vpc-cidr-block --vpc-id $vpc_id --cidr-block 100.64.0.0/16

# 아래 명령 실행 결과 새 CIDR 블록이 associated 상태가 될 때까지 대기
aws ec2 describe-vpcs --vpc-ids $vpc_id --query 'Vpcs[*].CidrBlockAssociationSet[*].{CIDRBlock: CidrBlock, State: CidrBlockState.State}' --out table

# Secondary CIDR의 서브넷 생성
export az_1=ap-northeast-2a
export az_2=ap-northeast-2b
export az_3=ap-northeast-2c
new_subnet_id_1=$(aws ec2 create-subnet --cidr-block 100.64.0.0/19 --vpc-id $vpc_id --availability-zone $az_1 | jq -r .Subnet.SubnetId)
new_subnet_id_2=$(aws ec2 create-subnet --cidr-block 100.64.32.0/19 --vpc-id $vpc_id --availability-zone $az_2 | jq -r .Subnet.SubnetId)
new_subnet_id_3=$(aws ec2 create-subnet --cidr-block 100.64.64.0/19 --vpc-id $vpc_id --availability-zone $az_3 | jq -r .Subnet.SubnetId)

# 생성된 서브넷 확인
aws ec2 describe-subnets --filters "Name=vpc-id,Values=$vpc_id" \
    --query 'Subnets[*].{SubnetId: SubnetId,AvailabilityZone: AvailabilityZone,CidrBlock: CidrBlock}' \
    --output table

 

Kubernetes 리소스 구성

cluster_name=eks-demo

# Custom Network를 사용하도록 AWS VPC CNI 설정 변경
kubectl set env daemonset aws-node -n kube-system AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG=true

# EKS 클러스터에 할당된 Security Group 확인
cluster_security_group_id=$(aws eks describe-cluster --name $cluster_name --query cluster.resourcesVpcConfig.clusterSecurityGroupId --output text)

# Pod를 배포하려는 Subnet에 기존 노드들과 통신을 위한 ENI를 제어하도록 ENIConfig 생성
cat >$az_1.yaml <<EOF
apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata: 
  name: $az_1
spec: 
  securityGroups: 
    - $cluster_security_group_id
  subnet: $new_subnet_id_1
EOF

cat >$az_2.yaml <<EOF
apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata: 
  name: $az_2
spec: 
  securityGroups: 
    - $cluster_security_group_id
  subnet: $new_subnet_id_2
EOF

cat >$az_3.yaml <<EOF
apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata: 
  name: $az_3
spec: 
  securityGroups: 
    - $cluster_security_group_id
  subnet: $new_subnet_id_3
EOF

kubectl apply -f $az_1.yaml
kubectl apply -f $az_2.yaml
kubectl apply -f $az_3.yaml

# ENIConfig 생성 확인
kubectl get ENIConfigs

# 신규 노드에 ENIConfig 적용
kubectl set env daemonset aws-node -n kube-system ENI_CONFIG_LABEL_DEF=topology.kubernetes.io/zone

 

Secondary CIDR로 서브넷을 생성하면 아래와 같이 기존 VPC로 접근할 수있는 라우팅 테이블 규칙이 기본으로 추가됨

 

신규 노드 그룹에 할당할 IAM Role 생성

기존 노드 그룹이 사용하는 IAM Role을 그대로 사용하는 경우

node_role_arn=$(aws eks describe-nodegroup --cluster-name $cluster_name --nodegroup-name <기존 노드 그룹 이름> --query "nodegroup.nodeRole" --output text)

 

IAM Role을 신규 생성하는 경우

cat >node-role-trust-relationship.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

export node_role_name=CustomNetworkingAmazonEKSNodeRole
node_role_arn=$(aws iam create-role --role-name $node_role_name --assume-role-policy-document file://"node-role-trust-relationship.json" \
    --query Role.Arn --output text)

aws iam attach-role-policy \
  --policy-arn arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy \
  --role-name $node_role_name
aws iam attach-role-policy \
  --policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly \
  --role-name $node_role_name
aws iam attach-role-policy \
    --policy-arn arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy \
    --role-name $node_role_name

 

신규 노드 그룹 생성

cat << EOF > create_nodegroup.yaml
---
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
  # EKS 클러스터명
  name: $cluster_name
  region: ap-northeast-1
managedNodeGroups:
# 노드그룹명
- name: $nodegroup_name
  labels: { type: customnetworking }
  privateNetworking: true
  desiredCapacity: 2
  instanceType: $instance_type
  subnets:
    - $new_subnet_id_1
    - $new_subnet_id_2
    - $new_subnet_id_3
  ssh:
    enableSsm: true
EOF

eksctl create nodegroup -f create_nodegroup.yaml

# 노드 생성될 때까지 대기
aws eks wait nodegroup-active --cluster-name $cluster_name --nodegroup-name $nodegroup_name

# 노드 생성 확인
kubectl get nodes -L eks.amazonaws.com/nodegroup

# Pod 재생성
kubectl rollout deployment <Deployment명>

# Pod 확인
kubectl get pods -o wide

 

이 후 신규 노드에 Pod가 배치되면서 추가된 IP를 사용하게 됩니다.