Blog Post
Hardening vLLM Inference Service di Kubernetes dengan Istio dan OPA
Panduan production-ready untuk menjalankan vLLM di Kubernetes dengan kontrol jaringan, policy admission, mTLS, dan guardrail operasional yang lebih aman.
TL;DR
- Menjalankan vLLM di Kubernetes bukan hanya soal GPU scheduling. Boundary jaringan, secret handling, dan policy admission biasanya lebih menentukan apakah service bisa aman di production.
- Istio membantu mTLS, ingress policy, dan observability jaringan. OPA Gatekeeper membantu menahan konfigurasi pod yang terlalu longgar sebelum masuk cluster.
- Jalur yang sehat adalah memisahkan namespace inference, membatasi egress, memaksa image pinning, dan memverifikasi request path sebelum service menerima trafik nyata.
Pendahuluan
Inference service untuk model open-weight seperti vLLM sering tumbuh cepat dari proof-of-concept menjadi dependency internal yang penting. Begitu beberapa tim mulai mengandalkan endpoint yang sama, service tersebut berhenti menjadi eksperimen GPU dan berubah menjadi komponen platform. Di tahap ini, ancamannya bukan hanya container crash. Masalah yang lebih sering muncul adalah image tidak dipin, pod bisa keluar ke internet tanpa batas, model token tersimpan sembarangan, atau service account yang terlalu luas dipakai lintas workload.
Kubernetes memang memudahkan scheduling GPU dan rolling deploy, tetapi default cluster jarang cukup ketat untuk inference workload. Service inference biasanya punya surface area yang cukup besar: model download, metrics endpoint, ingress public atau semi-public, cache volume, dan kadang custom sidecar. Jika tidak ada guardrail, satu tim bisa men-deploy image latest, menjalankan privileged pod, atau mengekspor endpoint tanpa auth yang layak.
Tutorial ini fokus pada pola yang pragmatis: vLLM dijalankan di namespace khusus, dilindungi mTLS dan ingress policy melalui Istio, lalu dikunci di admission phase lewat OPA Gatekeeper. Pendekatannya bukan yang paling rumit, tetapi cukup realistis untuk cluster production yang ingin menjaga inference path tetap terkontrol.
Problem di Production
Masalah paling umum pada inference service bukan modelnya salah, tetapi perimeter service terlalu longgar. Tim sering membuka endpoint di LoadBalancer, membiarkan pod menarik artifact dari mana saja, dan menaruh token model registry di environment variable yang bocor ke log. Begitu service mulai populer, abuse juga naik: prompt flood, token burn, scraping, dan lateral movement lewat service account yang salah scope.
Problem kedua adalah drift operasional. Inference workload sering punya kebutuhan khusus seperti toleration GPU, huge memory request, warm cache, dan pre-pull image. Jika hal-hal ini tidak diperlakukan sebagai kebijakan yang terdokumentasi, rollout akan terasa stabil saat trafik rendah lalu runtuh saat concurrency naik.
Prerequisites
- Cluster Kubernetes 1.29+ dengan node GPU yang sudah punya NVIDIA device plugin.
kubectl,helm, dan akses cluster-admin untuk instalasi awal.- Istio 1.23+ atau versi yang setara di cluster.
- OPA Gatekeeper sudah tersedia, atau cluster siap memasangnya.
- Image vLLM yang sudah diuji, misalnya
vllm/vllm-openai:v0.8.4. - PVC atau ephemeral storage plan untuk cache model.
- Secret token untuk model registry seperti Hugging Face jika model diambil dari private repository.
Architecture / Context
Arsitektur yang dipakai cukup sederhana tetapi kuat. Namespace ai-inference menjadi boundary deployment. Ingress publik masuk lewat Istio ingress gateway. Traffic internal ke pod vLLM menggunakan sidecar Istio sehingga mTLS dan policy jaringan bisa dipaksakan. Di depan deployment, OPA Gatekeeper memvalidasi bahwa pod inference tidak memakai latest, tidak privileged, dan wajib runAsNonRoot.
Model operational-nya adalah “secure by default, explicit by exception”. Artinya setiap perubahan deployment baru harus lolos policy admission terlebih dahulu. Kalau tim butuh exception, exception itu harus dinyatakan eksplisit, bukan terjadi karena default cluster terlalu permisif.
Step-by-Step Implementation
Step 1: Buat namespace inference dan aktifkan Istio injection
kubectl create namespace ai-inference
kubectl label namespace ai-inference istio-injection=enabled --overwrite
kubectl create namespace policy-system
Namespace khusus memudahkan quota, network policy, dan observability per workload. Jangan campur inference service dengan namespace aplikasi umum.
Step 2: Buat secret untuk model registry
kubectl -n ai-inference create secret generic hf-token \
--from-literal=HF_TOKEN='replace-me'
Jika model berasal dari artifact internal, gunakan secret store atau CSI secret provider. Jangan simpan token di manifest YAML mentah.
Step 3: Terapkan ResourceQuota dan LimitRange
apiVersion: v1
kind: ResourceQuota
metadata:
name: ai-inference-quota
namespace: ai-inference
spec:
hard:
requests.cpu: "32"
requests.memory: 128Gi
requests.nvidia.com/gpu: "4"
limits.nvidia.com/gpu: "4"
---
apiVersion: v1
kind: LimitRange
metadata:
name: ai-inference-defaults
namespace: ai-inference
spec:
limits:
- type: Container
defaultRequest:
cpu: "4"
memory: 16Gi
default:
cpu: "8"
memory: 32Gi
Apply:
kubectl apply -f quota.yaml
Quota mencegah satu tim memonopoli GPU dan memory tanpa terlihat.
Step 4: Deploy vLLM dengan security context yang ketat
apiVersion: apps/v1
kind: Deployment
metadata:
name: vllm-gptq
namespace: ai-inference
spec:
replicas: 1
selector:
matchLabels:
app: vllm-gptq
template:
metadata:
labels:
app: vllm-gptq
spec:
serviceAccountName: vllm-runtime
nodeSelector:
nodepool: gpu
containers:
- name: vllm
image: vllm/vllm-openai:v0.8.4
args:
- "--model"
- "meta-llama/Llama-3.1-8B-Instruct"
- "--host"
- "0.0.0.0"
- "--port"
- "8000"
- "--max-model-len"
- "8192"
env:
- name: HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-token
key: HF_TOKEN
ports:
- containerPort: 8000
resources:
requests:
cpu: "6"
memory: 24Gi
nvidia.com/gpu: "1"
limits:
cpu: "8"
memory: 32Gi
nvidia.com/gpu: "1"
securityContext:
runAsNonRoot: true
runAsUser: 10001
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: cache
mountPath: /root/.cache/huggingface
volumes:
- name: cache
persistentVolumeClaim:
claimName: hf-cache-pvc
Poin pentingnya adalah pin image tag, security context eksplisit, dan resource request yang realistis. readOnlyRootFilesystem sering memaksa tim lebih disiplin soal write path.
Step 5: Ekspos service hanya ke mesh internal
apiVersion: v1
kind: Service
metadata:
name: vllm-gptq
namespace: ai-inference
spec:
selector:
app: vllm-gptq
ports:
- name: http
port: 80
targetPort: 8000
kubectl apply -f service.yaml
Step 6: Paksa mTLS dan batasi ingress lewat Istio
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: ai-inference-mtls
namespace: ai-inference
spec:
mtls:
mode: STRICT
---
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: allow-inference-gateway
namespace: ai-inference
spec:
selector:
matchLabels:
app: vllm-gptq
rules:
- from:
- source:
principals:
- "cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"
to:
- operation:
methods: ["POST","GET"]
paths: ["/v1/chat/completions", "/v1/models", "/health"]
Apply:
kubectl apply -f istio-security.yaml
Step 7: Tambahkan VirtualService untuk endpoint publik
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: ai-public-gateway
namespace: ai-inference
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 443
name: https
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: ai-public-tls
hosts:
- "llm.example.com"
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: vllm-public
namespace: ai-inference
spec:
hosts:
- "llm.example.com"
gateways:
- ai-public-gateway
http:
- match:
- uri:
prefix: /v1/
route:
- destination:
host: vllm-gptq.ai-inference.svc.cluster.local
port:
number: 80
timeout: 60s
Step 8: Pasang OPA Gatekeeper dan policy guardrail
Jika Gatekeeper belum ada:
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm repo update
helm upgrade --install gatekeeper gatekeeper/gatekeeper -n gatekeeper-system --create-namespace
Buat constraint template:
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredsecuritycontext
spec:
crd:
spec:
names:
kind: K8sRequiredSecurityContext
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredsecuritycontext
violation[{"msg": msg}] {
container := input.review.object.spec.template.spec.containers[_]
not container.securityContext.runAsNonRoot
msg := "runAsNonRoot must be enabled"
}
violation[{"msg": msg}] {
container := input.review.object.spec.template.spec.containers[_]
container.securityContext.allowPrivilegeEscalation != false
msg := "allowPrivilegeEscalation must be false"
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredSecurityContext
metadata:
name: require-secctx-in-ai-namespace
spec:
match:
namespaces: ["ai-inference"]
kinds:
- apiGroups: ["apps"]
kinds: ["Deployment"]
Tambahkan policy untuk menolak image latest:
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sblocklatest
spec:
crd:
spec:
names:
kind: K8sBlockLatest
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sblocklatest
violation[{"msg": msg}] {
container := input.review.object.spec.template.spec.containers[_]
endswith(container.image, ":latest")
msg := sprintf("latest tag is forbidden: %v", [container.image])
}
Step 9: Batasi egress dan service account
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: vllm-default-deny
namespace: ai-inference
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-istio-and-dns
namespace: ai-inference
spec:
podSelector:
matchLabels:
app: vllm-gptq
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: istio-system
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
Jangan beri RBAC tambahan pada vllm-runtime kecuali memang ada alasan. Inference service jarang butuh akses Kubernetes API langsung.
Verification
Pastikan pod dan service sehat:
kubectl -n ai-inference get pods -o wide
kubectl -n ai-inference get svc
kubectl -n ai-inference describe pod -l app=vllm-gptq
Verifikasi sidecar Istio ter-inject:
kubectl -n ai-inference get pod -l app=vllm-gptq -o jsonpath='{.items[0].spec.containers[*].name}'
Output seharusnya menampilkan vllm dan istio-proxy.
Uji endpoint dari ingress:
curl -k https://llm.example.com/v1/models
Jika policy admission aktif, coba apply deployment yang melanggar:
kubectl apply -f broken-deployment-using-latest.yaml
Hasil yang benar adalah request ditolak Gatekeeper.
Troubleshooting
Jika pod Pending, cek scheduling GPU:
kubectl describe pod -n ai-inference -l app=vllm-gptq
kubectl get nodes -L nvidia.com/gpu.present,nodepool
Jika request gagal lewat ingress tetapi pod sehat, cek AuthorizationPolicy dan VirtualService:
kubectl -n ai-inference get authorizationpolicy,virtualservice,gateway
kubectl logs -n istio-system deploy/istio-ingressgateway
Jika model tidak mau dimuat, cek path cache dan secret:
kubectl -n ai-inference exec deploy/vllm-gptq -- env | grep HF_
kubectl -n ai-inference logs deploy/vllm-gptq
Masalah yang sering muncul adalah storage cache terlalu kecil, token tidak valid, atau image tidak cocok dengan driver GPU host.
Production Notes
Inference service biasanya perlu HPA atau KEDA berbasis concurrency, tetapi scaling GPU tetap harus konservatif karena cold start model cukup mahal. Tambahkan metric latency per route dan token throughput, bukan hanya CPU dan memory. Batasi log prompt mentah karena prompt bisa berisi data sensitif. Untuk service public, pertimbangkan API gateway atau ext-authz di depan Istio untuk rate limit dan tenant isolation.
Jika model berasal dari external registry, siapkan mekanisme prefetch atau image warm-up. Download model saat pod start bisa memperpanjang blast radius setiap rolling deploy.
Trade-offs
Istio dan Gatekeeper menambah kontrol, tetapi juga menambah lapisan konfigurasi yang harus dipahami tim platform. mTLS dan authz policy membuat boundary lebih sehat, tetapi debugging path jaringan jadi lebih kompleks. readOnlyRootFilesystem dan runAsNonRoot meningkatkan keamanan, tetapi sebagian image community belum siap dan perlu penyesuaian entrypoint atau cache path.
Failure Modes
- Policy admission terlalu longgar sehingga image
latestatau privileged pod lolos ke production. - Policy terlalu ketat dan memblokir rollout darurat karena exception path tidak disiapkan.
- mTLS aktif tetapi ingress atau service identity salah, sehingga service terlihat hidup tetapi tidak bisa diakses.
- Model warm-up terlalu lama dan autoscaling menciptakan antrian request yang tidak pernah stabil.
Best Practices
- Pisahkan namespace inference dari workload aplikasi umum.
- Pin image, model version, dan GPU resource request secara eksplisit.
- Gunakan Gatekeeper untuk menegakkan baseline minimal, bukan mengandalkan review manual semata.
- Audit egress dan secret handling sebelum service dibuka ke tenant atau internet.
- Simpan runbook rollback yang jelas untuk model update dan policy update.
Kesalahan Umum
- membuka service inference langsung sebagai
LoadBalancertanpa mesh policy - menyimpan token model registry di manifest plaintext
- membiarkan service account default dipakai inference pod
- menganggap GPU scheduling selesai berarti service sudah production-ready
Kesimpulan
vLLM di Kubernetes bisa menjadi inference layer yang sangat efektif, tetapi hanya jika cluster memperlakukannya sebagai service platform, bukan eksperimen notebook yang dipindahkan ke deployment YAML. Dengan Istio untuk jaringan dan OPA untuk admission guardrail, tim bisa menurunkan risiko paling umum sebelum inference workload benar-benar dipakai lintas organisasi.
Kalau artikel ini membantu, kamu bisa support eksperimen berikutnya.
Apresiasi di TrakteerKeep Reading
Related posts
Ephemeral GitHub Actions Runners di Kubernetes dengan Actions Runner Controller
Panduan operasional untuk menjalankan GitHub Actions runner yang ephemeral di Kubernetes dengan isolasi lebih baik, autoscaling, dan kontrol secret yang lebih rapi.
Membangun MCP Gateway Aman untuk Engineering Agents dengan FastAPI dan Redis
Panduan production-ready untuk membuat gateway tool access bagi engineering agents dengan FastAPI, Redis, rate limit, audit log, dan policy sederhana.
AI Attack Surface di DevOps Pipeline yang Lebih Berbahaya dari Dugaan
Analisis production-grade tentang attack surface baru saat AI masuk ke DevOps pipeline, termasuk prompt injection, tool abuse, dan governance untuk automation yang menyentuh production.