1. Amazon Linux 2

EKS Node Bootstrap

  1. AWS에서 제공하는 eks-node AMI를 사용하며 /etc/eks/bootstrap.sh 호출

  2. Bootstrap populates several files and make API queries to EKS

  3. Kubelet starts connects to the API server.

  4. Kubelet tries to register itself, role must match

  5. Kubelet creates Certificate Signing Request(CSR) and wait for it to be approved by EKS signer

  6. Certificated is signed, kubelet downloads it and serve traffic using 10250 port

    • https://kubernetes.io/docs/reference/access-authn-authz/kubelet-tls-bootstrapping/


EKS Node AMI

기본적으로 AMI가 설정된 Custom Launch Template을 사용하게되면 Userdata는 사용자에서 지정해야 한다. 그렇지 않은 모든 경우에는 EKS 서비스에서 자동으로 Injection 해준다.

Customized AMI Launch Template Userdata

Origin

MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="==MYBOUNDARY=="

--==MYBOUNDARY==
Content-Type: text/x-shellscript; charset="us-ascii"

#!/bin/bash
set -ex
/etc/eks/bootstrap.sh my-cluster \
  --b64-cluster-ca certificate-authority \
  --apiserver-endpoint api-server-endpoint \
  --dns-cluster-ip service-cidr.10 \
  --container-runtime containerd \
  --kubelet-extra-args '--max-pods=my-max-pods-value' \
  --use-max-pods false

--==MYBOUNDARY==--

Optional을 제외한 적용가능 Userdata - 클러스터명만 들어가면 모두 bootstrap.sh 에서 찾음

MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="==MYBOUNDARY=="

--==MYBOUNDARY==
Content-Type: text/x-shellscript; charset="us-ascii"

set -ex
/etc/eks/bootstrap.sh bys-dev-eks-main

--==MYBOUNDARY==--

kubelet에 추가적인 파라미터 설정을 위서 kubelet-extra-args

/etc/eks/bootstrap.sh cluster_name --kubelet-extra-args '--node-labels=something=hello,somethingelse=bye --register-with-taints=taint1=true'

Bootstrap.sh에서는 기본적으로 kubelet을 구동하기 위한 설정 및 값들이 적용된다.
버전에 따라 적용되는 내용이 다를 수 있으니 해당 내용을 꼭 참고한다.



2. Amazon Linux 2023

Requirement Configuration

---
apiVersion: node.eks.aws/v1alpha1
kind: NodeConfig
spec:
  cluster:
    name: my-cluster
    apiServerEndpoint: https://example.com
    certificateAuthority: Y2VydGlmaWNhdGVBdXRob3JpdHk=
    cidr: 172.20.0.0/16

Example

MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="BOUNDARY"

--BOUNDARY
Content-Type: application/node.eks.aws

---
apiVersion: node.eks.aws/v1alpha1
kind: NodeConfig
spec:
  cluster:
    name: my-cluster
    apiServerEndpoint: https://example.com
    certificateAuthority: Y2VydGlmaWNhdGVBdXRob3JpdHk=
    cidr: 172.20.0.0/16

--BOUNDARY--
Content-Type: application/node.eks.aws

---
apiVersion: node.eks.aws/v1alpha1
kind: NodeConfig
spec:
  kubelet:
    config:
      shutdownGracePeriod: 30s
      featureGates:
        DisableKubeletCloudCredentialProviders: true

--BOUNDARY--

Injected Userdata

# cat user-data.txt
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="//"

--//
Content-Type: application/node.eks.aws

---
apiVersion: node.eks.aws/v1alpha1
kind: NodeConfig
spec:
  cluster:
    apiServerEndpoint: https://1111122222.sk1.ap-northeast-2.eks.amazonaws.com
    certificateAuthority: Y2VydGlmaWNhdGVBdXRob3JpdHk==
    cidr: 172.20.0.0/16
    name: my-cluster
  kubelet:
    config:
      shutdownGracePeriod: 30s
      featureGates:
        DisableKubeletCloudCredentialProviders: true
      maxPods: 17
      clusterDNS:
      - 172.20.0.10
    flags:
    - "--node-labels=eks.amazonaws.com/nodegroup-image=ami-057051082a0861071,eks.amazonaws.com/capacityType=ON_DEMAND,eks.amazonaws.com/nodegroup=ng-al2023"
  # instance:
  #   localStorage: 
  #     strategy: 
  # containerd:
  #   config: |
  #     [plugins."io.containerd.grpc.v1.cri".containerd]
  #     discard_unpacked_layers = false

--//--



EKS managed node upgrade

EKS 클러스터 버전을 업데이트 한 이 후, EKS managed node group에 대한 버전 업데이트를 진행하면 다음과 같은 스텝으로 진행된다.

  1. 노드 그룹의 maximum unavailable 설정 값 까지 업그레이드해야 하는 노드를 무작위로 선택한다.
  2. 노드에서 Pods를 Draining 한다. Pods가 15분 이내에 노드를 떠나지 않고 force 플래그가 없으면 PodEvictionFailure 오류와 함께 업그레이드 단계가 실패한다. 이 경우에는 force 플래그를 적용하여 Pods를 강제로 삭제할 수 있다.
  3. 모든 Pod가 eviction되면 노드를 Cordon하고 60초를 기다린다. 이 작업은 service controller가 이 노드에 새 요청을 보내지 않고 active 노드 목록에서 이 노드를 제거하도록 하기 위해 수행된다.
  4. Cordon 노드를 위해 Auto Scaling 그룹에 종료 요청을 보냅니다.
  5. 이전 버전의 시작 템플릿으로 배포된 노드 그룹에 노드가 없을 때까지 이전 업그레이드 단계를 반복합니다.

이 단계에서 PodEvictionFailure 오류가 발생하는 이유는 다음과 같을 수 있다.



3. Bottlerocket

기본적으로 다음과 같은 ng-bottlerocket.yaml 파일을 생성하여 eksctl create nodegroup -f ng-bottlerocket.yaml 커맨드를 통해 생성할 수 있다.

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: bys-dev-eks-sec
  region: ap-northeast-2

vpc:
  id: "vpc-0ca96cd5c37d3bae8"
  subnets:
    private:
      bys-dev-sbn-az1-app:
          id: "subnet-0ea5be4984975e8ed"
      bys-dev-sbn-az2-app:
          id: "subnet-0b4076508ce121c27"

managedNodeGroups:
  - name: ng-bottlerocket
    amiFamily: Bottlerocket
    instanceType: m5.large
    volumeSize: 20
    minSize: 2
    maxSize: 4
    desiredCapacity: 2
    privateNetworking: true
    subnets:
      - bys-dev-sbn-az1-app
      - bys-dev-sbn-az2-app
    ssh:
      allow: true
      publicKeyName: "bys-console"
    tags:
      auto-delete: "no"

Custom AMI를 사용하는 LT를 통해 Bottlerocket을 이용할 때는 다음과 같이 userdata를 정의할 수 있다. 참고. Description of settings

settings.kubernetes.cluster-name = 'bys-dev-eks-sec'
settings.kubernetes.api-server = 'https://A2FAB60F0DBB94CDCD057CE2227F2252.yl4.ap-northeast-2.eks.amazonaws.com'
settings.kubernetes.cluster-certificate = '=='
settings.kubernetes.cluster-dns-ip = '172.20.0.10'
settings.kubernetes.max-pods = 20
settings.kubernetes.node-labels.'eks.amazonaws.com/sourceLaunchTemplateVersion' = '1'
settings.kubernetes.node-labels.'alpha.eksctl.io/cluster-name' = 'bys-dev-eks-sec'
settings.kubernetes.node-labels.'alpha.eksctl.io/nodegroup-name' = 'ng-bottlerocket'
settings.kubernetes.node-labels.'eks.amazonaws.com/nodegroup-image' = 'ami-0c0c53d11a3f898ea'
settings.kubernetes.node-labels.'eks.amazonaws.com/capacityType' = 'ON_DEMAND'
settings.kubernetes.node-labels.'eks.amazonaws.com/nodegroup' = 'ng-bottlerocket'
settings.kubernetes.node-labels.'eks.amazonaws.com/sourceLaunchTemplateId' = 'lt-05fd1822cf902ddbe'
settings.host-containers.admin.enabled = true

또는

[settings.host-containers.admin]
enabled = true

[settings.kubernetes]
cluster-name = 'bys-dev-eks-ue1'
api-server = 'https://DD583EB3684AFF562F6D89388683AB87.gr7.us-east-1.eks.amazonaws.com'
cluster-certificate = ''
node-labels.'label-key' = 'label-value'

Bottlerocket log 확인하는 방법

# Access admin container  
$ apiclient exec admin bash

# Get a full root shell
$ sudo sheltie

# Obtain an archive of log 
$ logdog
......
logs are at: /var/log/support/bottlerocket-logs.tar.gz

# To get bottlerocket log from node
kubectl get --raw "/api/v1/nodes/<node-name>/proxy/logs/support/bottlerocket-logs.tar.gz" > bottlerocket-logs.tar.gz



4. GPU

EKS에서 GPU는 주로 Nvidia 사의 칩을 사용한다. Nvidia의 제품군으로는 Nvidia Tesla가 존재하며 대략적으로 아래와 같다.

2007년: Tesla C80 (G80 아키텍처)
2009년: Tesla M10 (GT200 아키텍처)
2010년: Tesla S10 (Fermi 아키텍처)
2012년: Tesla K10, K20 (Kepler 아키텍처)
2013년: Tesla K40 (Kepler 아키텍처)
2014년: Tesla M40 (Maxwell 아키텍처)
2016년: Tesla P40, P100 (Pascal 아키텍처)
2017년: Tesla V100 (Volta 아키텍처)
2018년: Tesla T40 (Turing 아키텍처)
2019년: Tesla A100 (Ampere 아키텍처)
2020년: Tesla A30, A40 (Ampere 아키텍처)
......
2023년: L4 (Ada Lovelace 아키텍처)
2023년: H100 (Hopper 아키텍처)
2024년: H200 (Hopper 아키텍처)
2024년: GB200 NVL72 (Blackwell 아키텍처)

2020년 5월 부터 Nvidia는 테슬라 브랜드를 중단하고, 데이터 센터 GPU 제품으로 이름을 변경하였다. Nvidia GPU 이름에서 볼 수 있는 Tensor Core는 Nvidia의 인공지능(AI) 및 고성능 컴퓨팅(HPC) 분야에서 복잡한 계산을 향상시키도록 설계된 전용 하드웨어 가속기로 Volta 아키텍처에 처음 도입된 후 Turing, Ampere, Ada Lovelace, Hopper, Blackwell 아키텍처 등에 지속적으로 적용되고 있다.


AWS GPU Node

Amazon EC2에는 P3, P4, G3, G4, G5 등의 타입이 존재하며 각 인스턴스 타입에 따라 Tesla V100, A100 등의 GPU가 탑재된다. 또한 AWS에서는 Graviton 이라고 하는 ARM기반의 GPU를 제공하는데 현재 G5g 인스턴스타입에는 NVIDIA T4G Tensor Core GPU가 탑재되어 있다. 이 GPU는 ARM 기반 프로세서에서 동작하도록 설계된 Nvidia GPU다.


EKS GPU

EKS 클러스터에 GPU 노드를 설치하기 위해서는 GPU 기반의 최적화 이미지를 사용해야 한다. EKS AMI Release를 확인하면 AL2_x86_64_GPU 이미지에는 efa(Elastic Fabric Adapter, 고성능 컴퓨팅(HPC) 및 머신 러닝 애플리케이션의 성능을 크게 향상시키는 네트워크 디바이스), cuda-12-2, nvidia-driver-latest-dkms 등의 패키지가 기본적으로 설치된 것을 알 수 있다.

the accelerated AMI includes the following:


NVIDIA device plugin for Kubernetes

쿠버네티스 NVIDIA device plugin 이란 GPU노드에 있는 GPU를 사용가능하도록 하는 Device 장치다. 기본적으로 쿠버네티스는 GPU 같은 특수 하드웨어 리소스를 직접 관리하지 않기 때문에 NVIDIA device plugin을 사용하여 쿠버네티스가 NVIDIA GPU를 인식하고 이를 컨테이너에 할당할 수 있게 해야한다[5]. 만약, Nvidia device plugin을 배포하지 않고, nvidia.com/gpu 리소스를 요청하는 파드를 배포하면 gpu 노드가 존재하더라도 pending 상태로 스케줄이 불가능하다.

After install nvidia-device-plugin

│ Capacity:
│   cpu:                4
│   ephemeral-storage:  20959212Ki
│   hugepages-1Gi:      0
│   hugepages-2Mi:      0
│   memory:             16069028Ki
│   nvidia.com/gpu:     1
│   pods:               29
│ Allocatable:
│   cpu:                3920m
│   ephemeral-storage:  18242267924
│   hugepages-1Gi:      0
│   hugepages-2Mi:      0
│   memory:             15378852Ki
│   nvidia.com/gpu:     1
│   pods:               29

Deployment via helm

helm upgrade -i nvdp nvdp/nvidia-device-plugin \
  --namespace nvidia-device-plugin \
  --create-namespace \
  --version 0.15.0

Deployment via manifest Change version vX.X.X to release version. Refer to release version

kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/vX.X.X/deployments/static/nvidia-device-plugin.yml

이 때 기본으로 적용되는 values.yaml파일의 특정 Affinity가 적용되도록 GPU 노드의 레이블을 추가해준다.

정상적인 배포가 완료되면 nvidia-device-plugin이 Running 상태가 되고, nvidia.com/gpu 리소스를 요청하는 파드는 정상 생성된다.

$ k get po -n nvidia-device-plugin
NAME                              READY   STATUS    RESTARTS   AGE
nvdp-nvidia-device-plugin-srhsw   1/1     Running   0          3m30s

이 후 nvidia-smi(System Management Interface)를 통해 정보를 출력해 볼 수 있다. 이를 통해 Nvidia Driver 버전 535.161.08을 사용하고 있음을 알 수 있다.

$ kubectl logs -f nvidia-smi

Mon May 27 04:39:23 2024
+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 535.161.08             Driver Version: 535.161.08   CUDA Version: 12.4     |
|-----------------------------------------+----------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |         Memory-Usage | GPU-Util  Compute M. |
|                                         |                      |               MIG M. |
|=========================================+======================+======================|
|   0  Tesla T4                       On  | 00000000:00:1E.0 Off |                    0 |
| N/A   26C    P8              11W /  70W |      0MiB / 15360MiB |      0%      Default |
|                                         |                      |                  N/A |
+-----------------------------------------+----------------------+----------------------+

+---------------------------------------------------------------------------------------+
| Processes:                                                                            |
|  GPU   GI   CI        PID   Type   Process name                            GPU Memory |
|        ID   ID                                                             Usage      |
|=======================================================================================|
|  No running processes found                                                           |
+---------------------------------------------------------------------------------------+


NVIDIA GPU Operator with Amazon EKS

EKS 최적화 AMI와 NVIDIA device plugin for Kubernetes를 배포하여 사용하는 것은 가장 기본설정이지만 몇 가지 제약사항이 존재한다.

  1. AMI에 설치되어 릴리즈되는 NVIDIA GPU driver 버전과 NVIDIA container runtime 버전은 Nvidia에서 릴리즈하는 일정보다 느리다.
  2. NVIDIA device plugin을 항상 배포해야 하며, 사용자가 업그레이드를 통해 버전을 관리해야 한다.

위 제약사항을 수용할 수 있다면 최적화 이미지에 Nvidia 장치 플러그인을 직접 배포(설치)하여 사용하면 되지만, 이러한 제약사항을 벗어나기 위해서는 NVIDIA GPU Operator를 사용할 수 있다.



5. EKS Windows node

Windows 노드를 사용하기 위해서는 Windows 문서를 확인할 필요가 있다.

Summary

Creation

eksctl create nodegroup \
    --region ap-northeast-2 \
    --cluster eks-win \
    --name ng-win-v1 \
    --node-type m5.large \
    --nodes 2 \
    --nodes-min 2 \
    --nodes-max 2 \
    --managed=false \
    --node-ami-family WindowsServer2022FullContainer

managedNodeGroups:
  - name: ng-win-v1
    amiFamily: WindowsServer2022FullContainer
    instanceType: m5.large
    volumeSize: 80
    minSize: 2
    maxSize: 2
    desiredCapacity: 2
    privateNetworking: true
    subnets:
      - subnet1
      - subnet2
    ssh:
      allow: true
      publicKeyName: "keypair"
    tags:
      auto-delete: "no"

📚 References