AWS EKS에서 Node의 EC2 타입과 --max-pods 설정간 관계

8분 분량
콘텐츠 수준: 중급
3

본 기사에서는 AWS EKS의 노드의 EC2 인스턴스 타입에 따라 kubelet에서 최대로 Pod를 배치할 수 있는 —max-pods 설정간 관계에 대해 설명합니다. 또한 kubelet의 --max-pods 설정이 Pod 배치에 미치는 영향에 대해 설명합니다. 이 기사에서는 Pod 배치에 영향을 미치는 다른 리소스에 대해서는 고려하지 않습니다.

AWS EKS 클러스터에는 Pod를 하나 이상 배치할 수 있는 EC2 Node가 포함되어 있습니다. EC2 인스턴스의 유형이 다양한 만큼 Node 역시 다양한 유형의 인스턴스를 사용할 수 있습니다. 인스턴스 유형 별 컴퓨팅, 메모리, 스토리지 및 네트워크 기능들이 다양하게 제공되나 이 기사에서는 탄력적 네트워크 인터페이스(Elastic Network Interface)에 대해 집중적으로 다루어보겠습니다.

 

각 EC2 인스턴스는 유형별로 사용 가능한 네트워크 인터페이스(ENI)와 각 네트워크 인터페이스 별 프라이빗 IP 주소가 각각 다르게 지정되어 있습니다. [1]

예를 들어 m5.large 타입의 인스턴스의 경우, 최대 사용 가능한 네트워크 인터페이스는 3개, 각 네트워크 인터페이스 별로 프라이빗 IPv4는 10개 그리고 프라이빗 IPv6는 10개로 지정되어 있습니다.

$ aws ec2 describe-instance-types --filters "Name=instance-type,Values=m5.large" --query "InstanceTypes[].{Type: InstanceType, MaxENI: NetworkInfo.MaximumNetworkInterfaces, IPv4addr: NetworkInfo.Ipv4AddressesPerInterface, IPv6addr: NetworkInfo.Ipv6AddressesPerInterface}" --output table
------------------------------------------------
|             DescribeInstanceTypes            |
+----------+------------+---------+------------+
| IPv4addr | IPv6addr   | MaxENI  |   Type     |
+----------+------------+---------+------------+
|  10      |  10        |  3      |  m5.large  |
+----------+------------+---------+------------+

이러한 네트워크 인터페이스 설정으로 인해 kubelet[2]에 실행 가능한 최대 Pod 수를 나타내는 --max-pods 값에 적당한 값을 입력할 필요가 있습니다. 이를 위해 EKS AMI에서는 자동으로 —max-pods를 계산하기 위한 max-pods-calculator.sh[3] 및 각 인스턴스 타입별로 이미 계산된 결과인 eni-max-pods.txt[4]를 제공하고 있습니다.

 

최대 실행 가능한 Pod 수를 계산하는 계산식은 다음과 같습니다.

# of ENI * (# of IPv4 per ENI - 1) + 2

인스턴스 타입별로 정의된 네트워크 인터페이스에서 사용 가능한 프라이빗 IP 주소에서 네트워크 인터페이스의 첫번째 IP를 제외한 갯수만큼 Pod를 사용할 수 있으며 각 Node에서 기본적으로 호스트 네트워크를 사용하는 Pod인 aws-node와 kube-proxy Pod를 위해 임의로 2를 더한 갯수로 계산해 최대 사용 가능한 Pod 수를 정의합니다.

위의 계산식을 통해 m5.large 타입의 인스턴스가 사용 가능한 Pod 수는 다음과 같이 계산할 수 있습니다.

3 * (10 - 1) + 2 = 29

 

샘플 Pod를 통해 배포 테스트를 진행해보았습니다.

 

  1. m5.large 타입 인스턴스 노드를 1개 생성합니다.

    $ kubectl get nodes -A
    NAME                           STATUS   ROLES    AGE   VERSION
    ip-10-0-147-219.ec2.internal   Ready    <none>   98s   v1.27.6-eks-a5df82a
    
  2. 클러스터에 노드가 1개만 존재하는 경우 호스트 네트워크를 사용하는 파드와 coredns 2개가 위치하게 됩니다.

    $ kubectl get pods -A
    NAMESPACE     NAME                               READY   STATUS    RESTARTS   AGE
    kube-system   aws-node-xn476                     1/1     Running   0          3m22s
    kube-system   coredns-79df7fff65-fspsl           1/1     Running   0          12m
    kube-system   coredns-79df7fff65-psggs           1/1     Running   0          12m
    kube-system   kube-proxy-628x7                   1/1     Running   0          3m22s
    
  3. 샘플 Deployment의 replica를 25로 설정하여 배포합니다. [5]

    $ kubectl get deployment -A
    NAMESPACE     NAME              READY   UP-TO-DATE   AVAILABLE   AGE
    game-2048     deployment-2048   25/25   25           25          9m11s
    kube-system   coredns           2/2     2            2           19m
    
  4. Deployment Pod 25개, coredns Pod 2개, aws-node, kube-proxy까지 총 29개의 Pod가 정상 배포되었음을 확인할 수 있습니다.

    $ kubectl get pods -A
    NAMESPACE     NAME                              READY   STATUS    RESTARTS   AGE   IP             NODE                           NOMINATED NODE   READINESS GATES
    game-2048     deployment-2048-8886b7b6b-2kr9j   1/1     Running   0          27s   10.0.147.98    ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-2xfmd   1/1     Running   0          27s   10.0.151.226   ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-446l4   1/1     Running   0          26s   10.0.149.12    ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-5bpv8   1/1     Running   0          27s   10.0.153.85    ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-7mgdp   1/1     Running   0          27s   10.0.145.12    ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-7srh9   1/1     Running   0          27s   10.0.158.114   ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-8hnkt   1/1     Running   0          82s   10.0.152.6     ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-8vlmb   1/1     Running   0          26s   10.0.147.178   ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-dz9rj   1/1     Running   0          26s   10.0.157.96    ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-g9tbq   1/1     Running   0          27s   10.0.155.26    ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-gmmbz   1/1     Running   0          82s   10.0.159.126   ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-gnb84   1/1     Running   0          82s   10.0.148.62    ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-hg2bp   1/1     Running   0          82s   10.0.152.145   ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-jtx6k   1/1     Running   0          82s   10.0.147.52    ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-kj8sh   1/1     Running   0          91s   10.0.157.169   ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-ldmzd   1/1     Running   0          26s   10.0.159.74    ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-lwsbb   1/1     Running   0          26s   10.0.152.77    ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-mdsh9   1/1     Running   0          82s   10.0.159.157   ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-pdslz   1/1     Running   0          26s   10.0.152.49    ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-ptmm2   1/1     Running   0          26s   10.0.155.201   ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-r86w4   1/1     Running   0          82s   10.0.145.3     ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-tdzgd   1/1     Running   0          27s   10.0.145.24    ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-tpjkf   1/1     Running   0          91s   10.0.158.178   ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-v5r8w   1/1     Running   0          82s   10.0.157.250   ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-xczrl   1/1     Running   0          26s   10.0.145.148   ip-10-0-147-219.ec2.internal   <none>           <none>
    kube-system   aws-node-xn476                    1/1     Running   0          10m   10.0.147.219   ip-10-0-147-219.ec2.internal   <none>           <none>
    kube-system   coredns-79df7fff65-fspsl          1/1     Running   0          18m   10.0.156.64    ip-10-0-147-219.ec2.internal   <none>           <none>
    kube-system   coredns-79df7fff65-psggs          1/1     Running   0          18m   10.0.154.128   ip-10-0-147-219.ec2.internal   <none>           <none>
    kube-system   kube-proxy-628x7                  1/1     Running   0          10m   10.0.147.219   ip-10-0-147-219.ec2.internal   <none>           <none>
    
  5. Deployment Pod의 replica를 1개 증가시켜 26개로 증가하였습니다.

    $ kubectl get deploymemt -A
    NAMESPACE     NAME              READY   UP-TO-DATE   AVAILABLE   AGE
    game-2048     deployment-2048   25/26   26           25          10m
    kube-system   coredns           2/2     2            2           20m
    
  6. 추가된 Pod 1개가 Too many Pod 메시지와 함께 Pending 상태로 유지됨을 확인할 수 있습니다.

    $ kubectl get pods -A -o wide
    ...
    game-2048     deployment-2048-8886b7b6b-j9h69   0/1     Pending   0          10s    <none>         <none>                         <none>           <none>
    
    $ kubectl describe pod deployment-2048-8886b7b6b-j9h69 -n game-2048
    ...
    Events:
      Type     Reason            Age                  From               Message
      ----     ------            ----                 ----               -------
      Warning  FailedScheduling  84s (x2 over 6m49s)  default-scheduler  0/1 nodes are available: 1 Too many pods. preemption: 0/1 nodes are available: 1 No preemption victims found for incoming pod..
    

 

DaemonSet이 있는 경우

DaemonSet을 배포한 경우에 어떤 영향이 있는지 살펴보겠습니다.

 

  1. 이전의 샘플에서 26개까지 증가시킨 Deployment를 0으로 초기화시키겠습니다.

    $ kubectl scale -n game-2048 deploy/deployment-2048 --replicas 0
    deployment.apps/deployment-2048 scaled
    
  2. DaemonSet을 배포합니다.[6]

    $ kubectl apply -f https://k8s.io/examples/controllers/daemonset.yaml
    
  3. DaemonSet이 설치되었습니다.

    $ kubectl get ds -A                                                                                                                                                                                                                 
    NAMESPACE     NAME                    DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
    kube-system   aws-node                1         1         1       1            1           <none>          32m
    kube-system   fluentd-elasticsearch   1         1         1       1            1           <none>          8s
    kube-system   kube-proxy              1         1         1       1            1           <none>          32m
    
    $ kubectl get pods -A -o wide                                                                                                                                                                                                          
    NAMESPACE     NAME                          READY   STATUS    RESTARTS   AGE   IP             NODE                           NOMINATED NODE   READINESS GATES
    kube-system   aws-node-xn476                1/1     Running   0          24m   10.0.147.219   ip-10-0-147-219.ec2.internal   <none>           <none>
    kube-system   coredns-79df7fff65-fspsl      1/1     Running   0          33m   10.0.156.64    ip-10-0-147-219.ec2.internal   <none>           <none>
    kube-system   coredns-79df7fff65-psggs      1/1     Running   0          33m   10.0.154.128   ip-10-0-147-219.ec2.internal   <none>           <none>
    kube-system   fluentd-elasticsearch-pmsf7   1/1     Running   0          69s   10.0.157.169   ip-10-0-147-219.ec2.internal   <none>           <none>
    kube-system   kube-proxy-628x7              1/1     Running   0          24m   10.0.147.219   ip-10-0-147-219.ec2.internal   <none>           <none>
    
  4. Deployment의 Replica를 다시 25로 증가시킵니다.

    $ kubectl scale -n game-2048 deploy/deployment-2048 --replicas 25
    deployment.apps/deployment-2048 scaled
    
    $ kubectl get pods -A -o wide                                                                                                                                                                                                           [23/11/5| 3:45PM]
    NAMESPACE     NAME                              READY   STATUS    RESTARTS   AGE     IP             NODE                           NOMINATED NODE   READINESS GATES
    game-2048     deployment-2048-8886b7b6b-6g4cv   0/1     Pending   0          56s     <none>         <none>                         <none>           <none>
    game-2048     deployment-2048-8886b7b6b-6k7kt   1/1     Running   0          56s     10.0.157.96    ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-7cfkn   1/1     Running   0          56s     10.0.159.126   ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-7gd9r   1/1     Running   0          56s     10.0.159.74    ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-8dg6s   1/1     Running   0          56s     10.0.145.3     ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-9k9ss   1/1     Running   0          56s     10.0.155.15    ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-9nzhc   1/1     Running   0          56s     10.0.145.148   ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-fkj44   1/1     Running   0          56s     10.0.149.12    ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-gpbqq   1/1     Running   0          56s     10.0.148.62    ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-jlb5x   1/1     Running   0          56s     10.0.158.52    ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-k7rn2   1/1     Running   0          56s     10.0.147.178   ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-kjvp5   1/1     Running   0          56s     10.0.146.214   ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-m2v8m   1/1     Running   0          56s     10.0.147.99    ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-nnl5d   1/1     Running   0          56s     10.0.149.148   ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-pqkl7   1/1     Running   0          56s     10.0.147.52    ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-r87cf   1/1     Running   0          56s     10.0.145.222   ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-rw8lr   1/1     Running   0          56s     10.0.159.157   ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-s2cnk   1/1     Running   0          56s     10.0.155.201   ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-t649h   1/1     Running   0          56s     10.0.152.49    ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-thk4m   1/1     Running   0          56s     10.0.153.85    ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-v2dsh   1/1     Running   0          56s     10.0.152.38    ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-x6lv5   1/1     Running   0          56s     10.0.154.102   ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-xwc5w   1/1     Running   0          56s     10.0.152.77    ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-z7ql8   1/1     Running   0          56s     10.0.146.60    ip-10-0-147-219.ec2.internal   <none>           <none>
    game-2048     deployment-2048-8886b7b6b-z8vp9   1/1     Running   0          56s     10.0.158.178   ip-10-0-147-219.ec2.internal   <none>           <none>
    kube-system   aws-node-xn476                    1/1     Running   0          26m     10.0.147.219   ip-10-0-147-219.ec2.internal   <none>           <none>
    kube-system   coredns-79df7fff65-fspsl          1/1     Running   0          35m     10.0.156.64    ip-10-0-147-219.ec2.internal   <none>           <none>
    kube-system   coredns-79df7fff65-psggs          1/1     Running   0          35m     10.0.154.128   ip-10-0-147-219.ec2.internal   <none>           <none>
    kube-system   fluentd-elasticsearch-pmsf7       1/1     Running   0          2m55s   10.0.157.169   ip-10-0-147-219.ec2.internal   <none>           <none>
    kube-system   kube-proxy-628x7                  1/1     Running   0          26m     10.0.147.219   ip-10-0-147-219.ec2.internal   <none>           <none>
    
    $ kubectl describe pod deployment-2048-8886b7b6b-6g4cv -n game-2048
    ...
    Events:
      Type     Reason            Age                  From               Message
      ----     ------            ----                 ----               -------
      Warning  FailedScheduling  112s (x2 over 114s)  default-scheduler  0/1 nodes are available: 1 Too many pods. preemption: 0/1 nodes are available: 1 No preemption victims found for incoming pod.. 
    

기존과 동일하게 25개로 Pod를 배포하였으나 Pod 1개가 Too many Pod 메시지와 함께 Pending 상태로 유지됨을 확인할 수 있습니다. 기존 25개 모두 배포되었을 때와 비교하면 현재는 인스턴스의 프라이빗 IP 중 하나가 사용중이지 않은 상태입니다.

 

이러한 예시를 통해 다음과 같은 내용을 확인할 수 있었습니다.

  • --max-pods 에 호스트 네트워크를 사용하는 Pod의 갯수가 포함됩니다.
  • 호스트 네트워크를 사용하는 Pod를 사용하는 경우, 사용 가능한 프라이빗 IP가 남아 있음에도 Pod를 배포할 수 없습니다.

 

이미 —max-pods 계산식에도 호스트 네트워크를 사용하는 aws-node, kube-proxy 파드에 대해 고려되어 +2가 포함되었으나 그 외에 사용자가 생성한 별도의 DaemonSet 혹은 Amazon EBS CSI 드라이버[7], Amazon EFS CSI 드라이버[8] 등을 사용하는 경우에는 프라이빗 IP를 사용하지 않는 Pod로 인해 프라이빗 IP는 남아있지만 더 이상 Pod를 배포할 수 없는 상황이 발생할 수 있습니다.

 

만일 클러스터 사용시 호스트 네트워크를 사용하는 Pod에 대해 예상할 수 있는 상황이신 경우에는, Pod 배치에 대해 사전에 고려하시어 —max-pods 값을 증가시켜 Node에 사용 가능한 프라이빗 IP 낭비 없이 Pod를 배치 하실 수 있습니다.


References:

[1] https://docs.aws.amazon.com/ko_kr/AWSEC2/latest/UserGuide/using-eni.html#AvailableIpPerENI

[2] https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/

[3] https://github.com/awslabs/amazon-eks-ami/blob/master/files/max-pods-calculator.sh

[4] https://github.com/awslabs/amazon-eks-ami/blob/master/files/eni-max-pods.txt

[5] https://github.com/kubernetes-sigs/aws-load-balancer-controller/blob/v2.6.2/docs/examples/2048/2048_full.yaml

[6] https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/#create-a-daemonset

[7] https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/ebs-csi.html

[8] https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/efs-csi.html

profile pictureAWS
지원 엔지니어
게시됨 6달 전814회 조회