iT邦幫忙

2026 iThome 鐵人賽

DAY 11
0
Kubernetes

Kubernetes學習心得分享系列 第 11

Day 11:【配置】 環境變數抽離:ConfigMaps 與 Secrets 應用

  • 分享至 

  • xImage
  •  

前言

寫這篇想要分享的重點:
Kubernetes 提供 ConfigMap 與 Secret 兩種資源物件來實現將設定與應用程式分離。本篇除了介紹如何將設定注入容器外,更會探討 etcd 中的資安問題,以及如何啟用 Encryption at Rest 加密機制。

這篇想要講什麼:

  1. ConfigMap 與 Secret 的用途與三種注入方式(環境變數、單一變數、Volume 掛載)。
  2. Base64 編碼的誤區:Secret 預設不是加密,只是編碼!
  3. etcd Encryption at Rest:如何配置 EncryptionConfiguration 為 etcd 中的敏感資料提供加密。

為何要寫這篇:
許多人以為將密碼存進 Secret 就萬無一失,卻不知 etcd 預設是以明文(Plaintext)儲存 Base64 解碼後的內容。搞懂 Secret 的注入與底層 etcd 加密方式,讓資安更穩固。

名詞對應

  • Encryption at Rest: 靜態資料加密

為何會需要 ConfigMap & Secret?

在開發或容器化的早期階段,工程師常將資料庫密碼、API Key 或設定檔直接Hardcode在程式碼中,或是寫進 Docker Image。這樣做會帶來三個問題:

  • 安全風險極高:只要原始碼庫(如 GitHub)權限外洩,所有的敏感密碼與私鑰就會一覽無遺。
  • 部署缺乏彈性:當環境切換(例如從 Testing 切換至 Production)或修改參數時,必須重新編譯程式碼、重新 Build Image 並重新 Push 到 Image Registry,耗時且容易出錯。
  • 缺乏權限控管:開發人員與維護基礎設施的 DevOps 團隊職責無法分離,開發者可能會過問不該知道的生產環境密碼。

解法:解耦 (Decoupling)

Kubernetes 導入 ConfigMapSecret 的目的,就是為了實現「環境設定與應用程式碼完全解耦」:

+------------------------+      +-----------------------------------------+
|     應用程式 Image      |  +   |         K8s 組態資源 (環境解耦)            |
| (只保留純粹的商業邏輯)    |      | - ConfigMap: 非敏感設定 (如 LOG_LEVEL)    |
+-----------+------------+      | - Secret:    敏感資料   (如 DB_PASS)      |
            |                   +--------------------+--------------------+
            |                                        |
            +-------------------+--------------------+
                                |
                                v
                    +-----------------------+
                    |   運作中的 Pod 容器    |
                    +-----------------------+

  • ConfigMap:專門用於儲存一般非敏感設定(如資料庫連線 URL、LOG_LEVEL、開關參數)。
  • Secret:專門用於儲存敏感機密資料(如資料庫密碼、API Token、TLS 數位憑證),並在叢集中提供基礎的加密儲存與存取控管。

透過這套機制,你可以一個 Image 跑遍所有環境——只需在不同的 K8s 叢集中注入相對應的 ConfigMap 與 Secret,即可改變部署狀態,同時確保敏感資訊不外洩。

Secret 的 Base64 編碼建立

建立 Secret YAML 時,內容必須轉為 Base64 字串

echo -n 'my-db-password' | base64
# 輸出:bXktZGItcGFzc3dvcmQ=
apiVersion: v1
kind: Secret
metadata:
  name: db-secret
type: Opaque
data:
  DB_PASS: bXktZGItcGFzc3dvcmQ=

將 ConfigMap / Secret 注入 Pod 的三種方式

1. 全數轉為環境變數 (envFrom)

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

2. 單一環境變數注入 (valueFrom)

假設團隊維護一個 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            # 拿到密碼

3. 透過 Volume 掛載為檔案 (volumeMounts)

將 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

資安:Secret 預設未在 etcd 中加密

Base64 只是編碼 (Encoding),絕對不是加密 (Encryption)!

如果有人擁有 etcd 存取權限或拿到 etcd 備份檔,只需要下指令 etcdctl get /registry/secrets/...,就能直接以明文閱讀全叢集所有的 Secret 密碼!


etcd 加密設定:Encryption at Rest

要實現真正安全的加密儲存,必須啟用 K8s API Server 的 Encryption at Rest 功能。

加密設定步驟

1. 建立 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) 用於讀取過渡期資料

2. 修改 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

3. 重新加密現有的 Secret

啟用加密設定後,只有新建立的 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 側車容器的協同工作原理與生命週期!敬請期待!

Takeaway

  • 解耦設定檔:ConfigMap 用於一般設定,Secret 用於敏感資料;皆可透過環境變數(envFrom / valueFrom)或 Volume 檔案掛載注入容器中。
  • Base64 迷思:Secret 預設僅進行 Base64 編碼,絕非加密;存取 etcd 的任何人都可直接還原為明文。
  • Encryption at Rest 靜態加密:需在 kube-apiserver 配置 EncryptionConfiguration,採用 AES-CBC 等加密演算法將寫入 etcd 的 Secret 進行底層加密。
  • 新舊資料加密轉移:啟用加密機制後僅對新建 Secret 生效,必須執行全量更新(replace)才能將舊有 Secret 轉為加密儲存。

上一篇
Day 10:【系統】 應用程式升級策略與無縫回滾 (Deployment Rolling Update & Rollback)
下一篇
Day 12:【架構】多容器協作設計模式 (Multi-Container Pods)
系列文
Kubernetes學習心得分享12
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言