昨天我們看到如何用 ResourceQuota
和 LimitRange
來管理 Kubernetes Cluster 的資源配額。透過 ResourceQuota
控制整個 Namespace 的總資源使用量,用 LimitRange
限制單一容器的資源上下限,並為沒有設定資源的 Pod 自動分配預設值。我們也實際測試了當超過配額限制時會發生什麼,看到 Kubernetes 如何嚴格執行這些限制。
接下來我們看到如果把這些資訊都寫死在設定檔裡,不僅不安全,也很難維護。每次改個設定就要重新打包映像檔,實在太麻煩了。想想看這些實際情境:
所以我們今天要來看看 ConfigMap 和 Secrets!
ConfigMap 允許將設定檔 (配置文件) 從容器映像中解耦 (decouple),也就是分開管理,從而增強容器應用的可移值性。
將應用程式的程式碼與像是應用程式設定、環境變數等等內容分開。方便我們管理,且可以掛載到不同 Pod 內使用,達到「高內聚、低耦合」的設計目標。
高內聚,低耦合 (high cohesion、low coupling) 的意思是,物件的程式碼應該要有很高的比率只和物件內其他有關的程式碼有關聯,而對外部的程式碼,物件或元件等的關聯度要愈低愈好 (最佳的狀態是零耦合)。
Secrets 則是專門用來存放敏感資料的,比如密碼、API Keys、Token 等等。雖然它跟 ConfigMap 很像,但 Secrets 會用 base64 編碼來存放資料。Secrets 也支援當作環境變數注入到 Pod,也可以當作檔案掛載到指定的目錄。
- 超級重要 ‼️
- Secrets 只是用 base64 編碼,不是真正的加密,任何人都可以輕易解碼。如果真的要用 Secret 儲存敏感資訊,不要把 Secrets 的 YAML 檔案推到 GitHub 等公開儲存庫。並且同個 Namespace 裡能建立 Pod 的人都能存取到 Secrets。
- 最佳實踐 👍
- 考慮啟用 ETCD 的靜態加密,並且使用 RBAC 嚴格限制誰能存取 Secret。
- 考慮使用第三方 Secrets 管理工具(如 AWS、GCP Secrets Manager、HashiCorp Vault)
首先看 ConfigMap,我們可以用命令式指令 --from-literal
建立:
kubectl create configmap mydb-env-literal --from-literal=ACCOUNT=ITHOME2025 --from-literal=PASSWORD=K8sDevOps --from-literal=TZ="Asia/Taipei"
接下來將變數儲存到檔案中,用命令式從檔案匯入。有兩種方式:--from-file
和 --from-env-file
。
ACCOUNT=ITHOME2025
PASSWORD=K8sDevOps
TZ="Asia/Taipei"
使用 --from-env-file
建立:
kubectl create configmap mydb-env --from-env-file=mydb_env
可以看到會以 Key-Value 的方式存進 ConfigMap 物件,與用 --from-literal
的結果一樣:
使用 --from-file
建立:
kubectl create configmap mydb-file --from-file=mydb_env
這種方式會將整個檔案內容當作一個值存進去:
兩種方式差異很大,連 DATA 的數量都不一樣。如果要當作環境變數掛載到 Pod,需要用 Key-Value 的方式才能正確掛載:
接下來將 ConfigMap 掛載到 Deployment 裡。為了因應 CKAD 考試情境,我們先用 dry run 建立 template:
kubectl create deployment nginx --image nginx --dry-run=client -o yaml > nginx-dep.yaml
這樣可以在不建立任何物件的情況下,拿到結構相對完整的 YAML 檔:
修改 YAML 檔,將 ConfigMap 掛載為環境變數:
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: nginx
name: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
resources: {}
env:
- name: ACCOUNT
valueFrom:
configMapKeyRef:
key: ACCOUNT
name: mydb-env
- name: PASSWORD
valueFrom:
configMapKeyRef:
key: PASSWORD
name: mydb-env
- name: TZ
valueFrom:
configMapKeyRef:
key: TZ
name: mydb-env
status: {}
部署後驗證環境變數是否成功掛載:
kubectl exec -it your-pod -- env|grep -E "ACCOUNT|PASSWORD|TZ"
當然也可以用 YAML 聲明式建立 ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
APP_MODE: dev
除了一個個設定環境變數,也可以用 envFrom
直接掛載整個 ConfigMap:
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: nginx
name: nginx-envfrom
spec:
replicas: 1
selector:
matchLabels:
app: nginx
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
resources: {}
envFrom:
- configMapRef:
name: app-config
status: {}
會跟上面一樣成功掛載到 Deployment 裡面。
Secret 的使用方式跟 ConfigMap 幾乎一模一樣,差別只在 kind 變成 Secret。
# from literal
kubectl create secret generic app-secret --from-literal=PASSWORD=password
# mydb_env
PASSWORD=password
# from env file
kubectl create secret app-secret --from-env-file=mydb_env
YAML 檔聲明式:
apiVersion: v1
kind: Secret
metadata:
name: app-secret
data:
PASSWORD: password
首先看 Secret 支援的三種類型:
kubectl create secret --help
可以看到他提過三種主要類型,各自針對不同使用場景:
我們使用 generic
類型建立:
kubectl create secret generic app-secret --from-literal=PASSWORD=password
建立完成後,可以看到 Secret 的內容:
kubectl get secrets app-secret -o yaml
取得 yaml 檔內容之後,然後把我們要的資料拿出來做 base64 的解碼就可以看到原始資料的樣子了。
甚至我們只用一行指令就可以直接解碼:
kubectl get secrets app-secret -o yaml|grep PASSWORD|awk '{print $2}' | base64 --decode
這告訴我們 Kubernetes 原生的 Secrets 真的不適合拿來存放機敏資料!
現在來看看 Kubernetes 原生支援的 ETCD 靜態加密要如何實現,可以參考官方文件:ETCD 靜態加密
ETCD 靜態加密是把「存到 etcd 裡的資料」先加密,真正入庫的是密文;但 API Server 讀出來時會自動解密、回傳明文給客戶端。首先了解 ETCD 靜態加密的工作流程:
/etc/kubernetes/enc/enc.yaml
的 provider 設定,用 AES-CBC 或 KMS 加密。只有新的寫入會被加密。舊資料會維持舊狀態,等你做「重寫(re-write)」才會轉成加密版。
kubectl create secret generic my-secret --from-literal=key1=supersecret
etcdctl
看 Secrets 寫入 etcd
的樣子sudo apt-get install etcd-client
etcd
安裝完成!ETCDCTL_API=3 etcdctl \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
get /registry/secrets/default/my-secret | hexdump -C
這個指令的作用是:
/registry/secrets/default/my-secret
(Kubernetes 在 etcd 中存儲 Secret 的路徑)從下圖黃框處可以看到,資料以明文形式存儲,包含我們的敏感資料 "supersecret",這證明目前沒有啟用靜態加密:
enc.yaml
:apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: <這裡需要是 32 bytes 的 base64 編碼金鑰>
- identity: {}
head -c 32 /dev/urandom | base64
secret
欄位中,然後將檔案移動到 /etc/kubernetes/enc
路徑:/etc/kubernetes/manifests/kube-apiserver.yaml
,加入以下配置:# This is a fragment of a manifest for a static Pod.
# Check whether this is correct for your cluster and for your API server.
#
apiVersion: v1
kind: Pod
metadata:
annotations:
kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 10.20.30.40:443
creationTimestamp: null
labels:
app.kubernetes.io/component: kube-apiserver
tier: control-plane
name: kube-apiserver
namespace: kube-system
spec:
containers:
- command:
- kube-apiserver
# ... 其他參數 ...
- --encryption-provider-config=/etc/kubernetes/enc/enc.yaml # 加入這行
volumeMounts:
# ... 其他掛載 ...
- name: enc # 加入這行
mountPath: /etc/kubernetes/enc # 加入這行
readOnly: true # 加入這行
volumes:
# ... 其他 volumes ...
- name: enc # 加入這行
hostPath: # 加入這行
path: /etc/kubernetes/enc # 加入這行
type: DirectoryOrCreate # 加入這行
由於這是靜態 Pod,kubeadm 會自動偵測檔案變更並重啟 API Server。
encryption-provider-config
加密配置:ps -aux | grep kube-api | grep encryption-provider-config
這個輸出顯示 API Server 進程已經包含 --encryption-provider-config
參數,表示加密配置已成功載入。
kubectl get secrets --all-namespaces -o json | kubectl replace -f -
這個指令會:
執行後再次檢查原本的 Secret,可以看到原本以明文存儲的 Secret 現在也變成加密格式了!
今天我們學會了 ConfigMap 和 Secret 如何幫助我們管理應用程式的配置資料。透過 ConfigMap,我們可以將應用程式的運行參數從程式碼中抽離出來,讓程式更靈活地在不同環境中重複使用。
Secret 提供了集中管理敏感資訊的方式,雖然它本質上只是 base64 編碼,但搭配適當的權限控制和安全措施,仍然比寫死在程式碼裡安全得多。我們也實作了 ETCD 靜態加密的設定過程,看到如何在 etcd 層面加強資料保護。不過最重要的領悟是:Kubernetes 原生的 Secret 機制只是基礎建設,真正的安全需要搭配 RBAC 權限控制、網路隔離,以及考慮使用第三方密鑰管理工具來增強安全性。
到目前為止,我們已經學會了如何建立和管理 Pod、控制副本數量、分配資源、管理配置資料等等。但還有一個重要問題:這些 Pod 要如何對外提供服務?其他 Pod 或外部使用者要怎麼存取這些應用程式?
明天我們要看看 Service,看看 Kubernetes 如何讓我們的應用程式真正能被其他人使用!