들어가며
요즘 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
실습이 완료되었으면 꼭! 리소스를 삭제해주셔야 합니다! (안그러면 돈이 청구돼요!!!)
참고
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
'스터디 이야기 > 25' AWS EKS' 카테고리의 다른 글
Amazon VPC Lattice로 EKS 외부 연결 구성하기 (0) | 2025.04.25 |
---|---|
HashiCorp Vault를 활용한 CI/CD 환경의 Secret 관리 자동화 (Jenkins, ArgoCD) (0) | 2025.04.11 |
AWS EKS 업그레이드 실습: Control Plane부터 Node Group까지 (0) | 2025.04.02 |
ArgoCD Image Updater로 이미지 자동으로 감지하여 배포하기 (0) | 2025.03.28 |