環境變數雖然使用方便,但並非所有情境的最佳解。對於複雜組態,環境變數的管理會變得繁瑣,且無法動態更新。Kubernetes 提供的 ConfigMap 和 Secret 能有效分離組態與程式碼,提升應用程式可移植性和安全性。ConfigMap 適用於一般組態,而 Secret 則用於敏感資訊的加密儲存。兩者皆可作為環境變數或掛載至 Pod 磁碟區使用,並支援熱更新。然而,環境變數在程式啟動後無法更改,更新的 ConfigMap 值需要重新啟動 Pod 才會生效。對於大型組態資料,則可以考慮使用不可變組態模式,將組態資料封裝成容器映像,並透過 Init Container 或 Docker Volume 等方式掛載到應用程式容器中,確保組態資料的版本控制和一致性。
環境變數的應用與限制
環境變數是組態應用程式的常見方式,它們易於使用且被廣泛支援。然而,在使用環境變數時,也需要注意其限制和潛在問題。
預設值的困擾
預設值可以簡化組態過程,但也可能成為維護的負擔。更改預設值不僅需要更新程式碼,還可能影響依賴預設值的使用者。因此,在決定使用預設值時,必須謹慎評估其長期影響。如果無法確定一個合理的預設值,考慮移除預設值並強制使用者提供組態值可能是一個更好的選擇。
程式碼範例:移除預設值
// 不使用預設值,強制使用者提供組態
public class Config {
private String databaseUrl;
public Config(String databaseUrl) {
if (databaseUrl == null || databaseUrl.isEmpty()) {
throw new IllegalArgumentException("Database URL is required");
}
this.databaseUrl = databaseUrl;
}
}
內容解密:
- 強制使用者提供
databaseUrl,避免使用預設值。 - 在建構子中檢查
databaseUrl是否為空,若為空則丟擲例外。 - 這種做法確保了組態的明確性和安全性。
環境變數的高階用法
除了直接設定環境變數外,還可以使用 envFrom 匯入 Secrets 或 ConfigMaps 中的所有值。此外,還可以使用 downward API 和依賴變數來增強環境變數的功能。
程式碼範例:使用依賴變數
apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
env:
- name: PORT
value: "8181"
- name: IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: MY_URL
value: "https://$(IP):$(PORT)"
內容解密:
- 定義
PORT和IP環境變數。 - 使用
valueFrom和fieldRef取得 Pod 的 IP 地址。 - 使用依賴變數
$(IP)和$(PORT)組成MY_URL。 - 這種方式允許動態生成組態值。
環境變數的限制
儘管環境變數易於使用,但它們並不適合複雜的組態需求。當組態引數眾多時,管理環境變數會變得困難。此外,環境變數無法在應用程式執行時動態更改,這既是優點也是缺點。
圖表說明:環境變數的適用場景
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam rectangleBackgroundColor #F5F5F5
skinparam rectangleBorderColor #333333
skinparam arrowColor #333333
title 圖表說明:環境變數的適用場景
rectangle "適合" as node1
rectangle "不適合" as node2
node1 --> node2
@enduml
此圖示說明瞭環境變數的適用場景。
Kubernetes 組態管理:ConfigMap 與 Secret 的應用
Kubernetes 提供兩種原生組態資源:ConfigMap 和 Secret,分別用於儲存一般組態資料和敏感資訊。這兩種資源允許將組態生命週期與應用生命週期分離,從而提高應用的可移植性和安全性。
組態管理的挑戰
在 EnvVar 組態模式中(詳見第 19 章),環境變數的定義可能分散在多個地方,這使得查詢和管理變得困難。此外,當環境變數被多個資源定義覆寫時,其真實值可能難以確定。例如,在 OCI 映像中定義的環境變數可能會在 Kubernetes Deployment 資源中被替換。
ConfigMap 與 Secret 的優勢
Kubernetes 的 ConfigMap 和 Secret 資源提供比純環境變數更靈活的組態管理方式。兩者都以鍵值對的形式儲存資料,主要區別在於 Secret 使用 Base64 編碼儲存敏感資料。
使用 ConfigMap 和 Secret
一旦建立了 ConfigMap 或 Secret,就可以透過兩種方式使用其鍵值:
- 環境變數參考:將鍵名作為環境變數名稱。
- 掛載到 Pod 的磁碟區:將鍵名作為檔案名。
當 ConfigMap 被掛載到磁碟區時,其內容會在 ConfigMap 更新後自動更新(透過 Kubernetes API)。如果應用支援熱過載組態檔案,則可以立即受益於更新。然而,當 ConfigMap 條目用作環境變數時,由於環境變數在程式啟動後無法更改,因此更新不會反映在環境變數中。
ConfigMap 的使用範例
以下範例展示瞭如何建立和使用 ConfigMap。由於 Secret 的使用方式與 ConfigMap 類別似,因此這些範例也適用於 Secret,唯一的區別是 Secret 的值需要進行 Base64 編碼。
建立 ConfigMap
ConfigMap 資源包含鍵值對,如下所示:
apiVersion: v1
kind: ConfigMap
metadata:
name: random-generator-config
data:
PATTERN: "Configuration Resource"
application.properties: |
# Random Generator config
log.file=/tmp/generator.log
server.port=7070
EXTRA_OPTIONS: "high-secure,native"
SEED: "432576345"
使用 kubectl 建立 ConfigMap
除了手動建立 ConfigMap 資源描述符外,也可以使用 kubectl 命令建立 ConfigMap,如下所示:
kubectl create cm spring-boot-config \
--from-literal=PATTERN="Configuration Resource" \
--from-literal=EXTRA_OPTIONS="high-secure,native" \
--from-literal=SEED="432576345" \
--from-file=application.properties
在 Pod 中參照 ConfigMap
可以在 Pod 的定義中參照 ConfigMap,如下所示:
apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
containers:
- env:
- name: PATTERN
valueFrom:
configMapKeyRef:
name: random-generator-config
key: PATTERN
將 ConfigMap 中的所有條目設為環境變數
可以使用 envFrom 將 ConfigMap 中的所有條目設為環境變數,並可選擇新增字首,如下所示:
apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
containers:
envFrom:
- configMapRef:
name: random-generator-config
prefix: CONFIG_
將 ConfigMap 用作磁碟區
也可以將 ConfigMap 的內容掛載到 Pod 的磁碟區中,其鍵名將用作檔案名,如下所示:
apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
containers:
- volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: random-generator-config
內容解密:
此範例展示瞭如何將 ConfigMap 的內容掛載到 Pod 的 /etc/config 目錄下。ConfigMap 中的每個鍵值對將被建立為一個檔案,鍵名作為檔案名,鍵值作為檔案內容。這種方式使得應用可以透過讀取檔案來取得組態資訊。
Kubernetes 中的 ConfigMap 與 Secret:組態管理的基礎
Kubernetes 提供了兩種重要的資源物件:ConfigMap 和 Secret,用於管理和分發組態資訊和敏感資料。本文將探討這兩種資源的用法、特點及安全性考量。
ConfigMap:組態資料的管理
ConfigMap 是一種 Kubernetes 資源,用於將組態資料與應用程式碼分離,使得組態管理更加靈活和方便。ConfigMap 可以透過多種方式掛載到 Pod 中,例如作為環境變數或作為 Volume 掛載。
將 ConfigMap 掛載為 Volume
將 ConfigMap 掛載為 Volume 時,ConfigMap 中的每個 key-value 對將被建立為一個檔案,key 成為檔名,value 成為檔案內容。例如:
apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
volumeMounts:
- name: config-volume
mountPath: /config
volumes:
- name: config-volume
configMap:
name: random-generator-config
細粒度控制 ConfigMap 的掛載
可以透過 items 欄位來選擇性地掛載 ConfigMap 中的特定 key,並指設定檔名和許可權。例如:
apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
volumeMounts:
- name: config-volume
mountPath: /config
volumes:
- name: config-volume
configMap:
name: random-generator-config
items:
- key: application.properties
path: spring/myapp.properties
mode: 0400
ConfigMap 的熱更新
當 ConfigMap 被更新時,掛載該 ConfigMap 的 Volume 也會被更新,應用程式可以監控這些檔案的變化並即時更新組態。
Secret:敏感資料的管理
Secret 用於儲存敏感資料,如密碼、Token 等。Secret 的資料會被 Base64 編碼,但這並不意味著它是安全的。Kubernetes 透過多種機制來保護 Secret 的安全,例如:
- Secret 只會被分發到需要它的 Pod 所在的節點。
- 在節點上,Secret 儲存在記憶體中的 tmpfs,不會寫入物理儲存。
- 在 etcd 中,Secret 可以被加密儲存。
Secret 的安全性
儘管 Kubernetes 對 Secret 提供了多層保護,但仍然存在風險。例如,具有 Pod 建立許可權的使用者可以透過建立 Pod 來存取 Secret。因此,建議對敏感資料進行額外的加密處理。
不可變的 Secret 和 ConfigMap
從 Kubernetes 1.21 版本開始,支援將 ConfigMap 和 Secret 設定為不可變(immutable),這樣可以防止它們被更新,並提高叢集的效能。例如:
apiVersion: v1
kind: Secret
metadata:
name: random-config
data:
user: cm9sYW5k
immutable: true
不可變組態(Immutable Configuration)
不可變組態模式提供兩種方法,使組態資料不可變,以確保應用程式的組態始終處於已知且已記錄的狀態。透過這種模式,我們不僅可以使用不可變和版本化的組態資料,還能克服儲存在環境變數或 ConfigMaps 中的組態資料的大小限制。
問題
正如你在第19章“環境變陣列態”中所看到的,環境變數提供了一種簡單的方法來組態根據容器的應用程式。雖然它們易於使用且得到普遍支援,但一旦環境變數的數量超過某個閾值,管理它們就會變得困難。
這種複雜性可以透過使用組態資源(Configuration Resources)來一定程度地處理,如第20章“組態資源”所述。自 Kubernetes 1.21 起,組態資源可以被宣告為不可變的。然而,ConfigMaps 仍然有大小限制,因此,如果你處理大型組態資料(如機器學習環境中的預計算資料模型),則即使標記為不可變,ConfigMaps 也不適合。
此處的不可變性意味著我們在應用程式啟動後無法更改組態,以確保我們的組態資料始終處於明確定義的狀態。此外,不可變的組態可以置於版本控制之下,並遵循變更控制流程。
解決方案
有多種選項可以解決組態不可變性的問題。最簡單和首選的選項是使用在其宣告中標記為不可變的 ConfigMaps 或 Secrets。你在第20章中瞭解了不可變的 ConfigMaps。如果你的組態適合 ConfigMap 且易於維護,則 ConfigMaps 應該是首選。在現實場景中,組態資料的數量可能會迅速增加。雖然 WildFly 應用程式伺服器組態可能仍適合 ConfigMap,但它相當龐大。當你必須在 YAML 中巢狀 XML 或 YAML 時(即當你的組態內容也是 YAML 且你將其嵌入 ConfigMaps YAML 部分時),情況會變得非常糟糕。此類別使用案例的編輯器支援有限,因此你必須非常小心縮排,即使這樣,你也可能會多次搞砸(相信我們!)。另一個噩夢是必須在單個 ConfigMap 中維護數十或數百個條目,因為你的應用程式需要許多不同的組態檔案。雖然這種痛苦可以透過良好的工具來一定程度地緩解,但像預訓練的機器學習資料模型這樣的大型組態資料集由於後端大小限制為 1 MB,因此無法使用 ConfigMap。
為瞭解決複雜組態資料的問題,我們可以將所有與環境相關的組態資料放入單個被動資料映像中,我們可以像常規容器映像一樣分發它。在執行時,應用程式和資料映像被連結在一起,以便應用程式可以從資料映像中提取組態。透過這種方法,很容易為各種環境製作不同的組態資料映像。這些映像然後結合了特定環境的所有組態資訊,並且可以像其他容器映像一樣進行版本控制。
建立這樣的資料映像是微不足道的,因為它是一個只包含資料的簡單容器映像。挑戰在於啟動時的連結步驟。我們可以根據平台使用各種方法。
Docker Volumes
在考慮 Kubernetes 之前,讓我們退一步考慮 vanilla Docker 的情況。在 Docker 中,容器可以公開包含來自容器的資料的卷。透過 Dockerfile 中的 VOLUME 指令,你可以指定稍後可以分享的目錄。在啟動時,此目錄內的內容會被複制到此分享目錄。如圖 21-1 所示,這種卷連結是從專用的組態容器與另一個應用程式容器分享組態資訊的好方法。
圖 21-1. 使用 Docker 卷進行不可變組態
讓我們來看一個例子。對於開發環境,我們建立了一個 Docker 映像,它儲存了開發者組態並建立了一個掛載點為 /config 的卷。我們可以使用 Dockerfile-config 建立這樣的映像,如範例 21-1 所示。
FROM scratch
ADD app-dev.properties /config/app.properties
VOLUME /config
程式碼解析:
FROM scratch:使用空的基礎映像。ADD app-dev.properties /config/app.properties:將app-dev.properties檔案新增到/config目錄並命名為app.properties。VOLUME /config:宣告/config目錄為卷,用於資料持久化和分享。
現在,我們使用 Docker CLI 建立映像本身和 Docker 容器,如範例 21-2 所示。
docker build -t k8spatterns/config-dev-image:1.0.1 -f Dockerfile-config .
docker create --name config-dev k8spatterns/config-dev-image:1.0.1 .
程式碼解析:
docker build -t k8spatterns/config-dev-image:1.0.1 -f Dockerfile-config .:根據Dockerfile-config構建一個名為k8spatterns/config-dev-image:1.0.1的 Docker 映像。docker create --name config-dev k8spatterns/config-dev-image:1.0.1 .:建立一個名為config-dev的容器例項,但不啟動它。
最後一步是啟動應用程式容器並將其連線到此組態容器(範例 21-3)。
docker run --volumes-from config-dev k8spatterns/welcome-servlet:1.0
程式碼解析:
docker run --volumes-from config-dev k8spatterns/welcome-servlet:1.0:啟動k8spatterns/welcome-servlet:1.0容器,並掛載config-dev容器的卷。
當你將此應用程式從開發環境遷移到生產環境時,你只需更改啟動命令。無需更改應用程式映像本身。相反,你只需將應用程式容器與生產組態容器進行卷連結,如範例 21-4 所示。
docker build -t k8spatterns/config-prod-image:1.0.1 -f Dockerfile-config-prod .
docker create --name config-prod k8spatterns/config-prod-image:1.0.1 .
docker run --volumes-from config-prod k8spatterns/welcome-servlet:1.0
程式碼解析:
與開發環境類別似,但使用不同的組態檔案和容器名稱,以適應生產環境。
Kubernetes Init Containers
在 Kubernetes 中,Pod 內的卷分享非常適合這種組態和應用程式容器的連結。然而,如果我們想將 Docker 卷連結的技術轉移到 Kubernetes 世界中,我們會發現目前 Kubernetes 中尚不支援容器卷。考慮到討論的時間和實作此功能的複雜性與其有限的好處,容器卷很可能不會很快到來。
因此,容器可以分享(外部)卷,但它們尚不能直接分享位於容器內的目錄。要在 Kubernetes 中使用不可變的組態容器,我們可以使用第15章中的 Init Containers 模式,該模式可以在啟動期間初始化一個空的分享卷。