본 글은 CloudNet@ 팀 가시다님의 'AWS EKS Hands-on' 스터디 활동 내용을 정리하였습니다. 스터디 협력자님의 도움으로 제공받은 workshop 환경에서 실습한 내용을 바탕으로 합니다. Kubernetes(이하 k8s)에 대한 지식(아키텍처, 사용법 등)이 있다고 가정하고 작성했습니다. 잘못된 정보가 있으면 언제든지 알려주시기 바랍니다. |
1. 워크샵 소개 및 목적
2. 아키텍처 개요
- 사전 지식
- 전체 구성도
- 핵심 구성 요소
- 서비스 흐름
3. 실습
- Amazon EKS 클러스터 구성 및 배포
- FSx for Lustre + S3 연동 구성
- Chat 애플리케이션 배포 및 테스트
- Mistral-7B 모델 데이터 확인 및 S3를 통한 다중 리전 복제 실습
6. 정리
해당 워크숍에서는 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(재해 복구) 또는 분산 처리 시나리오에 대비한 설계 학습
<사전 지식>
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 대비
- 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
> 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
. 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
. 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 성능 차트 참고
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 |