返回文章列表

Kubernetes 初始化容器組態管理

本文探討如何在 Kubernetes 中使用初始化容器和組態範本模式來管理應用程式組態,特別是如何利用 busybox 映象和 emptyDir 卷實作不可變組態,以及如何使用 OpenShift Templates 簡化多環境佈署的組態管理。

Kubernetes 系統設計

在 Kubernetes 環境中,管理應用程式組態需要考慮多種因素,例如組態資料的大小、多環境佈署的差異以及安全性。本文介紹的方案利用初始化容器和 emptyDir 卷,實作了應用程式組態的動態生成和不可變性。透過 busybox 映象,初始化容器可以在 Pod 啟動時將組態檔案複製到分享卷,應用程式容器再從分享卷掛載組態。這種方法有效地分離了組態的構建和使用,並允許使用版本控制來管理組態的變更。此外,OpenShift Templates 提供了更便捷的方式來引數化組態映象,簡化了多環境佈署的流程。然而,這種方法也存在一定的複雜性,例如需要構建和維護額外的容器映象,以及處理敏感組態資料的安全性問題。因此,在實際應用中,需要根據具體情況權衡其優缺點,並考慮其他組態管理方案,例如 ConfigMaps 和 Secrets。

不可變組態模式(Immutable Configuration)與 Kubernetes 初始化容器

在前面的 Docker 範例中,我們根據 scratch 建立了一個空的 Docker 映象,用於存放組態資料並透過 Docker 卷分享資料。然而,在 Kubernetes 中,初始化容器需要一個基礎映象來協助將組態資料複製到分享的 Pod 卷中。一個適合的選擇是使用 busybox,它足夠精簡,允許我們使用 Unix 的 cp 命令來完成這項任務。

初始化分享卷的組態

讓我們來看看一個具體的範例。首先,我們需要使用 Dockerfile 建立一個組態映象,如 範例 21-5 所示。

範例 21-5:開發環境組態映象

FROM busybox
ADD dev.properties /config-src/demo.properties
ENTRYPOINT [ "sh", "-c", "cp /config-src/* $1", "--" ]

這個 Dockerfile 與純 Docker 範例的不同之處在於,我們使用了 busybox 作為基礎映象,並增加了一個 ENTRYPOINT,用於將屬性檔案複製到容器啟動時指定的目錄。

這個映象可以在 Deployment 的 .template.spec 中被參照,如 範例 21-6 所示。

範例 21-6:使用初始化容器複製組態的 Deployment

initContainers:
- image: k8spatterns/config-dev:1
  name: init
  args:
  - "/config"
  volumeMounts:
  - mountPath: "/config"
    name: config-directory
containers:
- image: k8spatterns/demo:1
  name: demo
  ports:
  - containerPort: 8080
    name: http
    protocol: TCP
  volumeMounts:
  - mountPath: "/var/config"
    name: config-directory
volumes:
- name: config-directory
  emptyDir: {}

這個 Deployment 的 Pod 範本規格包含一個卷和兩個容器:

  • config-directory 的型別為 emptyDir,因此它在託管此 Pod 的節點上被建立為一個空目錄。
  • 初始化容器在啟動期間被 Kubernetes 呼叫,它從剛剛建立的映象構建,並設定了一個引數 /config,該引數被映象的 ENTRYPOINT 使用,指示初始化容器將其內容複製到指定的目錄。目錄 /config 是從卷 config-directory 掛載的。
  • 應用容器掛載卷 config-directory 以存取由初始化容器複製過來的組態。

圖示說明

圖 21-2:使用初始化容器的不可變組態

此圖示展示了應用容器如何透過分享卷存取由初始化容器建立的組態資料。

切換組態

要從開發環境切換到生產環境,只需更換初始化容器的映象即可。這可以透過修改 YAML 定義或使用 kubectl 更新來實作。然而,對於每個環境都需要編輯資源描述符並不是理想的做法。如果您使用的是 Red Hat OpenShift,一個企業級的 Kubernetes 發行版,OpenShift Templates 可以幫助解決這個問題。

OpenShift Templates

OpenShift Templates 是引數化的常規資源描述符。如 範例 21-7 所示,我們可以輕鬆地將組態映象用作引數。

範例 21-7:用於引數化組態映象的 OpenShift Template

apiVersion: v1
kind: Template
metadata:
  name: demo
parameters:
- name: CONFIG_IMAGE
  description: Name of configuration image
  value: k8spatterns/config-dev:1
objects:
- apiVersion: apps/v1
  kind: Deployment
  # ....
  spec:
    template:
      metadata:
        # ....
      spec:
        initContainers:
        - name: init
          image: ${CONFIG_IMAGE}
          args: [ "/config" ]
          volumeMounts:
          - mountPath: /config
            name: config-directory
        containers:
        - image: k8spatterns/demo:1
          # ...
          volumeMounts:
          - mountPath: /var/config
            name: config-directory
        volumes:
        - name: config-directory
          emptyDir: {}

只顯示了完整描述符的一部分,但可以快速識別出我們在初始化容器宣告中參照的引數 CONFIG_IMAGE。如果我們在 OpenShift 叢集上建立此範本,可以透過呼叫 oc 命令來例項化它,如 範例 21-8 所示。

範例 21-8:應用 OpenShift 範本以建立新應用程式

oc new-app demo -p CONFIG_IMAGE=k8spatterns/config-prod:1

詳細討論

使用資料容器實作不可變組態模式確實有一定複雜性。只有當不可變的 ConfigMaps 和 Secret 不適合您的使用場景時,才建議使用這種方法。

資料容器的優點包括:

  • 環境特定的組態被封裝在容器內,因此可以像其他容器映象一樣進行版本控制。
  • 這樣建立的組態可以分發到容器登入檔中,即使無法存取叢集,也可以檢查組態。
  • 組態是不可變的,就像儲存組態的容器映象一樣;組態的更改需要版本更新和新的容器映象。
  • 組態資料映象在組態資料過於複雜,無法放入環境變數或 ConfigMaps 時非常有用,因為它可以容納任意大小的組態資料。

然而,不可變組態模式也有一些缺點:

  • 複雜度較高,因為需要構建和透過登入檔分發額外的容器映象。
  • 它不能解決圍繞敏感組態資料的安全問題。
  • 由於 Kubernetes 工作負載實際上沒有映象卷支援,因此這裡描述的技術仍然侷限於可以接受將資料從初始化容器複製到本地卷的開銷的使用場景。我們希望將來能夠直接將容器映象掛載為卷,但截至 2023 年,只有實驗性的 CSI 支援可用。
  • 需要額外的初始化容器處理,因此我們需要為不同的環境管理不同的 Deployment 物件。

總之,您應該仔細評估是否真的需要這樣一個複雜的方法。下一章介紹的 組態範本模式 提供瞭解決大組態檔案差異問題的另一種方法。

更多資訊

  • 不可變組態範例
  • 如何在 Kubernetes 中模擬 --volumes-from
  • 不可變 ConfigMaps
  • 功能請求:映象卷和容器卷
  • docker-flexvol:支援 Docker 卷的 Kubernetes 驅動程式
  • Red Hat OpenShift:使用範本

組態範本模式

組態範本模式使您能夠在應用程式啟動期間建立和處理大型且複雜的組態。所產生的組態特定於目標執行環境,這反映在處理組態範本時使用的引數。

問題

在第20章“組態資源”中,您瞭解瞭如何使用Kubernetes原生資源物件ConfigMap和Secret來組態應用程式。但是,有時組態檔案可能會變得龐大且複雜。將組態檔案直接放入ConfigMaps中可能會出現問題,因為它們必須正確嵌入資源定義中。我們需要小心避免使用特殊字元(如引號)並破壞Kubernetes資源語法。組態的大小是另一個需要考慮的問題,因為ConfigMaps或Secrets的所有值的總和有1 MB的限制(這是底層後端儲存etcd施加的限制)。

大型組態檔案通常僅在不同的執行環境中略有不同。這種相似性導致ConfigMaps中的大量重複和冗餘,因為每個環境大多具有相同的資料。本章探討的組態範本模式解決了這些特定的使用案例問題。

解決方案

為了減少重複,僅將不同的組態值(如資料函式庫連線引數)儲存在ConfigMap中,甚至直接儲存在環境變數中是有意義的。在容器啟動期間,這些值會與組態範本一起處理,以建立完整的組態檔案(如WildFly的standalone.xml)。有多種工具,如Tiller(Ruby)或Gomplate(Go),可用於在應用程式初始化期間處理範本。圖22-1是一個組態範本示例,其中填充了來自環境變數或掛載卷的資料,可能由ConfigMap支援。

圖表說明

此圖示展示了組態範本的處理流程。

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title Kubernetes 初始化容器組態管理

package "Kubernetes Cluster" {
    package "Control Plane" {
        component [API Server] as api
        component [Controller Manager] as cm
        component [Scheduler] as sched
        database [etcd] as etcd
    }

    package "Worker Nodes" {
        component [Kubelet] as kubelet
        component [Kube-proxy] as proxy
        package "Pods" {
            component [Container 1] as c1
            component [Container 2] as c2
        }
    }
}

api --> etcd : 儲存狀態
api --> cm : 控制迴圈
api --> sched : 調度決策
api --> kubelet : 指令下達
kubelet --> c1
kubelet --> c2
proxy --> c1 : 網路代理
proxy --> c2

note right of api
  核心 API 入口
  所有操作經由此處
end note

@enduml

在應用程式啟動之前,完全處理的組態檔案將被放置在可以直接使用的位置。

有兩種技術可以在執行時進行這種實時處理:

  1. 我們可以將範本處理器作為ENTRYPOINT的一部分新增到Dockerfile中,以便範本處理成為容器映像的直接部分。這裡的入口點通常是一個指令碼,它首先執行範本處理,然後啟動應用程式。範本的引數來自環境變數。
  2. 使用Kubernetes,一種更好的初始化方法是使用Pod中的init容器,其中範本處理器執行並為Pod中的應用程式容器建立組態。Init容器模式在第15章中有詳細描述。

對於Kubernetes,init容器方法最具吸引力,因為我們可以直接使用ConfigMaps作為範本引數。圖22-1展示了這種技術。

應用程式的Pod定義至少包含兩個容器:一個init容器用於範本處理,一個用於應用程式容器。init容器不僅包含範本處理器,還包含組態範本本身。除了容器之外,此Pod還定義了兩個卷:一個卷用於範本引數,由ConfigMap支援,以及一個emptyDir卷,用於在init容器和應用程式容器之間分享處理後的範本。

透過這種設定,在啟動此Pod期間,將執行以下步驟:

  1. init容器啟動,並執行範本處理器。處理器從其映像中取得範本,從掛載的ConfigMap卷中取得範本引數,並將結果儲存在emptyDir卷中。
  2. init容器完成後,應用程式容器啟動並從emptyDir卷中載入組態檔案。

以下示例使用init容器來管理兩種環境(開發環境和生產環境)的整套WildFly組態檔案。兩者非常相似,僅在日誌記錄方式上略有不同:每行日誌分別以“DEVELOPMENT:”或“PRODUCTION:”為字首。

範例程式碼

<formatter name="COLOR-PATTERN">
    <pattern-formatter pattern="{{(datasource "config").logFormat}}"/>
</formatter>

內容解密:

  • 上述XML片段展示瞭如何使用Go範本語法引數化WildFly的standalone.xml組態檔案中的日誌格式。
  • pattern-formatter元素的pattern屬性使用了範本語法,從名為“config”的資料來源中取得logFormat的值。
FROM k8spatterns/gomplate
COPY in /in

內容解密:

  • 此Dockerfile用於構建包含Gomplate範本處理器的init容器映像。
  • FROM k8spatterns/gomplate指令指定了基礎映像,該映像包含了Gomplate工具和預設的入口點指令碼。
  • COPY in /in指令將包含WildFly組態範本的本地目錄“in”複製到映像中的“/in”目錄。
kubectl create configmap wildfly-cm \
--from-literal='config.yml=logFormat: "DEVELOPMENT: %-5p %s%e%n'

內容解密:

  • 此命令建立了一個名為wildfly-cm的ConfigMap,其中包含一個名為config.yml的條目,其值為logFormat: "DEVELOPMENT: %-5p %s%e%n"
  • 這裡定義的日誌格式將被用於替換WildFly組態範本中的相應部分。
apiVersion: apps/v1
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"
          name: wildfly-parameters
        - mountPath: "/out"
          name: config-out
      containers:
      - image: wildfly
        name: wildfly
        volumeMounts:
        - mountPath: "/opt/wildfly/standalone/configuration"
          name: config-out
      volumes:
      - name: wildfly-parameters
        configMap:
          name: wildfly-cm
      - name: config-out
        emptyDir: {}

內容解密:

  • 此Deployment資源定義了一個包含init容器和WildFly應用程式容器的Pod。
  • init容器使用k8spatterns/example-config-cm-template-init映像執行,掛載了兩個卷:wildfly-parameters(由ConfigMap wildfly-cm支援)和config-out(一個emptyDir卷)。
  • WildFly容器掛載了config-out卷,以便使用由init容器生成的組態。
  • 這種設定允許在應用程式啟動之前動態生成組態,從而實作了組態的靈活性和可重用性。

Kubernetes 組態範本模式詳解

Kubernetes 的組態範本模式(Configuration Template Pattern)是一種用於管理和維護複雜應用程式組態的方法,尤其是在多環境佈署的情況下。這種模式根據組態資源模式(Configuration Resource Pattern),並且特別適用於需要處理大量組態資料的應用程式。

組態範本模式的工作原理

組態範本模式的核心是使用範本引擎來動態生成組態檔案。以下是一個使用 WildFly 伺服器的範例:

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"
  volumeMounts:
  - mountPath: "/config"
    name: wildfly-config
volumes:
- name: wildfly-parameters
  configMap:
    name: wildfly-cm
- name: wildfly-config
  emptyDir: {}

內容解密:

  1. 容器定義:定義了一個名為 server 的容器,使用 jboss/wildfly:10.1.0.Final 映象,並執行 standalone.sh 命令,指定組態目錄為 /config
  2. 卷掛載:將 wildfly-config 卷掛載到容器的 /config 目錄,用於存放生成的組態檔案。
  3. 卷定義:定義了兩個卷,wildfly-parameterswildfly-configwildfly-parameters 卷從 ConfigMap wildfly-cm 中取得組態引數,而 wildfly-config 是一個臨時空目錄,用於存放處理後的組態檔案。

組態範本模式的優勢

這種模式的最大優勢在於它遵循了 “Don’t Repeat Yourself” (DRY) 原則,避免了在多個環境中重複維護相同的組態檔案的問題。當 WildFly 組態需要變更時,只需更新 init 容器中的範本檔案即可,大大簡化了維護工作。

除錯技巧

在除錯過程中,如果需要檢查生成的組態檔案,可以在節點上的 /var/lib/kubelet/pods/{podid}/volumes/kubernetes.io~empty-dir/ 目錄下查詢,或者直接透過 kubectl exec 命令進入 Pod 內部檢查 /config 目錄。

安全模式的相關性

組態範本模式是 Kubernetes 安全模式的一部分,尤其與安全組態模式(Secure Configuration Pattern)相關,因為它涉及到如何安全地管理和使用敏感的組態資料。