By 2028, 95% of new AI deployments will use Kubernetes, up from less than 30% today
Gartner ® , Magic Quadrant™ for Container Management, 6 August 2025.
จากผลคาดการณ์ของ Gartner ระบุว่า ภายในปี 2028 เกือบทุกองค์กรจะนำ Kubernetes มาใช้เป็นโครงสร้างพื้นฐานมาตรฐานในการบริหารจัดการระบบ AI ระดับ Production เพื่อเพิ่มประสิทธิภาพในการสเกลทรัพยากร GPU และยกระดับความปลอดภัยในระดับองค์กร ตัวเลขนี้เน้นย้ำอย่างชัดเจนถึงความจำเป็นในการ Reskill ทักษะสาย IT ไปสู่สายงาน Platform Engineer และ MLOps
ผมจึงตั้งใจเขียนซีรีส์บทความนี้ขึ้นมาเพื่อแบ่งปันความรู้ให้กับทุกคนที่สนใจ โดยมาในธีมหลักอย่าง "Serving LLM บน Kubernetes Platform"
ประเดิมด้วย EP.1 นี้ ผมจะพาทุกคนมาลอง Deploy LLM Model ด้วย vLLM บน Amazon EKS (Kubernetes) ตั้งแต่ขั้นตอนการสร้าง EKS Cluster ไปจนถึงการได้ OpenAI-compatible API และหน้าเว็บ Chat ที่พร้อมใช้งานจริง โดยทุก Command ในบทความนี้สามารถพิมพ์ตามไปพร้อมกันได้เลยครับ
💡 ความตั้งใจของบทความนี้:
ตอนที่ผมพยายาม Deploy LLM บน Kubernetes ครั้งแรก ผมเสียเวลาลองผิดลองถูกไปหลายชั่วโมงเหมือนกันครับ ถือว่าบทความนี้สรุปจากประสบการณ์จริงมาให้แล้ว หวังว่าจะช่วยให้คนที่กำลังเริ่มต้นไม่ต้องมาเจอ Error หรือเสียเวลาแบบที่ผมเคยเจอครับ 😂
มาเริ่มกันเลยครับ...
Architecture Diagram
🛠️ Tech Stacks
- Terminal: AWS CloudShell (ใช้สำหรับรัน Command ทั้งหมดในบทความนี้)
- Cloud Provider: AWS (Region: us-east-1)
- Kubernetes Platform: Amazon EKS (Auto Mode)
-
Compute Instance: EC2 Instance Type
g6.2xlarge- GPU Spec: NVIDIA L4 Tensor Core (24GB GPU Memory)
- GPU Driver: NVIDIA CUDA
- LLM Inference Engine: vLLM
- Chat UI Platform: OpenUI
- Model Hub: Hugging Face
-
LLM Model: meta-llama/Llama-3.1-8B-Instruct
- Parameters: 8B
- Inference Quantization: FP16
- VRAM Requirement: ต้องการ VRAM ขั้นต่ำประมาณ 17–18 GB (รวม Weights + Activations/CUDA Graphs + KV Cache) ดังนั้นจึงต้องใช้ GPU ที่มี VRAM 24 GB ขึ้นไป (เช่น NVIDIA L4 หรือ A10G) เพื่อให้มีพื้นที่เหลือสำหรับ KV Cache
- 💡 ข้อแนะนำ: หากมี VRAM ไม่พอ สามารถเปลี่ยนไปใช้โมเดลที่ทำ Quantization เป็น FP8 แทนได้ : RedHatAI/Meta-Llama-3.1-8B-Instruct-FP8
⚡ ทำไมถึงเลือกใช้ vLLM ?
vLLM คือ Open-source Inference Engine ที่กำลังมาแรงมากในปัจจุบัน และกำลังกลายเป็นมาตรฐานหลักสำหรับการ Serve LLM บน Production เหตุผลหลักๆ ที่ผมเลือกใช้ตัวนี้ ได้แก่:
-
จัดการ GPU Memory ได้มีประสิทธิภาพสูงสุด:
- PagedAttention: เทคโนโลยีจัดการ KV Cache รูปแบบใหม่ที่ได้แรงบันดาลใจมาจาก Virtual Memory ของ OS ช่วยลด Memory Fragmentation
- Continuous Batching: อนุญาตให้ Request ใหม่แทรกเข้า Batch ที่กำลังประมวลผลอยู่ได้ทันทีโดยไม่ต้องรอให้ Batch เดิมทำงานเสร็จ ส่งผลให้ TTFT (Time to First Token) และ Tokens/s ทำได้ดีกว่า Engine ตัวอื่นอย่างชัดเจน
-
OpenAI-Compatible API: ให้ API ในรูปแบบเดียวกับ OpenAI ช่วยให้สามารถเชื่อมต่อกับ Application หรือ Tools เดิมที่มีอยู่ได้ทันทีโดยไม่ต้องแก้ Code นอกจากนี้ยังมี Native Endpoints อย่าง
/healthและ/metricsสำหรับทำ Health Checks หรือตั้งค่า Auto-scaling บน Kubernetes ได้ง่าย - High-Concurrency Production Ready: รองรับการใช้งานที่มี Traffic เข้ามาพร้อมกันจำนวนมาก (High Concurrency) ได้ ในขณะที่การ setup ไม่ยุ่งยากซับซ้อนจนเกินไป
- Multi-Node & Multi-GPU Serving: รองรับการทำ Tensor Parallelism และ Pipeline Parallelism ทั้งภายใน Node เดียวกัน และขยายข้ามหลาย Node (Multi-Node)
- Kubernetes Ecosystem Friendly: ถูกนำไปใช้เป็น Backend หลักใน Framework สาย Cloud-Native อย่าง KServe รวมถึง AWS เองก็มี Deep Learning Containers (DLC) ที่ปรับแต่ง vLLM มาให้พร้อมรันบน EKS ได้ทันที
- Open Source & Active Community: ใช้ใบอนุญาตแบบ Apache 2.0 และมี Community ที่เติบโตอย่างต่อเนื่อง
📋 สิ่งที่ต้องเตรียมก่อนเริ่ม
1. AWS Account
- Region: ใน Lab นี้ผมจะใช้ Region us-east-1 (N. Virginia) เนื่องจากเป็น Region ที่มี Instance Family ตระกูล g6 ให้ใช้งาน
-
EC2 Service Quotas (สำคัญมาก):
- โดยปกติ AWS Account ใหม่จะตั้งค่า Quota สำหรับ GPU Instance (G and O instances) ไว้ที่ 0 vCPUs
- ต้องทำการส่ง Request ขอเพิ่ม Service Quotas สำหรับ Running On-Demand G and VT instances ก่อน (แนะนำให้ขออย่างน้อย 8 vCPUs เพื่อรองรับ g6.2xlarge)
-
Permissions: หากเป็นการทดสอบใน Sandbox/Lab แนะนำให้ตั้งค่า Permission เป็น
AdministratorAccessเพื่อป้องกันปัญหาการติด Permission ระหว่าง Deploy
2. Hugging Face Account
-
Create Access Token:
- เข้าไปที่ Settings > Access Tokens จากนั้นสร้าง Token ใหม่โดยกำหนด Role เป็น Read เพื่อนำมาใช้ดึงโมเดลลงมายัง Cluster ครับ
-
Request Model Access:
- เนื่องจากในบทความนี้เลือกใช้โมเดล
meta-llama/Llama-3.1-8B-Instructซึ่งเป็น Gated Model บน Hugging Face จึงต้องทำการกดยอมรับเงื่อนไขสิทธิ์การใช้งานก่อน - (หมายเหตุ: หากต้องการหลีกเลี่ยงขั้นตอน Gated Model สามารถเลือกใช้โมเดล Open-weight อื่นๆ เช่น Qwen, DeepSeek หรือ Mistral ก็ได้เช่นกันครับ )
- (หรือใช้ลองใช้ ungated mirror NousResearch/Meta-Llama-3.1-8B-Instruct แทนได้ เพราะใช้ weights ชุดเดียวกันกับของ meta)
- เนื่องจากในบทความนี้เลือกใช้โมเดล
📌 ขั้นตอนการขอ Request Access Model:
- ไปที่หน้า Model Repository meta-llama/Llama-3.1-8B-Instruct
กรอกข้อมูลส่วนตัวและยอมรับเงื่อนไขใบอนุญาต จากนั้นกด Submit เพื่อส่งคำขอ

ตรวจสอบสถานะการอนุมัติได้ที่หน้า Gated Repos Status ในตั้งค่าบัญชีของตัวเอง

และเมื่อเราเตรียมทั้งหมดแล้ว หลังจากนี้เราจะเริ่ม Deploy LLM บน Amazon EKS กันเลยครับ
🚀 Deploy LLM บน Amazon EKS
ก่อนจะเริ่มลงมือ ทำความเข้าใจกันก่อนว่า เป้าหมายของบทความนี้คือความง่ายและรวดเร็ว เพื่อให้ทุกคนได้เห็นผลลัพธ์จริงแบบเร็วที่สุด รวมถึงเข้าใจ component ต่างในระบบ ดังนั้น สคริปต์และขั้นตอนทั้งหมดผมจึงออกแบบมาให้กระชับและทำตามได้ทันทีครับ (ผมคิดว่าน่าจะเป็นแนวทางง่ายที่สุดละ 😂)
นอกจากนี้ ผมจะใช้ AWS CloudShell ในการรันสคริปต์และคำสั่งทั้งหมดตลอดทั้งบทความ ไม่จำเป็นต้องติดตั้ง Terraform, kubectl, หรือ Tools ใดๆ ลงบนเครื่องตัวเองเลยครับ
เริ่มต้นด้วยการให้เราเปิด AWS CloudShell บน AWS Console ขึ้นมาได้เลยครับ (อย่าลืมตรวจสอบ Region มุมขวาบนให้แน่ใจว่าเลือกเป็น us-east-1 ด้วยนะครับ)
ขั้นตอนที่ 1: ตั้งค่า Environment Variables และติดตั้งเครื่องมือ
รันคำสั่งกำหนดค่าตัวแปลบน AWS CloudShell (อย่าลืมเปลี่ยนค่า HF_TOKEN ให้เป็น Hugging Face Token ด้วยนะครับ):
# กำหนด Hugging Face Access Token (เปลี่ยนเป็น Token ของเรา)
export HF_TOKEN="hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# กำหนด AWS Region และชื่อ EKS Cluster
export Region=us-east-1
export EKSClusterName=llm-eks-lab
ติดตั้ง CLI Tools ที่จำเป็น
# 1. ติดตั้ง envsubst สำหรับจัดการ Template File
sudo yum install gettext -y
# 2. ติดตั้ง eksctl (CLI สำหรับจัดการ EKS Cluster)
ARCH=amd64
PLATFORM=$(uname -s)_$ARCH
curl -sLO "https://github.com/eksctl-io/eksctl/releases/latest/download/eksctl_$PLATFORM.tar.gz"
tar -xzf eksctl_$PLATFORM.tar.gz -C /tmp && rm eksctl_$PLATFORM.tar.gz
sudo install -m 0755 /tmp/eksctl /usr/local/bin && rm /tmp/eksctl
# 3. ติดตั้ง kubectx และ kubens (สำหรับสลับ Context / Namespace)
sudo git clone https://github.com/ahmetb/kubectx /opt/kubectx
sudo ln -s /opt/kubectx/kubectx /usr/local/bin/kubectx
sudo ln -s /opt/kubectx/kubens /usr/local/bin/kubens
# 4. ตรวจสอบความถูกต้องของการติดตั้ง
eksctl version
kubectx -h
เครื่องมือต่างๆ พร้อมแล้ว!
ขั้นตอนที่ 2: สร้าง AWS EKS Cluster
ในขั้นตอนนี้เราจะใช้ eksctl ในการ provisioning ตัว EKS Cluster โดยเปิดใช้งานฟีเจอร์ EKS Auto Mode เพื่อให้ AWS ช่วยดูแลจัดการเรื่อง Infrastructure และ Node Management ให้อัตโนมัติครับ
cat <<EOF | eksctl create cluster -f -
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: ${EKSClusterName}
region: ${Region}
version: "1.35"
iam:
withOIDC: true
autoModeConfig:
enabled: true
nodePools: ["general-purpose", "system"]
EOF
📌 สรุปคอนฟิกสำคัญในการสร้าง EKS Cluster
-
EKS Version:
1.35#(ระบุเวอร์ชัน EKS ที่จะใช้) -
Enable Auto Mode:
true#(เปิดใช้งาน EKS Auto Mode ให้ AWS บริหารจัดการ Compute/Node ให้อัตโนมัติ) -
Node Pools: กำหนด Built-in Node Pools เริ่มต้น 2 กลุ่ม:
-
system: สำหรับรัน System Workloads และ Core Kubernetes Components -
general-purpose: สำหรับรัน General Application Workloads ทั่วไป
-
(รอประมาณ 10-15 นาทีในการสร้าง EKS Cluster ครับ...)
เมื่อรันคำสั่งสร้าง Cluster เสร็จเรียบร้อยแล้ว ให้ทำการตรวจสอบสถานะของ EKS Cluster ดูครับ (หาก Cluster พร้อมใช้งานแล้ว สถานะจะแสดงผลเป็น "ACTIVE"):
aws eks describe-cluster \
--name $EKSClusterName \
--region $Region \
--output json \
--query 'cluster.status'
# Output: "ACTIVE"
เมื่อ EKS พร้อมใช้งาน ก็มาทดสอบเรียก Kubernetes API ด้วย kubectl ต่อครับ
# Switch context ไปยัง cluster "llm-eks-la"
kubectx $(kubectx | grep "llm-eks-lab")
# ทดสอบ list node
kubectl get node
# ทดสอบ list Pod
kubectl get pod -A
จุดเด่นของ Amazon EKS Auto Mode คือการรวบรวม Tools สำคัญมาให้ครบถ้วนตั้งแต่แรก ไม่ว่าจะเป็น Karpenter, AWS Load Balancer Controller รวมถึง Nvidia Device Plugin ทำให้พร้อมรองรับ Workload GPU และเริ่มรัน Pod ได้ทันที
ขั้นตอนที่ 3: เตรียม GPU NodePool (Karpenter)
ในขั้นตอนนี้เราจะสร้าง Karpenter NodePool สำหรับจัดเตรียม GPU Nodeเพื่อใช้สำหรับสั่ง Provision เครื่อง g6.2xlarge มารองรับ vLLM ในขั้นตอนถัดไปครับ
รันคำสั่งสั่งสร้าง NodePool ผ่าน kubectl ได้เลย:
cat <<EOF | kubectl apply -f -
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: gpu
spec:
disruption:
consolidationPolicy: WhenEmpty
consolidateAfter: 5m
template:
metadata:
labels:
node-type: gpu
spec:
nodeClassRef:
group: eks.amazonaws.com
kind: NodeClass
name: default
requirements:
- key: "karpenter.sh/capacity-type"
operator: In
values: ["on-demand"]
- key: "kubernetes.io/arch"
operator: In
values: ["amd64"]
- key: "node.kubernetes.io/instance-type"
operator: In
values: ["g6.2xlarge"]
taints:
- key: nvidia.com/gpu
value: "true"
effect: NoSchedule
terminationGracePeriod: 24h0m0s
EOF
# Output: nodepool.karpenter.sh/gpu created
📌 สรุปคอนฟิกสำคัญของ NodePool
-
node.kubernetes.io/instance-type:
g6.2xlarge# ล็อกประเภท EC2 Instance เป็นg6.2xlargetype -
taints:
nvidia.com/gpu=true:NoSchedule# ใส่ Taint เพื่อป้องกันไม่ให้ Pod ทั่วไป (ที่ไม่ได้ต้องการ GPU) ถูก Schedule มาลงใน Node นี้ -
metadata.labels:node-type:
gpu# ติด Label ประจำ Node เพื่อให้ vLLM Deployment สามารถใช้ nodeSelector ชี้มาถูกเครื่อง -
disruption.consolidationPolicy:
WhenEmpty# ตั้งค่าให้คืนเครื่องออโต้เมื่อไม่มี Pod ใช้งานเกิน 5 นาที เพื่อช่วยประหยัดค่าใช้จ่าย
ข้อควรระวังสำหรับผู้ที่ใช้ Custom EKS (Non-Auto Mode):
- หากใช้ AL2023 AMI จำเป็นต้องติดตั้ง NVIDIA Device Plugin เพิ่มเติมเอง เพื่อให้ Kubernetes Schedule Pod ลงบน GPU Node ได้
- หากใช้ AL2 หรือ Custom AMI นอกเหนือจากการติดตั้ง NVIDIA Device Plugin แล้l จะต้องติดตั้ง NVIDIA Driver บน Node เองด้วย เพื่อให้ Container Runtime สามารถเข้าถึง GPU Card ได้
- (ข้อดีของ EKS Auto Mode ในบทความนี้คือ ตัว EKS จะจัดการ Driver และ Device Plugin ให้เราอัตโนมัติทั้งหมดครับ!)
💡Q: ทำไม Kubernetes ต้องมี Device Plugin?
A: เพราะโดยปกติแล้ว Kubernetes ไม่รู้จัก GPU ครับ! ตัวkube-schedulerจะรู้จักและคำนวณ resource พื้นฐานแค่cpu,memoryและephemeral-storageเท่านั้น ส่วน GPU นั้นถือเป็น Extended Resource ที่ต้องมีคนช่วย "โฆษณา" (Advertise) ให้ Kubernetes รับรู้ ซึ่งทำหน้าที่โดย NVIDIA Device Plugin นั่นเอง โดยตัว Plugin จะไปบอกกับkubeletบน Node เพื่อประกาศว่าเครื่องนี้มีทรัพยากร[nvidia.com/gpu]: 1เพิ่มเข้ามา ทำให้ค่านี้ไปปรากฏอยู่ใน node.status.allocatable และทำให้ Kubernetes สามารถ Schedule งานที่ร้องขอ GPU ได้ถูกต้องครับ
📌ตัวอย่างการติดตั้ง NVIDIA Driver และ Device Plugin
(⚠️ หมายเหตุ: สำหรับ Lab ในบทความนี้ ไม่จำเป็นต้องรันคำสั่งด้านล่างนี้ เนื่องจาก EKS Auto Mode จะจัดการส่วนนี้ให้อัตโนมัติทั้งหมดครับ นำมาลงไว้ให้ศึกษาเพิ่มเติมเท่านั้น)
# เพิ่ม Helm Repository ของ NVIDIA
helm repo add nvidia https://helm.ngc.nvidia.com/nvidia && helm repo update
# กรณีที่ใช้ AMI ที่ยังไม่มี driver (ex. Ubuntu EKS AMI)
# ต้องติดตั้งทั้ง GPU Driver, Container Toolkit และ Device Plugin
helm install gpu-operator nvidia/gpu-operator \
-n gpu-operator --create-namespace \
--set driver.enabled=true \
--set toolkit.enabled=true \
--version v25.3.0
# กรณีที่ใช้ GPU-Accelerated AMI (ex. Amazon Linux 2023 : AL2023) ที่มี Driver ติดตั้งมาแล้ว
# ติดตั้งเฉพาะ Device Plugin โดยไม่ต้องลง Driver ซ้ำ
helm install gpu-operator nvidia/gpu-operator \
-n gpu-operator --create-namespace \
--set driver.enabled=false \
--set toolkit.enabled=false
ขั้นตอนที่ 4: เริ่ม Deploy vLLM และ LLM Model
1. สร้าง Namespace และ Hugging Face Secret
เริ่มต้นด้วยการสร้าง Namespace local-llm และสร้าง Kubernetes Secret เพื่อเก็บ HF_TOKEN สำหรับใช้ยืนยันตัวตนในการดาวน์โหลดโมเดลจาก Hugging Face:
# สร้าง Namespace สำหรับระบบ LLM
kubectl create namespace local-llm
# สร้าง Secret เก็บ Hugging Face Access Token
kubectl create secret generic hf-secret -n local-llm \
--from-literal=HF_TOKEN=${HF_TOKEN}
2. ตรวจสอบสิทธิ์การเข้าถึง Gated Model
ถ้าใครใช้โมเดล meta-llama/Llama-3.1-8B-Instruct เหมือนในบทความนี้ ให้รันคำสั่งด้านล่างเพื่อทดสอบว่า Hugging Face Token ได้รับการอนุมัติสิทธิ์เข้าถึง Gated Repository เรียบร้อยแล้วหรือยัง:
curl -sS -o /dev/null -w 'HF gated repo check: %{http_code}\n' \
-H "Authorization: Bearer ${HF_TOKEN}" \
https://huggingface.co/meta-llama/Llama-3.1-8B-Instruct/resolve/main/config.json
# Output ที่ถูกต้อง: HF gated repo check: 200
⚠️ หมายเหตุ: หากผลลัพธ์แสดงเป็น 401 หรือ 403 แสดงว่า Token ไม่ถูกต้อง หรือยังไม่ได้รับการอนุมัติสิทธิ์เข้าถึงโมเดลบน Hugging Face ให้กลับไปเช็กขั้นตอนในส่วน "สิ่งที่ต้องเตรียมก่อนเริ่ม" อีกครั้งครับ
ถัดมา เราจะทำการสร้างทั้ง Deployment และ Service ของ vLLM ไปพร้อมๆ กัน
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: vllm-llama31-8b
namespace: local-llm
labels:
app: vllm-llama31-8b
spec:
replicas: 1
selector:
matchLabels:
app: vllm-llama31-8b
template:
metadata:
labels:
app: vllm-llama31-8b
spec:
nodeSelector:
node-type: gpu
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
containers:
- name: vllm
image: vllm/vllm-openai:v0.26.0
args:
- --model=meta-llama/Llama-3.1-8B-Instruct
- --served-model-name=llama31-8b
- --max-model-len=8192
- --gpu-memory-utilization=0.90
- --port=8000
env:
- name: HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-secret
key: HF_TOKEN
- name: HF_HOME
value: /root/.cache/huggingface
ports:
- containerPort: 8000
name: http
resources:
requests:
cpu: "4"
memory: 16Gi
nvidia.com/gpu: "1"
limits:
memory: 22Gi
nvidia.com/gpu: "1"
volumeMounts:
# For shared memory (vLLM requirement)
- name: dshm
mountPath: /dev/shm
- name: model-cache
mountPath: /root/.cache/huggingface
startupProbe:
httpGet:
path: /health
port: 8000
periodSeconds: 10
failureThreshold: 120
readinessProbe:
httpGet:
path: /health
port: 8000
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: 8000
periodSeconds: 30
failureThreshold: 5
volumes:
- name: dshm
emptyDir:
medium: Memory
sizeLimit: 8Gi
- name: model-cache
emptyDir:
sizeLimit: 60Gi
---
apiVersion: v1
kind: Service
metadata:
name: vllm-llama31-8b
namespace: local-llm
labels:
app: vllm-llama31-8b
spec:
type: ClusterIP
selector:
app: vllm-llama31-8b
ports:
- name: http
port: 8000
targetPort: 8000
EOF
📌 สรุปรายละเอียดคอนฟิกสำคัญ
-
Scheduling & Placement:
-
nodeSelector.node-type:
gpu# ระบุให้ Pod วิ่งไปลงเฉพาะ Node ที่มี Label node-type:gpu - tolerations: nvidia.com/gpu:NoSchedule operator: Exists # ปลด Taint บน GPU Node เพื่อให้ Pod สามารถ Schedule ลงไปทำงานได้
-
nodeSelector.node-type:
-
vLLM Arguments:
-
--model: ระบุ Model Repository บน Hugging Face (meta-llama/Llama-3.1-8B-Instruct) -
--served-model-name: กำหนดชื่อโมเดลอ้างอิงผ่าน API (เช่น เวลาเรียกใช้งานผ่าน /v1/models จะเห็นชื่อllama31-8b) -
--gpu-memory-utilization=0.90: จอง VRAM ไว้ 90% สำหรับ Model Weights และ KV Cache โดยเหลือไว้ 10% ให้กับ CUDA Context และ System Driver -
--max-model-len=8192: (ค่าสำคัญที่สุดใน Lab นี้!) ควบคุมขนาด Context Window รวม (Prompt + Output) ไว้ที่ 8,192 Tokens แม้ว่า Llama 3.1 จะรองรับสูงสุดถึง 131,072 Tokens แต่เนื่องจาก GPU L4 (VRAM 24GB) มีพื้นที่จำกัด หากไม่จำกัดค่านี้ไว้ VRAM จะเต็มและ Container จะระเบิด (OOM) ทันทีครับ 😂 (เรื่องวิธีคำนวณ VRAM เดี๋ยวผมมาแชร์รายละเอียดใน EP ถัดๆ ไปครับ)
-
-
Resource Request & Memory Volumes:
-
nvidia.com/gpu: "1": Extended Resource สำหรับแจ้ง kube-scheduler ให้ทำการ Reserve GPU 1 ใบให้กับ Pod -
dshm volume (medium: Memory, sizeLimit: 8Gi): ค่านี้ยังไม่มีผลมาก แต่จะมีประโยชน์เมื่อมีการ share ข้อมูลกับ Worker Processes หลายตัวใน node เดียวกัน -
model-cache volume (emptyDir, sizeLimit: 60Gi): จองดิสก์ขนาด 60GB สำหรับ Cache ตัว Model Weights ที่ดาวน์โหลดมาจาก Hugging Face
-
หลังจากสั่ง Deploy เรียบร้อยแล้ว ให้รันคำสั่งด้านล่างนี้เพื่อติดตามสถานะการรันและดู Log ของ vLLM Pod:
# ตรวจสอบสถานะของ Pod (กด Ctrl+C เพื่อออกจากโหมด watch)
kubectl get pod -l app=vllm-llama31-8b -n local-llm -w
# ดู Log การทำงานของ vLLM แบบ Real-time
kubectl logs -l app=vllm-llama31-8b -n local-llm -f
เมื่อดู pod status สถานะของ Pod จะค่อยๆ เปลี่ยนตามลำดับดังนี้:
Pending ➔ ContainerCreating ➔ Running (0/1 Ready) ➔ Running (1/1 Ready)
⏱️ สิ่งที่เกิดขึ้นในช่วง Running (0/1 Ready):
เมื่อ Pod เปลี่ยนเป็น Running แต่สถานะ Ready ยังเป็น 0/1 แปลว่า Karpenter ได้ Provision ตัว GPU Node และ Schedule Pod ลงเครื่องเรียบร้อยแล้ว ในช่วงนี้ระบบกำลังทำขั้นตอนสำคัญคือ
- ดาวน์โหลด Weights ของโมเดลจาก Hugging Face ลง Storage Cache
- โหลด Model Weights ทั้งหมดขึ้นไปยัง GPU VRAM (NVIDIA L4)
- เริ่มต้นเปิดบริการ OpenAI-compatible API Server
เมื่อระบบพร้อมใช้งาน Pod จะเปลี่ยนสถานะเป็น Ready 1/1 และใน Log จะแสดงข้อความการตอบรับ Health Check จาก Kubernetes ดังภาพ:
💡 Troubleshooting Tips:
- หาก Pod ค้างที่สถานะ
Pendingนานเกินไป: ให้เช็กว่า nodeSelector / tolerations ถูกต้องหรือไม่ หรือ Karpenter ติด Quota ในการสร้าง GPU Node- หาก Pod มีการ Restart หรือ CrashLoopBackOff: ให้เช็ก Log ของ vLLM ลำดับแรก มักเกิดจาก
HF_TOKENไม่ถูกต้อง หรือการตั้งค่า vLLM Arguments เช่น--max-model-lenสูงเกินกว่า VRAM ที่มี
Route สำคัญที่ควรรู้จัก
-
/health: readiness/liveness probe -
/metrics: Prometheus scrape (มี TTFT, TPOT, queue size, KV cache usage) -
/v1/models: ตรวจว่า served_model_name ถูกต้อง -
/v1/chat/completions: OpenAI-compatible endpoint หลัก -
/v1/messages: Anthropic-compatible endpoint
✨ ตอนนี้เราได้ Local LLM Server ที่พร้อมใช้งานเรียบร้อยแล้วครับ! 👏👏
ขั้นตอนที่ 5 : Provision Chat Interface (Open WebUI)
เพื่อให้ทดสอบและใช้งานโมเดลได้เห็นภาพชัดเจนยิ่งขึ้น ผมจะ Deploy Web Chat Platform อย่าง Open WebUI มาทำหน้าที่เป็น Client สำหรับเรียกใช้งาน OpenAI-compatible API ที่รันอยู่บน vLLM ครับ
เรามาเริ่ม Deploy Open WebUI กันเลย
cat <<EOF | envsubst | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: open-webui
namespace: local-llm
labels:
app: open-webui
spec:
replicas: 1
selector:
matchLabels:
app: open-webui
template:
metadata:
labels:
app: open-webui
spec:
containers:
- name: open-webui
image: ghcr.io/open-webui/open-webui:v0.9.2
ports:
- containerPort: 8080
resources:
requests:
cpu: 500m
memory: 500Mi
limits:
cpu: "1"
memory: 1Gi
env:
- name: OPENAI_API_BASE_URLS
value: "http://vllm-llama31-8b:8000/v1"
- name: OPENAI_API_KEY
value: "dummy" # lab only
- name: WEBUI_AUTH
value: "False" # lab only
- name: ENABLE_OLLAMA_API
value: "False"
- name: ENABLE_EVALUATION_ARENA_MODELS
value: "False"
- name: RAG_EMBEDDING_ENGINE
value: ""
volumeMounts:
- name: webui-volume
mountPath: /app/backend/data
volumes:
- name: webui-volume
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: open-webui
namespace: local-llm
labels:
app: open-webui
spec:
type: ClusterIP
selector:
app: open-webui
ports:
- protocol: TCP
port: 80
targetPort: 8080
EOF
# Output:
# deployment.apps/open-webui created
# service/open-webui created
📌 สรุปรายละเอียดคอนฟิกสำคัญ
-
OPENAI_API_BASE_URLS: "http://vllm-llama31-8b:8000/v1": ชี้ endpoint ไปยัง vLLM Service ภายใน EKS Cluster -
OPENAI_API_KEY: "dummy": เนื่องจากใน vLLM Deployment ของเราไม่ได้ตั้งค่าใส่ API Key ไว้ จึงใส่ค่าอะไรก็ได้เพื่อให้ Open WebUI ส่ง Request ผ่านได้ -
WEBUI_AUTH: "False": ปิดระบบ Login/Register เพื่อให้เข้าใช้งานหน้าเว็บแชทได้ทันทีโดย authentication (เหมาะสำหรับทดสอบใน Lab เท่านั้น) -
ENABLE_OLLAMA_API: "False": ปิดฟีเจอร์การเชื่อมต่อ Ollama เนื่องจากเราใช้ vLLM เป็น Inference Engine หลักเพียงตัวเดียว
จากนั้นลองตรวจสอบสถานะการรันของ Open WebUI Pod ด้วยคำสั่ง:
kubectl get pod -l app=open-webui -n local-llm -w
เมื่อ Pod แสดงสถานะ Ready 1/1 แปลว่าตัว Application ทำงานเรียบร้อยแล้วครับ!
🌐 สร้าง Ingress (AWS ALB) เพื่อเปิดให้เข้าใช้งานจากภายนอก
เนื่องจากตอนนี้ Stack ทั้งหมดของเราทำงานอยู่ภายใน VPC ของ EKS Cluster เราจึงจำเป็นต้องสร้าง Ingress เพื่อสั่งให้ EKS (AWS ALB Controller) ทำการ Provision Application Load Balancer (ALB) แบบ Public ให้อัตโนมัติครับ
รันคำสั่งด้านล่างนี้บน CloudShell เพื่อสร้างทั้ง IngressClassParams, IngressClass และ Ingress:
# 1. สร้าง IngressClassParams และ IngressClass สำหรับ ALB
cat <<EOF | kubectl apply -f -
apiVersion: eks.amazonaws.com/v1
kind: IngressClassParams
metadata:
name: alb
spec:
scheme: internet-facing
---
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: alb
annotations:
ingressclass.kubernetes.io/is-default-class: "true"
spec:
controller: eks.amazonaws.com/alb
parameters:
apiGroup: eks.amazonaws.com
kind: IngressClassParams
name: alb
EOF
# 2. สร้าง Ingress Resource เพื่อ Provision Public ALB
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: open-webui
namespace: local-llm
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP":80}]'
alb.ingress.kubernetes.io/healthcheck-path: /health
alb.ingress.kubernetes.io/load-balancer-attributes: idle_timeout.timeout_seconds=300
spec:
ingressClassName: alb
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: open-webui
port:
number: 80
EOF
หลังจากสร้าง Ingress เรียบร้อยแล้ว ให้รอสักครู่ (ประมาณ 1–2 นาที) แล้วรันคำสั่งตรวจสอบสถานะของ Ingress เพื่อเอา Public DNS URL ของ ALB:
kubectl get ingress/open-webui -n local-llm
ในช่อง ADDRESS จะเห็น URL ของ AWS ALB ขึ้นมา (เช่น k8s-localllm-openwebu-xxx.us-east-1.elb.amazonaws.com) ซึ่งเราจะใช้ URL นี้ในการเข้าใช้งาน Chat Platform ผ่าน Web Browser ในขั้นตอนถัดไปครับ
⚠️ คำเตือนเรื่องความปลอดภัย (Security Alert):
ในบทความนี้เราสร้าง Public ALB แบบให้เข้าถึงผ่านอินเทอร์เน็ตได้โดยไม่ได้จำกัด Source IP Address เพื่อความสะดวกในการทดสอบ Lab สำหรับใครนำไปประยุกต์ใช้ในองค์กรหรือ Production ควรกำหนด Security Group หรือใช้ WAF/Inbound CIDR IP Restriction เพิ่มเติม เพื่อความปลอดภัยครับ
ขั้นตอนที่ 6: ทดสอบใช้งานจริงผ่าน Web Browser
คัดลอก ALB URL Address ที่ได้จากขั้นตอนก่อนหน้า นำมาเปิดบน Web Browser ได้เลยครับ
เมื่อหน้าเว็บ Open WebUI โหลดขึ้นมา สังเกตที่ด้านบนของช่องแชทจะเห็นชื่อโมเดล llama31-8b แสดงอยู่อย่างถูกต้อง (ซึ่งเป็นชื่อเดียวกับที่เรากำหนดผ่าน --served-model-name ใน vLLM)
ลองพิมพ์คำถามหรือเริ่มส่ง Prompt ทดสอบได้เลย!

จะเห็นว่าตัวโมเดลตอบกลับได้อย่างรวดเร็วผ่าน vLLM Engine ที่รันอยู่บน GPU L4 บน Amazon EKS ครับ

ขั้นตอนที่ 7 : Cleanup / Uninstall Resource
เมื่อทดสอบเสร็จเรียบร้อยแล้ว อย่าลืมลบ Resource ทั้งหมดที่สร้างขึ้นด้วยนะครับ ไม่งั้น Cost พุ่งแน่นอน
คำสั่งในการ Cleanup Resource ทั้งหมด
# 1. ลบ Deployments ทั้งหมด
kubectl delete deployment/open-webui -n local-llm
kubectl delete deployment/vllm-llama31-8b -n local-llm
# 2. ลบ Services ทั้งหมด
kubectl delete service/open-webui -n local-llm
kubectl delete service/vllm-llama31-8b -n local-llm
# 3. ลบ Ingress (เพื่อสั่งลบ AWS ALB)
kubectl delete ingress/open-webui -n local-llm
# 4. ลบ Kubernetes Secrets
kubectl delete secret hf-secret -n local-llm
# 5. Karpenter GPU NodePool
kubectl delete nodepool/gpu
# 6. ลบ EKS Cluster ทั้งหมดผ่าน eksctl
eksctl delete cluster llm-eks-lab --region us-east-1
💳 ประมาณการค่าใช้จ่าย (Cost Estimates) สำหรับ Lab นี้:
-
EKS Cluster Control Plane (Auto Mode):
$0.1/ ชั่วโมง -
EC2 System Node (1 Instance):
$0.1075/ ชั่วโมง -
EC2 General-Purpose Node (1 Instance):
$0.2150/ ชั่วโมง -
EC2 GPU Node
g6.2xlarge(1 Instance):$1.0539/ ชั่วโมง -
AWS Application Load Balancer:
$0.0405/ ชั่วโมง - และมีค่าใช้จ่ายแฝงอื่นๆ อีกเช่น NAT Gateway , EBS
รวมค่าใช้จ่ายทั้งหมดโดยประมาณ: ~$1.6 / ชั่วโมง หรือ $1,600 ต่อเดือน
💡 Tip: ค่าใช้จ่ายส่วนที่มากที่สุดจะตกอยู่กับ GPU Node หากมีแผนการใช้งานอย่างจริงจังบน Production เราสามารถซื้อแผน Reserved Instance หรือ Saving Plan เพื่อลดค่าใช้จ่ายลงได้ 30-35% ครับ
🎯 สรุปท้ายบทความ
เป็นอย่างไรบ้างครับสำหรับ EP.1 Serving LLM บน EKS Platform ด้วย vLLM
เพียงไม่กี่ขั้นตอน เราสามารถสร้างโครงสร้างพื้นฐานระดับ Production บน Amazon EKS ตั้งแต่การใช้ EKS Auto Mode + Karpenter ในการจัดการ GPU Node, การรัน vLLM เป็น Inference Engine ไปจนถึงการเปิด OpenAI-compatible API และต่อหน้าจอ Chat ด้วย Open WebUI ได้สำเร็จ
สำหรับซีรีส์นี้ ใน EP ถัดไปเราจะมาเจาะลึกในหัวข้อเหล่านี้กันต่อครับ:
- 🛠️ Best Practices: Serving LLM on EKS
- 💻 LLM on CPU EKS: ทางเลือกราคาประหยัดสำหรับ Small-Scale Model
- 📊 LLM Observability: ติดตามประสิทธิภาพ TTFT, TPOT และ GPU Metrics
- 📈 LLM Multi-Node & Dynamic Scale: การทำ Auto-scaling ตามปริมาณ Request
- 📦 Quantization (FP8/AWQ): บีบอัดโมเดลเพื่อประหยัด VRAM และเพิ่ม Throughput
ฝากกด Like หรือพิมพ์ Comment พูดคุยติชมกันได้เลยนะครับ แล้วพบกันใหม่ใน EP ถัดไปครับ! 👋✨







Top comments (0)