「組態範本」模式讓我們能在應用啟動期間建立並處理龐大而複雜的組態。產生出來的組態,會依處理範本時所用的參數,對應到目標執行環境。
問題#
ConfigMap 與 Secret 可以用來設定應用,但有時組態檔會變得又大又複雜,此時直接把組態檔塞進 ConfigMap 就會出問題:
- 必須正確地嵌入資源定義,要小心引號這類特殊字元,避免破壞 Kubernetes 資源語法。
- 大小限制:ConfigMap 或 Secret 所有值的總和上限為 1 MB(由底層後端儲存 Etcd 施加)。
更關鍵的是:大型組態檔在不同執行環境之間通常只有些微差異。 這種相似性導致 ConfigMap 裡出現大量重複與冗餘,因為每個環境的資料幾乎都一樣。
解法#
要減少重複,合理的做法是只把有差異的組態值(例如資料庫連線參數)存進 ConfigMap、甚至直接放在環境變數裡。容器啟動時,再用組態範本處理這些值,產生完整的組態檔(例如 JBoss WildFly 的 standalone.xml)。應用啟動前,處理完成的組態檔會被放到一個位置,之後就能像任何其他組態檔一樣直接使用。
有許多工具可在應用初始化期間處理範本,例如 Tiller(Ruby)與 Gomplate(Go)。

圖 21-1:組態範本——以來自環境變數或掛載 volume(可能由 ConfigMap 支撐)的資料填入
執行期進行這種即時處理有兩種技術:
- 把範本處理器放進 Dockerfile 的
ENTRYPOINT,讓範本處理直接成為容器映像檔的一部分。這裡的進入點通常是一個腳本:先做範本處理,再啟動應用;範本參數來自環境變數。 - 用 Pod 的 Init Container 執行範本處理器,為 Pod 中的應用容器產生組態。
對 Kubernetes 而言,Init Container 的做法最吸引人,因為我們可以直接用 ConfigMap 作為範本參數。
運作結構#
應用的 Pod 定義至少包含兩個容器:一個負責範本處理的 init container,以及應用容器。init container 不只裝著範本處理器,也裝著組態範本本身。 此外還定義兩個 volume:
- 一個由 ConfigMap 支撐的 volume,存放範本參數。
- 一個
emptyDirvolume,用來在 init container 與應用容器之間共享處理完的範本。
Pod 啟動時的步驟為:
- init container 啟動並執行範本處理器。處理器從自己的映像檔取得範本、從掛載的 ConfigMap volume 取得範本參數,把結果存進
emptyDirvolume。 - init container 結束後,應用容器啟動,並從
emptyDirvolume 載入組態檔。
完整範例:WildFly 兩環境組態#
以下範例用 init container 管理 WildFly 在開發與正式兩個環境的完整組態檔。兩者極為相似,只在日誌方式上有差別:每行日誌分別以 DEVELOPMENT: 或 PRODUCTION: 為前綴。
日誌樣式存放在 standalone.xml 中,我們用 Go template 語法把它參數化:
<formatter name="COLOR-PATTERN">
<pattern-formatter pattern="{{(datasource "config").logFormat}}"/>
</formatter>這裡使用 Gomplate 作為範本處理器,它以「資料來源(data source)」的概念來引用待填入的範本參數。本例中,資料來源來自掛載到 init container 的 ConfigMap volume,其中包含一個鍵 logFormat,實際格式即由此取出。
接著建立 init container 的 Docker 映像檔,Dockerfile 非常簡單:
FROM k8spatterns/gomplate
COPY in /in基礎映像檔 k8spatterns/gomplate 包含範本處理器與一個進入點腳本,預設使用以下目錄:
/in:存放 WildFly 組態範本,包含參數化的standalone.xml,直接加入映像檔中。/params:查找 Gomplate 資料來源(YAML 檔案)的位置,由 ConfigMap 支撐的 Pod volume 掛載而來。/out:處理完的檔案存放目錄,會被掛載到 WildFly 應用容器中作為組態使用。
第二項材料是存放參數的 ConfigMap,本例只用一個簡單的鍵值檔案:
logFormat: "DEVELOPMENT: %-5p %s%e%n"名為 wildfly-parameters 的 ConfigMap 以鍵 config.yml 引用這份 YAML 資料,由 init container 取用。最後是 WildFly 伺服器的 Deployment 資源:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
example: cm-template
name: wildfly-cm-template
spec:
replicas: 1
template:
metadata:
labels:
example: cm-template
spec:
initContainers:
# 存放組態範本的映像檔
- image: k8spatterns/example-config-cm-template-init
name: init
volumeMounts:
- mountPath: "/params" # 參數由 ConfigMap wildfly-parameters 掛載而來
name: wildfly-parameters
- mountPath: "/out" # 寫出處理後範本的目標目錄,由空 volume 掛載
name: wildfly-config
containers:
- image: jboss/wildfly:10.1.0.Final
name: server
command:
- "/opt/jboss/wildfly/bin/standalone.sh"
- "-Djboss.server.config.dir=/config"
ports:
- containerPort: 8080
name: http
protocol: TCP
volumeMounts:
- mountPath: "/config" # 存放產生之完整組態檔的目錄掛載為 /config
name: wildfly-config
volumes:
# 參數用的 ConfigMap,以及用於共享處理後組態的空目錄
- name: wildfly-parameters
configMap:
name: wildfly-parameters
- name: wildfly-config
emptyDir: {}啟動這個 Deployment 時會發生:
- init container 被建立並執行其指令:從 ConfigMap volume 取得
config.yml,填入/in目錄中的範本,把處理後的檔案存到/out(也就是wildfly-configvolume 掛載的位置)。 - init container 完成後,WildFly 伺服器啟動,並以參數指示它從
/config目錄讀取完整組態——/config正是那個共享 volumewildfly-config。
關鍵在於:從開發切換到正式環境時,完全不必更動這些 Deployment 資源描述檔,只有存放範本參數的 ConfigMap 不同。
有了這個技巧,就能輕鬆做出符合 DRY(Don’t Repeat Yourself)原則的組態,不必複製與維護重複的大型組態檔。例如當 WildFly 組態要為所有環境做調整時,只需更新 init container 中的單一範本檔,維護上有顯著優勢,也不再有組態漂移(configuration drift)的風險。
使用 Pod 與 volume 時,出問題不容易除錯。若你想檢視處理後的範本,可以查看節點上的
/var/lib/kubelet/pods/{podid}/volumes/kubernetes.io~empty-dir/目錄,它包含emptyDirvolume 的內容。Pod 執行中時kubectl exec進去,檢查此目錄中產生的檔案即可。
討論#
組態範本模式建立在「組態資源」之上,特別適合「需要在多個環境中操作、且組態複雜相似」的應用。
但組態範本的配置較複雜,可動部件更多、更容易出錯。只在你的應用確實需要龐大組態資料時才用它。
這類應用通常需要相當大量的組態資料,其中卻只有一小部分與環境相關。即使一開始把整份組態直接複製到各環境專屬的 ConfigMap 行得通,它終究會隨時間分歧,為維護帶來負擔。在這種情況下,範本做法就是完美解。
更多資訊#
- Configuration Template Example
- Tiller Template Engine
- Gomplate
- Go Template Syntax