상세 컨텐츠

본문 제목

11주차 - Amazon EKS, FSx, Inferentia를 활용한 생성형 AI 및 ML 환경 구축

K8S - EKS

by 존자르징어 2025. 4. 19. 23:24

본문

본 글은 CloudNet@ 팀 가시다님의 'AWS EKS Hands-on' 스터디 활동 내용을 정리하였습니다.
스터디 협력자님의 도움으로 제공받은 workshop 환경에서 실습한 내용을 바탕으로 합니다.
Kubernetes(이하 k8s)에 대한 지식(아키텍처, 사용법 등)이 있다고 가정하고 작성했습니다.
잘못된 정보가 있으면 언제든지 알려주시기 바랍니다.

 

<목차>

더보기

1. 워크샵 소개 및 목적

 

2. 아키텍처 개요

  - 사전 지식

  - 전체 구성도

  - 핵심 구성 요소

  - 서비스 흐름

 

3. 실습

  - Amazon EKS 클러스터 구성 및 배포

  - FSx for Lustre + S3 연동 구성

  - Chat 애플리케이션 배포 및 테스트

  - Mistral-7B 모델 데이터 확인 및 S3를 통한 다중 리전 복제 실습

  - FSx PV 동적 프로비저닝과 성능 테스트

 

6. 정리


 

1. 워크샵 소개 및 목적

해당 워크숍에서는 Amazon EKS, FSx, S3, Inferentia를 활용해 생성형 AI 챗봇을 구축하고, 자동 확장 및 고성능 데이터 공유 구조를 실습하며 학습합니다.

 
 

 - 생성형 AI 챗봇 애플리케이션 배포

    . Amazon EKS 클러스터에 vLLM 모델 서버와 WebUI Pod를 배포

    . Mistral-7B 모델을 Amazon FSx for Lustre와 Amazon S3에 저장하고 액세스

    . AWS Inferentia 가속기를 활용해 고성능 생성형 AI 워크로드 실행

 

- Karpenter를 활용한 EKS 노드 자동 확장

    . 새로운 Pod 요청이 있을 때 자동으로 노드를 확장하여 운영 효율성과 확장성 확보

 

- Inferentia 노드풀 구성

    . EKS 클러스터 내에 Inferentia 기반 노드풀을 구성하여 생성형 AI 애플리케이션에 활용

 

- 고성능 데이터 계층 구성

    . Amazon FSx for Lustre와 S3를 연동하여 모델 및 데이터를 고속·확장 가능하게 처리

    . 여러 Pod에서 중복 저장 없이 동일한 모델 데이터를 공유

    . DR(재해 복구) 또는 분산 처리 시나리오에 대비한 설계 학습

 

2. 아키텍처 개요

<사전 지식>

 

1. Generative AI와 LLM (Large Language Model)

    - Generative AI: 텍스트, 이미지, 오디오, 코드 등 새로운 콘텐츠를 생성하는 AI.

    - LLM: 대규모 텍스트 데이터를 학습하여 자연어 이해/생성 능력을 갖춘 AI 모델

    - Mistral-7B-Instruct: 오픈소스 LLM, 70억 개 파라미터 보유, 지시를 따르는 데 특화되어 있어 챗봇에 적합

 

2. vLLM

  - LLM 서빙을 위한 오픈소스 라이브러리.

  - 특징:

    . OpenAI API 호환 → 쉽게 통합 가능

    . 빠른 응답 속도 (CUDA/HIP 그래프, PagedAttention, 연속 배치)

    . 다양한 하드웨어 지원 (NVIDIA GPU, AWS Inferentia 등)

 

3. Amazon EKS (Elastic Kubernetes Service)

  - AWS 관리형 Kubernetes 서비스

  - 컨테이너 기반 앱을 쉽게 배포/확장 가능

  - 수천 개의 컨테이너를 효율적으로 오케스트레이션 → LLM 워크로드로 활용

 

4. Karpenter

  - Pod 자원 요청에 따라 노드를 자동으로 생성/삭제해주는 오토스케일러

  - 워크로드 증가 시 유연한 리소스 확장 가능

 

5. AWS Inferentia & Neuron SDK

  - Inferentia: AWS에서 만든 AI 추론 전용 칩셋

  - Neuron SDK: PyTorch, TensorFlow 모델을 Inferentia에서 돌릴 수 있도록 지원하는 툴킷

 

6. Amazon FSx for Lustre, Amazon S3

  - Amazon S3: 오브젝트 스토리지 (LLM 모델 파일을 저장)

  - Amazon FSx for Lustre: 고속·고성능 파일 시스템. S3와 연동되어 빠르게 모델을 읽을 수 있음.

    . 모델 데이터를 FSx를 통해 컨테이너에 마운트하여 사용함 → 중복 저장 없이 효율적인 데이터 처리 가능

 

- 전체 구성도

 

- 핵심 구성 요소의 역할

  . Amazon EKS: 컨테이너 오케스트레이션

  . Amazon FSx for Lustre & S3: 모델 및 데이터 저장소

  . AWS Inferentia: 고성능 추론

  . vLLM & 오픈소스 파운데이션 모델(Mistral-7B): 모델 서빙 및 채팅 처리

 

- 구성 요소 상세

  . 모델 저장 및 준비: Mistral‑7B‑Instruct 모델 파일을 Amazon S3에 저장

  . 고성능 파일 시스템 연동: S3에 저장된 모델은 Amazon FSx for Lustre 파일 시스템과 연결

  . EKS 클러스터에 vLLM 배포: EKS 클러스터 위에 vLLM 모델 서버와 WebUI Pod를 배포

  . vLLM을 통한 추론 서비스 제공: OpenAI API 호환 엔드포인트를 통해 Mistral‑7B‑Instruct 모델의 텍스트 생성(추론) 기능을 노출

  . 사용자 인터페이스 연결: Open WebUI 애플리케이션을 배포하여 vLLM 엔드포인트와 상호작용하며, 대화형 챗봇 제공

  . 자동 확장 및 가속화: 트래픽 증가 시 Karpenter가 신규 Pod 일정에 맞게 Inferentia2 기반 EC2 Inf2 노드를 자동으로 프로비저닝

  . Inferentia 가속기 활용: AWS Inferentia 추론 칩을 사용해 대규모 LLM 추론 연산을 저비용·고성능으로 처리

  . 데이터 공유 및 DR(재해 복구) 대비: 모든 Pod는 중복 복사 없이 동일한 FSx for Lustre 마운트를 통해 모델 데이터를 공유하며 다중 리전 간 S3 복제/액세스를 설정해 DR 대비

 

 

3. 실습

  - Amazon EKS 클러스터 구성 및 배포

  이 워크숍에서 사용하는 Amazon EKS 클러스터는 Terraform과 EKS Blueprints for Terraform을 사용하여 자동으로 생성되었습니다.

    . Terraform: 인프라의 효율적으로 생성, 변경, 버전 관리를 위한 IaC 도구

    . EKS Blueprints for Terraform: EKS 클러스터를 구성하기 위한 모범 사례 기반 템플릿 모음

 

GitHub - aws-ia/terraform-aws-eks-blueprints: Configure and deploy complete EKS clusters.

Configure and deploy complete EKS clusters. Contribute to aws-ia/terraform-aws-eks-blueprints development by creating an account on GitHub.

github.com

 

  - 카펜터(Karpenter) 설치 및 구성

  이 워크숍 환경에서는 Karpenter가 Amazon EKS 클러스터에 미리 설치되어 있습니다.
  Helm 차트를 통해 설치하거나, EKS Blueprints for Terraform를 사용하는 경우 미리 설치된  EKS 클러스터 생성이 가능합니다.

 

GitHub - aws/karpenter-provider-aws: Karpenter is a Kubernetes Node Autoscaler built for flexibility, performance, and simplicit

Karpenter is a Kubernetes Node Autoscaler built for flexibility, performance, and simplicity. - aws/karpenter-provider-aws

github.com

 

      . Karpenter는 Kubernetes의 Custom Resource Definition (CRD) 기반으로 설정

      . NodePool CRD를 사용하여 노드풀 구성

# 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:44:11Z"
  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: "696141"
  uid: 992f424e-1779-4b35-b961-0e944270ba76
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://C70A29DB16DC0EBACDD10193C90E69A8.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:44:31Z"
    lastUpdateTime: "2025-04-17T01:44:31Z"
    message: Deployment has minimum availability.
    reason: MinimumReplicasAvailable
    status: "True"
    type: Available
  - lastTransitionTime: "2025-04-17T01:44:11Z"
    lastUpdateTime: "2025-04-17T01:44:41Z"
    message: ReplicaSet "karpenter-7bc78d6f78" 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 Blueprint에 의해 생성된 SQS 큐의 엔드포인트

    . 스팟 인스턴스 중단 알림 및 AWS Health 이벤트를 저장하는 데 사용

 

  - 카펜터(Karpenter) 상태 확인

# 카펜터 pod 확인
kubectl get pods --namespace karpenter

---

NAME                         READY   STATUS    RESTARTS     AGE
karpenter-7bc78d6f78-fkszp   1/1     Running   0            2d
karpenter-7bc78d6f78-zfm6j   1/1     Running   2 (2d ago)   2d

---

# 카펜터 로그 확인
alias kl='kubectl -n karpenter logs -l app.kubernetes.io/name=karpenter --all-containers=true -f --tail=20'

kl

---
{"level":"INFO","time":"2025-04-17T01:45:03.331Z","logger":"controller","message":"discovered ssm parameter","commit":"62a726c","controller":"nodeclass.status","controllerGroup":"karpenter.k8s.aws","controllerKind":"EC2NodeClass","EC2NodeClass":{"name":"sysprep"},"namespace":"","name":"sysprep","reconcileID":"c46e106c-6854-45e8-888b-9dfe7d067071","parameter":"/aws/service/eks/optimized-ami/1.30/amazon-linux-2-gpu/amazon-eks-gpu-node-1.30-v20240917/image_id","value":"ami-0356f40aea17e9b9e"}
{"level":"INFO","time":"2025-04-17T01:45:03.349Z","logger":"controller","message":"discovered ssm parameter","commit":"62a726c","controller":"nodeclass.status","controllerGroup":"karpenter.k8s.aws","controllerKind":"EC2NodeClass","EC2NodeClass":{"name":"sysprep"},"namespace":"","name":"sysprep","reconcileID":"c46e106c-6854-45e8-888b-9dfe7d067071","parameter":"/aws/service/eks/optimized-ami/1.30/amazon-linux-2/amazon-eks-node-1.30-v20240917/image_id","value":"ami-05f7e80c30f28d8b9"}
{"level":"ERROR","time":"2025-04-17T01:45:04.217Z","logger":"controller","message":"nodePool not ready","commit":"62a726c","controller":"provisioner","namespace":"","name":"","reconcileID":"055879dc-4e2e-4e45-8451-61506dd95ec0","NodePool":{"name":"sysprep"}}
{"level":"ERROR","time":"2025-04-17T01:45:04.217Z","logger":"controller","message":"nodePool not ready","commit":"62a726c","controller":"provisioner","namespace":"","name":"","reconcileID":"055879dc-4e2e-4e45-8451-61506dd95ec0","NodePool":{"name":"default"}}
{"level":"INFO","time":"2025-04-17T01:45:04.217Z","logger":"controller","message":"no nodepools found","commit":"62a726c","controller":"provisioner","namespace":"","name":"","reconcileID":"055879dc-4e2e-4e45-8451-61506dd95ec0"}
{"level":"INFO","time":"2025-04-17T01:45:14.377Z","logger":"controller","message":"found provisionable pod(s)","commit":"62a726c","controller":"provisioner","namespace":"","name":"","reconcileID":"81154a6a-f697-4338-bcb2-bed37aa3e5b3","Pods":"default/sysprep-q89fg","duration":"159.092045ms"}
{"level":"INFO","time":"2025-04-17T01:45:14.377Z","logger":"controller","message":"computed new nodeclaim(s) to fit pod(s)","commit":"62a726c","controller":"provisioner","namespace":"","name":"","reconcileID":"81154a6a-f697-4338-bcb2-bed37aa3e5b3","nodeclaims":1,"pods":1}
{"level":"INFO","time":"2025-04-17T01:45:14.391Z","logger":"controller","message":"created nodeclaim","commit":"62a726c","controller":"provisioner","namespace":"","name":"","reconcileID":"81154a6a-f697-4338-bcb2-bed37aa3e5b3","NodePool":{"name":"sysprep"},"NodeClaim":{"name":"sysprep-q6vld"},"requests":{"cpu":"180m","memory":"120Mi","pods":"9"},"instance-types":"c5.large, c5.xlarge, c5a.large, c5a.xlarge, c5ad.large and 55 other(s)"}
{"level":"INFO","time":"2025-04-17T01:45:16.558Z","logger":"controller","message":"launched nodeclaim","commit":"62a726c","controller":"nodeclaim.lifecycle","controllerGroup":"karpenter.sh","controllerKind":"NodeClaim","NodeClaim":{"name":"sysprep-q6vld"},"namespace":"","name":"sysprep-q6vld","reconcileID":"b1c7b58a-7aee-43f6-9e1c-5f5da3f4a4a1","provider-id":"aws:///us-west-2b/i-0c4664a8ff0a2bc4a","instance-type":"c6a.large","zone":"us-west-2b","capacity-type":"on-demand","allocatable":{"cpu":"1930m","ephemeral-storage":"89Gi","memory":"3114Mi","pods":"29","vpc.amazonaws.com/pod-eni":"9"}}
{"level":"INFO","time":"2025-04-17T01:45:36.219Z","logger":"controller","message":"registered nodeclaim","commit":"62a726c","controller":"nodeclaim.lifecycle","controllerGroup":"karpenter.sh","controllerKind":"NodeClaim","NodeClaim":{"name":"sysprep-q6vld"},"namespace":"","name":"sysprep-q6vld","reconcileID":"58a28d24-5851-4e2c-a585-566d5a5d96e4","provider-id":"aws:///us-west-2b/i-0c4664a8ff0a2bc4a","Node":{"name":"ip-10-0-89-211.us-west-2.compute.internal"}}
{"level":"INFO","time":"2025-04-17T01:45:46.074Z","logger":"controller","message":"initialized nodeclaim","commit":"62a726c","controller":"nodeclaim.lifecycle","controllerGroup":"karpenter.sh","controllerKind":"NodeClaim","NodeClaim":{"name":"sysprep-q6vld"},"namespace":"","name":"sysprep-q6vld","reconcileID":"1b8f816d-3d71-4b25-a268-03d85d4c9214","provider-id":"aws:///us-west-2b/i-0c4664a8ff0a2bc4a","Node":{"name":"ip-10-0-89-211.us-west-2.compute.internal"},"allocatable":{"cpu":"1930m","ephemeral-storage":"95551679124","hugepages-1Gi":"0","hugepages-2Mi":"0","memory":"3210128Ki","pods":"29"}}
{"level":"INFO","time":"2025-04-17T01:56:15.025Z","logger":"controller","message":"tainted node","commit":"62a726c","controller":"node.termination","controllerGroup":"","controllerKind":"Node","Node":{"name":"ip-10-0-89-211.us-west-2.compute.internal"},"namespace":"","name":"ip-10-0-89-211.us-west-2.compute.internal","reconcileID":"7fe1eee3-8faa-40c5-bb2a-9605c4f70da7","taint.Key":"karpenter.sh/disrupted","taint.Value":"","taint.Effect":"NoSchedule"}
{"level":"INFO","time":"2025-04-17T01:56:57.157Z","logger":"controller","message":"deleted node","commit":"62a726c","controller":"node.termination","controllerGroup":"","controllerKind":"Node","Node":{"name":"ip-10-0-89-211.us-west-2.compute.internal"},"namespace":"","name":"ip-10-0-89-211.us-west-2.compute.internal","reconcileID":"afe5ab79-fd1d-4d6b-a840-87a32228c63f"}
{"level":"INFO","time":"2025-04-17T01:56:57.425Z","logger":"controller","message":"deleted nodeclaim","commit":"62a726c","controller":"nodeclaim.termination","controllerGroup":"karpenter.sh","controllerKind":"NodeClaim","NodeClaim":{"name":"sysprep-q6vld"},"namespace":"","name":"sysprep-q6vld","reconcileID":"e611424a-d4aa-4d5a-880d-b612c323b596","Node":{"name":"ip-10-0-89-211.us-west-2.compute.internal"},"provider-id":"aws:///us-west-2b/i-0c4664a8ff0a2bc4a"}
{"level":"INFO","time":"2025-04-18T01:45:40.237Z","logger":"controller","message":"discovered ssm parameter","commit":"62a726c","controller":"nodeclass.status","controllerGroup":"karpenter.k8s.aws","controllerKind":"EC2NodeClass","EC2NodeClass":{"name":"default"},"namespace":"","name":"default","reconcileID":"df6eb3fe-07b0-4242-a1cc-32d57f755d64","parameter":"/aws/service/eks/optimized-ami/1.30/amazon-linux-2-arm64/amazon-eks-arm64-node-1.30-v20240917/image_id","value":"ami-0b402b9a4c1bacaa5"}
{"level":"INFO","time":"2025-04-18T01:45:40.259Z","logger":"controller","message":"discovered ssm parameter","commit":"62a726c","controller":"nodeclass.status","controllerGroup":"karpenter.k8s.aws","controllerKind":"EC2NodeClass","EC2NodeClass":{"name":"default"},"namespace":"","name":"default","reconcileID":"df6eb3fe-07b0-4242-a1cc-32d57f755d64","parameter":"/aws/service/eks/optimized-ami/1.30/amazon-linux-2-gpu/amazon-eks-gpu-node-1.30-v20240917/image_id","value":"ami-0356f40aea17e9b9e"}
{"level":"INFO","time":"2025-04-18T01:45:40.274Z","logger":"controller","message":"discovered ssm parameter","commit":"62a726c","controller":"nodeclass.status","controllerGroup":"karpenter.k8s.aws","controllerKind":"EC2NodeClass","EC2NodeClass":{"name":"default"},"namespace":"","name":"default","reconcileID":"df6eb3fe-07b0-4242-a1cc-32d57f755d64","parameter":"/aws/service/eks/optimized-ami/1.30/amazon-linux-2/amazon-eks-node-1.30-v20240917/image_id","value":"ami-05f7e80c30f28d8b9"}
{"level":"INFO","time":"2025-04-19T01:46:08.909Z","logger":"controller","message":"discovered ssm parameter","commit":"62a726c","controller":"nodeclass.status","controllerGroup":"karpenter.k8s.aws","controllerKind":"EC2NodeClass","EC2NodeClass":{"name":"default"},"namespace":"","name":"default","reconcileID":"618f8b39-dbce-4b22-b40b-1a9d47722833","parameter":"/aws/service/eks/optimized-ami/1.30/amazon-linux-2-arm64/amazon-eks-arm64-node-1.30-v20240917/image_id","value":"ami-0b402b9a4c1bacaa5"}
{"level":"INFO","time":"2025-04-19T01:46:08.924Z","logger":"controller","message":"discovered ssm parameter","commit":"62a726c","controller":"nodeclass.status","controllerGroup":"karpenter.k8s.aws","controllerKind":"EC2NodeClass","EC2NodeClass":{"name":"default"},"namespace":"","name":"default","reconcileID":"618f8b39-dbce-4b22-b40b-1a9d47722833","parameter":"/aws/service/eks/optimized-ami/1.30/amazon-linux-2-gpu/amazon-eks-gpu-node-1.30-v20240917/image_id","value":"ami-0356f40aea17e9b9e"}
{"level":"INFO","time":"2025-04-19T01:46:08.945Z","logger":"controller","message":"discovered ssm parameter","commit":"62a726c","controller":"nodeclass.status","controllerGroup":"karpenter.k8s.aws","controllerKind":"EC2NodeClass","EC2NodeClass":{"name":"default"},"namespace":"","name":"default","reconcileID":"618f8b39-dbce-4b22-b40b-1a9d47722833","parameter":"/aws/service/eks/optimized-ami/1.30/amazon-linux-2/amazon-eks-node-1.30-v20240917/image_id","value":"ami-05f7e80c30f28d8b9"}
{"level":"INFO","time":"2025-04-17T01:44:35.047Z","logger":"controller.controller-runtime.metrics","message":"Serving metrics server","commit":"62a726c","bindAddress":":8080","secure":false}
{"level":"INFO","time":"2025-04-17T01:44:35.047Z","logger":"controller","message":"starting server","commit":"62a726c","name":"health probe","addr":"[::]:8081"}
{"level":"INFO","time":"2025-04-17T01:44:35.248Z","logger":"controller","message":"attempting to acquire leader lease karpenter/karpenter-leader-election...","commit":"62a726c"}
{"level":"INFO","time":"2025-04-18T15:56:12.954Z","logger":"controller","message":"k8s.io/client-go@v0.30.3/tools/cache/reflector.go:232: watch of *v1.Pod ended with: an error on the server (\"unable to decode an event from the watch stream: http2: client connection lost\") has prevented the request from succeeding","commit":"62a726c"}
{"level":"INFO","time":"2025-04-18T15:56:12.954Z","logger":"controller","message":"k8s.io/client-go@v0.30.3/tools/cache/reflector.go:232: watch of *v1.NodePool ended with: an error on the server (\"unable to decode an event from the watch stream: http2: client connection lost\") has prevented the request from succeeding","commit":"62a726c"}
{"level":"INFO","time":"2025-04-18T15:56:12.954Z","logger":"controller","message":"k8s.io/client-go@v0.30.3/tools/cache/reflector.go:232: watch of *v1.ConfigMap ended with: an error on the server (\"unable to decode an event from the watch stream: http2: client connection lost\") has prevented the request from succeeding","commit":"62a726c"}
{"level":"INFO","time":"2025-04-18T15:56:12.954Z","logger":"controller","message":"k8s.io/client-go@v0.30.3/tools/cache/reflector.go:232: watch of *v1.VolumeAttachment ended with: an error on the server (\"unable to decode an event from the watch stream: http2: client connection lost\") has prevented the request from succeeding","commit":"62a726c"}
{"level":"INFO","time":"2025-04-18T15:56:12.954Z","logger":"controller","message":"k8s.io/client-go@v0.30.3/tools/cache/reflector.go:232: watch of *v1.Secret ended with: an error on the server (\"unable to decode an event from the watch stream: http2: client connection lost\") has prevented the request from succeeding","commit":"62a726c"}
{"level":"INFO","time":"2025-04-18T15:56:12.954Z","logger":"controller","message":"k8s.io/client-go@v0.30.3/tools/cache/reflector.go:232: watch of *v1.Node ended with: an error on the server (\"unable to decode an event from the watch stream: http2: client connection lost\") has prevented the request from succeeding","commit":"62a726c"}
{"level":"INFO","time":"2025-04-18T15:56:12.954Z","logger":"controller","message":"k8s.io/client-go@v0.30.3/tools/cache/reflector.go:232: watch of *v1.CustomResourceDefinition ended with: an error on the server (\"unable to decode an event from the watch stream: http2: client connection lost\") has prevented the request from succeeding","commit":"62a726c"}
{"level":"INFO","time":"2025-04-18T15:56:12.954Z","logger":"controller","message":"k8s.io/client-go@v0.30.3/tools/cache/reflector.go:232: watch of *v1.NodeClaim ended with: an error on the server (\"unable to decode an event from the watch stream: http2: client connection lost\") has prevented the request from succeeding","commit":"62a726c"}
{"level":"ERROR","time":"2025-04-18T15:56:12.954Z","logger":"controller","message":"error retrieving resource lock karpenter/karpenter-leader-election: Get \"https://172.20.0.1:443/apis/coordination.k8s.io/v1/namespaces/karpenter/leases/karpenter-leader-election\": http2: client connection lost","commit":"62a726c"}
{"level":"INFO","time":"2025-04-18T15:56:29.272Z","logger":"controller","message":"Trace[1724764428]: \"Reflector ListAndWatch\" name:k8s.io/client-go@v0.30.3/tools/cache/reflector.go:232 (18-Apr-2025 15:56:13.827) (total time: 15445ms):\nTrace[1724764428]: ---\"Objects listed\" error:<nil> 15445ms (15:56:29.272)\nTrace[1724764428]: [15.445046861s] [15.445046861s] END","commit":"62a726c"}
{"level":"INFO","time":"2025-04-18T15:56:29.273Z","logger":"controller","message":"Trace[803973801]: \"Reflector ListAndWatch\" name:k8s.io/client-go@v0.30.3/tools/cache/reflector.go:232 (18-Apr-2025 15:56:13.913) (total time: 15359ms):\nTrace[803973801]: ---\"Objects listed\" error:<nil> 15359ms (15:56:29.273)\nTrace[803973801]: [15.359287293s] [15.359287293s] END","commit":"62a726c"}
{"level":"INFO","time":"2025-04-18T15:56:29.273Z","logger":"controller","message":"Trace[184432298]: \"Reflector ListAndWatch\" name:k8s.io/client-go@v0.30.3/tools/cache/reflector.go:232 (18-Apr-2025 15:56:14.199) (total time: 15074ms):\nTrace[184432298]: ---\"Objects listed\" error:<nil> 15074ms (15:56:29.273)\nTrace[184432298]: [15.074098566s] [15.074098566s] END","commit":"62a726c"}
{"level":"INFO","time":"2025-04-18T15:56:29.274Z","logger":"controller","message":"Trace[878126375]: \"Reflector ListAndWatch\" name:k8s.io/client-go@v0.30.3/tools/cache/reflector.go:232 (18-Apr-2025 15:56:14.112) (total time: 15161ms):\nTrace[878126375]: ---\"Objects listed\" error:<nil> 15161ms (15:56:29.274)\nTrace[878126375]: [15.16141812s] [15.16141812s] END","commit":"62a726c"}
{"level":"INFO","time":"2025-04-18T15:56:29.277Z","logger":"controller","message":"Trace[657244504]: \"Reflector ListAndWatch\" name:k8s.io/client-go@v0.30.3/tools/cache/reflector.go:232 (18-Apr-2025 15:56:13.957) (total time: 15320ms):\nTrace[657244504]: ---\"Objects listed\" error:<nil> 15320ms (15:56:29.277)\nTrace[657244504]: [15.320296662s] [15.320296662s] END","commit":"62a726c"}
{"level":"INFO","time":"2025-04-18T15:56:29.284Z","logger":"controller","message":"Trace[2026486697]: \"Reflector ListAndWatch\" name:k8s.io/client-go@v0.30.3/tools/cache/reflector.go:232 (18-Apr-2025 15:56:13.860) (total time: 15423ms):\nTrace[2026486697]: ---\"Objects listed\" error:<nil> 15423ms (15:56:29.284)\nTrace[2026486697]: [15.423659221s] [15.423659221s] END","commit":"62a726c"}
{"level":"INFO","time":"2025-04-18T15:56:29.290Z","logger":"controller","message":"Trace[1621031503]: \"Reflector ListAndWatch\" name:k8s.io/client-go@v0.30.3/tools/cache/reflector.go:232 (18-Apr-2025 15:56:14.235) (total time: 15055ms):\nTrace[1621031503]: ---\"Objects listed\" error:<nil> 15055ms (15:56:29.290)\nTrace[1621031503]: [15.055476086s] [15.055476086s] END","commit":"62a726c"}
{"level":"INFO","time":"2025-04-18T15:56:29.397Z","logger":"controller","message":"Trace[1461869097]: \"Reflector ListAndWatch\" name:k8s.io/client-go@v0.30.3/tools/cache/reflector.go:232 (18-Apr-2025 15:56:14.491) (total time: 14905ms):\nTrace[1461869097]: ---\"Objects listed\" error:<nil> 14905ms (15:56:29.397)\nTrace[1461869097]: [14.905601775s] [14.905601775s] END","commit":"62a726c"}

---

 

 

  - FSx for Lustre + S3 연동 구성

  이 워크숍에서는 Mistral-7B-Instruct 모델이 Amazon S3 버킷에 저장되어 있으며, Amazon FSx for Lustre 파일 시스템과 연결됩니다.

 

   * Amazon FSx for Lustre

    . 머신러닝, HPC, 분석 등 고성능 파일 접근이 필요한 워크로드를 위한 완전관리형 고성능 병렬 파일 시스템

    . 서브 밀리초 대기시간, TB/s급 처리량, 수백만 IOPS 지원

    . S3와 연동되어, S3 객체를 Lustre 상의 파일처럼 다룰 수 있고, Lustre에서 변경된 내용을 S3에 자동 동기화

 

 

What is Amazon FSx for Lustre? - FSx for Lustre

What is Amazon FSx for Lustre? FSx for Lustre makes it easy and cost-effective to launch and run the popular, high-performance Lustre file system. You use Lustre for workloads where speed matters, such as machine learning, high performance computing (HPC),

docs.aws.amazon.com

 

   

. 구성도

> kubernetes 스토리지와 FSx for Lustre 통합

  - CSI 드라이버 (Container Storage Interface)

    . FSx for Lustre CSI 드라이버를 통해 Kubernetes에서 FSx 스토리지의 생성/삭제 등 수명 주기 관리 가능

  - StorageClass

    . 스토리지의 종류나 속성 정의 (예: Amazon FSx, EBS, EFS 등을 각각의 StorageClass로 제공)

  -  Persistent Volume (PV)

    . Pod 종료시에도 유지되어 데이터 공유나 지속성 유지가 필요한 경우 사용

  - Persistent Volume Claim (PVC)

    . 사용자가 필요한 스토리지를 요청하는 방식 정의

    . 크기나 접근 모드(ReadWriteOnce, ReadOnlyMany 등) 지정 가능

 

    * 정적 vs 동적 프로비저닝

     - 정적 프로비저닝

        . 관리자: FSx 인스턴스 생성 + PV 정의
        . 사용자: PVC로 해당 PV 사용

     - 동적 프로비저닝

        . 사용자가 PVC 생성 시, 자동으로 PV와 FSx 인스턴스가 생성

 

    > CSI Driver 배포

# 1. 사전 준비 - account-id 환경 변수 설정
ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)

# 2. CSI Driver IAM 정책 생성

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
        
# 3. Service Accoount 생성
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
    
 ---
 2023-09-29 07:40:56 [ℹ]  created serviceaccount "kube-system/fsx-csi-controller-sa"
 ---
 
# 4. FSx CSI driver 배포
## 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)
echo $ROLE_ARN

## 배포
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

# 5. Kubernetes의 ServiceAccount에 IAM 역할을 연결
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

IAM 정책 생성
serviceaccount 생성
CSI Driver 배포
IAM Role 연결

    > PV 생성

      . FSx for Lustre 스토리지를 Kubernetes에서 사용할 수 있도록 PV와 PVC를 Static 방식으로 설정

      . 이 스토리지는 이후 vLLM Pod가 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)

# 1. PV 생성
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

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
---

kubectl apply -f fsxL-persistent-volume.yaml

kubectl get pv

 

 

    > PVC 생성

kubectl apply -f fsxL-claim.yaml

---
# fsxL-claim.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: fsx-lustre-claim
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: ""
  resources:
    requests:
      storage: 1200Gi
  volumeName: fsx-pv
---

kubectl get pv,pvc

 

    > FSx console에서 옵션 및 성능 확인

     - 새로운 FSx 생성하며 옵션 확인하기

      1. Amazon FSx 콘솔 접속 및 리전 설정

      - Amazon FSx 콘솔 -> 화면 오른쪽 상단에서 AWS 리전(Region)을 선택 (예: us-west-2, ap-northeast-2 등)

 

      2. FSx for Lustre 인스턴스 확인

      - FSx 콘솔에서는 현재 생성된 FSx 인스턴스 목록을 확인

      - EKS 클러스터의 Persistent Volume으로 설정했던 인스턴스 확인 가능

 

      3. 새로운 FSx for Lustre 인스턴스 생성 옵션 확인

      - 화면 우측 상단의 “Create file system” 버튼을 클릭

 

      4. FSx for Lustre 선택

      - “Create file system” 페이지에서 Amazon FSx for Lustre를 선택하고, “Next” 버튼을 클릭

      5. 배포 옵션 확인

     - 다음 화면에서는 다양한 FSx for Lustre 배포 옵션 존재

        . PERSISTENT-SSD 또는 SCRATCH 스토리지 선택

        . Throughput (처리량) 설정: 스토리지 용량당 처리 속도 구성

        . Metadata IOPS 성능 설정

        . 압축 옵션 설정

        . S3 버킷 연동을 통한 자동 데이터 가져오기/내보내기 설정

 

    - 옵션 확인이 끝났으면 “Cancel” 버튼을 클릭하여 해당 화면을 닫고,다시 FSx 콘솔로 복귀

   

    - 생성된 FSx로 성능 확인하기

      . FSx 콘솔에서, FSx for Lustre 인스턴스의 File system ID를 클릭하여 해당 인스턴스의 상세 설정 화면으로 이동

 

      - 인스턴스 설정 정보 확인

        . 선택한 인스턴스에 대한 다양한 구성 정보 및 온라인으로 수정 가능한 항목들을 확인할 수 있습니다.

        . 다음과 같은 항목들을 수정(업데이트) 가능

           - 스토리지 용량(Storage Capacity)

           - 스토리지 단위당 처리 성능(Throughput per unit of storage)

 

              * 스토리지 용량을 증가시키면, 단위 스토리지 당 처리 성능(Throughput)도 자동으로 증가

              * Throughput은 스토리지 용량과 무관하게 독립적으로 조정 가능 (예: 저장 공간은 충분하지만 처리 성능만 더 필요한 경우)

       - 성능 모니터링 탭 확인

         . 화면 아래쪽으로 스크롤하여 “Monitoring & performance” 탭을 클릭

            - 요약 지표: 용량(Capacity), 처리량(Throughput), IOPS

            - 세부 성능 지표: 메타데이터 처리 성능, 네트워크 등

 

  - Chat 애플리케이션 배포 및 테스트

    . Amazon EKS에 vLLM과 WebUI를 배포하고, FSx 및 S3로 Mistral-7B 모델을 연동하며, Inferentia로 고성능 생성형 AI를 실행합니다.

    - AWS Interntia 노드에 vLLM 배포

    . 카펜터 NodePool 및 NodeClass 생성

# 작업 디렉토리 이동
cd /home/ec2-user/environment/eks/genai

kubectl apply -f inferentia_nodepool.yaml

kubectl get nodepool,ec2nodeclass inferentia

# 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
---

   

       . Neuron device plugin 및 scheduler 설치

      ※ 이 워크숍에서는 Mistral-7B 모델은 이미 AWS Neuron SDK를 사용하여 다운로드 및 컴파일되어 있습니다.

# Neuron device plugin이 Neuron cores와 devices를 k8s 리소스 형태로 노출
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 코어나 장치가 필요한 Pod에 대해 연속된 자원을 할당
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

 

      . vLLM 어플리케이션 Pod 배포

kubectl apply -f mistral-fsxl.yaml

kubectl get pod

# mistral-fsxl.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: vllm-mistral-inf2-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: vllm-mistral-inf2-server
  template:
    metadata:
      labels:
        app: vllm-mistral-inf2-server
    spec:
      tolerations:
      - key: "aws.amazon.com/neuron"
        operator: "Exists"
        effect: "NoSchedule"
      containers:
      - name: inference-server
        image: public.ecr.aws/u3r1l1j7/eks-genai:neuronrayvllm-100G-root
        resources:
          requests:
            aws.amazon.com/neuron: 1
          limits:
            aws.amazon.com/neuron: 1
        args:
        - --model=$(MODEL_ID)
        - --enforce-eager
        - --gpu-memory-utilization=0.96
        - --device=neuron
        - --max-num-seqs=4
        - --tensor-parallel-size=2
        - --max-model-len=10240
        - --served-model-name=mistralai/Mistral-7B-Instruct-v0.2-neuron
        env:
        - name: MODEL_ID
          value: /work-dir/Mistral-7B-Instruct-v0.2/
        - name: NEURON_COMPILE_CACHE_URL
          value: /work-dir/Mistral-7B-Instruct-v0.2/neuron-cache/
        - name: PORT
          value: "8000"
        volumeMounts:
        - name: persistent-storage
          mountPath: "/work-dir"
      volumes:
      - name: persistent-storage
        persistentVolumeClaim:
          claimName: fsx-lustre-claim
---
apiVersion: v1
kind: Service
metadata:
  name: vllm-mistral7b-service
spec:
  selector:
    app: vllm-mistral-inf2-server
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8000
---

 

    - WebUI 어플리케이션 배포

# 배포
kubectl apply -f open-webui.yaml

# ingres 정보(접속 주소) 확인
kubectl get ing

  - Mistral-7B 모델 데이터 확인 및 S3를 통한 다중 리전 복제 실습

  . vLLM Pod에 접속해 FSx for Lustre로 연결된 Persistent Volume의 Mistral-7B 모델 데이터 구조 확인
  . S3 연동을 통해 생성한 파일이 자동으로 다른 리전 S3 버킷으로 복제되는 방식 실습

  - S3 버킷 간 교차 리전 복제(Cross Region Replication) 설정

    1. s3 console에서 bucket 선택 (2ndregion x)

    2. Maangement - Replication rules - Create replication rule 선택

    3. Enables Bucket Vesioning 후 Rule name 입력

 

    4. Source Bucket 메뉴에서 limit the scope of this rule using one or more filters 선택 -> prefix /test 입력

    5. 대상(Destination) 항목에서 Browse S3를 클릭 -> us-east-2 리전에 미리 생성된 대상 S3 버킷(fsx-lustre-bucket-2ndregion-xxxx 형식의 이름)을 선택하고 Choose path를 클릭

    6. 화면 상단에 빨간색 팝업창이 나타나면 Enable Bucket Versioning을 클릭

 

  7. Amazon S3가 사용자를 대신하여 S3 버킷 간에 객체를 복제할 수 있도록 AWS Identity and Access Management(IAM) 역할을 설정 (미리 생성된 역할 중에서 이름이 s3-cross-region-replication-role 로 시작하는 기존 역할을 선택)

 

    8. 암호화 설정에서 "AWS KMS로 암호화된 객체 복제(Replicate objects encrypted with AWS KMS)" 를 선택한 후, "사용 가능한 AWS KMS 키(Available AWS KMS keys)" 항목에서 표시되는 하나의 키를 선택

 

9. 다른 옵션은 모두 기본값으로 두고 "Save(저장)" 를 클릭하여 설정 저장

10. 이후 기존 객체를 복제할지 묻는 팝업이 나타나면, "NO(아니오), 기존 객체는 복제하지 않음" 을 선택

 

- Mistral-7B 데이터를 확인하고, 테스트 파일을 Pod에서 생성하여 데이터의 자동 내보내기 및 복제가 작동하도록 설정

  . pod에 로그인하고, 모델 데이터 확인 및 복제 대상 테스트 파일 생성

# 작업 디렉토리 이동
cd /home/ec2-user/environment/eks/FSxL

# pod attach
kubectl get pods
kubectl exec -it <YOUR-vLLM-POD-NAME> -- bash

# volume mount directory 확인
df -h
cd /work-dir/
ls -ll

cd Mistral-7B-Instruct-v0.2/
ls -ll

# test file 생성
cd /work-dir
mkdir test
cd test
cp /work-dir/Mistral-7B-Instruct-v0.2/README.md /work-dir/test/testfile
ls -ll /work-dir/test

 

 

  . 복제 확인 

 

  - FSx PV 동적 프로비저닝과 성능 테스트

    . 동적 프로비저닝을 사용해 자동으로 FSx Lustre 인스턴스를 생성하고, 이를 Pod에 마운트

    - 동적 프로비저닝을 사용하여 테스트를 위한 새로운 PV와 FSx Lustre 인스턴스를 배포

      . StorageClass 정의하기

# cloud9에서 변수 정의
VPC_ID=$(aws eks describe-cluster --name $CLUSTER_NAME --region $AWS_REGION --query "cluster.resourcesVpcConfig.vpcId" --output text)
SUBNET_ID=$(aws eks describe-cluster --name $CLUSTER_NAME --region $AWS_REGION --query "cluster.resourcesVpcConfig.subnetIds[0]" --output text)
SECURITY_GROUP_ID=$(aws ec2 describe-security-groups --filters Name=vpc-id,Values=${VPC_ID} Name=group-name,Values="FSxLSecurityGroup01"  --query "SecurityGroups[*].GroupId" --output text)  

echo $SUBNET_ID
echo $SECURITY_GROUP_ID

# 작업 디렉토리 전환
cd /home/ec2-user/environment/eks/FSxL

sed -i'' -e "s/SUBNET_ID/$SUBNET_ID/g" fsxL-storage-class.yaml
sed -i'' -e "s/SECURITY_GROUP_ID/$SECURITY_GROUP_ID/g" fsxL-storage-class.yaml

cat fsxL-storage-class.yaml

---
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
    name: fsx-lustre-sc
provisioner: fsx.csi.aws.com
parameters:
  subnetId: SUBNET_ID
  securityGroupIds: SECURITY_GROUP_ID
  deploymentType: SCRATCH_2
  fileSystemTypeVersion: "2.15"
mountOptions:
  - flock
---

 

      . StorageClass 생성

kubectl apply -f fsxL-storage-class.yaml
kubectl get sc
FSx for Lustre 인스턴스와 PV를 생성하는 동적 프로비저닝 작업은 약 15분 정도 소요됩니다. STATUS는 최대 15분 동안 Pending으로 표시될 수 있으며, 이후 Bound로 변경됩니다. STATUS가 Bound로 변경될 때까지 성능 테스트 모듈로 진행하지 마세요.

 

      . PVC 생성

#
cat fsxL-dynamic-claim.yaml

# 생성
kubectl apply -f fsxL-dynamic-claim.yaml

# 확인
kubectl describe pvc/fsx-lustre-dynamic-claim
kubectl get pvc

 

 

    - 성능 테스트

      . Step 1: FSx for Lustre와 10GB 스토리지를 사용하는 테스트 파드 생성

# 작업 디렉토리로 이동
cd /home/ec2-user/environment/eks/FSxL

# FSx for Lustre 인스턴스의 가용 영역(Availability Zone) 확인
# 이 가용 영역은 나중에 pod 설정에 필요
aws ec2 describe-subnets --subnet-id $SUBNET_ID --region $AWS_REGION | jq .Subnets[0].AvailabilityZone
출력 예시: "us-west-2c"


# Pod 설정 파일 열기
vi pod_performance.yaml

# vi 편집기에서 아래 작업 수행
# i 키를 눌러 편집 모드 진입
# 마지막 두 줄의 # nodeSelector:와 # topology.kubernetes.io/zone: 앞에 있는 # 제거
# us-east-2c를 위에서 확인한 가용 영역(us-west-2c 등)으로 수정
# ESC → :wq 입력 후 Enter로 저장하고 종료

# Pod 생성
kubectl apply -f pod_performance.yaml

# Pod 상태 확인
kubectl get pods

     . Step 2: 성능 테스트용 도구 설치 및 실

kubectl exec -it fsxl-performance -- bash

# FIO, IOping 도구 설치
apt-get update
apt-get install fio ioping -y

# IOping으로 레이턴시 측정
# 평균 지연 시간(latency)이 보통 0.5ms 미만이면 성능 우수
ioping -c 20 .

# FIO로 Throughput 테스트 실행
# 결과에서 평균 읽기/쓰기 처리량(Throughput)을 확인
mkdir -p /data/performance 
cd /data/performance 
fio --randrepeat=1 --ioengine=libaio --direct=1 --gtod_reduce=1 --name=fiotest --filename=testfio8gb --bs=1MB --iodepth=64 --size=8G --readwrite=randrw --rwmixread=50 --numjobs=8 --group_reporting --runtime=10

# Pod에서 로그아웃
exit

# 본 실습은 1.2TiB 크기의 최소 FSx for Lustre 구성으로, 기본적으로 240MB/s의 처리량 제공
# 처리량 및 IOPS는 파일 시스템 크기, 구성, 워크로드 특성에 따라 달라짐
# 자세한 성능 정보는 Amazon FSx for Lustre 성능 차트 참고

 

 

'K8S - EKS' 카테고리의 다른 글

12주차 - Amazon VPC Lattice for Amazon EKS  (0) 2025.04.27
10주차 - k8s 시크릿 관리  (0) 2025.04.12
9주차 - EKS Blue/Green Upgrade 실습  (0) 2025.04.05
8주차 - K8S CI/CD  (0) 2025.03.29
7주차 - EKS Mode/Nodes  (0) 2025.03.22

관련글 더보기