前言

應用程式除了程式碼本身,還需要配置資訊(如資料庫連線設定)、敏感資訊(如密碼)以及資料持久化。Kubernetes 提供了完整的解決方案:

  • ConfigMap:管理非敏感配置
  • Secret:管理敏感資訊
  • Volume:提供資料儲存

本篇將深入探討這些資源的使用方式和最佳實踐。


ConfigMap:配置管理

為什麼需要 ConfigMap?

在容器化之前,我們可能這樣管理配置:

  • 配置寫在程式碼裡(❌ 難以修改)
  • 使用環境變數(✅ 但不易管理)
  • 使用配置檔案(✅ 但需要掛載)

ConfigMap 統一解決這些問題,將配置與容器映像檔分離。

graph LR
    CM[ConfigMap] -->|環境變數| POD1[Pod]
    CM -->|掛載檔案| POD2[Pod]
    CM -->|命令列參數| POD3[Pod]

創建 ConfigMap

方法一:從文字值創建

1
2
3
4
kubectl create configmap app-config \
--from-literal=DATABASE_HOST=db.example.com \
--from-literal=DATABASE_PORT=5432 \
--from-literal=LOG_LEVEL=info

方法二:從檔案創建

1
2
3
4
5
6
7
8
# 從單一檔案
kubectl create configmap nginx-config --from-file=nginx.conf

# 從目錄(所有檔案)
kubectl create configmap all-configs --from-file=./config-dir/

# 自訂 Key 名稱
kubectl create configmap app-config --from-file=settings=config.yaml

方法三:使用 YAML 定義

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
# 簡單的 Key-Value
DATABASE_HOST: "db.example.com"
DATABASE_PORT: "5432"
LOG_LEVEL: "info"

# 多行文字(配置檔)
app.properties: |
server.port=8080
spring.profiles.active=production
logging.level.root=INFO

nginx.conf: |
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
}
}

使用 ConfigMap

方式一:作為環境變數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: v1
kind: Pod
metadata:
name: app-pod
spec:
containers:
- name: app
image: my-app:latest

# 引用單一值
env:
- name: DB_HOST
valueFrom:
configMapKeyRef:
name: app-config
key: DATABASE_HOST

# 引用所有值
envFrom:
- configMapRef:
name: app-config

方式二:作為檔案掛載

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
spec:
containers:
- name: nginx
image: nginx:alpine
volumeMounts:
- name: config-volume
mountPath: /etc/nginx/conf.d # 掛載到此目錄
readOnly: true

volumes:
- name: config-volume
configMap:
name: nginx-config
items: # 選擇性掛載(可選)
- key: nginx.conf
path: default.conf # 檔案名稱

方式三:作為命令列參數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: v1
kind: Pod
metadata:
name: app-pod
spec:
containers:
- name: app
image: my-app:latest
command: ["./app"]
args: ["--log-level=$(LOG_LEVEL)", "--port=$(PORT)"]
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config
key: LOG_LEVEL
- name: PORT
valueFrom:
configMapKeyRef:
name: app-config
key: PORT

ConfigMap 熱更新

環境變數:Pod 必須重啟才能讀取新值
掛載檔案:自動更新(約 1 分鐘內),但應用程式需要自己偵測變化

1
2
3
4
5
# 掛載為單一檔案時,可使用 subPath,但不會自動更新
volumeMounts:
- name: config
mountPath: /app/config.yaml
subPath: config.yaml # ⚠️ 不會自動更新

Secret:敏感資訊管理

ConfigMap vs Secret

特性 ConfigMap Secret
用途 非敏感配置 敏感資訊
儲存方式 明文 Base64 編碼
記憶體存放 是(tmpfs)
大小限制 1MiB 1MiB

注意:Secret 的 Base64 不是加密,只是編碼。生產環境應啟用 etcd 加密。

Secret 類型

類型 用途
Opaque 預設,任意用戶定義資料
kubernetes.io/basic-auth 基本認證
kubernetes.io/ssh-auth SSH 私鑰
kubernetes.io/tls TLS 憑證
kubernetes.io/dockerconfigjson Docker Registry 認證

創建 Secret

方法一:命令列創建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Generic Secret
kubectl create secret generic db-secret \
--from-literal=username=admin \
--from-literal=password=supersecret

# 從檔案
kubectl create secret generic tls-secret \
--from-file=tls.crt=./cert.pem \
--from-file=tls.key=./key.pem

# Docker Registry Secret
kubectl create secret docker-registry regcred \
--docker-server=https://index.docker.io/v1/ \
--docker-username=myuser \
--docker-password=mypassword \
[email protected]

# TLS Secret
kubectl create secret tls my-tls \
--cert=path/to/cert.pem \
--key=path/to/key.pem

方法二:YAML 定義

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
data:
# Base64 編碼
username: YWRtaW4= # echo -n "admin" | base64
password: c3VwZXJzZWNyZXQ= # echo -n "supersecret" | base64

---
# 使用 stringData(不需要 Base64 編碼)
apiVersion: v1
kind: Secret
metadata:
name: db-secret-v2
type: Opaque
stringData:
username: admin
password: supersecret
1
2
3
# 編碼/解碼
echo -n "admin" | base64 # YWRtaW4=
echo "YWRtaW4=" | base64 -d # admin

使用 Secret

與 ConfigMap 類似,可以作為環境變數或檔案掛載:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
apiVersion: v1
kind: Pod
metadata:
name: app-pod
spec:
containers:
- name: app
image: my-app:latest

# 作為環境變數
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: db-secret
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password

# 作為檔案掛載
volumeMounts:
- name: secret-volume
mountPath: /etc/secrets
readOnly: true

volumes:
- name: secret-volume
secret:
secretName: db-secret
defaultMode: 0400 # 設定檔案權限

拉取私有映像檔

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Pod
metadata:
name: private-app
spec:
containers:
- name: app
image: myregistry.io/private-app:latest
imagePullSecrets:
- name: regcred # Docker Registry Secret

Secret 最佳實踐

  1. 不要將 Secret 提交到 Git
  2. 使用外部密鑰管理:AWS Secrets Manager、HashiCorp Vault
  3. 啟用 etcd 加密:在叢集層級加密儲存
  4. 限制存取權限:使用 RBAC 控制誰可以讀取 Secret
  5. 定期輪換:定期更換密碼和憑證

Volume:資料存儲

為什麼需要 Volume?

容器的檔案系統是臨時的

  • 容器重啟後,檔案會消失
  • 同一 Pod 內的容器需要共享檔案
  • 資料庫等應用需要持久化資料

Volume 類型概覽

graph TD
    V[Volume 類型] --> T[臨時儲存]
    V --> P[持久儲存]
    V --> S[特殊用途]

    T --> E[emptyDir]
    T --> CM[configMap]
    T --> SEC[secret]

    P --> HP[hostPath]
    P --> PVC[persistentVolumeClaim]
    P --> NFS[nfs]
    P --> CLOUD[雲端儲存]

    S --> DOWN[downwardAPI]
    S --> PROJ[projected]

emptyDir:臨時共享儲存

Pod 內容器間共享資料,Pod 刪除後資料消失。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
apiVersion: v1
kind: Pod
metadata:
name: shared-data-pod
spec:
containers:
- name: writer
image: busybox
command: ["/bin/sh", "-c"]
args: ["echo 'Hello' > /data/message.txt && sleep 3600"]
volumeMounts:
- name: shared-data
mountPath: /data

- name: reader
image: busybox
command: ["/bin/sh", "-c"]
args: ["cat /data/message.txt && sleep 3600"]
volumeMounts:
- name: shared-data
mountPath: /data

volumes:
- name: shared-data
emptyDir: {}
# emptyDir:
# medium: Memory # 使用記憶體(tmpfs)
# sizeLimit: 100Mi # 大小限制

hostPath:掛載主機目錄

將節點上的檔案或目錄掛載到 Pod。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: Pod
metadata:
name: hostpath-pod
spec:
containers:
- name: app
image: my-app:latest
volumeMounts:
- name: host-logs
mountPath: /var/log/myapp

volumes:
- name: host-logs
hostPath:
path: /var/log/pods # 節點上的路徑
type: DirectoryOrCreate # 類型(見下表)
hostPath type 說明
"" 不檢查
DirectoryOrCreate 目錄不存在則創建
Directory 目錄必須存在
FileOrCreate 檔案不存在則創建
File 檔案必須存在

⚠️ 安全警告:hostPath 可存取節點檔案系統,生產環境應謹慎使用。


持久化儲存:PV 與 PVC

概念說明

graph LR
    ADMIN[管理員] -->|創建| PV[PersistentVolume<br/>實際儲存]
    DEV[開發者] -->|創建| PVC[PersistentVolumeClaim<br/>資源請求]
    PVC -->|綁定| PV
    POD[Pod] -->|使用| PVC
  • PersistentVolume(PV):叢集中的一塊儲存,由管理員創建
  • PersistentVolumeClaim(PVC):對儲存的請求,由開發者創建
  • StorageClass:動態供應儲存的模板

PersistentVolume 範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-storage
spec:
capacity:
storage: 10Gi # 容量
accessModes:
- ReadWriteOnce # 存取模式
persistentVolumeReclaimPolicy: Retain # 回收策略
storageClassName: manual # StorageClass 名稱

# 底層儲存(以下擇一)
hostPath:
path: /mnt/data

# 或 NFS
# nfs:
# server: nfs-server.example.com
# path: /exports/data

# 或 AWS EBS
# awsElasticBlockStore:
# volumeID: vol-12345678
# fsType: ext4

存取模式

模式 縮寫 說明
ReadWriteOnce RWO 單一節點讀寫
ReadOnlyMany ROX 多節點唯讀
ReadWriteMany RWX 多節點讀寫
ReadWriteOncePod RWOP 單一 Pod 讀寫

回收策略

策略 說明
Retain PVC 刪除後保留 PV,需手動處理
Delete PVC 刪除後自動刪除 PV
Recycle 清除資料後重新可用(已棄用)

PersistentVolumeClaim 範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-storage
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi # 請求容量
storageClassName: manual # 匹配 PV 的 StorageClass
# selector: # 可選:選擇特定 PV
# matchLabels:
# type: ssd

在 Pod 中使用 PVC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1
kind: Pod
metadata:
name: pvc-pod
spec:
containers:
- name: app
image: my-app:latest
volumeMounts:
- name: storage
mountPath: /data

volumes:
- name: storage
persistentVolumeClaim:
claimName: pvc-storage

StorageClass:動態供應

不用預先創建 PV,由 StorageClass 自動創建。

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-storage
provisioner: kubernetes.io/aws-ebs # 或其他 provisioner
parameters:
type: gp3
iopsPerGB: "10"
fsType: ext4
reclaimPolicy: Delete
allowVolumeExpansion: true # 允許擴容
volumeBindingMode: WaitForFirstConsumer # 延遲綁定
1
2
3
4
5
6
7
8
9
10
11
12
# 使用 StorageClass 的 PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: dynamic-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
storageClassName: fast-storage # 指定 StorageClass

常見雲端 StorageClass

雲端 Provisioner 說明
AWS ebs.csi.aws.com EBS 儲存
GCP pd.csi.storage.gke.io Persistent Disk
Azure disk.csi.azure.com Azure Disk

實戰範例:有狀態應用部署

部署 PostgreSQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# 1. StorageClass(雲端環境可能已內建)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer

---
# 2. PersistentVolume
apiVersion: v1
kind: PersistentVolume
metadata:
name: postgres-pv
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
hostPath:
path: /data/postgres

---
# 3. PersistentVolumeClaim
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: local-storage

---
# 4. Secret(資料庫密碼)
apiVersion: v1
kind: Secret
metadata:
name: postgres-secret
type: Opaque
stringData:
POSTGRES_PASSWORD: supersecretpassword

---
# 5. ConfigMap(初始化 SQL)
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-initdb
data:
init.sql: |
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL
);

---
# 6. Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:15-alpine
ports:
- containerPort: 5432
env:
- name: POSTGRES_DB
value: myapp
- name: POSTGRES_USER
value: postgres
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: POSTGRES_PASSWORD
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
- name: initdb
mountPath: /docker-entrypoint-initdb.d
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
readinessProbe:
exec:
command: ["pg_isready", "-U", "postgres"]
initialDelaySeconds: 5
periodSeconds: 10

volumes:
- name: postgres-data
persistentVolumeClaim:
claimName: postgres-pvc
- name: initdb
configMap:
name: postgres-initdb

---
# 7. Service
apiVersion: v1
kind: Service
metadata:
name: postgres
spec:
selector:
app: postgres
ports:
- port: 5432

本章重點回顧

ConfigMap

  1. 用途:管理非敏感的配置資訊
  2. 使用方式:環境變數、檔案掛載、命令列參數
  3. 熱更新:檔案掛載可自動更新(但需應用程式配合)

Secret

  1. 用途:管理敏感資訊(密碼、憑證)
  2. 編碼:使用 Base64,不是加密
  3. 最佳實踐:不提交 Git、啟用 etcd 加密、使用外部密鑰管理

Volume

  1. 臨時儲存:emptyDir(Pod 內共享)
  2. 主機儲存:hostPath(掛載節點目錄)
  3. 持久儲存:PV + PVC + StorageClass

PV/PVC 流程

graph LR
    A[創建 StorageClass] --> B[創建 PVC]
    B --> C[動態創建 PV]
    C --> D[PVC 綁定 PV]
    D --> E[Pod 使用 PVC]

下一篇預告

在下一篇文章中,我們將探討 資源管理與自動擴展

  • 資源請求與限制(Requests/Limits)
  • Horizontal Pod Autoscaler (HPA)
  • Pod 調度策略
  • 探針機制(Liveness/Readiness/Startup)

系列文章導覽

  • Part 1:入門篇 - 認識 K8s 與核心架構
  • Part 2:基礎篇 - Pod、Deployment 與 Service
  • Part 3:網路篇 - 服務發現與流量管理
  • Part 4:配置與存儲篇 - ConfigMap、Secret 與 Volume(本篇)
  • Part 5:進階篇 - 資源管理與自動擴展
  • Part 6:實戰篇 - 部署完整微服務應用

參考資源