昨天我們用 ConfigMap 把設定從 Image 中抽離,解決了「設定與應用程式耦合」的問題。
不過,還有一個重要問題:
資料庫密碼、API Key、Token、TLS 憑證⋯⋯這些機密資訊,也適合放在 ConfigMap 嗎?
答案是:不適合。
ConfigMap 的定位是儲存非機密設定資料,例如環境名稱、服務位址或一般應用程式設定,並不是用來管理密碼、Token 等敏感資訊。
對於這類機密資料,Kubernetes 提供了專門的資源物件 —— Secret。
Secret 的使用方式和 ConfigMap 很相似,同樣可以透過環境變數或 Volume 提供給 Pod 使用;但它的設計目的就是管理機密資訊,並可以搭配 RBAC、Encryption at Rest 等機制進一步加強保護。
今天會學:
以下操作皆在 master 節點 執行。
很多人以為 Secret 是「加密儲存」的,這是一個超常見的誤解。
先建立一個 Secret 來看看:
kubectl create secret generic my-secret \
--from-literal=DB_PASSWORD=super-secret-123 \
--from-literal=API_KEY=ak-78f9d2e1
查看 Secret 的內容:
kubectl get secret my-secret -o yaml
你會看到 data 欄位裡的值長這樣:

看起來好像是加密過的?來解碼看看:
echo "c3VwZXItc2VjcmV0LTEyMw==" | base64 --decode; echo

震驚吧?這只是 base64 編碼,不是加密! 任何人拿到這串字串都能解碼。
base64 ≠ 加密
base64 只是一種編碼方式,目的是讓二進位資料能用文字表示,完全沒有安全性可言。Secret 的安全性不是靠編碼,而是靠 Kubernetes 的存取控制機制(後面會說明)。
既然預設也不是加密的,為什麼還要用 Secret?
| 項目 | ConfigMap | Secret |
|---|---|---|
| 用途 | 一般、非機密設定 | 密碼、Token、憑證等機密資訊 |
| API 顯示格式 | 一般字串 | data 通常以 Base64 編碼表示 |
| RBAC | 可透過 RBAC 控制 | 可透過 RBAC 控制,官方特別建議採最小權限原則 |
| Volume 掛載 | 可掛載為檔案 | 可掛載為檔案,並可設定檔案權限 |
| 支援類型 | 無特定機密資料類型 | Opaque、kubernetes.io/tls、kubernetes.io/dockerconfigjson 等 |
| etcd 加密 | 可透過 API Server Encryption at Rest 保護 | 可透過 API Server Encryption at Rest 保護 |
| 節點上的 Volume 儲存 | 一般 Volume 機制 | Secret Volume 由 kubelet 以 tmpfs 保存,避免寫入持久化磁碟 |
👉 Secret 的重點不在 Base64,而在於它是專門管理機密資訊的資源:
tmpfs 保存,避免寫入節點的持久化磁碟Kubernetes 內建了多種 Secret 類型,常見的包括:
| 類型 | 用途 | 說明 |
|---|---|---|
| Opaque | 通用機密資料 | 最常用,可存放任意 Key-Value |
kubernetes.io/tls |
TLS 憑證 | 通常包含 tls.crt 與 tls.key |
kubernetes.io/dockerconfigjson |
Container Registry 認證 | 用來儲存存取私有 Registry 所需的認證資訊 |
kubernetes.io/basic-auth |
Basic Authentication | 通常包含 username 與 password |
kubernetes.io/ssh-auth |
SSH 認證 | 使用 ssh-privatekey 儲存 SSH Private Key |
今天我們主要使用 Opaque 類型,也會示範 TLS Secret 的建立與使用方式。
跟 ConfigMap 類似,最快的方式:
kubectl create secret generic db-credentials \
--from-literal=DB_HOST=db-prod.example.com \
--from-literal=DB_USER=admin \
--from-literal='DB_PASSWORD=P@ssw0rd!2025'
⚠️ 注意:
!是 Bash 的特殊字元如果密碼中包含
!,Bash 可能會將它視為 history expansion,例如!2025可能被當成歷史指令處理。建議使用單引號包住整個
--from-literal參數,避免特殊字元被 Shell 解析。
查看 Secret:
kubectl get secret db-credentials -o yaml
會看到所有值都被 base64 編碼了。

用 YAML 的話,值需要自己先做 base64 編碼:
# 先把值編碼
echo -n "admin" | base64
# → YWRtaW4=
echo -n "P@ssw0rd!2025" | base64
# → UEBzc3cwcmQhMjAyNQ==
vim db-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: db-credentials-yaml
type: Opaque
data:
DB_USER: YWRtaW4=
DB_PASSWORD: UEBzc3cwcmQhMjAyNQ==
kubectl apply -f db-secret.yaml
💡 不想自己編碼?用
stringData!Kubernetes 也支援
stringData欄位,可以直接寫明文,Apply 的時候會自動幫你編碼:apiVersion: v1 kind: Secret metadata: name: db-credentials-easy type: Opaque stringData: DB_USER: admin DB_PASSWORD: P@ssw0rd!2025注意:
stringData是寫入用的,kubectl get顯示的還是data(Base64 編碼後的值)。
適合存放憑證、金鑰等檔案:
# 建立一個模擬的 API Key 檔案
echo -n "sk-a1b2c3d4e5f6g7h8" > api-key.txt
# 從檔案建立 Secret
kubectl create secret generic api-secret --from-file=API_KEY=api-key.txt
查看:
kubectl get secret api-secret -o yaml

如果你有 TLS 憑證,可以用專用指令:
# 產生自簽憑證(測試用)
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout tls.key -out tls.crt \
-subj "/CN=example.com"
# 建立 TLS Secret
kubectl create secret tls my-tls-secret \
--cert=tls.crt \
--key=tls.key
kubectl get secret my-tls-secret -o yaml
你會看到 type: kubernetes.io/tls,裡面包含 tls.crt 和 tls.key。
Secret 提供給 Pod 使用的方式和 ConfigMap 很相似,常見同樣有三種:
vim pod-secret-envfrom.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-secret-envfrom
spec:
containers:
- name: app
image: nginx
envFrom:
- secretRef:
name: db-credentials
kubectl apply -f pod-secret-envfrom.yaml
驗證:
kubectl exec pod-secret-envfrom -- env | grep DB_

會看到 Secret 的值以明文形式出現在環境變數中(Kubernetes 會自動解碼 base64)。
vim pod-secret-env.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-secret-env
spec:
containers:
- name: app
image: nginx
env:
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: DB_PASSWORD
- name: DATABASE_USER
valueFrom:
secretKeyRef:
name: db-credentials
key: DB_USER
kubectl apply -f pod-secret-env.yaml
驗證:
kubectl exec pod-secret-env -- env | grep DATABASE_

跟 ConfigMap 一樣,可以用不同的變數名稱。
vim pod-secret-volume.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-secret-volume
spec:
containers:
- name: app
image: nginx
volumeMounts:
- name: secret-volume
mountPath: /etc/secrets
readOnly: true
volumes:
- name: secret-volume
secret:
secretName: db-credentials
kubectl apply -f pod-secret-volume.yaml
驗證:
# 查看掛載的檔案
kubectl exec pod-secret-volume -- ls -la /etc/secrets/
# 查看檔案內容
kubectl exec pod-secret-volume -- cat /etc/secrets/DB_PASSWORD
⚠️ 注意檔案權限
Secret 透過 Volume 掛載成檔案時,預設權限為
0644。如果希望限制只有擁有者可以讀取,可以透過defaultMode: 0400自行設定。ConfigMap vs Secret 注入方式對照
Secret 與 ConfigMap 提供給 Pod 使用的方式非常相似,主要差別在幾個欄位名稱:
configMapRef→secretRefconfigMapKeyRef→secretKeyRefconfigMap:→secret:,並使用secretName指定 Secret其他 YAML 結構大致相同。
| 方式 | 適用場景 | Secret 更新後 |
|---|---|---|
envFrom |
將多個 Key-Value 一次匯入為環境變數 | ❌ 已啟動的 Pod 不會自動更新,需重新建立 Pod |
env.valueFrom |
引用指定 Key,並可自訂環境變數名稱 | ❌ 已啟動的 Pod 不會自動更新,需重新建立 Pod |
| Volume 掛載 | 將 Secret 內容掛載為檔案 | ✅ 掛載內容會自動同步,但可能有延遲 |
Secret 在這三種使用方式上的更新行為,和 ConfigMap 大致相同。
前面提過,Secret 中的資料雖然通常以 Base64 編碼表示,但 Base64 並不是加密。
更重要的是,Kubernetes 預設會將 Secret 以未加密的形式儲存在 etcd 中。因此,如果有人能直接存取 etcd,就可能取得其中的 Secret 資料。
要進一步保護 Secret,可以啟用 Encryption at Rest,讓 Kubernetes API Server 在資料寫入 etcd 前先進行加密。
預設情況下,Secret 在 etcd 中是 base64 明文儲存的。你可以啟用加密,讓 Secret 在 etcd 中是真正加密的狀態。
Step 1:建立加密設定檔
vim /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: <base64-encoded-32-byte-key>
- identity: {}
💡 產生 32-byte 的加密金鑰:
head -c 32 /dev/urandom | base64把輸出的值填入上面
secret:欄位。
Step 2:修改 kube-apiserver 的 Static Pod Manifest
--encryption-provider-config 不是直接在 Shell 執行的指令,而是 kube-apiserver 的啟動參數。
先編輯 Static Pod Manifest:
vim /etc/kubernetes/manifests/kube-apiserver.yaml
在 spec.containers[0].command 中加上:
- --encryption-provider-config=/etc/kubernetes/encryption-config.yaml

同時確保加密設定檔有掛載進 API Server 的 Pod 中。在同一份 kube-apiserver.yaml 裡,找到 spec.containers[0].volumeMounts,加上:
- name: encryption-config
mountPath: /etc/kubernetes/encryption-config.yaml
readOnly: true

再找到 spec.volumes,加上:
- name: encryption-config
hostPath:
path: /etc/kubernetes/encryption-config.yaml
type: File

⚠️ 注意
volumeMounts和volumes底下通常已經有許多現有的掛載設定,例如etcd-certs、ca-certs等。只需要在原有內容後面追加新的
encryption-config設定即可,不要刪除或覆蓋原本的掛載內容。
存檔後,kubelet 會偵測到 Static Pod Manifest 的變更,並重新建立 kube-apiserver Pod,讓新的加密設定生效
Step 3:驗證加密是否生效
接下來直接從 etcd 讀取 Secret,確認資料是否已經被加密。
在 kubeadm 建立的叢集中,etcd 通常以 Static Pod 的方式執行,因此可以透過 kubectl exec 進入 etcd Pod,再使用 etcdctl 查詢資料。
先建立一個測試用的 Secret:
kubectl create secret generic test-encryption --from-literal=mykey=myvalue
接著透過 kubectl exec 進入 etcd Pod,使用 etcdctl 直接讀取這個 Secret 在 etcd 中的原始資料:
kubectl exec -n kube-system etcd-master -- 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/test-encryption | hexdump -C
⚠️ 注意
上面指令中的
etcd-master是 etcd Pod 的名稱,通常格式為etcd-<Control Plane 節點名稱>,請依實際環境替換。另外,etcd v3.5 之後預設使用 API v3,因此不需要再額外設定
ETCDCTL_API=3。
如果加密已啟用,從 etcd 直接讀取 Secret 時,資料會以 k8s:enc:aescbc:v1:key1: 開頭,後面接著加密後的內容,而不會直接看到原始的 myvalue。

如果仍然看到明文的 myvalue,表示 Encryption at Rest 尚未生效,需要回頭檢查 EncryptionConfiguration、kube-apiserver 啟動參數,以及設定檔掛載是否正確。
⚠️ 已有的 Secret 不會自動重新加密
啟用 Encryption at Rest 後,新建立或之後重新寫入的 Secret 會使用新的加密設定,但原本已經存在於 etcd 中的 Secret 不會自動重新加密。
如果要讓既有的 Secret 也套用新的加密設定,可以重新寫入所有 Secret:
kubectl get secrets --all-namespaces -o json | kubectl replace -f -這個指令會讀取現有 Secret,再以相同內容重新寫回 Kubernetes,讓 API Server 依目前的
EncryptionConfiguration重新加密後儲存到 etcd。
⚠️ 修改前請先備份
Encryption at Rest 屬於叢集層級設定。修改
kube-apiserverStatic Pod Manifest 前,建議先備份:cp /etc/kubernetes/manifests/kube-apiserver.yaml ~/kube-apiserver.yaml.bak
託管 Kubernetes 服務
如果使用 EKS、GKE 等託管 Kubernetes 服務,Control Plane 與 etcd 通常由雲端平台負責管理,因此不需要、也通常無法直接修改
kube-apiserver.yaml。這類平台通常已提供靜態資料加密機制;實際的加密方式與可設定項目,則依雲端服務與叢集版本而有所不同。
問題: 如果某個 ServiceAccount 被授予過大的 Secret 權限,例如可以 get、list 或 watch 整個 Namespace 中的 Secret,那麼使用這個 ServiceAccount 的 Pod 就可能讀取到不屬於自己的機密資料。
尤其是 list 和 watch Secret,也等同能取得 Secret 的內容,因此不應隨意授予。
解法: 使用 RBAC(Role-Based Access Control)遵循最小權限原則,只允許特定的 ServiceAccount 存取真正需要的 Secret。
簡單來說,就是建立一個規則:只有指定的 ServiceAccount 能讀取指定的 Secret。
Step 1:建立 Role(定義「可以做什麼」)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: secret-reader
namespace: default
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["db-credentials"] # 只能存取這個 Secret
verbs: ["get"] # 只能讀取,不能修改或刪除
這個 Role 的意思是:在 default Namespace 中,只允許對名為 db-credentials 的 Secret 執行指定的讀取操作,其他 Secret 不在這個 Role 的授權範圍內。
Step 2:建立 RoleBinding(定義「誰」能用這個 Role)
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-db-secret
namespace: default
subjects:
- kind: ServiceAccount
name: my-app-sa # 只有這個 ServiceAccount
namespace: default
roleRef:
kind: Role
name: secret-reader # 綁定上面的 Role
apiGroup: rbac.authorization.k8s.io
這個 RoleBinding 會把前面建立的 secret-reader Role 授權給 my-app-sa ServiceAccount。
因此,使用 my-app-sa 的 Pod 可以透過 Kubernetes API 讀取 db-credentials;沒有取得相同權限的其他 ServiceAccount,則無法透過這組 RBAC 規則讀取這個 Secret。
驗證:用 kubectl auth can-i 檢查權限
不需要真的建立 Pod,Kubernetes 提供了 kubectl auth can-i 指令,可以直接模擬特定身分並檢查是否具有對應權限:
# 先建立 ServiceAccount
kubectl create serviceaccount my-app-sa
# my-app-sa 可以讀取 db-credentials
kubectl auth can-i get secret/db-credentials --as=system:serviceaccount:default:my-app-sa
# my-app-sa 不能讀取其他 Secret
kubectl auth can-i get secret/my-secret --as=system:serviceaccount:default:my-app-sa
# 預設的 default ServiceAccount 不能讀取 db-credentials
kubectl auth can-i get secret/db-credentials --as=system:serviceaccount:default:default
--as 參數可以模擬指定的使用者或 ServiceAccount 身分,用來驗證 RBAC 規則是否符合預期,不需要真的建立 Pod 來測試

💡 RBAC 的核心概念
- Role:定義「可以對哪些資源執行哪些操作」
- RoleBinding:定義「誰可以使用這個 Role」
- 兩者搭配使用,就能更精確地控制資源存取權限
本章先透過 Secret 的案例簡單理解 RBAC 的用途,後續會在 RBAC 專章深入介紹 Role、ClusterRole、RoleBinding、ClusterRoleBinding 與 ServiceAccount。
這是很常見的錯誤。Secret YAML 中的 data 雖然看起來是 Base64 編碼,但 Base64 並不是加密,因此不應直接把包含機密資料的 Secret YAML 提交到 Git。
替代方案:
SealedSecret 後再提交到 Git,只有叢集中的 Sealed Secrets Controller 能解密| 項目 | 預設狀態 | 建議做法 |
|---|---|---|
| Encryption at Rest | ⚠️ Secret 預設不保證加密儲存 | ✅ 啟用 Encryption at Rest |
| RBAC | ⚠️ 權限依叢集設定而定 | ✅ 採用最小權限原則,只授予必要的 Secret 存取權限 |
| Git 儲存 | ⚠️ Base64 不是加密 | ✅ 不要直接提交 Secret 明文,可使用 Sealed Secrets、External Secrets 或 SOPS |
| Secret 注入方式 | ⚠️環境變數可能增加機密資訊暴露風險 | ✅ 依應用需求評估,機密檔案可優先考慮 Secret Volume |
💡 最佳實踐:ConfigMap + Secret 搭配使用
- 一般設定(例如環境、Port、Log Level)→ 使用 ConfigMap
- 機密資訊(例如密碼、API Key、Token、憑證)→ 使用 Secret
- ConfigMap 與 Secret 可以在同一個 Pod 中同時使用,各自管理不同類型的設定資料
今天我們學會了使用 Secret 管理 Kubernetes 中的機密資訊:
| 概念 | 重點 | 說明 |
|---|---|---|
| Secret | 機密資料管理 | 用來儲存密碼、Token、憑證等敏感資訊,通常以 Base64 編碼表示 |
| Opaque | 通用類型 | 最常用的 Secret 類型,可存放任意 Key-Value |
| TLS Secret | 憑證管理 | 專門存放 TLS 憑證與私鑰 |
stringData |
方便寫入 | YAML 中可直接填入原始字串,建立後會轉入 data 欄位 |
| 安全補強 | 強化保護 | 搭配 RBAC、Encryption at Rest,並避免將機密資料直接提交到 Git |
到目前為止,我們已經掌握了 Kubernetes 的設定管理:ConfigMap 負責一般設定,Secret 則用來管理機密資訊。
不過,這些解決的是「設定」的問題。如果應用程式需要保存資料,例如資料庫內容、使用者上傳的檔案,該怎麼辦?
Pod 可能因為更新、故障或重新排程而被重建,如果資料只存在 Container 內部,這些資料也可能跟著消失。
明天我們會接著介紹 PV 與 PVC,看看 Kubernetes 如何提供持久化儲存,讓資料不會因為 Pod 被重建而消失。