iT邦幫忙

2026 iThome 鐵人賽

DAY 8
0
Kubernetes

從零到 CKA:30 天掌握 Kubernetes 核心觀念與實作系列 第 8

Day 08|Secret — 別再把密碼寫進 YAML!Secret 的正確使用方式

  • 分享至 

  • xImage
  •  

前言

昨天我們用 ConfigMap 把設定從 Image 中抽離,解決了「設定與應用程式耦合」的問題。

不過,還有一個重要問題:

資料庫密碼、API Key、Token、TLS 憑證⋯⋯這些機密資訊,也適合放在 ConfigMap 嗎?

答案是:不適合。

ConfigMap 的定位是儲存非機密設定資料,例如環境名稱、服務位址或一般應用程式設定,並不是用來管理密碼、Token 等敏感資訊。

對於這類機密資料,Kubernetes 提供了專門的資源物件 —— Secret

Secret 的使用方式和 ConfigMap 很相似,同樣可以透過環境變數或 Volume 提供給 Pod 使用;但它的設計目的就是管理機密資訊,並可以搭配 RBAC、Encryption at Rest 等機制進一步加強保護。

今天會學:

  1. Secret 跟 ConfigMap 差在哪? — 先破解一個常見迷思
  2. Secret 的建立方式 — 指令、YAML、檔案
  3. 三種注入方式 — 環境變數與 Volume 掛載
  4. Secret 的安全性 — 預設不夠安全,怎麼補強?

以下操作皆在 master 節點 執行。


一、先破解一個迷思:Secret 是加密的嗎?

很多人以為 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 欄位裡的值長這樣:

https://ithelp.ithome.com.tw/upload/images/20260810/20181928wpGhCX2JTQ.png

看起來好像是加密過的?來解碼看看:

echo "c3VwZXItc2VjcmV0LTEyMw==" | base64 --decode; echo

https://ithelp.ithome.com.tw/upload/images/20260810/20181928pnr771dmxL.png

震驚吧?這只是 base64 編碼,不是加密! 任何人拿到這串字串都能解碼。

base64 ≠ 加密

base64 只是一種編碼方式,目的是讓二進位資料能用文字表示,完全沒有安全性可言。Secret 的安全性不是靠編碼,而是靠 Kubernetes 的存取控制機制(後面會說明)。


二、那 Secret 到底比 ConfigMap 強在哪?

既然預設也不是加密的,為什麼還要用 Secret?

項目 ConfigMap Secret
用途 一般、非機密設定 密碼、Token、憑證等機密資訊
API 顯示格式 一般字串 data 通常以 Base64 編碼表示
RBAC 可透過 RBAC 控制 可透過 RBAC 控制,官方特別建議採最小權限原則
Volume 掛載 可掛載為檔案 可掛載為檔案,並可設定檔案權限
支援類型 無特定機密資料類型 Opaquekubernetes.io/tlskubernetes.io/dockerconfigjson
etcd 加密 可透過 API Server Encryption at Rest 保護 可透過 API Server Encryption at Rest 保護
節點上的 Volume 儲存 一般 Volume 機制 Secret Volume 由 kubelet 以 tmpfs 保存,避免寫入持久化磁碟

👉 Secret 的重點不在 Base64,而在於它是專門管理機密資訊的資源:

  • RBAC 控制 — 可以限制哪些使用者或 ServiceAccount 有權限讀取 Secret
  • Encryption at Rest — 可以設定 API Server 將 Secret 加密後再儲存到 etcd
  • 用途分離 — 明確區分一般設定與機密資訊,方便進行權限管理與稽核
  • Secret Volume — 掛載到 Pod 時,Secret 資料由 kubelet 以 tmpfs 保存,避免寫入節點的持久化磁碟

三、Secret 的類型

Kubernetes 內建了多種 Secret 類型,常見的包括:

類型 用途 說明
Opaque 通用機密資料 最常用,可存放任意 Key-Value
kubernetes.io/tls TLS 憑證 通常包含 tls.crttls.key
kubernetes.io/dockerconfigjson Container Registry 認證 用來儲存存取私有 Registry 所需的認證資訊
kubernetes.io/basic-auth Basic Authentication 通常包含 usernamepassword
kubernetes.io/ssh-auth SSH 認證 使用 ssh-privatekey 儲存 SSH Private Key

今天我們主要使用 Opaque 類型,也會示範 TLS Secret 的建立與使用方式。


四、建立 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 編碼了。

https://ithelp.ithome.com.tw/upload/images/20260810/20181928xCgSrKm9fy.png

建立方式二:用 YAML 檔案建立

用 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

https://ithelp.ithome.com.tw/upload/images/20260810/201819281KySZgUPj8.png

建立 TLS 類型的 Secret

如果你有 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.crttls.key


五、將 Secret 注入 Pod — 三種常見方式

Secret 提供給 Pod 使用的方式和 ConfigMap 很相似,常見同樣有三種:

方式一:envFrom — 一次匯入所有 Key

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_

https://ithelp.ithome.com.tw/upload/images/20260810/20181928MIZRXlubD0.png

會看到 Secret 的值以明文形式出現在環境變數中(Kubernetes 會自動解碼 base64)。

方式二:env.valueFrom — 只引用指定的 Key

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_

https://ithelp.ithome.com.tw/upload/images/20260810/20181928hn9YdqdgxS.png

跟 ConfigMap 一樣,可以用不同的變數名稱。

方式三:Volume 掛載 — 將 Secret 掛載為檔案

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 使用的方式非常相似,主要差別在幾個欄位名稱:

  • configMapRefsecretRef
  • configMapKeyRefsecretKeyRef
  • configMap:secret:,並使用 secretName 指定 Secret

其他 YAML 結構大致相同。

三種注入方式比較

方式 適用場景 Secret 更新後
envFrom 將多個 Key-Value 一次匯入為環境變數 ❌ 已啟動的 Pod 不會自動更新,需重新建立 Pod
env.valueFrom 引用指定 Key,並可自訂環境變數名稱 ❌ 已啟動的 Pod 不會自動更新,需重新建立 Pod
Volume 掛載 將 Secret 內容掛載為檔案 ✅ 掛載內容會自動同步,但可能有延遲

Secret 在這三種使用方式上的更新行為,和 ConfigMap 大致相同。


六、Secret 的安全性 — 預設不夠,怎麼補強?

前面提過,Secret 中的資料雖然通常以 Base64 編碼表示,但 Base64 並不是加密。

更重要的是,Kubernetes 預設會將 Secret 以未加密的形式儲存在 etcd 中。因此,如果有人能直接存取 etcd,就可能取得其中的 Secret 資料。

要進一步保護 Secret,可以啟用 Encryption at Rest,讓 Kubernetes API Server 在資料寫入 etcd 前先進行加密。

1. 啟用 etcd 靜態加密(Encryption at Rest)

預設情況下,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

https://ithelp.ithome.com.tw/upload/images/20260810/20181928ninQTYmXzX.png

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

- name: encryption-config
  mountPath: /etc/kubernetes/encryption-config.yaml
  readOnly: true

https://ithelp.ithome.com.tw/upload/images/20260810/201819284DMRKdsAb8.png

再找到 spec.volumes,加上:

- name: encryption-config
  hostPath:
    path: /etc/kubernetes/encryption-config.yaml
    type: File

https://ithelp.ithome.com.tw/upload/images/20260810/20181928ERcku1x9ny.png

⚠️ 注意

volumeMountsvolumes 底下通常已經有許多現有的掛載設定,例如 etcd-certsca-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

https://ithelp.ithome.com.tw/upload/images/20260810/20181928SLSdROpXoA.png

如果仍然看到明文的 myvalue,表示 Encryption at Rest 尚未生效,需要回頭檢查 EncryptionConfigurationkube-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-apiserver Static Pod Manifest 前,建議先備份:

cp /etc/kubernetes/manifests/kube-apiserver.yaml ~/kube-apiserver.yaml.bak

託管 Kubernetes 服務

如果使用 EKS、GKE 等託管 Kubernetes 服務,Control Plane 與 etcd 通常由雲端平台負責管理,因此不需要、也通常無法直接修改 kube-apiserver.yaml

這類平台通常已提供靜態資料加密機制;實際的加密方式與可設定項目,則依雲端服務與叢集版本而有所不同。

2. 用 RBAC 限制 Secret 存取

問題: 如果某個 ServiceAccount 被授予過大的 Secret 權限,例如可以 getlistwatch 整個 Namespace 中的 Secret,那麼使用這個 ServiceAccount 的 Pod 就可能讀取到不屬於自己的機密資料。

尤其是 listwatch 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 來測試

https://ithelp.ithome.com.tw/upload/images/20260810/20181928MSLKl1nROp.png

💡 RBAC 的核心概念

  • Role:定義「可以對哪些資源執行哪些操作」
  • RoleBinding:定義「誰可以使用這個 Role」
  • 兩者搭配使用,就能更精確地控制資源存取權限

本章先透過 Secret 的案例簡單理解 RBAC 的用途,後續會在 RBAC 專章深入介紹 Role、ClusterRole、RoleBinding、ClusterRoleBinding 與 ServiceAccount。

3. 不要把 Secret YAML 推進 Git

這是很常見的錯誤。Secret YAML 中的 data 雖然看起來是 Base64 編碼,但 Base64 並不是加密,因此不應直接把包含機密資料的 Secret YAML 提交到 Git。

替代方案:

  • Sealed Secrets — 將 Secret 加密成 SealedSecret 後再提交到 Git,只有叢集中的 Sealed Secrets Controller 能解密
  • External Secrets Operator — 從 AWS Secrets Manager、HashiCorp Vault、Google Secret Manager 等外部機密管理服務同步 Secret 到 Kubernetes
  • SOPS — 用來加密 YAML、JSON、ENV 等設定檔,可搭配 AWS KMS、GCP KMS、Azure Key Vault、age 等金鑰管理方式

Secret 安全性檢查清單

項目 預設狀態 建議做法
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 被重建而消失。


參考資源


上一篇
Day 07|ConfigMap — 把設定從程式碼中抽離
下一篇
Day 09|PV 與 PVC — Kubernetes 如何讓「無狀態 Pod」支援「有狀態資料」
系列文
從零到 CKA:30 天掌握 Kubernetes 核心觀念與實作11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言