K8s · 維運與部署 · 第 35 課 · · 14min read

從零部署完整系統(上):12 步驟把網站搬上 K8s(前 6 步)

空叢集到一個能跑的網站要做哪些事?這篇給你 12 步驟 SOP 的前 6 步:規劃命名空間 → 寫 Deployment → 開 Service → 設 Ingress → 注入 ConfigMap → 處理 Secret。

#Kubernetes #從零部署 #完整系統 #SOP
章節目錄 · 11

學完八大概念,但要怎麼串起來?

到目前為止,你學了:

  • Pod / Deployment / Service / Ingress
  • ConfigMap / Secret / PV/PVC / StatefulSet / Helm
  • Probe / Resource limits / HPA / RBAC / NetworkPolicy
每個都會用,但實戰一個完整網站要怎麼做?

這 2 篇用 12 個步驟空 Namespace 開始,部署一套真正可上線的應用。

目標架構

使用者 → Ingress(myapp.local)
          ├── /     → frontend-svc → frontend Pod x2
          └── /api  → api-svc → api Pod x3(HPA)
                                    ↓
                           mysql-headless → mysql Pod x1
                                            (StatefulSet + PVC)

NetworkPolicy:前端 → API → DB,逐層隔離
所有 Pod:Probe + Resource limits

12 步部署總覽

步驟做什麼對應第幾堂
1建 Namespace第 5
2建 Secret(DB 密碼)第 6
3建 ConfigMap(API 設定)第 6
4部署 MySQL(StatefulSet + PVC)第 6
5部署 API(Deployment + Probe + Resource)第 5+7
6部署前端第 5
7建 Service第 5
8建 Ingress第 6
9設 NetworkPolicy第 7
10設 HPA第 7
11完整驗證全部
12壓測 HPA第 7
這篇做前 6 步,下一篇做後 6 步。

---

Step 1:建 Namespace — 邏輯隔離

\\\yaml
# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: prod
\
\\

\\\bash
kubectl apply -f namespace.yaml
kubectl get ns prod
# NAME STATUS AGE
# prod Active 3s
\
\\

之後所有資源都加 \-n prod\——避免跟 default namespace 混。

---

Step 2:建 Secret — DB 密碼

\\\yaml
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: mysql-secret
namespace: prod
type: Opaque
stringData:
MYSQL_ROOT_PASSWORD: "rootpw"
MYSQL_PASSWORD: "apppw"
MYSQL_DATABASE: "appdb"
MYSQL_USER: "appuser"
\
\\

\\\bash
kubectl apply -f secret.yaml
kubectl get secret -n prod
\
\\

注意:這裡用 \stringData\,K8s 會自動幫你 Base64。生產環境應該改用 Sealed SecretsExternal Secrets Operator 加密。

---

Step 3:建 ConfigMap — 應用設定

\\\yaml
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: api-config
namespace: prod
data:
LOG_LEVEL: "info"
MAX_CONN: "100"
DB_HOST: "mysql-headless"
---
apiVersion: v1
kind: ConfigMap
metadata:
name: frontend-nginx-config
namespace: prod
data:
default.conf: |
server {
listen 80;
location / {
root /usr/share/nginx/html;
}
location /api/ {
proxy_pass http://api-svc/;
}
}
\
\\

\\\bash
kubectl apply -f configmap.yaml
kubectl get cm -n prod
# api-config, frontend-nginx-config
\
\\

前端 nginx 用 ConfigMap 掛載設定檔——這是反向代理 \/api/\ 到 API 的關鍵。

---

Step 4:部署 MySQL — StatefulSet + PVC

\\\yaml
# mysql-statefulset.yaml
apiVersion: v1
kind: Service
metadata:
name: mysql-headless
namespace: prod
spec:
clusterIP: None # Headless!
selector:
app: mysql
ports:
- port: 3306
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
namespace: prod
spec:
serviceName: mysql-headless
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
envFrom:
- secretRef:
name: mysql-secret
ports:
- containerPort: 3306
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
volumeClaimTemplates:
- metadata:
name: mysql-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
\
\\

\\\bash
kubectl apply -f mysql-statefulset.yaml
kubectl get pods -n prod -w
# mysql-0 0/1 Pending
# mysql-0 0/1 ContainerCreating
# mysql-0 1/1 Running ← 等到這個

kubectl get pvc -n prod
# mysql-data-mysql-0 Bound pvc-xxx 1Gi
\
\\

這一步串了 5 個概念:StatefulSet + PVC + Headless Service + Secret + ConfigMap。

---

Step 5:部署 API — 帶 Probe + Resource

\\\yaml
# api-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
namespace: prod
spec:
replicas: 3
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: my-api:1.0
envFrom:
- configMapRef:
name: api-config
- secretRef:
name: mysql-secret
ports:
- containerPort: 8080
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5
\
\\

\\\bash
kubectl apply -f api-deployment.yaml
kubectl get pods -n prod -l app=api
# api-xxx-1 1/1 Running
# api-xxx-2 1/1 Running
# api-xxx-3 1/1 Running
\
\\

單一 YAML 用了 5 個概念:Deployment + ConfigMap + Secret + Probe + Resource。

---

Step 6:部署前端 — 透過 ConfigMap 掛 nginx 設定

\\\yaml
# frontend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
namespace: prod
spec:
replicas: 2
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
resources:
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "200m"
memory: "128Mi"
volumes:
- name: nginx-config
configMap:
name: frontend-nginx-config
\
\\

\\\bash
kubectl apply -f frontend-deployment.yaml
kubectl get pods -n prod -l app=frontend
# frontend-xxx-1 1/1 Running
# frontend-xxx-2 1/1 Running
\
\\

重點:\subPath\ 只掛單一檔案——ConfigMap 預設掛整個目錄會把 \/etc/nginx/conf.d/\ 蓋掉。

---

重點整理

  • 12 步驟 SOP:從 Namespace 開始、一層層加上去
  • Step 1-3 是準備:邊界(Namespace)、機密(Secret)、設定(ConfigMap)
  • Step 4 最關鍵:StatefulSet + PVC + Headless + Secret 一次串 5 個概念
  • Step 5 API 是濃縮版:Deployment + Probe + Resource + ConfigMap + Secret
  • Step 6 前端配 nginx + ConfigMap subPath 注意陷阱

下一步

到這裡,6 個 Pod 都跑起來了——但還沒 Service、沒 Ingress、沒 HPA、沒安全設定,外面還連不到。

下一篇:從零部署完整系統(下)— 後 6 步:Service + Ingress + NetworkPolicy + HPA

📅 下一篇從零部署完整系統(下)— 後 6 步:Service + Ingress + NetworkPolicy + HPA
接著上一篇,把 Service/Ingress/NetworkPolicy/HPA/RBAC 補完——12 步真正跑完一輪。
📚 完整系列總覽K8s 系列教學首頁(共 40 課,按學習路徑順序排)