寫這篇想要分享的重點:
Kubernetes 提供 ConfigMap 與 Secret 兩種資源物件來實現將設定與應用程式分離。本篇除了介紹如何將設定注入容器外,更會探討 etcd 中的資安問題,以及如何啟用 Encryption at Rest 加密機制。這篇想要講什麼:
- ConfigMap 與 Secret 的用途與三種注入方式(環境變數、單一變數、Volume 掛載)。
- Base64 編碼的誤區:Secret 預設不是加密,只是編碼!
- etcd Encryption at Rest:如何配置
EncryptionConfiguration為 etcd 中的敏感資料提供加密。為何要寫這篇:
許多人以為將密碼存進 Secret 就萬無一失,卻不知 etcd 預設是以明文(Plaintext)儲存 Base64 解碼後的內容。搞懂 Secret 的注入與底層 etcd 加密方式,讓資安更穩固。
在開發或容器化的早期階段,工程師常將資料庫密碼、API Key 或設定檔直接Hardcode在程式碼中,或是寫進 Docker Image。這樣做會帶來三個問題:
Kubernetes 導入 ConfigMap 與 Secret 的目的,就是為了實現「環境設定與應用程式碼完全解耦」:
+------------------------+ +-----------------------------------------+
| 應用程式 Image | + | K8s 組態資源 (環境解耦) |
| (只保留純粹的商業邏輯) | | - ConfigMap: 非敏感設定 (如 LOG_LEVEL) |
+-----------+------------+ | - Secret: 敏感資料 (如 DB_PASS) |
| +--------------------+--------------------+
| |
+-------------------+--------------------+
|
v
+-----------------------+
| 運作中的 Pod 容器 |
+-----------------------+
ConfigMap:專門用於儲存一般非敏感設定(如資料庫連線 URL、LOG_LEVEL、開關參數)。Secret:專門用於儲存敏感機密資料(如資料庫密碼、API Token、TLS 數位憑證),並在叢集中提供基礎的加密儲存與存取控管。透過這套機制,你可以一個 Image 跑遍所有環境——只需在不同的 K8s 叢集中注入相對應的 ConfigMap 與 Secret,即可改變部署狀態,同時確保敏感資訊不外洩。
建立 Secret YAML 時,內容必須轉為 Base64 字串:
echo -n 'my-db-password' | base64
# 輸出:bXktZGItcGFzc3dvcmQ=
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
data:
DB_PASS: bXktZGItcGFzc3dvcmQ=
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
APP_ENV: "production"
MAX_CONNECTIONS: "100"
LOG_LEVEL: "info"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: web
image: nginx
# 一口氣將 ConfigMap 裡的所有 Key-Value 注入為環境變數
envFrom:
- configMapRef:
name: app-config
假設團隊維護一個 user-service,該服務預期透過環境變數 DATABASE_URL 或 DB_PASSWORD 來建立資料庫連線。
集群的維運團隊(DevOps)已經預先在 K8s 中建立了統一的金庫(Secret),裡面存放了資料庫的各項敏感資訊(用戶名、密碼、主機位址)。
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
stringData:
DB_HOST: "postgres-main.database.svc.cluster.local"
DB_USER: "user_service_app"
DB_PASS: "SuperSecureP@ssw0rd2026!" # 機密! DB密碼
ADMIN_PASS: "RootMasterPass999!" # 最高權限密碼(此服務不需要)
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service-deployment
spec:
replicas: 2
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
spec:
containers:
- name: user-api
image: my-registry.com/user-service:v1.2.0
ports:
- containerPort: 3000
env:
# 一般非敏感變數可直接寫或來自 ConfigMap
- name: NODE_ENV
value: "production"
- name: DB_PORT
value: "5432"
# 提取 Secret 中的 DB_PASS,並改名為 DB_PASSWORD 注入容器
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials # 抓資料名稱 db-credentials
key: DB_PASS # 拿到密碼
將 Secret 掛載為檔案時,資料夾內會為每個 Key 自動建立同名檔案, 就像給機器插上一個外接隨身碟,隨身碟裡面放著憑證檔案(如 tls.crt)。程式不需要透過環境變數去讀,而是直接像打開電腦資料夾一樣,去 /etc/secrets/ 這個路徑讀取裡面的實體檔案。
apiVersion: v1
kind: Secret
metadata:
name: tls-secret
type: Opaque
stringData:
tls.crt: "---BEGIN CERTIFICATE---..."
tls.key: "---BEGIN PRIVATE KEY---..."
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: web
image: nginx
# 將 Volume 掛載至容器內的特定目錄
volumeMounts:
- name: cert-volume
mountPath: "/etc/secrets"
readOnly: true
# 定義 Volume 來源為 Secret
volumes:
- name: cert-volume
secret:
secretName: tls-secret
Base64 只是編碼 (Encoding),絕對不是加密 (Encryption)!
如果有人擁有 etcd 存取權限或拿到 etcd 備份檔,只需要下指令 etcdctl get /registry/secrets/...,就能直接以明文閱讀全叢集所有的 Secret 密碼!
要實現真正安全的加密儲存,必須啟用 K8s API Server 的 Encryption at Rest 功能。
EncryptionConfiguration 檔案在 Control Plane 節點建立 /etc/kubernetes/enc/enc.yaml:
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc: # 使用 AES-CBC 加密演算法
keys:
- name: key1
secret: <base64-encoded-32-byte-key>
- identity: {} # 允許讀取舊有的未加密明文資料
重點原則:
providers的優先順序非常重要!排在第一個的 provider (aescbc) 會被用來寫入新資料;後續的 provider (identity) 用於讀取過渡期資料。
kube-apiserver.yaml Static Pod編輯 /etc/kubernetes/manifests/kube-apiserver.yaml,加入啟動參數與 Volume 掛載:
spec:
containers:
- command:
- kube-apiserver
- --encryption-provider-config=/etc/kubernetes/enc/enc.yaml # 指定加密配置
volumeMounts:
- name: enc-config
mountPath: /etc/kubernetes/enc
readOnly: true
volumes:
- name: enc-config
hostPath:
path: /etc/kubernetes/enc
type: DirectoryOrCreate
啟用加密設定後,只有新建立的 Secret 會自動被加密。若要讓過往已存在的 Secret 也轉為加密格式,需要執行全量更新指令:
kubectl get secrets --all-namespaces -o json | kubectl replace -f -
本篇總結:
ConfigMap 與 Secret 能將設定與程式碼分離並注入容器;而為了確保真正的資安,必須在 kube-apiserver 配置 Encryption at Rest (AES-CBC) 將存入 etcd 的 Secret 進行靜態加密。
下一篇預告:
解決了環境變數與資安防護後,明天 Day 12 我們將探討 多容器協作設計模式 (Multi-Container Pods)——深入剖析 InitContainers 前置準備與 Sidecar 側車容器的協同工作原理與生命週期!敬請期待!
envFrom / valueFrom)或 Volume 檔案掛載注入容器中。EncryptionConfiguration,採用 AES-CBC 等加密演算法將寫入 etcd 的 Secret 進行底層加密。replace)才能將舊有 Secret 轉為加密儲存。