跳至主要内容

K8s 部署工具比較 & Terraform 定位


Terraform 不是 K8s 部署工具

Terraform 是 infrastructure 管理工具,K8s 只是它能管的其中一個東西。

Terraform 能管的東西

AWS → VPC, EKS, EC2, RDS, S3, IAM, Route53, ALB...
Azure → AKS, VM, Storage, AD...
GCP → GKE, Compute, Cloud SQL...
K8s → Helm release, kubectl manifest, namespace...
其他 → GitHub repo, Datadog monitor, PagerDuty,
Cloudflare DNS, Vault secrets...

基本上任何有 API 的服務都能用 Terraform 管。


K8s 部署主流工具比較

工具定位適合
Terraform + Helm建 infra + 部署 app 一起管Ops/Platform team
Helm 單獨用K8s app 部署只管 K8s,不管雲端資源
ArgoCD / FluxCDGitOps — git push 自動部署到 K8s持續部署、app team
kubectl手動 apply YAMLdebug、學習
KustomizeYAML overlay 管多環境不想用 Helm template
Pulumi用程式語言(Python/TypeScript)寫 infra不想學 HCL

Terraform + Helm 搭配

Terraform 管:
→ K8s cluster 本身(EKS / AKS / GKE)
→ 雲端資源(Load Balancer, IAM, Secrets, Storage...)
→ 呼叫 Helm 部署 app

Helm 管:
→ K8s 裡面的 app(Deployment, Service, ConfigMap, RBAC...)

如果只用 Helm — 能部署 app 到 K8s,但誰來建 K8s cluster?Load Balancer?Secrets?

Terraform 管整個 stack(雲端 + K8s),Helm 只管 K8s 裡面的 app。


Helm chart vs 手寫 YAML

Helm chart — 幫你寫好 YAML

手動:
手寫 Deployment YAML
手寫 Service YAML
手寫 ServiceAccount + RBAC YAML
kubectl apply -f 一個一個套

Helm:
chart 裡已包好所有 YAML template
只需要填 values(image tag, replica count...)
helm install 一次全部搞定

kubectl_manifest — 還是自己寫 YAML

# 放在 Terraform 裡,但 YAML 是自己寫的
yaml_body = <<-YAML
apiVersion: apps/v1
kind: Deployment
...
YAML

比較

手動 kubectlTerraform + Helm
複雜元件自己寫 20+ YAMLHelm chart 包好,填 values
簡單資源kubectl apply -fkubectl_manifest(一樣自己寫 YAML)
版本控制手動管 YAMLgit + Terraform state 自動追蹤
多環境每個環境複製一份改值同一份 code 不同 inputs
回滾apply 舊版本 YAMLTerraform state 追蹤,自動 diff

Terraform 的價值不是「幫你寫 YAML」,而是管理狀態 + 多環境 + 版本控制 + 依賴順序。


Helm values.yaml 多環境管理

Helm 的核心思想:一份 chart template + 不同環境的 values.yaml

目錄結構

my-inference-chart/
├── Chart.yaml
├── templates/
│ ├── deployment.yaml # 用 {{ .Values.xxx }} 取值
│ ├── service.yaml
│ ├── hpa.yaml
│ └── configmap.yaml
├── values.yaml # 預設值(也是文件)
├── values-staging.yaml # staging 環境覆蓋
└── values-production.yaml # production 環境覆蓋

values.yaml(預設值)

# values.yaml — 所有環境的預設值 + 文件化
replicaCount: 1

image:
repository: my-org/inference-server
tag: latest
pullPolicy: IfNotPresent

resources:
requests:
cpu: "2"
memory: "8Gi"
nvidia.com/gpu: "1"
limits:
cpu: "4"
memory: "16Gi"
nvidia.com/gpu: "1"

autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 5
targetCPUUtilizationPercentage: 70

model:
path: s3://my-models/llm-7b/
maxBatchSize: 32

probes:
readiness:
initialDelaySeconds: 60
periodSeconds: 10
liveness:
initialDelaySeconds: 120
periodSeconds: 30

values-staging.yaml(staging 環境只覆蓋不同的部分)

# values-staging.yaml — 只寫跟預設值不同的部分
replicaCount: 1

image:
tag: "v1.2.0-rc1"

resources:
requests:
cpu: "1"
memory: "4Gi"
nvidia.com/gpu: "1"
limits:
cpu: "2"
memory: "8Gi"
nvidia.com/gpu: "1"

model:
maxBatchSize: 8 # staging 用小 batch,省資源

values-production.yaml

# values-production.yaml
replicaCount: 3

image:
tag: "v1.2.0"
pullPolicy: Always

autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10

model:
maxBatchSize: 64

probes:
readiness:
initialDelaySeconds: 90 # production model 可能更大,需要更多時間

部署指令

# staging
helm upgrade --install inference-server ./my-inference-chart \
-f values.yaml \
-f values-staging.yaml \
--namespace staging

# production
helm upgrade --install inference-server ./my-inference-chart \
-f values.yaml \
-f values-production.yaml \
--namespace production \
--set image.tag=v1.2.0 # 也可以用 --set 覆蓋單一值(CI/CD 常用)

Kustomize — YAML overlay 不用 template

Kustomize 的思想:保留原始 YAML,用 overlay 疊加差異。不用 template 語法,純 YAML。

目錄結構

k8s/
├── base/ # 原始設定,所有環境共用
│ ├── kustomization.yaml
│ ├── deployment.yaml
│ ├── service.yaml
│ └── configmap.yaml
└── overlays/
├── staging/
│ ├── kustomization.yaml
│ └── patch-resources.yaml # 只寫要改的部分
└── production/
├── kustomization.yaml
└── patch-resources.yaml

base/kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- deployment.yaml
- service.yaml
- configmap.yaml

base/deployment.yaml(原始 YAML,不含任何 template 語法)

apiVersion: apps/v1
kind: Deployment
metadata:
name: inference-server
spec:
replicas: 1
template:
spec:
containers:
- name: inference-server
image: my-org/inference-server:latest
resources:
requests:
cpu: "2"
memory: "8Gi"
nvidia.com/gpu: "1"
limits:
cpu: "4"
memory: "16Gi"
nvidia.com/gpu: "1"

overlays/production/kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

# 基於 base
resources:
- ../../base

# 修改 image tag
images:
- name: my-org/inference-server
newTag: "v1.2.0" # 只改 tag,不用複製整個 YAML

# 設定 replica 數量
replicas:
- name: inference-server
count: 3

# 套用 resource patch
patches:
- path: patch-resources.yaml

overlays/production/patch-resources.yaml

# 只寫要改的部分(strategic merge patch)
apiVersion: apps/v1
kind: Deployment
metadata:
name: inference-server
spec:
template:
spec:
containers:
- name: inference-server
resources:
requests:
cpu: "4"
memory: "16Gi"
limits:
cpu: "8"
memory: "32Gi"

部署指令

# 預覽最終 YAML(不實際部署)
kubectl kustomize overlays/production

# 部署
kubectl apply -k overlays/production

# 也可以用 Helm 管 Kustomize(兩者可以共存)

ArgoCD GitOps 流程

ArgoCD 的核心思想:git repo 是真相來源,cluster 狀態要和 git 保持一致

GitOps 流程:

開發者 push code


Git Repo(YAML / Helm / Kustomize)

▼ ArgoCD 定期 pull(或 webhook 觸發)
ArgoCD 比較:
「git 裡的 desired state」vs「cluster 的 current state」

├── 一致 → 什麼都不做(Synced)
└── 不一致 → 自動(或手動)同步到 cluster(Out of Sync → Synced)
┌─────────────────────────────────────────────────────┐
│ Git Repository │
│ │
│ main branch: │
│ k8s/production/ │
│ deployment.yaml (image: v1.1.0) │
│ │
│ PR merged: image tag changed to v1.2.0 │
│ k8s/production/ │
│ deployment.yaml (image: v1.2.0) ← 新的 │
└────────────────────┬────────────────────────────────┘
│ ArgoCD 偵測到變化

┌─────────────────────────────────────────────────────┐
│ ArgoCD │
│ │
│ App: inference-server │
│ Status: OutOfSync ← git 和 cluster 不一致 │
│ │
│ Diff: │
│ - image: my-org/inference-server:v1.1.0 │
│ + image: my-org/inference-server:v1.2.0 │
│ │
│ Auto-sync: Enabled → 自動 apply 變更 │
└────────────────────┬────────────────────────────────┘
│ kubectl apply

┌─────────────────────────────────────────────────────┐
│ EKS Cluster │
│ │
│ Deployment rolling update: v1.1.0 → v1.2.0 │
│ Status: Synced ✓ │
└─────────────────────────────────────────────────────┘

ArgoCD Application 設定

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: inference-server
namespace: argocd
spec:
project: default

# 從哪裡讀設定
source:
repoURL: https://github.com/my-org/k8s-manifests
targetRevision: main
path: apps/inference-server/overlays/production # Kustomize overlay

# 部署到哪裡
destination:
server: https://kubernetes.default.svc # 當前 cluster
namespace: inference

syncPolicy:
automated:
prune: true # git 刪了資源,cluster 也刪
selfHeal: true # 有人手動改 cluster,自動 revert 回 git 的狀態
syncOptions:
- CreateNamespace=true

ArgoCD 的優勢(vs 手動 kubectl apply)

手動 kubectl applyArgoCD GitOps
部署紀錄誰部署了什麼?不知道git commit history = 完整部署紀錄
回滾手動 apply 舊 YAMLgit revert + ArgoCD 自動同步
誰改了 cluster?不知道git blame 一目了然
環境一致性可能有人手動改了 clusterselfHeal 自動修正回 git 狀態
部署 PR review沒有k8s YAML 的 PR review 就是部署 review