K8s · 設定與儲存 · 第 25 課 · · 10min read

Secret 教學:K8s 怎麼存密碼才不會明文洩漏?

ConfigMap 是明文,密碼放進去等於放在街上。Secret 用 base64 編碼存(不是加密),配 RBAC 控誰能看,才是正解。這篇實作 MySQL 密碼用 Secret 注入。

#Kubernetes #Secret #密碼 #RBAC #base64
章節目錄 · 12

ConfigMap 的安全死角

上一篇 ConfigMap 漂亮地解決了「設定寫死」問題。但隨手做個實驗:

$ kubectl create configmap db-config \
    --from-literal=DB_PASSWORD=my-secret-pw

$ kubectl get configmap db-config -o yaml
apiVersion: v1
data:
DB_PASSWORD: my-secret-pw # ← 明文!
kind: ConfigMap

任何能跑 kubectl 的人都看得到密碼。如果這份 YAML 還 commit 到 Git,全世界都看得到。

ConfigMap 只能放非敏感設定。密碼 / API Key / 憑證要用 Secret

Secret 跟 ConfigMap 哪裡不一樣?

短答:用法幾乎一樣,但有兩個關鍵差別

項目ConfigMapSecret
儲存格式明文Base64 編碼
kubectl describe直接顯示值只顯示大小
用途一般設定密碼、API Key、TLS
RBAC 預設不嚴格嚴格控管

建立 Secret

最快的方式:

$ kubectl create secret generic db-cred \
    --from-literal=username=admin \
    --from-literal=password=my-secret-pw

注意 generic 對應 type: Opaque(最常用的通用型)。

$ kubectl describe secret db-cred
Type:  Opaque
Data
====
password:  12 bytes        # ← 不顯示值
username:  5 bytes

跟 ConfigMap 不一樣,describe 不會直接秀值。算是一道防線(防止操作失誤把密碼貼上 Slack)。

⚠️ Base64 ≠ 加密

新手最容易誤會的地方。試一下:

$ kubectl get secret db-cred -o yaml
apiVersion: v1
data:
  password: bXktc2VjcmV0LXB3
  username: YWRtaW4=
kind: Secret

# 隨便挑一個 decode
$ echo "bXktc2VjcmV0LXB3" | base64 -d
my-secret-pw # ← 馬上還原

Base64 只是編碼(換個格式呈現),不是加密。任何人拿到 Secret 都能 decode。

那 Secret 的安全靠什麼?RBAC(Role-Based Access Control)— 控制誰能 kubectl get secret

RBAC:誰能讀 Secret?

預設情況,誰只要能跑 kubectl 對著叢集,就能讀所有 Secret。這是不對的

正式環境的做法:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: production
  name: secret-reader
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    resourceNames: ["db-cred"]    # ← 只允許讀這個 Secret
    verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: alice-secret-reader
  namespace: production
subjects:
  - kind: User
    name: alice
roleRef:
  kind: Role
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io

Alice 在 production namespace 只能讀 db-cred 這一個 Secret,其他 Secret 連列表都看不到。

RBAC 細節到 第七堂的 RBAC 篇展開,這裡先知道:Secret 的安全靠的不是 Base64,是 RBAC

Secret 三種類型

$ kubectl get secret -A
NAME              TYPE
db-cred           Opaque
default-token-x   kubernetes.io/service-account-token
myapp-tls         kubernetes.io/tls
docker-pull       kubernetes.io/dockerconfigjson
類型用途怎麼建
Opaque通用(密碼、API Key)kubectl create secret generic
kubernetes.io/tlsTLS 憑證(Ingress HTTPS)kubectl create secret tls
kubernetes.io/dockerconfigjson拉私有 Image 的帳密kubectl create secret docker-registry
90% 的場景用 Opaque。tls 在 Ingress + TLS 篇用過。

注入到 Pod:跟 ConfigMap 一樣

語法幾乎相同,只是把 configMapRef 換成 secretRefconfigMapKeyRef 換成 secretKeyRef

envFrom:整包注入

spec:
  containers:
    - name: app
      image: yanchen184/k8s-demo-app:latest
      envFrom:
        - configMapRef:
            name: app-config       # ← 一般設定
        - secretRef:
            name: db-cred          # ← 密碼

env.valueFrom:單一 key

env:
  - name: DB_PASSWORD
    valueFrom:
      secretKeyRef:
        name: db-cred
        key: password

MySQL 範例:完整配 Secret

apiVersion: v1
kind: Secret
metadata:
  name: mysql-cred
type: Opaque
stringData:                       # ← 用 stringData 不用自己 base64
  MYSQL_ROOT_PASSWORD: my-secret-pw
  MYSQL_USER: appuser
  MYSQL_PASSWORD: app-pw
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
        - name: mysql
          image: mysql:8.0
          envFrom:
            - secretRef:
                name: mysql-cred  # ← 帳密整包進來
          ports:
            - containerPort: 3306
💡 stringData 是寫 YAML 時的便利,K8s 收到後會自動轉成 data 的 base64。手寫時用這個比較直覺,不用自己 echo -n 'xxx' | base64

驗證:

$ kubectl apply -f mysql-secret.yaml
$ kubectl exec -it deploy/mysql -- mysql -u appuser -p
Enter password: app-pw
mysql>

⚠️ 不要把 Secret YAML commit 到 Git

stringData: my-secret-pw 直接 commit 上去 = 全世界都看得到密碼。

正式環境兩個做法:

1. Sealed Secret(推薦)

Sealed Secret 用 RSA 把 Secret 加密成 SealedSecret只有叢集裡的 controller 能解密

$ kubeseal -f mysql-secret.yaml -o yaml > mysql-sealed.yaml

mysql-sealed.yaml 裡面是加密過的字串,安全 commit 到 Git。部署時 controller 自動解密成真正的 Secret。

2. 外部 Secret 管理(HashiCorp Vault / AWS Secrets Manager)

把密碼存在專業的密碼管理服務,K8s 用 External Secrets Operator 同步進來。適合大型組織。

對照 Docker

DockerKubernetes
-e DB_PASSWORD=xxxSecret + envFrom
docker login 個人 .docker/config.jsonkubernetes.io/dockerconfigjson
TLS 憑證手動放容器kubernetes.io/tls
Vault / dotenv 放本機Sealed Secret 上 Git
Docker 的密碼管理普遍是「想辦法不要 commit .env」。K8s 把這件事做成資源類型,更結構化。

重點整理

  • 密碼用 Secret 不是 ConfigMap,至少 describe 不會直接秀值
  • Base64 不是加密,安全靠 RBAC
  • 用法幾乎跟 ConfigMap 一樣(換 secretRef / secretKeyRef
  • 寫 YAML 用 stringData 比較方便
  • Secret YAML 不要 直接 commit 到 Git,用 Sealed Secret

下一步

ConfigMap + Secret 都會了。但工作上常常兩個一起用 — Ingress + ConfigMap 設置 nginx config + Secret 放憑證,整個串起來。

下一篇實戰:Ingress + ConfigMap + Secret 整合實作

📅 下一篇Ingress + ConfigMap + Secret 整合實作
把這篇學的 Secret 跟 ConfigMap、Ingress 三者串起來——一條真實微服務的設定組合。
📚 完整系列總覽K8s 系列教學首頁(共 40 課,按學習路徑順序排)