본문 바로가기
스터디 이야기/25' AWS EKS

AWS EKS에서 Graviton 인스턴스 사용하기(Feat. GPU Time-Slicing)

by lakescript 2025. 4. 23.
728x90

들어가며

 

 

요즘 GPU에 대한 수요는 폭발적으로 증가하고 있습니다. 하지만 고가의 GPU 자원을 효율적으로 운영하는 것은 여전히 어려운 과제이며, 리소스 낭비나 충돌이 빈번하게 발생하는 상황입니다.

이러한 문제를 해결할 수 있는 기술로는 GPU 가상화Time-Slicing이 있습니다. 이번 포스팅에서는 Amazon EKS 환경에서 GPU를 사용하는 기본적인 구성부터, Time-Slicing을 적용한 GPU 공유 방식까지 실습을 진행해보겠습니다.

 

GPU 활용의 과거와 미래

GPU 사용의 필요성

머신러닝과 딥러닝이 요즘 가장 핫하고 유망한 기술로 평가받고 있습니다. 그렇기 때문에 점점 더 복잡하고 계산 집약적인 모델을 다루게 되었고, 이러한 모델을 효과적으로 학습하고 배포하기 위해서는 고성능 컴퓨팅 자원이 필수입니다. 특히 병렬 처리에 뛰어난 GPU는 딥러닝 워크로드 가속화에 있어 핵심적인 역할을 합니다.

 

베어메탈 기반 GPU 활용의 한계

과거에는 ML 엔지니어들이 베어메탈 서버에 직접 GPU 드라이버와 라이브러리를 설치해 사용하는 방식이 일반적이었습니다. 하지만 이 방식은 여러 가지 문제점을 가지고 있었는데, CUDA나 cuDNN과 같은 드라이버 스택 설치가 복잡하고, 프레임워크별로 지원하는 버전이 달라 호환성 문제도 자주 발생했습니다. 또한 동일한 실험 환경을 다른 시스템에서 재현하기 어려워 협업이나 배포에 어려움이 많았습니다.

 

리소스 측면에서도 고가의 GPU가 특정 사용자에게 고정되면서 전체 활용률이 떨어지고, 인프라 확장 시마다 위와 같은 설정 과정을 반복해야 하기에 굉장히 비효율적이었습니다.

 

컨테이너 기술의 한계와 GPU 가상화의 필요성

위와 같은 GPU 활용의 한계를 해결하기 위해 컨테이너 기술이 도입되었는데, 컨테이너 기술의 핵심인 Linux 커널의 Cgroups와 Namespaces로는 GPU와 같은 특수 장치에 대해서는 몇 가지 제약이 존재했습니다. 특히, GPU는 CPU 코어나 메모리처럼 단순히 나누어 쓰기 어려웠기 때문에, 하나의 GPU를 여러 컨테이너에서 효율적으로 공유하는 데 한계가 있었습니다.

이러한 구조적 한계를 극복하기 위해 GPU 가상화 기술이 등장하게 되었습니다. 대표적으로 NVIDIA의 A100 GPU에서 제공하는 MIG(Multi-Instance GPU) 기능은 하나의 물리 GPU를 여러 개의 독립된 인스턴스로 분할하여 사용할 수 있게 해 줍니다. 각 인스턴스는 메모리, 캐시, 컴퓨팅 코어를 하드웨어 수준에서 격리해 운영되며, 서로 다른 모델이나 워크로드를 동시에 실행할 수 있습니다. 예를 들어 A100 40GB GPU는 20GB 크기의 인스턴스 2개, 혹은 10GB 2개 + 5GB 4개처럼 유연하게 나눌 수 있습니다. 이렇듯 MIG는 베어메탈 환경에서도 사용할 수 있어, 자원의 활용률을 극대화하고 다양한 요구에 대응할 수 있습니다.

 

vGPU를 통한 VM 단위 GPU 분할 사용

vGPU(Virtual GPU)는 하이퍼바이저 기반의 GPU 가상화 방식으로, 여러 개의 가상 머신이 하나의 GPU를 공유할 수 있게 합니다. vCPU와 유사하게, 연산 리소스는 시간 단위로 분할되고, GPU 메모리는 각 vGPU 인스턴스마다 고정 구간을 갖도록 설계되어 있습니다. 


하지만, 여전히 GPU 드라이버와 라이브러리는 매우 민감하고 복잡한 구성 요소로 남아 있습니다. 사용자 공간 라이브러리와 커널 드라이버 간의 긴밀한 연동이 필요하고, /dev/nvidia*와 같은 장치 파일 접근을 안전하게 통제하는 것도 여전히 중요한 과제입니다.

 

현재의 GPU 활용

결과적으로, 컨테이너 기술과 GPU 가상화 기술(MIG, vGPU 등)의 도입은 딥러닝 인프라 환경의 효율성과 확장성을 크게 개선했습니다. 이제는 동일한 GPU를 다양한 팀이 나누어 쓰거나, 다양한 형태의 워크로드를 동시에 실행하는 것이 가능해졌으며, 자원 활용률 역시 획기적으로 향상되었습니다. 다만 여전히 드라이버 구성과 보안 통제 등 해결해야 할 기술적 과제는 존재합니다.

 

실습 환경 구성

Quota 신청

https://us-west-2.console.aws.amazon.com/servicequotas/home/services/ec2/quotas

 

https://us-west-2.console.aws.amazon.com/servicequotas/home/services/ec2/quotas

 

us-west-2.console.aws.amazon.com

 

기본적으로 Graviton 리소스의 Quota는 0으로 설정되어 있습니다. 그렇기 때문에 실습하기 전에 위의 링크에 접속하여 Graviton 리소스에 대해 Quota를 증설해야 합니다.

 

먼저 Service Quotas 메뉴에 접근하신 후 AWS 서비스 탭에서 on-Demand G까지 검색하시면 위와 같이 현재 계정에서 할당된  G 타입 인스턴스를 확인하실 수 있습니다.

현재 저는 기본 할당량이 0인데요. 해당 값을 늘려주셔야 EC2에서 인스턴스를 생성할 때 G 타입의 인스턴스를 생성하실 수 있습니다. 

 

 

왼쪽 사진에서 상단에 보이는 [계정 수준에서 증가 요청] 버튼을 클릭하시면 오른쪽 사진과 같이 할당을 늘리는 요청을 하실 수 있습니다.

 

 

요청이 되었으면 [요청 기록]에서 최근 할당량 증가 요청 세션에 대기 중인 요청을 확인하실 수 있습니다.

 

그 후 할당량 증가 요청에 대한 AWS 측에서의 확인 메일이 오게 됩니다.

 

 

위와 같이 보통 1~2일 정도 후면 요청이 승인되며, 1시간 정도 후에 본격적으로 G 타입의 EC2 인스턴스를 생성하실 수 있게 됩니다.

 

 

요청이 승인된 후 다시 Service Quota 메뉴에서 확인해 보면 위와 같이 요청이 승인되어 요청한 할당량만큼 값이 변경되어 있음을 확인하실 수 있습니다.

 

 

eksdemo 설치

https://github.com/awslabs/eksdemo#install-eksdemo

 

GitHub - awslabs/eksdemo: The easy button for learning, testing and demoing Amazon EKS

The easy button for learning, testing and demoing Amazon EKS - awslabs/eksdemo

github.com

 

그 후 AWS EKS의 클러스터를 쉽게 생성하고 관리할 수 있고, 빠르게 테스트해 볼 수 있도록 AWS에서 제공하는 eksdemo를 설치해 줍니다.

 

brew tap aws/tap
brew install eksdemo

 

위 명령어를 통해 설치를 진행합니다.

 

 

설치 시 Error가 발생한다면?

더보기

 

사전에 eksctl을 Homebrew로 설치한 경우, 위와 같이 eksctl 삭제를 먼저 진행하라는 안내가 나올 수 있습니다.  이는 eksdemo가 공식 Weaveworks 탭(weaveworks/tap)을 종속적으로 사용하기 때문입니다.

brew uninstall eksctl

 

eksctl을 삭제 후 eksdemo를 다시 설치하면 정상적으로 진행됩니다.

 

eksdemo version

 

version 명령어를 통해 설치가 되었는지 확인합니다.

 

 

AWS EKS Cluster 생성하기 전 리소스 확인

eksdemo create cluster gpusharing-demo -i t3.large -N 2 --region us-west-2 --dry-run

 

eksdemo를 통해 EKS Cluster를 생성하는데, 먼저 dry-run으로 생성될 리소스를 확인합니다.

 

각 옵션에 대해 설명을 하자면 아래와 같습니다.

 

 eksdemo create cluster   : 새로운 EKS Cluster를 생성하는 명령

 gpusharing-demo   : Cluster의 이름을 설정

 -i t3.large   :  -i   옵션으로 Cluster의 Worker Node로 사용할 EC2의 유형 지정 

 -N 2   :  -N  옵션으로 NodeGroup에 포함될 Node의 수를 지정

 

 

위 명령으로로 EKS Cluster를 생성하는데,  t3.large  타입의 일단 CPU를 사용하는 노드 그룹을 생성합니다.

 

dry-run 결과

더보기
Eksctl Resource Manager Dry Run:
eksctl create cluster -f -
---
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: gpusharing-demo
  region: us-west-2
  version: "1.32"
  tags:
    eksdemo.io/version: 0.18.2

addons:
- name: vpc-cni
  version: latest
  configurationValues: |-
    enableNetworkPolicy: "true"
    env:
      ENABLE_PREFIX_DELEGATION: "false"

cloudWatch:
  clusterLogging:
    enableTypes: ["*"]

iam:
  withOIDC: true
  serviceAccounts:
  - metadata:
      name: aws-load-balancer-controller
      namespace: awslb
    roleName: eksdemo.us-west-2.gpusharing-demo.awslb.aws-load-balanc-e4dab3bd
    roleOnly: true
    attachPolicy:
      Version: '2012-10-17'
      Statement:
      - Effect: Allow
        Action:
        - iam:CreateServiceLinkedRole
        Resource: "*"
        Condition:
          StringEquals:
            iam:AWSServiceName: elasticloadbalancing.amazonaws.com
      - Effect: Allow
        Action:
        - ec2:DescribeAccountAttributes
        - ec2:DescribeAddresses
        - ec2:DescribeAvailabilityZones
        - ec2:DescribeInternetGateways
        - ec2:DescribeVpcs
        - ec2:DescribeVpcPeeringConnections
        - ec2:DescribeSubnets
        - ec2:DescribeSecurityGroups
        - ec2:DescribeInstances
        - ec2:DescribeNetworkInterfaces
        - ec2:DescribeTags
        - ec2:GetCoipPoolUsage
        - ec2:DescribeCoipPools
        - elasticloadbalancing:DescribeLoadBalancers
        - elasticloadbalancing:DescribeLoadBalancerAttributes
        - elasticloadbalancing:DescribeListeners
        - elasticloadbalancing:DescribeListenerCertificates
        - elasticloadbalancing:DescribeSSLPolicies
        - elasticloadbalancing:DescribeRules
        - elasticloadbalancing:DescribeTargetGroups
        - elasticloadbalancing:DescribeTargetGroupAttributes
        - elasticloadbalancing:DescribeTargetHealth
        - elasticloadbalancing:DescribeTags
        - elasticloadbalancing:DescribeTrustStores
        - elasticloadbalancing:DescribeListenerAttributes
        Resource: "*"
      - Effect: Allow
        Action:
        - cognito-idp:DescribeUserPoolClient
        - acm:ListCertificates
        - acm:DescribeCertificate
        - iam:ListServerCertificates
        - iam:GetServerCertificate
        - waf-regional:GetWebACL
        - waf-regional:GetWebACLForResource
        - waf-regional:AssociateWebACL
        - waf-regional:DisassociateWebACL
        - wafv2:GetWebACL
        - wafv2:GetWebACLForResource
        - wafv2:AssociateWebACL
        - wafv2:DisassociateWebACL
        - shield:GetSubscriptionState
        - shield:DescribeProtection
        - shield:CreateProtection
        - shield:DeleteProtection
        Resource: "*"
      - Effect: Allow
        Action:
        - ec2:AuthorizeSecurityGroupIngress
        - ec2:RevokeSecurityGroupIngress
        Resource: "*"
      - Effect: Allow
        Action:
        - ec2:CreateSecurityGroup
        Resource: "*"
      - Effect: Allow
        Action:
        - ec2:CreateTags
        Resource: arn:aws:ec2:*:*:security-group/*
        Condition:
          StringEquals:
            ec2:CreateAction: CreateSecurityGroup
          'Null':
            aws:RequestTag/elbv2.k8s.aws/cluster: 'false'
      - Effect: Allow
        Action:
        - ec2:CreateTags
        - ec2:DeleteTags
        Resource: arn:aws:ec2:*:*:security-group/*
        Condition:
          'Null':
            aws:RequestTag/elbv2.k8s.aws/cluster: 'true'
            aws:ResourceTag/elbv2.k8s.aws/cluster: 'false'
      - Effect: Allow
        Action:
        - ec2:AuthorizeSecurityGroupIngress
        - ec2:RevokeSecurityGroupIngress
        - ec2:DeleteSecurityGroup
        Resource: "*"
        Condition:
          'Null':
            aws:ResourceTag/elbv2.k8s.aws/cluster: 'false'
      - Effect: Allow
        Action:
        - elasticloadbalancing:CreateLoadBalancer
        - elasticloadbalancing:CreateTargetGroup
        Resource: "*"
        Condition:
          'Null':
            aws:RequestTag/elbv2.k8s.aws/cluster: 'false'
      - Effect: Allow
        Action:
        - elasticloadbalancing:CreateListener
        - elasticloadbalancing:DeleteListener
        - elasticloadbalancing:CreateRule
        - elasticloadbalancing:DeleteRule
        Resource: "*"
      - Effect: Allow
        Action:
        - elasticloadbalancing:AddTags
        - elasticloadbalancing:RemoveTags
        Resource:
        - arn:aws:elasticloadbalancing:*:*:targetgroup/*/*
        - arn:aws:elasticloadbalancing:*:*:loadbalancer/net/*/*
        - arn:aws:elasticloadbalancing:*:*:loadbalancer/app/*/*
        Condition:
          'Null':
            aws:RequestTag/elbv2.k8s.aws/cluster: 'true'
            aws:ResourceTag/elbv2.k8s.aws/cluster: 'false'
      - Effect: Allow
        Action:
        - elasticloadbalancing:AddTags
        - elasticloadbalancing:RemoveTags
        Resource:
        - arn:aws:elasticloadbalancing:*:*:listener/net/*/*/*
        - arn:aws:elasticloadbalancing:*:*:listener/app/*/*/*
        - arn:aws:elasticloadbalancing:*:*:listener-rule/net/*/*/*
        - arn:aws:elasticloadbalancing:*:*:listener-rule/app/*/*/*
      - Effect: Allow
        Action:
        - elasticloadbalancing:ModifyLoadBalancerAttributes
        - elasticloadbalancing:SetIpAddressType
        - elasticloadbalancing:SetSecurityGroups
        - elasticloadbalancing:SetSubnets
        - elasticloadbalancing:DeleteLoadBalancer
        - elasticloadbalancing:ModifyTargetGroup
        - elasticloadbalancing:ModifyTargetGroupAttributes
        - elasticloadbalancing:DeleteTargetGroup
        - elasticloadbalancing:ModifyListenerAttributes
        Resource: "*"
        Condition:
          'Null':
            aws:ResourceTag/elbv2.k8s.aws/cluster: 'false'
      - Effect: Allow
        Action:
        - elasticloadbalancing:AddTags
        Resource:
        - arn:aws:elasticloadbalancing:*:*:targetgroup/*/*
        - arn:aws:elasticloadbalancing:*:*:loadbalancer/net/*/*
        - arn:aws:elasticloadbalancing:*:*:loadbalancer/app/*/*
        Condition:
          StringEquals:
            elasticloadbalancing:CreateAction:
            - CreateTargetGroup
            - CreateLoadBalancer
          'Null':
            aws:RequestTag/elbv2.k8s.aws/cluster: 'false'
      - Effect: Allow
        Action:
        - elasticloadbalancing:RegisterTargets
        - elasticloadbalancing:DeregisterTargets
        Resource: arn:aws:elasticloadbalancing:*:*:targetgroup/*/*
      - Effect: Allow
        Action:
        - elasticloadbalancing:SetWebAcl
        - elasticloadbalancing:ModifyListener
        - elasticloadbalancing:AddListenerCertificates
        - elasticloadbalancing:RemoveListenerCertificates
        - elasticloadbalancing:ModifyRule
        Resource: "*"

  - metadata:
      name: ebs-csi-controller-sa
      namespace: kube-system
    roleName: eksdemo.us-west-2.gpusharing-demo.kube-system.ebs-csi-c-937ae3a3
    roleOnly: true
    attachPolicyARNs:
    - arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy
  - metadata:
      name: external-dns
      namespace: external-dns
    roleName: eksdemo.us-west-2.gpusharing-demo.external-dns.external-dns
    roleOnly: true
    attachPolicy:
      Version: '2012-10-17'
      Statement:
      - Effect: Allow
        Action:
        - route53:ChangeResourceRecordSets
        Resource:
        - arn:aws:route53:::hostedzone/*
      - Effect: Allow
        Action:
        - route53:ListHostedZones
        - route53:ListResourceRecordSets
        - route53:ListTagsForResource
        Resource:
        - "*"

  - metadata:
      name: karpenter
      namespace: karpenter
    roleName: eksdemo.us-west-2.gpusharing-demo.karpenter.karpenter
    roleOnly: true
    attachPolicy:
      Version: "2012-10-17"
      Statement:
      - Sid: AllowScopedEC2InstanceAccessActions
        Effect: Allow
        Resource:
        - arn:aws:ec2:us-west-2::image/*
        - arn:aws:ec2:us-west-2::snapshot/*
        - arn:aws:ec2:us-west-2:*:security-group/*
        - arn:aws:ec2:us-west-2:*:subnet/*
        Action:
        - ec2:RunInstances
        - ec2:CreateFleet
      - Sid: AllowScopedEC2LaunchTemplateAccessActions
        Effect: Allow
        Resource: arn:aws:ec2:us-west-2:*:launch-template/*
        Action:
        - ec2:RunInstances
        - ec2:CreateFleet
        Condition:
          StringEquals:
            aws:ResourceTag/kubernetes.io/cluster/gpusharing-demo: owned
          StringLike:
            aws:ResourceTag/karpenter.sh/nodepool: "*"
      - Sid: AllowScopedEC2InstanceActionsWithTags
        Effect: Allow
        Resource:
        - arn:aws:ec2:us-west-2:*:fleet/*
        - arn:aws:ec2:us-west-2:*:instance/*
        - arn:aws:ec2:us-west-2:*:volume/*
        - arn:aws:ec2:us-west-2:*:network-interface/*
        - arn:aws:ec2:us-west-2:*:launch-template/*
        - arn:aws:ec2:us-west-2:*:spot-instances-request/*
        Action:
        - ec2:RunInstances
        - ec2:CreateFleet
        - ec2:CreateLaunchTemplate
        Condition:
          StringEquals:
            aws:RequestTag/kubernetes.io/cluster/gpusharing-demo: owned
            aws:RequestTag/eks:eks-cluster-name: gpusharing-demo
          StringLike:
            aws:RequestTag/karpenter.sh/nodepool: "*"
      - Sid: AllowScopedResourceCreationTagging
        Effect: Allow
        Resource:
        - arn:aws:ec2:us-west-2:*:fleet/*
        - arn:aws:ec2:us-west-2:*:instance/*
        - arn:aws:ec2:us-west-2:*:volume/*
        - arn:aws:ec2:us-west-2:*:network-interface/*
        - arn:aws:ec2:us-west-2:*:launch-template/*
        - arn:aws:ec2:us-west-2:*:spot-instances-request/*
        Action: ec2:CreateTags
        Condition:
          StringEquals:
            aws:RequestTag/kubernetes.io/cluster/gpusharing-demo: owned
            aws:RequestTag/eks:eks-cluster-name: gpusharing-demo
            ec2:CreateAction:
            - RunInstances
            - CreateFleet
            - CreateLaunchTemplate
          StringLike:
            aws:RequestTag/karpenter.sh/nodepool: "*"
      - Sid: AllowScopedResourceTagging
        Effect: Allow
        Resource: arn:aws:ec2:us-west-2:*:instance/*
        Action: ec2:CreateTags
        Condition:
          StringEquals:
            aws:ResourceTag/kubernetes.io/cluster/gpusharing-demo: owned
          StringLike:
            aws:ResourceTag/karpenter.sh/nodepool: "*"
          StringEqualsIfExists:
            aws:RequestTag/eks:eks-cluster-name: gpusharing-demo
          ForAllValues:StringEquals:
            aws:TagKeys:
            - eks:eks-cluster-name
            - karpenter.sh/nodeclaim
            - Name
      - Sid: AllowScopedDeletion
        Effect: Allow
        Resource:
        - arn:aws:ec2:us-west-2:*:instance/*
        - arn:aws:ec2:us-west-2:*:launch-template/*
        Action:
        - ec2:TerminateInstances
        - ec2:DeleteLaunchTemplate
        Condition:
          StringEquals:
            aws:ResourceTag/kubernetes.io/cluster/gpusharing-demo: owned
          StringLike:
            aws:ResourceTag/karpenter.sh/nodepool: "*"
      - Sid: AllowRegionalReadActions
        Effect: Allow
        Resource: "*"
        Action:
        - ec2:DescribeImages
        - ec2:DescribeInstances
        - ec2:DescribeInstanceTypeOfferings
        - ec2:DescribeInstanceTypes
        - ec2:DescribeLaunchTemplates
        - ec2:DescribeSecurityGroups
        - ec2:DescribeSpotPriceHistory
        - ec2:DescribeSubnets
        Condition:
          StringEquals:
            aws:RequestedRegion: "us-west-2"
      - Sid: AllowSSMReadActions
        Effect: Allow
        Resource: arn:aws:ssm:us-west-2::parameter/aws/service/*
        Action:
        - ssm:GetParameter
      - Sid: AllowPricingReadActions
        Effect: Allow
        Resource: "*"
        Action:
        - pricing:GetProducts
      - Sid: AllowInterruptionQueueActions
        Effect: Allow
        Resource: arn:aws:sqs:us-west-2:346614024082:karpenter-gpusharing-demo
        Action:
        - sqs:DeleteMessage
        - sqs:GetQueueUrl
        - sqs:ReceiveMessage
      - Sid: AllowPassingInstanceRole
        Effect: Allow
        Resource: arn:aws:iam::346614024082:role/KarpenterNodeRole-gpusharing-demo
        Action: iam:PassRole
        Condition:
          StringEquals:
            iam:PassedToService:
            - ec2.amazonaws.com
            - ec2.amazonaws.com.cn
      - Sid: AllowScopedInstanceProfileCreationActions
        Effect: Allow
        Resource: arn:aws:iam::346614024082:instance-profile/*
        Action:
        - iam:CreateInstanceProfile
        Condition:
          StringEquals:
            aws:RequestTag/kubernetes.io/cluster/gpusharing-demo: owned
            aws:RequestTag/eks:eks-cluster-name: gpusharing-demo
            aws:RequestTag/topology.kubernetes.io/region: "us-west-2"
          StringLike:
            aws:RequestTag/karpenter.k8s.aws/ec2nodeclass: "*"
      - Sid: AllowScopedInstanceProfileTagActions
        Effect: Allow
        Resource: arn:aws:iam::346614024082:instance-profile/*
        Action:
        - iam:TagInstanceProfile
        Condition:
          StringEquals:
            aws:ResourceTag/kubernetes.io/cluster/gpusharing-demo: owned
            aws:ResourceTag/topology.kubernetes.io/region: "us-west-2"
            aws:RequestTag/kubernetes.io/cluster/gpusharing-demo: owned
            aws:RequestTag/eks:eks-cluster-name: gpusharing-demo
            aws:RequestTag/topology.kubernetes.io/region: "us-west-2"
          StringLike:
            aws:ResourceTag/karpenter.k8s.aws/ec2nodeclass: "*"
            aws:RequestTag/karpenter.k8s.aws/ec2nodeclass: "*"
      - Sid: AllowScopedInstanceProfileActions
        Effect: Allow
        Resource: arn:aws:iam::346614024082:instance-profile/*
        Action:
        - iam:AddRoleToInstanceProfile
        - iam:RemoveRoleFromInstanceProfile
        - iam:DeleteInstanceProfile
        Condition:
          StringEquals:
            aws:ResourceTag/kubernetes.io/cluster/gpusharing-demo: owned
            aws:ResourceTag/topology.kubernetes.io/region: "us-west-2"
          StringLike:
            aws:ResourceTag/karpenter.k8s.aws/ec2nodeclass: "*"
      - Sid: AllowInstanceProfileReadActions
        Effect: Allow
        Resource: arn:aws:iam::346614024082:instance-profile/*
        Action: iam:GetInstanceProfile
      - Sid: AllowAPIServerEndpointDiscovery
        Effect: Allow
        Resource: arn:aws:eks:us-west-2:346614024082:cluster/gpusharing-demo
        Action: eks:DescribeCluster


vpc:
  cidr: 192.168.0.0/16
  hostnameType: resource-name

managedNodeGroups:
- name: main
  ami: ami-055638a1af9c8856d
  amiFamily: AmazonLinux2
  desiredCapacity: 2
  iam:
    attachPolicyARNs:
    - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
    - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
    - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
  instanceType: t3.large
  minSize: 0
  maxSize: 10
  volumeSize: 80
  volumeType: gp3
  overrideBootstrapCommand: |
    #!/bin/bash
    /etc/eks/bootstrap.sh gpusharing-demo
  privateNetworking: true
  spot: false

 

eksdemo로 EKS Cluster 생성

해당 명령어는 대략 15~20분 정도 소요되니 참고 바랍니다.
eksdemo create cluster gpusharing-demo -i t3.large -N 2 --region us-west-2

 

이제 위에서 실행한 명령어에서 dry-run 옵션을 제외하고 실행시킵니다.

 

 

위와 같이 실행로그가 찍히는 것을 확인하실 수 있습니다.

 

AWS Console의 CloudFormation에서도 해당 stack을 확인하실 수 있습니다. 

( eksdemo는 내부적으로 eksctl을 호출하고, eksctl은 CloudFormation을 사용하기 때문입니다. )

 

 

이제 EKS Cluster는 생성이 완료되었습니다.

 

 

 t3.large  type의 EC2 노드그룹이 할당된 것을 확인하실 수 있습니다.

 

GPU Nodegroup 생성하기 전 리소스 확인

eksdemo create nodegroup gpu -i g5.8xlarge -N 1 -c gpusharing-demo --dry-run

 

dry-run 명령어를 통해 생성될 노드 그룹에 대해 먼저 확인해 보겠습니다.

 

gpu라는 이름의 노드그룹을 생성하는데,  -i  옵션으로 EC2 type을 g5.8 xlarge로 설정하고  -N  옵션으로 노드의 개수를 지정합니다.

 

dry-run 결과

더보기
Eksctl Resource Manager Dry Run:
eksctl create nodegroup -f - --install-nvidia-plugin=false --install-neuron-plugin=false
---
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: gpusharing-demo
  region: us-west-2
  version: "1.32"
  tags:
    eksdemo.io/version: 0.18.2

managedNodeGroups:
- name: gpu
  ami: ami-0874f6b71e79b254a
  amiFamily: AmazonLinux2
  desiredCapacity: 1
  iam:
    attachPolicyARNs:
    - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
    - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
    - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
  instanceType: g5.8xlarge
  minSize: 0
  maxSize: 10
  volumeSize: 80
  volumeType: gp3
  overrideBootstrapCommand: |
    #!/bin/bash
    /etc/eks/bootstrap.sh gpusharing-demo
  privateNetworking: true
  spot: false
  taints:
  - key: nvidia.com/gpu
    value: ""
    effect: NoSchedule

 

GPU 노드 그룹 생성

해당 명령어는 약 5분 정도 소요됩니다.
eksdemo create nodegroup gpu -i g5.2xlarge -N 1 -c gpusharing-demo

 

이제 dry-run 옵션을 제외하고 명령어를 실행합니다.

 

 

위와 같이 생성되는 것을 확인하실 수 있습니다.

 

실습 - Time-Slicing 하지 않았을 때

Node 목록 상세 확인

kubectl get node --label-columns=node.kubernetes.io/instance-type,eks.amazonaws.com/capacityType,topology.kubernetes.io/zone

 

위 명령어를 통해 각 노드의 인스턴스 타입, 온디맨드/스폿 여부, 가용 영역 라벨 정보를 함께 확인해 보겠습니다.

 

기존 node 그룹에 의해 생성된 t3.large 타입의 노드 2대와 새롭게 gpu node 그룹으로 생성된 g5.xlarge 타입의 노드 1대를 확인하실 수 있습니다.

 

GPU 노드에 label 설정

kubectl label node i-0730fd5df43f1ecf3.us-west-2.compute.internal eks-node=gpu

 

위에서 확인한 node 중 GPU 타입의 node에 eks-node=gpu라는 label을 설정합니다.

 

nvdp-values.yaml 파일 다운로드

curl -O https://raw.githubusercontent.com/sanjeevrg89/eks-gpu-sharing-demo/refs/heads/main/nvdp-values.yaml

 

 

Helm을 사용하여 nvidia-device-plugin 설치

helm repo add nvdp https://nvidia.github.io/k8s-device-plugin
helm repo update

 

먼저 nvidia-device-plugin helm repo를 추가합니다.

 

helm upgrade -i nvdp nvdp/nvidia-device-plugin \
  --namespace kube-system \
  -f nvdp-values.yaml \
  --version 0.14.0

 

그 후 Cluster에 Helm 명령어를 통해서 0.14.0 버전의 nvdp/nvidia-device-plugin을 설치합니다.

 

 

kubectl get daemonset -n kube-system | grep nvidia

 

해당 플러그인은 kube-system namespace에 daemonset으로 배포되기 때문에 위 명령어로 확인해 보겠습니다.

 

 

GPU가 탑재된 노드들의 자원 용량(capacity) 확인

kubectl get nodes -o json | jq -r '.items[] | select(.status.capacity."nvidia.com/gpu" != null) | {name: .metadata.name, capacity: .status.capacity}'

 

이 명령어는 GPU가 할당된 노드들만 필터링하여, 각 노드의 이름과 GPU를 포함한 리소스 용량(capacity) 정보를 출력하는 명령어입니다.

 

 

즉, GPU가 1개 탑재된 8 코어 CPU / 32GB RAM / 80GB 스토리지의 EC2 인스턴스인 것을 확인할 수 있습니다.

 

GPU를 활용하는 애플리케이션 배포

kubectl create namespace gpu-demo

 

먼저, namespace를 생성합니다.

 

kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tensorflow-cifar10-deployment
  namespace: gpu-demo
  labels:
    app: tensorflow-cifar10
spec:
  replicas: 5
  selector:
    matchLabels:
      app: tensorflow-cifar10
  template:
    metadata:
      labels:
        app: tensorflow-cifar10
    spec:
      containers:
      - name: tensorflow-cifar10
        image: public.ecr.aws/r5m2h0c9/cifar10_cnn:v2
        resources:
          limits:
            nvidia.com/gpu: 1
EOF

 

위의 Deployment 명세를 통해 gpu 리소스를 활용하는 tensorflow 애플리케이션을 배포합니다. 

 

kubectl get po -n gpu-demo

 

일정 시간이 지난 후 gpu-demo namespace에 배포된 pod 목록을 확인합니다.

 

 

위와 같이 하나의 pod만 Running 상태인 것을 확인할 수 있습니다.

 

다른 Pod의 상세 확인

kubectl describe pod -n gpu-demo tensorflow-cifar10-deployment-7c6f89c8d6-5jtsx

 

Pending 상태인 다른 Pod의 상세를 확인해 보겠습니다.

 

 

 

0/3 nodes are available: 3 Insufficient nvidia.com/gpu. preemption: 0/3 nodes are available: 3 No preemption victims found for incoming pod.

 

GPU 리소스 부족으로 생성 불가능한 상황인 것을 알 수 있습니다. 즉, GPU가 공유되지 못하는 상황입니다.

 

 

실습 - Time-Slicing 적용

ConfigMap 정의

kubectl apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
  name: nvidia-device-plugin
  namespace: kube-system
data:
  any: |-
    version: v1
    flags:
      migStrategy: none
    sharing:
      timeSlicing:
        resources:
        - name: nvidia.com/gpu
          replicas: 10
EOF

 

GPU time-slicing을 적용하기 위해 NVIDIA Device Plugin의 ConfigMap을 적용합니다.

 

 migStrategy: none  으로 MIG 기능을 사용하지 않고,  sharing.timeSlicing.resources.replicas: 10  으로 하나의 GPU를 10개의 논리 GPU(virtual GPU)처럼 time-slicing으로 나누어 쓸 수 있도록 설정합니다.

 

새로운 ConfigMap 기반으로 애플리케이션 재배포

helm upgrade -i nvdp nvdp/nvidia-device-plugin \
  --namespace kube-system \
  -f nvdp-values.yaml \
  --version 0.14.0 \
  --set config.name=nvidia-device-plugin \
  --force

 

 

GPU가 탑재된 노드들의 자원 용량(capacity) 확인

kubectl get nodes -o json | jq -r '.items[] | select(.status.capacity."nvidia.com/gpu" != null) | {name: .metadata.name, capacity: .status.capacity}'


위의 configmap을 적용한 후, node의 GPU 자원 용량을 확인해 보겠습니다.

 

 

10개로 증가된 것을 확인하실 수 있습니다.

 

Pod 확인

kubectl get pods -n gpu-demo

 

이제 다시 gpu-demo의 pod 목록을 확인해 보겠습니다.

 

 

모두 Running 상태로 Pod가 배포된 것을 확인하실 수 있습니다.

 

 

실습 리소스 삭제

eksdemo delete cluster gpusharing-demo

 

실습이 완료되었으면 꼭! 리소스를 삭제해주셔야 합니다! (안그러면 돈이 청구돼요!!!)

 

 


참고

https://aws.amazon.com/ko/blogs/containers/gpu-sharing-on-amazon-eks-with-nvidia-time-slicing-and-accelerated-ec2-instances/

 

GPU sharing on Amazon EKS with NVIDIA time-slicing and accelerated EC2 instances | Amazon Web Services

In today’s fast-paced technological landscape, the demand for accelerated computing is skyrocketing, particularly in areas like artificial intelligence (AI) and machine learning (ML). One of the primary challenges the enterprises face is the efficient ut

aws.amazon.com

 

728x90