DevOps

[AWS EKS] (30) EKS 스터디 11주차 ( AWS EKS ML Infra(GPU) workshop )

국두리 2025. 4. 17. 22:28
CloudNet@팀의 EKS 스터디 AEWS 2기에 작성된 자료를 토대로 작성합니다.
  1. Amazon EKS 클러스터에 vLLM과 WebUI 파드를 배포하여 생성형 AI 챗봇 애플리케이션을 Kubernetes에 배포합니다.
    Mistral-7B 모델은 Amazon FSx for Lustre 및 Amazon S3를 사용하여 저장하고 액세스하며, 생성형 AI 워크로드를 위해 AWS Inferentia 가속 컴퓨팅을 활용합니다.
  2. 추가 파드 요청으로 인해 추가 노드가 필요한 경우, Karpenter를 사용하여 EKS 노드 수를 자동으로 확장하여 확장성과 운영 효율성을 확보합니다.

 

 Build Gen AI & ML using EKS , Amazon FSx and AWS Inferentia

  • Cloud9 검색 하여 클릭 
  • Select genaifsxworkshoponeks
  • Click the Open under the Cloud9 IDE to launch the Cloud9 environment.

 

대부분의 경우 Cloud9은 IAM 자격 증명을 동적으로 관리하지만, 현재는 Amazon EKS IAM 인증과 호환되지 않습니다. 따라서 이를 비활성화하고 대신 AWS IAM 역할을 사용해야 합니다
# AWS IAM 설정 비활성화  
aws cloud9 update-environment --environment-id ${C9_PID} --managed-credentials-action DISABLE
rm -vf ${HOME}/.aws/credentials

# 사용할 assume role 의 arn을 확인한다. 
aws sts get-caller-identity

# The output assumed-role name should look like the following:
{
    "UserId": "AROA5FD66U2OXT7GULSMN:i-089c79db4f72948fd",
    "Account": "904356865693",
    "Arn": "arn:aws:sts::904356865693:assumed-role/genaifsxworkshoponeks-C9Role-IZ3MRtV3LetW/i-089c79db4f72948fd"
}
  • region 환경설정 + token 설정 
  • eksctl 사용 위해 Cluster name env 등록 
TOKEN=`curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"`
export AWS_REGION=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/placement/region)
export CLUSTER_NAME=eksworkshop
  • 확인
echo $AWS_REGION
echo $CLUSTER_NAME

  • kubeconfig 설정 
aws eks update-kubeconfig --name $CLUSTER_NAME --region $AWS_REGION
  • 확인
kubectl get nodes

NAME                                       STATUS   ROLES    AGE   VERSION
ip-10-0-63-76.us-west-2.compute.internal   Ready    <none>   11h   v1.30.9-eks-5d632ec
ip-10-0-71-68.us-west-2.compute.internal   Ready    <none>   11h   v1.30.9-eks-5d632ec

EKS Blueprints for Terraform 

 

The Amazon Elastic Kubernetes Service (EKS) cluster in this workshop was created with Terraform  using the EKS Blueprints for Terraform 

  • 이 섹션에서는 Amazon EKS 클러스터에 사전 설치된 Karpenter를 확인합니다.
  • Karpenter는 공식 Helm 차트를 사용하여 설치할 수 있지만, 이 클러스터는 Amazon EKS 블루프린트를 사용하여 프로비저닝되었으며, Karpenter가 미리 설치되어 있습니다.
kubectl -n karpenter get deploy/karpenter -o yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "1"
    meta.helm.sh/release-name: karpenter
    meta.helm.sh/release-namespace: karpenter
  creationTimestamp: "2025-04-17T01:03:21Z"
  generation: 1
  labels:
    app.kubernetes.io/instance: karpenter
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: karpenter
    app.kubernetes.io/version: 1.0.1
    helm.sh/chart: karpenter-1.0.1
  name: karpenter
  namespace: karpenter
  resourceVersion: "2893"
  uid: 77394dea-7e6f-4f60-8ae1-9c8244e154d1
spec:
  progressDeadlineSeconds: 600
  replicas: 2
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app.kubernetes.io/instance: karpenter
      app.kubernetes.io/name: karpenter
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 1
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app.kubernetes.io/instance: karpenter
        app.kubernetes.io/name: karpenter
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: karpenter.sh/nodepool
                operator: DoesNotExist
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels:
                app.kubernetes.io/instance: karpenter
                app.kubernetes.io/name: karpenter
            topologyKey: kubernetes.io/hostname
      containers:
      - env:
        - name: KUBERNETES_MIN_VERSION
          value: 1.19.0-0
        - name: KARPENTER_SERVICE
          value: karpenter
        - name: WEBHOOK_PORT
          value: "8443"
        - name: WEBHOOK_METRICS_PORT
          value: "8001"
        - name: DISABLE_WEBHOOK
          value: "false"
        - name: LOG_LEVEL
          value: info
        - name: METRICS_PORT
          value: "8080"
        - name: HEALTH_PROBE_PORT
          value: "8081"
        - name: SYSTEM_NAMESPACE
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace
        - name: MEMORY_LIMIT
          valueFrom:
            resourceFieldRef:
              containerName: controller
              divisor: "0"
              resource: limits.memory
        - name: FEATURE_GATES
          value: SpotToSpotConsolidation=false
        - name: BATCH_MAX_DURATION
          value: 10s
        - name: BATCH_IDLE_DURATION
          value: 1s
        - name: CLUSTER_NAME
          value: eksworkshop
        - name: CLUSTER_ENDPOINT
          value: https://1162655CEF80DBC83049FA585E24F646.gr7.us-west-2.eks.amazonaws.com
        - name: VM_MEMORY_OVERHEAD_PERCENT
          value: "0.075"
        - name: INTERRUPTION_QUEUE
          value: karpenter-eksworkshop
        - name: RESERVED_ENIS
          value: "0"
        image: public.ecr.aws/karpenter/controller:1.0.1@sha256:fc54495b35dfeac6459ead173dd8452ca5d572d90e559f09536a494d2795abe6
        imagePullPolicy: IfNotPresent
        livenessProbe:
          failureThreshold: 3
          httpGet:
            path: /healthz
            port: http
            scheme: HTTP
          initialDelaySeconds: 30
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 30
        name: controller
        ports:
        - containerPort: 8080
          name: http-metrics
          protocol: TCP
        - containerPort: 8001
          name: webhook-metrics
          protocol: TCP
        - containerPort: 8443
          name: https-webhook
          protocol: TCP
        - containerPort: 8081
          name: http
          protocol: TCP
        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /readyz
            port: http
            scheme: HTTP
          initialDelaySeconds: 5
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 30
        resources: {}
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop:
            - ALL
          readOnlyRootFilesystem: true
          runAsGroup: 65532
          runAsNonRoot: true
          runAsUser: 65532
          seccompProfile:
            type: RuntimeDefault
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      nodeSelector:
        kubernetes.io/os: linux
      priorityClassName: system-cluster-critical
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext:
        fsGroup: 65532
      serviceAccount: karpenter
      serviceAccountName: karpenter
      terminationGracePeriodSeconds: 30
      tolerations:
      - key: CriticalAddonsOnly
        operator: Exists
      topologySpreadConstraints:
      - labelSelector:
          matchLabels:
            app.kubernetes.io/instance: karpenter
            app.kubernetes.io/name: karpenter
        maxSkew: 1
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: DoNotSchedule
status:
  availableReplicas: 2
  conditions:
  - lastTransitionTime: "2025-04-17T01:03:32Z"
    lastUpdateTime: "2025-04-17T01:03:32Z"
    message: Deployment has minimum availability.
    reason: MinimumReplicasAvailable
    status: "True"
    type: Available
  - lastTransitionTime: "2025-04-17T01:03:21Z"
    lastUpdateTime: "2025-04-17T01:03:32Z"
    message: ReplicaSet "karpenter-5b8d8d5b9" has successfully progressed.
    reason: NewReplicaSetAvailable
    status: "True"
    type: Progressing
  observedGeneration: 1
  readyReplicas: 2
  replicas: 2
  updatedReplicas: 2

 

 

  • CLUSTER_ENDPOINT: 신규 노드들이 연결할 외부 Kubernetes 클러스터 엔드포인트입니다. 이 값을 명시하지 않으면, 노드들은 DescribeCluster API를 통해 클러스터 엔드포인트를 자동으로 탐색합니다.
  • INTERRUPTION_QUEUE: EKS Terraform 블루프린트의 일부로 생성된 SQS 큐의 엔드포인트입니다. 이 SQS 큐는 Spot 인스턴스 중단 알림AWS Health 이벤트를 수신하는 데 사용됩니다.

 

karpenter 확인

$ kubectl get pods --namespace karpenter
NAME                        READY   STATUS    RESTARTS   AGE
karpenter-5b8d8d5b9-cd6f5   1/1     Running   0          11h
karpenter-5b8d8d5b9-dnh6f   1/1     Running   0          11h

 

alias kl='kubectl -n karpenter logs -l app.kubernetes.io/name=karpenter --all-containers=true -f --tail=20'

FSx for Lustre 인스턴스를 배포하고 Amazon EKS 클러스터와 통합

이번 워크숍에서는 Mistral-7B-Instruct 모델Amazon S3 버킷에 저장되어 있으며,
해당 버킷은 Amazon FSx for Lustre 파일 시스템과 연결되어 있습니다.
vLLM 컨테이너는 이를 사용하여 생성형 AI 챗봇 애플리케이션을 구동하게 됩니다.

이 모듈에서는 FSx for Lustre 인스턴스를 배포하고 Amazon EKS 클러스터와 통합하는 과정을 다룹니다.
FSx for Lustre는 모델 데이터를 제공하고, Amazon EKS는 생성형 AI 애플리케이션이 구동되는 플랫폼 역할을 합니다.

또한, 다음과 같은 Kubernetes 스토리지 개념을 학습하게 됩니다:

  • CSI 드라이버
  • Persistent Volumes (PV)
  • StorageClass
  • 정적(Static) 및 동적(Dynamic) 스토리지 프로비저닝 방식

이 모듈의 인프라는 다음으로 구성되어 있습니다:

  • Amazon EKS 클러스터 (EC2 워커 노드 2개 포함)
  • Amazon FSx for Lustre 파일 시스템
  • Amazon S3 버킷

 

  • Amazon FSx for Lustre
    • 속도가 중요한 워크로드(예: 머신 러닝, 분석, 고성능 컴퓨팅)를 위한 고성능 병렬 파일 시스템을 제공하는 완전 관리형 서비스
    • 밀리초 미만의 대기 시간으로 데이터에 액세스하고 TB/s의 처리량과 수백만 개의 IOPS로 확장 가능
    • Amazon S3와도 통합되어 있어 클라우드 데이터의 대량을 쉽게 저장, 액세스 및 처리

Amazon S3와 통합되면 다음과 같은 이점이 있습니다:

  • FSx for Lustre 파일 시스템은 S3 버킷의 객체들을 파일처럼 투명하게 사용자에게 제공합니다.
  • 사용자가 Lustre 파일 시스템에 파일을 추가, 수정, 삭제하면 해당 변경 사항이 S3 버킷에 자동으로 반영됩니다.

즉, 클라우드 데이터의 대량 저장, 액세스 및 처리를 고성능 파일 시스템 환경에서 효율적으로 수행할 수 있습니다.

 

쿠버네티스 환경에서  FSx 파일시스템을 사용하면? 

 

CSI 드라이버 (Container Storage Interface)

CSI는 Kubernetes와 같은 컨테이너 오케스트레이션 시스템에 블록 및 파일 스토리지를 제공하는 표준 인터페이스입니다.
이를 통해 Kubernetes가 컨테이너화된 애플리케이션을 위한 영구 스토리지(persistent storage)를 네이티브로 관리할 수 있습니다.

FSx for Lustre CSI 드라이버는 Amazon EKS 클러스터가 FSx for Lustre 기반의 Persistent Volume의 수명 주기를 관리할 수 있도록 합니다.
이를 통해 고성능, 저지연의 스토리지를 컨테이너 워크로드에 쉽고 빠르게 통합할 수 있습니다.


StorageClass

StorageClass는 EKS 관리자들이 제공하는 스토리지의 '클래스'를 정의하는 방법입니다.
각 StorageClass는 서로 다른 AWS 스토리지 서비스(Amazon FSx, Amazon EBS, Amazon EFS 등) 또는 백업 정책에 매핑될 수 있습니다.
Kubernetes는 StorageClass가 실제로 어떤 스토리지인지에 대해 특별한 제약 없이 유연하게 정의할 수 있도록 지원합니다.


Persistent Volume (PV)

PV는 관리자가 사전에 프로비저닝한 스토리지 볼륨으로, EKS 클러스터에 매핑됩니다.
PV는 Pod의 수명보다 더 오래 유지되며, 여러 Pod이 공유 데이터를 필요로 하는 경우에 적합합니다.


Persistent Volume Claim (PVC)

PVC는 사용자가 요청하는 스토리지 볼륨입니다.
요청 시, 스토리지의 크기와 액세스 모드(예: ReadWriteOnce, ReadOnlyMany, ReadWriteMany 등)를 지정할 수 있습니다.

 

 

정적(Static) vs 동적(Dynamic) 스토리지 프로비저닝

🔹 정적 프로비저닝 (Static provisioning)

  1. 관리자가 먼저 스토리지 인스턴스(FSx for Lustre 등)를 생성하고, 해당 인스턴스를 위한 **Persistent Volume(PV)**를 Kubernetes 클러스터에 생성합니다.
  2. 이후 애플리케이션 개발자가 PVC 요청을 통해 해당 PV를 Pod에서 사용할 수 있도록 설정합니다.

🔹 동적 프로비저닝 (Dynamic provisioning)

  • PVC 요청이 들어올 때마다 자동으로 스토리지를 생성 및 연결합니다.
  • 관리자가 미리 PV를 생성할 필요가 없으며, PVC 요청 시 FSx for Lustre 인스턴스 및 PV가 자동 생성됩니다.
  • 사용자 입장에서 더 편리하고 유연하게 스토리지 자원을 사용할 수 있는 방식입니다.

  deploy CSI Driver

Deploy CSI Driver

  • csi 드라이버 설치를 위해서 준비 
  • 환경 변수를 설정하고, 서비스 계정을 생성하며, IAM 정책을 생성 및 연결하게 됩니다. ( IRSA 방식 )
ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)

cat << EOF >  fsx-csi-driver.json
{
    "Version":"2012-10-17",
    "Statement":[
        {
            "Effect":"Allow",
            "Action":[
                "iam:CreateServiceLinkedRole",
                "iam:AttachRolePolicy",
                "iam:PutRolePolicy"
            ],
            "Resource":"arn:aws:iam::*:role/aws-service-role/s3.data-source.lustre.fsx.amazonaws.com/*"
        },
        {
            "Action":"iam:CreateServiceLinkedRole",
            "Effect":"Allow",
            "Resource":"*",
            "Condition":{
                "StringLike":{
                    "iam:AWSServiceName":[
                        "fsx.amazonaws.com"
                    ]
                }
            }
        },
        {
            "Effect":"Allow",
            "Action":[
                "s3:ListBucket",
                "fsx:CreateFileSystem",
                "fsx:DeleteFileSystem",
                "fsx:DescribeFileSystems",
                "fsx:TagResource"
            ],
            "Resource":[
                "*"
            ]
        }
    ]
}
EOF

aws iam create-policy \
        --policy-name Amazon_FSx_Lustre_CSI_Driver \
        --policy-document file://fsx-csi-driver.json
        
eksctl create iamserviceaccount \
    --region $AWS_REGION \
    --name fsx-csi-controller-sa \
    --namespace kube-system \
    --cluster $CLUSTER_NAME \
    --attach-policy-arn arn:aws:iam::$ACCOUNT_ID:policy/Amazon_FSx_Lustre_CSI_Driver \
    --approve
  • Copy and run the below command to save the role ARN.
export ROLE_ARN=$(aws cloudformation describe-stacks --stack-name "eksctl-${CLUSTER_NAME}-addon-iamserviceaccount-kube-system-fsx-csi-controller-sa" --query "Stacks[0].Outputs[0].OutputValue"  --region $AWS_REGION --output text)
  • CSI driver 설치 ( FSx for Lustre )
kubectl apply -k "github.com/kubernetes-sigs/aws-fsx-csi-driver/deploy/kubernetes/overlays/stable/?ref=release-1.2"
  • 확인
kubectl get pods -n kube-system -l app.kubernetes.io/name=aws-fsx-csi-driver
$ kubectl get pods -n kube-system -l app.kubernetes.io/name=aws-fsx-csi-driver
NAME                                  READY   STATUS    RESTARTS   AGE
fsx-csi-controller-6f4c577bd4-l9xnk   4/4     Running   0          12s
fsx-csi-controller-6f4c577bd4-vvwp4   4/4     Running   0          12s
fsx-csi-node-h54lv                    3/3     Running   0          12s
fsx-csi-node-nxw2g                    3/3     Running   0          12s
  • annotation 을 통해 서비스 어카운트에 ROLE_ARN을 삽입해준다.
kubectl annotate serviceaccount -n kube-system fsx-csi-controller-sa \
 eks.amazonaws.com/role-arn=$ROLE_ARN --overwrite=true
  • 확인 ( 서비스 어카운트 )
kubectl get sa/fsx-csi-controller-sa -n kube-system -o yaml
$ kubectl get sa/fsx-csi-controller-sa -n kube-system -o yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::904356865693:role/eksctl-eksworkshop-addon-iamserviceaccount-ku-Role1-E5Z7hc16bMMV
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"ServiceAccount","metadata":{"annotations":{},"labels":{"app.kubernetes.io/name":"aws-fsx-csi-driver"},"name":"fsx-csi-controller-sa","namespace":"kube-system"}}
  creationTimestamp: "2025-04-17T13:01:01Z"
  labels:
    app.kubernetes.io/managed-by: eksctl
    app.kubernetes.io/name: aws-fsx-csi-driver
  name: fsx-csi-controller-sa
  namespace: kube-system
  resourceVersion: "396978"
  uid: 9875151c-42d7-42a2-a513-4532eca361d1

이 섹션에서는 다음과 같은 사전 작업을 완료했습니다:

  • 환경 변수 생성
  • 적절한 IAM 정책과 역할 ARN을 가진 서비스 계정 생성
  • FSx for Lustre용 CSI 드라이버 배포

이제 다음 섹션에서는 FSx for Lustre를 위한 다음 리소스들을 생성하게 됩니다:

  • Persistent Volume (PV)
  • Persistent Volume Claim (PVC)
  • StorageClass

이를 통해 Kubernetes 환경에서 FSx for Lustre 스토리지를 실제로 사용할 수 있도록 설정하게 됩니다.

 

PV 생성 

Create Persistent Volume on EKS Cluster

이 랩 섹션에서는 정적 프로비저닝(Static Provisioning) 방식을 사용하여 Persistent Volume(PV)을 설정합니다.

이미 여러분을 위해 FSx for Lustre 인스턴스가 사전 프로비저닝되어 있으며,
이 인스턴스는 Mistral-7B 모델이 저장된 Amazon S3 버킷과 연결되어 있습니다.

이제 여러분은 다음 작업을 수행하게 됩니다:

  1. Persistent Volume (PV) 정의 생성
  2. Persistent Volume Claim (PVC) 생성

이러한 설정을 통해 vLLM 파드에서 Mistral-7B 모델 데이터에 접근할 수 있는 스토리지 볼륨을 사용할 수 있게 됩니다.

cd /home/ec2-user/environment/eks/FSxL
FSXL_VOLUME_ID=$(aws fsx describe-file-systems --query 'FileSystems[].FileSystemId' --output text)
DNS_NAME=$(aws fsx describe-file-systems --query 'FileSystems[].DNSName' --output text)
MOUNT_NAME=$(aws fsx describe-file-systems --query 'FileSystems[].LustreConfiguration.MountName' --output text)

 

Persistent Volume 생성

이번 단계에서는 fsxL-persistent-volume.yaml이라는 YAML 파일을 사용해 Persistent Volume(PV)를 생성합니다.
이 PV 정의 파일에는 미리 준비된 1200GiB 크기의 FSx for Lustre 인스턴스를 EKS 클러스터 리소스로 등록하는 내용이 포함되어 있습니다.

 

  • PV의 이름을 fsx-pv 로 지정
  • 해당 FSx for Lustre 인스턴스를 참조하도록 구성
  • 용량은 1200GiB로 설정
# cat fsxL-persistent-volume.yaml
# fsxL-persistent-volume.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: fsx-pv
spec:
  persistentVolumeReclaimPolicy: Retain
  capacity:
    storage: 1200Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  mountOptions:
    - flock
  csi:
    driver: fsx.csi.aws.com
    volumeHandle: FSXL_VOLUME_ID
    volumeAttributes:
      dnsname: DNS_NAME
      mountname: MOUNT_NAME
sed -i'' -e "s/FSXL_VOLUME_ID/$FSXL_VOLUME_ID/g" fsxL-persistent-volume.yaml
sed -i'' -e "s/DNS_NAME/$DNS_NAME/g" fsxL-persistent-volume.yaml
sed -i'' -e "s/MOUNT_NAME/$MOUNT_NAME/g" fsxL-persistent-volume.yaml
  • PV 배포 (apply) 
kubectl apply -f fsxL-persistent-volume.yaml
  • 확인
$ kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM                                                                      STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
fsx-pv                                     1200Gi     RWX            Retain           Available                                                                                             <unset>                          5s
pvc-c98a27e4-0bfe-41fc-bd4c-f37f1ab14f92   50Gi       RWO            Delete           Bound       kube-prometheus-stack/data-prometheus-kube-prometheus-stack-prometheus-0   gp3            <unset>                          22h
  • pvc 배포
# fsxL-claim.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: fsx-lustre-claim
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: ""
  resources:
    requests:
      storage: 1200Gi
  volumeName: fsx-pv
kubectl apply -f fsxL-claim.yaml


WSParticipantRole:~/environment/eks/FSxL $ kubectl get pv,pvc
NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                                                      STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
persistentvolume/fsx-pv                                     1200Gi     RWX            Retain           Bound    default/fsx-lustre-claim                                                                  <unset>                          3m57s
persistentvolume/pvc-c98a27e4-0bfe-41fc-bd4c-f37f1ab14f92   50Gi       RWO            Delete           Bound    kube-prometheus-stack/data-prometheus-kube-prometheus-stack-prometheus-0   gp3            <unset>                          23h

NAME                                     STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/fsx-lustre-claim   Bound    fsx-pv   1200Gi     RWX                           <unset>                 7s
WSParticipantRole:~/environment/eks/FSxL $

FSx console에서 확인 

View options and performance details in the Amazon FSx console

 
  1. In the FSx console you will see a list of your FSx Instances, including the details of the FSx for Lustre instance that was pre-provisioned for you as part of the lab (which you then configured as a Persistent Volume in the EKS cluster).
  2. FSx 콘솔에서 FSx 인스턴스 목록을 확인할 수 있으며, 이미 1200GiB 크기의 FSx for Lustre 인스턴스가 사전 프로비저닝되어 있는 것을 볼 수 있습니다.
    이 인스턴스는 Persistent-SSD 스토리지를 사용하며, 스토리지 단위(1TiB)당 250MB/s의 처리량(Throughput Capacity)을 제공합니다.
  3. FSx 인스턴스와 관련된 세부 정보를 확인할 수 있으며, 온라인에서 업데이트할 수 있는 항목들도 함께 표시됩니다.
    여기서 스토리지 용량(Storage Capacity)과 스토리지 단위당 처리량(Throughput per unit of storage)을
    업데이트할 수 있는 옵션이 제공되는 것을 확인할 수 있습니다.

Gen AI 어플리케이션 배포

Deploy Generative AI Chat application

✅ 모듈 개요

이 모듈에서는 Kubernetes 위에 생성형 AI 챗봇 애플리케이션을 배포하는 방법을 학습합니다.
Amazon EKS 클러스터에 vLLM 파드WebUI 파드를 배포하고, Amazon FSx for Lustre 및 Amazon S3를 활용해 Mistral-7B 모델을 저장하고 액세스합니다.
또한, 생성형 AI 워크로드를 위해 AWS Inferentia 가속기를 사용하는 방법을 배웁니다.


✅ 생성형 AI와 머신러닝 (ML)

생성형 AI와 머신러닝은 기업이 운영 방식과 혁신 방식을 변화시키는 데 큰 역할을 하고 있습니다.
생성형 AI는 대규모 언어 모델(LLM)을 기반으로 텍스트, 이미지, 오디오, 소프트웨어 코드 등 새로운 콘텐츠를 프롬프트로부터 생성하는 AI의 한 형태입니다.


✅ 대규모 언어 모델(LLM)이란?

LLM은 방대한 텍스트 데이터를 학습하여 자연어의 패턴과 구조를 이해하는 머신러닝 모델입니다.
이러한 모델은 텍스트 생성, 질의 응답, 번역 등 다양한 자연어 처리 작업에 사용됩니다.

이 랩에서는 오픈소스 모델인 Mistral-7B-Instruct를 사용합니다.
이 모델은 70억 개의 파라미터를 가진 LLM이며, 단순한 텍스트 생성 이상의 작업을 수행할 수 있도록 지시문(instruction)을 따르는 방식으로 훈련되었습니다.
따라서 챗봇 애플리케이션에 적합한 모델입니다.


✅ vLLM이란?

vLLM(Virtual Large Language Model)은 LLM 추론 및 서빙을 위한 오픈소스 라이브러리입니다.
Mistral-7B-Instruct와 같은 모델을 배포하여 텍스트 생성 기능을 제공하며, OpenAI API와 호환되는 인터페이스를 제공하여 통합이 용이합니다.

vLLM의 특징:

✅ 성능:

  • 최첨단 추론 처리량
  • PagedAttention을 활용한 키/값 메모리 효율 관리
  • 요청의 연속적 배치 처리
  • CUDA/HIP 그래프를 통한 빠른 실행

✅ 유연성:

  • HuggingFace 모델과의 통합
  • OpenAI 호환 API 서버
  • Prefix caching 지원
  • 다양한 칩셋 지원: AWS Neuron, NVIDIA GPU 등

✅ vLLM을 Amazon EKS에 배포하기

생성형 텍스트 추론 기능을 OpenAI API 호환 엔드포인트 형태로 제공하기 위해,
Amazon EKS 클러스터 위에 vLLM 프레임워크를 사용해 Mistral-7B-Instruct 모델을 배포합니다.

이 과정에서는 Karpenter를 통해 AWS Inferentia2 EC2 노드(생성형 AI용 가속 컴퓨팅 인스턴스)를 자동 생성하며,
vLLM 파드는 해당 노드에서 컨테이너 이미지로 실행됩니다.


✅ AWS Inferentia 가속기란?

AWS Inferentia는 AWS가 생성형 AI 및 딥러닝 추론을 가속화하기 위해 설계한 맞춤형 머신러닝 칩입니다.
Amazon EC2에서 낮은 비용으로 높은 성능을 제공하며, TensorFlow, PyTorch, MXNet 등의 프레임워크를 지원합니다.

특히, 대규모 언어 모델(LLM)잠재 디퓨전 모델(latent diffusion model) 같은 복잡한 모델을 대규모로 배포하는 데 최적화되어 있습니다.

Inferentia는 추론(Inference) 단계의 성능을 가속화합니다.
이 단계는 훈련된 모델이 새로운 데이터에 대해 예측을 수행하는 것으로, 실시간 서비스에 중요한 낮은 지연 시간과 높은 처리량이 요구됩니다.

Inferentia2의 주요 사양:

  • 2개의 2세대 NeuronCore를 탑재한 가속기 최대 12개 (EC2 Inf2 인스턴스당)
  • 각 가속기는 FP16 기준 최대 190 TFLOPS 성능
  • 가속기당 32GB HBM 메모리
  • 이전 세대 대비 4배의 총 메모리10배의 메모리 대역폭 제공

✅ AWS Neuron SDK

AWS Neuron SDK는 컴파일러, 런타임, 프로파일링 도구를 포함한 SDK로,
딥러닝 추론을 고성능·저비용으로 실행할 수 있게 해줍니다.

  • PyTorch, TensorFlow 등 주요 ML 프레임워크와 네이티브 통합
  • 최소한의 코드 변경으로 Inferentia에서 모델 배포 가능
  • NLP, 번역, 텍스트 요약, 이미지·비디오 생성, 음성 인식, 개인화, 사기 탐지 등 다양한 생성형 AI 워크로드에 활용 가능

이 모듈을 통해 사용자는 EKS 기반의 생성형 AI 서비스 인프라를 구성하고 최적화된 컴퓨팅 리소스와 스토리지를 통합하는 전체 흐름을 학습하게 됩니다.

 

Karpenter 구성은 NodePool 커스텀 리소스(Custom Resource, CR) 형태로 제공됩니다.
NodePool은 Karpenter가 생성할 수 있는 노드와 해당 노드에서 실행될 수 있는 파드에 대한 제약 조건을 설정합니다.

NodePool을 사용하면,

  • 특정 컴퓨팅 아키텍처로만 노드를 생성하도록 제한할 수 있고,
  • 여러 아키텍처를 유연하게 사용할 수 있도록 구성할 수도 있습니다.

하나의 Karpenter NodePool은 다양한 형태의 파드(Pod shapes)를 처리할 수 있습니다.
Karpenter는 파드의 라벨, 어피니티(Affinity) 등의 속성을 기준으로 스케줄링 및 노드 프로비저닝 결정을 내립니다.

클러스터에는 여러 개의 NodePool을 생성할 수 있지만,

 

Gen AI 어플리케이션 배포

  • 이번에는 추가적인 하나의 NodePool, 즉 Inferentia 전용 NodePool을 선언합니다.
  • Lets take a look at the Karpenter NodePool definition that we will deploy.
  • It will create a new nodepool for AWS Inferentia INF2 Accelerated Compute nodes that we will use to power our Generative AI application (vLLM pod)
$ cd /home/ec2-user/environment/eks/genai

$ cat inferentia_nodepool.yaml
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: inferentia
  labels:
    intent: genai-apps
    NodeGroupType: inf2-neuron-karpenter
spec:
  template:
    spec:
      taints:
        - key: aws.amazon.com/neuron
          value: "true"
          effect: "NoSchedule"
      requirements:
        - key: "karpenter.k8s.aws/instance-family"
          operator: In
          values: ["inf2"]
        - key: "karpenter.k8s.aws/instance-size"
          operator: In
          values: [ "xlarge", "2xlarge", "8xlarge", "24xlarge", "48xlarge"]
        - key: "kubernetes.io/arch"
          operator: In
          values: ["amd64"]
        - key: "karpenter.sh/capacity-type"
          operator: In
          values: ["spot", "on-demand"]
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: inferentia
  limits:
    cpu: 1000
    memory: 1000Gi
  disruption:
    consolidationPolicy: WhenEmpty
    # expireAfter: 720h # 30 * 24h = 720h
    consolidateAfter: 180s
  weight: 100
---
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
  name: inferentia
spec:
  amiFamily: AL2
  amiSelectorTerms:
  - alias: al2@v20240917
  blockDeviceMappings:
    - deviceName: /dev/xvda
      ebs:
        deleteOnTermination: true
        volumeSize: 100Gi
        volumeType: gp3
  role: "Karpenter-eksworkshop" 
  subnetSelectorTerms:          
    - tags:
        karpenter.sh/discovery: "eksworkshop"
  securityGroupSelectorTerms:
    - tags:
        karpenter.sh/discovery: "eksworkshop"
  tags:
    intent: apps
    managed-by: karpenter
$ kubectl get nodepool,ec2nodeclass inferentia
NAME                               NODECLASS    NODES   READY   AGE
nodepool.karpenter.sh/inferentia   inferentia   0       True    11m

NAME                                        READY   AGE
ec2nodeclass.karpenter.k8s.aws/inferentia   True    11m

이제 EKS 클러스터에 Neuron Device Plugin과 Neuron Scheduler를 설치해야 합니다.


✅ Neuron Device Plugin

Neuron Device Plugin은 Neuron 코어 및 디바이스를 Kubernetes 리소스로 노출시켜 사용할 수 있도록 합니다.

아래 명령어를 실행하여 Neuron Device Plugin을 설치하세요
(kubectl 경고 메시지는 무시해도 됩니다)

kubectl apply -f https://raw.githubusercontent.com/aws-neuron/aws-neuron-sdk/master/src/k8/k8s-neuron-device-plugin-rbac.yml
kubectl apply -f https://raw.githubusercontent.com/aws-neuron/aws-neuron-sdk/master/src/k8/k8s-neuron-device-plugin.yml

✅ Neuron Scheduler

Neuron Scheduler 확장 기능하나 이상의 Neuron 코어나 디바이스 리소스를 요구하는 파드의 스케줄링을 위해 필요합니다.

 

이 스케줄러는 연속되지 않은(non-contiguous) 코어/디바이스 ID를 가진 노드를 필터링하고,
연속된(contiguous) 코어/디바이스 ID를 요구하는 파드에 대해 올바르게 할당되도록 보장합니다.

 

아래 명령어를 실행하여 Neuron Scheduler를 설치하세요

kubectl apply -f https://raw.githubusercontent.com/aws-neuron/aws-neuron-sdk/master/src/k8/k8s-neuron-scheduler-eks.yml
kubectl apply -f https://raw.githubusercontent.com/aws-neuron/aws-neuron-sdk/master/src/k8/my-scheduler.yml
$ kubectl apply -f mistral-fsxl.yaml
deployment.apps/vllm-mistral-inf2-deployment created
service/vllm-mistral7b-service created

$ kubectl get pod
NAME                                            READY   STATUS    RESTARTS   AGE
kube-ops-view-5d9d967b77-4nq6b                  1/1     Running   0          24h
vllm-mistral-inf2-deployment-7d886c8cc8-6w49z   0/1     Pending   0          19s
WSParticipantRole:~/environment/eks/genai $

Click on the Compute tab, you will see there is now a new AWS Inferentia inf2.xlarge compute node

 

Model 배포 

 

워크숍에서 배포하게 될 vLLM 기반 Mistral-7B-Instruct 모델OpenAI 호환 엔드포인트를 제공합니다.

이 엔드포인트를 사용하기 위해, "Open WebUI" 애플리케이션을 이용할 수 있습니다.

Open WebUI는 사용자들이 채팅 기반 인터페이스를 통해 LLM 모델과 상호작용할 수 있도록 설계된 애플리케이션입니다.
이 애플리케이션은 백엔드에서 vLLM 모델과 통신하며, 사용자는 직관적인 웹 UI를 통해 자연스럽게 대화를 이어갈 수 있습니다.

사용 방법은 다음과 같습니다:

  1. Open WebUI 애플리케이션 컨테이너를 배포합니다.
  2. 제공되는 WebUI URL에 접속합니다.
  3. LLM 모델과 대화를 시작하면 됩니다.
$ kubectl apply -f open-webui.yaml
$ kubectl get ing
NAME                 CLASS   HOSTS   ADDRESS                                                     PORTS   AGE
open-webui-ingress   alb     *       open-webui-ingress-1236350700.us-west-2.elb.amazonaws.com   80      14s