「組態範本」模式讓我們能在應用啟動期間建立並處理龐大而複雜的組態。產生出來的組態,會依處理範本時所用的參數,對應到目標執行環境。

問題#

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,存放範本參數。
  • 一個 emptyDir volume,用來在 init container 與應用容器之間共享處理完的範本。

Pod 啟動時的步驟為:

  1. init container 啟動並執行範本處理器。處理器從自己的映像檔取得範本、從掛載的 ConfigMap volume 取得範本參數,把結果存進 emptyDir volume。
  2. init container 結束後,應用容器啟動,並從 emptyDir volume 載入組態檔。

完整範例: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 時會發生:

  1. init container 被建立並執行其指令:從 ConfigMap volume 取得 config.yml,填入 /in 目錄中的範本,把處理後的檔案存到 /out(也就是 wildfly-config volume 掛載的位置)。
  2. init container 完成後,WildFly 伺服器啟動,並以參數指示它從 /config 目錄讀取完整組態——/config 正是那個共享 volume wildfly-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/ 目錄,它包含 emptyDir volume 的內容。Pod 執行中時 kubectl exec 進去,檢查此目錄中產生的檔案即可。

討論#

組態範本模式建立在「組態資源」之上,特別適合「需要在多個環境中操作、且組態複雜相似」的應用

但組態範本的配置較複雜,可動部件更多、更容易出錯。只在你的應用確實需要龐大組態資料時才用它。

這類應用通常需要相當大量的組態資料,其中卻只有一小部分與環境相關。即使一開始把整份組態直接複製到各環境專屬的 ConfigMap 行得通,它終究會隨時間分歧,為維護帶來負擔。在這種情況下,範本做法就是完美解。

更多資訊#

  • Configuration Template Example
  • Tiller Template Engine
  • Gomplate
  • Go Template Syntax