iT邦幫忙

2025 iThome 鐵人賽

DAY 9
0
DevOps

30 天挑戰 CKAD 認證!菜鳥的 Kubernetes 學習日記系列 第 9

【Day09】配置檔案不寫死!ConfigMap 與 Secret 管理應用程式配置

  • 分享至 

  • xImage
  •  

gh

前情提要

昨天我們看到如何用 ResourceQuotaLimitRange 來管理 Kubernetes Cluster 的資源配額。透過 ResourceQuota 控制整個 Namespace 的總資源使用量,用 LimitRange 限制單一容器的資源上下限,並為沒有設定資源的 Pod 自動分配預設值。我們也實際測試了當超過配額限制時會發生什麼,看到 Kubernetes 如何嚴格執行這些限制。

接下來我們看到如果把這些資訊都寫死在設定檔裡,不僅不安全,也很難維護。每次改個設定就要重新打包映像檔,實在太麻煩了。想想看這些實際情境:

  • 資料庫連線字串、API Keys 等資料不要 Hard Code 寫死在程式碼裡
  • 不同環境(開發、測試、生產)需要不同的設定參數
  • 應用程式的設定檔需要動態調整

所以我們今天要來看看 ConfigMapSecrets

ConfigMap 和 Secret 是什麼?

ConfigMap

ConfigMap 允許將設定檔 (配置文件) 從容器映像中解耦 (decouple),也就是分開管理,從而增強容器應用的可移值性。

將應用程式的程式碼與像是應用程式設定、環境變數等等內容分開。方便我們管理,且可以掛載到不同 Pod 內使用,達到「高內聚、低耦合」的設計目標。

高內聚,低耦合 (high cohesion、low coupling) 的意思是,物件的程式碼應該要有很高的比率只和物件內其他有關的程式碼有關聯,而對外部的程式碼,物件或元件等的關聯度要愈低愈好 (最佳的狀態是零耦合)。

Secrets

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

首先看 ConfigMap,我們可以用命令式指令 --from-literal 建立:

kubectl create configmap mydb-env-literal --from-literal=ACCOUNT=ITHOME2025 --from-literal=PASSWORD=K8sDevOps --from-literal=TZ="Asia/Taipei"

gh

接下來將變數儲存到檔案中,用命令式從檔案匯入。有兩種方式:--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 的結果一樣:

gh

使用 --from-file 建立:

kubectl create configmap mydb-file --from-file=mydb_env

這種方式會將整個檔案內容當作一個值存進去:

gh

兩種方式差異很大,連 DATA 的數量都不一樣。如果要當作環境變數掛載到 Pod,需要用 Key-Value 的方式才能正確掛載:

gh

接下來將 ConfigMap 掛載到 Deployment 裡。為了因應 CKAD 考試情境,我們先用 dry run 建立 template:

kubectl create deployment nginx --image nginx --dry-run=client -o yaml > nginx-dep.yaml

這樣可以在不建立任何物件的情況下,拿到結構相對完整的 YAML 檔:

gh

修改 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"

gh

當然也可以用 YAML 聲明式建立 ConfigMap:

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  APP_MODE: dev

gh

除了一個個設定環境變數,也可以用 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 裡面。

gh

Secrets

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(通用型):用來存放一般敏感資料,如密碼、API Keys
  • tls(TLS 憑證型):專門存放 TLS/SSL 憑證和私鑰
  • docker-registry(Docker Registry 認證型):存放私有 Registry 登入憑證

gh

我們使用 generic 類型建立:

kubectl create secret generic app-secret --from-literal=PASSWORD=password

建立完成後,可以看到 Secret 的內容:

kubectl get secrets app-secret -o yaml

取得 yaml 檔內容之後,然後把我們要的資料拿出來做 base64 的解碼就可以看到原始資料的樣子了。

gh

甚至我們只用一行指令就可以直接解碼:

kubectl get secrets app-secret -o yaml|grep PASSWORD|awk '{print $2}' | base64 --decode

這告訴我們 Kubernetes 原生的 Secrets 真的不適合拿來存放機敏資料!

ETCD Encryption at Rest (ETCD 靜態加密)

現在來看看 Kubernetes 原生支援的 ETCD 靜態加密要如何實現,可以參考官方文件:ETCD 靜態加密

ETCD 靜態加密是把「存到 etcd 裡的資料」先加密,真正入庫的是密文;但 API Server 讀出來時會自動解密、回傳明文給客戶端。首先了解 ETCD 靜態加密的工作流程:

  1. Client 請求 API Server 建立一個 Secret。
  2. kube-apiserver/etc/kubernetes/enc/enc.yaml 的 provider 設定,用 AES-CBCKMS 加密。
  3. 加密過的資料才存進 etcd。
  4. 讀取時由 kube-apiserver 自動解密,再把明文回給 Client。

只有新的寫入會被加密。舊資料會維持舊狀態,等你做「重寫(re-write)」才會轉成加密版。

  1. 了解流程後,我們先建立一個 Secret 來測試:
kubectl create secret generic my-secret --from-literal=key1=supersecret
  1. 可以看到這個 Secret 很容易就被 base64 解碼了,這就是我們要解決的問題:

gh

  1. 接下來我們要安裝 etcdctl 看 Secrets 寫入 etcd 的樣子
sudo apt-get install etcd-client
  1. 確認 etcd 安裝完成!

gh

  1. 現在用 etcdctl 直接讀取我們剛才建立的 Secret 在 etcd 中的實際存儲內容:如果我們已經啟用 etcd 靜態加密的話,這個輸出就會是密文亂碼;反之沒啟動的話會直接看到 Secrets 的明文。
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

這個指令的作用是:

  • 使用 etcd v3 API 直接讀取資料
  • 用 Kubernetes 的 etcd 憑證進行認證
  • 讀取路徑 /registry/secrets/default/my-secret(Kubernetes 在 etcd 中存儲 Secret 的路徑)
  • 用 hexdump 以十六進制和 ASCII 格式顯示內容

從下圖黃框處可以看到,資料以明文形式存儲,包含我們的敏感資料 "supersecret",這證明目前沒有啟用靜態加密:

gh

  1. 接下來建立加密配置文件 enc.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: <這裡需要是 32 bytes 的 base64 編碼金鑰>
      - identity: {}
  1. 生成 32 bytes 的隨機金鑰:
head -c 32 /dev/urandom | base64
  1. 將產生的金鑰替換到 secret 欄位中,然後將檔案移動到 /etc/kubernetes/enc 路徑:

gh

  1. 修改 API Server 配置,編輯 /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。

  1. 驗證配置生效,檢查 API Server 是否載入 encryption-provider-config 加密配置:
ps -aux | grep kube-api | grep encryption-provider-config

這個輸出顯示 API Server 進程已經包含 --encryption-provider-config 參數,表示加密配置已成功載入。

gh

  1. 建立一個新的 Secret 來測試加密是否生效,並再次用 etcdctl 檢查新 Secret 的存儲狀態:從圖中可以看到,新建立的 Secret 在 etcd 中已經變成亂碼,這表示資料已經被成功加密存儲。

gh

  1. 加密既有的 Secrets,需要手動觸發重新寫入來套用加密:
kubectl get secrets --all-namespaces -o json | kubectl replace -f -

這個指令會:

  1. 取得所有 Namespace 中的所有 Secret
  2. 以 JSON 格式輸出
  3. 重新套用這些 Secret,觸發 API Server 用新的加密設定重新寫入

執行後再次檢查原本的 Secret,可以看到原本以明文存儲的 Secret 現在也變成加密格式了!

gh

總結

今天我們學會了 ConfigMap 和 Secret 如何幫助我們管理應用程式的配置資料。透過 ConfigMap,我們可以將應用程式的運行參數從程式碼中抽離出來,讓程式更靈活地在不同環境中重複使用。

Secret 提供了集中管理敏感資訊的方式,雖然它本質上只是 base64 編碼,但搭配適當的權限控制和安全措施,仍然比寫死在程式碼裡安全得多。我們也實作了 ETCD 靜態加密的設定過程,看到如何在 etcd 層面加強資料保護。不過最重要的領悟是:Kubernetes 原生的 Secret 機制只是基礎建設,真正的安全需要搭配 RBAC 權限控制、網路隔離,以及考慮使用第三方密鑰管理工具來增強安全性。

到目前為止,我們已經學會了如何建立和管理 Pod、控制副本數量、分配資源、管理配置資料等等。但還有一個重要問題:這些 Pod 要如何對外提供服務?其他 Pod 或外部使用者要怎麼存取這些應用程式?

明天我們要看看 Service,看看 Kubernetes 如何讓我們的應用程式真正能被其他人使用!

下一篇文章:服務要曝光:Service 入門實戰


上一篇
【Day08】叢集資源怎麼分?ResourceQuota 和 LimitRange 配額管理實戰
下一篇
【Day10】服務要曝光:Service 入門實戰
系列文
30 天挑戰 CKAD 認證!菜鳥的 Kubernetes 學習日記11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言