在 Kubernetes 應用佈署過程中,初始化容器扮演著至關重要的角色,用於在應用程式容器啟動前執行必要的設定任務,例如準備環境、複製組態檔案或等待外部服務就緒。本文除了詳解初始化容器的概念和用法外,也涵蓋 Sidecar 和大使者模式,說明如何透過這些模式擴充套件和增強容器功能。此外,文章也探討了外部組態模式的最佳實踐,特別是環境變陣列態方法,並提供 Java 程式碼範例,展示如何讀取環境變數並應用於應用程式組態。這些技術的整合運用能有效提升 Kubernetes 應用佈署的效率和可靠性,確保應用程式在正確的環境中穩定執行。
初始化容器模式詳解
在 Kubernetes 中,初始化容器(Init Container)是一種特殊的容器,用於在應用程式容器啟動之前執行初始化任務。這些任務可以包括設定環境、複製組態檔案、等待其他服務就緒等。初始化容器提供了一種可靠且可重複使用的方式來確保應用程式容器的正確初始化。
初始化容器的基本概念
初始化容器與應用程式容器分享相同的 Pod,但它們有不同的生命週期和用途。初始化容器在應用程式容器之前執行,並且只有當所有初始化容器成功完成後,應用程式容器才會啟動。
初始化容器的典型用法
以下是一個典型的初始化容器用法範例,該範例將一個外部 Git 儲存函式庫克隆到掛載的目錄中:
name: source
containers:
- name: run
image: docker.io/centos/httpd
ports:
- containerPort: 80
volumeMounts:
- mountPath: /var/www/html
name: source
volumes:
- emptyDir: {}
name: source
內容解密:
name: source定義了一個名為source的 Pod 或容器組。containers部分定義了應用程式容器,在此範例中是一個執行httpd的 CentOS 容器,並將containerPort設定為 80。volumeMounts將一個名為source的卷掛載到容器的/var/www/html目錄。volumes部分定義了一個名為source的空目錄卷,用於在 Pod 內部分享資料。
為何使用初始化容器
使用初始化容器有以下幾個好處:
- 保證初始化順序:初始化容器保證按照定義的順序執行,並且只有當前一個初始化容器成功完成後,下一個才會開始。
- 提高可重複使用性:透過將初始化邏輯與應用程式邏輯分離,可以提高容器的可重複使用性。
- 提升安全性:某些初始化任務可能需要提升的許可權,使用初始化容器可以最小化應用程式容器的許可權。
初始化技術的進階討論
除了初始化容器之外,Kubernetes 還提供了其他初始化技術,例如:
- Admission Controllers:用於在資源持久化之前對請求進行攔截和修改。
- Admission Webhooks:外部的 Admission Controllers,可以對資源進行自定義的預設值設定和驗證。
這些技術與初始化容器的主要區別在於它們作用的時間點不同。Admission Webhooks 在資源建立時進行驗證和修改,而初始化容器則在 Pod 啟動時執行。
更多資訊
- Init Container Example
- Init Containers
- Configuring Pod Initialization
- Admission Controllers Reference
- Dynamic Admission Control
- Metacontroller
- Kyverno
- Demystifying Istio’s Sidecar Injection Model
第16章 Sidecar模式:容器協作的基礎
Sidecar容器可以在不改變原有容器的情況下,擴充套件和增強其功能。Sidecar模式是基本的容器模式之一,允許單一用途的容器緊密合作。在本章中,我們將探討Sidecar模式的基本概念,並在後續章節中討論其衍生的Adapter和Ambassador模式。
問題背景
容器技術使得開發者和系統管理員能夠以統一的方式構建、交付和執行應用程式。每個容器代表了一個具有獨立執行時、發布週期、API和開發團隊的功能單元。理想的容器應該像單一的Linux程式一樣,專注於解決一個問題並做好它,同時具備可替換性和可重用性。這種設計允許開發者透過利用現有的專門容器來更快地構建應用程式。
如同現在進行HTTP呼叫時,我們不需要自己編寫客戶端函式庫,而是可以直接使用現有的函式庫;同樣地,要架設一個網站,我們不需要自己建立一個包含Web伺服器的容器,而是可以使用現有的容器。這種方法避免了重複造輪子,並創造了一個維護成本更低、品質更高的容器生態系統。
然而,單一用途的可重用容器需要有一種方式來擴充套件容器的功能,並實作容器之間的協作。Sidecar模式正是描述了這種協作方式,其中一個容器增強了另一個現有容器的功能。
解決方案
在第1章中,我們介紹了Pod這個基本單元,它允許我們將多個容器組合成一個單元。Pod本身在執行時也是一個容器,但它首先以一個暫停的程式(實際上是透過pause命令)啟動,然後才啟動Pod中的其他容器。Pod的主要作用是持有所有應用程式容器在其生命週期內使用的Linux名稱空間。除此之外,Pod抽象還提供了許多重要的特性。
Pod是一種非常基礎的單元,在許多雲原生平台上都有類別似的實作。作為佈署單元,Pod對其包含的容器施加了一定的執行時約束。例如,所有容器都會被佈署到同一個節點上,並且它們分享同一個Pod的生命週期。此外,Pod允許其容器之間分享卷,並透過本地網路或主機IPC進行通訊。這些特性是使用者將一組容器放入同一個Pod的原因。
Sidecar(有時也稱為Sidekick)用於描述將一個容器放入Pod中,以擴充套件和增強另一個容器的行為的場景。
典型範例
一個典型的Sidecar模式例子是HTTP伺服器和Git同步器的組合。HTTP伺服器容器只專注於透過HTTP服務檔案,而不知道這些檔案來自哪裡或如何更新。同樣地,Git同步器容器的唯一目標是將資料從Git伺服器同步到本地檔案系統,而不關心這些資料之後會如何處理。範例16-1展示了一個包含這兩個容器的Pod定義,它們透過一個卷來交換檔案。
apiVersion: v1
kind: Pod
metadata:
name: web-app
spec:
containers:
- name: app
image: docker.io/centos/httpd
ports:
- containerPort: 80
volumeMounts:
- mountPath: /var/www/html
name: git
- name: poll
image: axeclbr/git
volumeMounts:
- mountPath: /var/lib/data
name: git
env:
- name: GIT_REPO
value: https://github.com/mdn/beginner-html-site-scripted
command:
- "sh"
- "-c"
- "git clone $(GIT_REPO) . && watch -n 600 git pull"
workingDir: /var/lib/data
volumes:
- emptyDir: {}
name: git
內容解密:
- 主要應用容器(HTTP伺服器):使用
docker.io/centos/httpd映象,提供HTTP服務,將分享卷掛載到/var/www/html。 - Sidecar容器(Git同步器):使用
axeclbr/git映象,將Git儲存函式庫同步到/var/lib/data,並每10分鐘檢查更新。 - 分享卷:使用
emptyDir卷,讓兩個容器可以交換資料。 - 環境變數和命令:Git同步器透過環境變數
GIT_REPO取得倉函式庫地址,並執行git clone和定期git pull來保持資料同步。
這個範例展示了Git同步器如何增強HTTP伺服器的功能,為其提供內容並保持同步。我們也可以說這兩個容器是協作關係,並且同等重要,但在Sidecar模式中,有一個主容器和一個輔助容器,共同增強整體行為。通常,主容器是容器列表中的第一個,並且代表預設容器(例如,當我們執行kubectl exec命令時)。
圖示說明
此圖示展示了Sidecar模式的基本架構。
@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圖示解說:
- 主應用容器和Sidecar容器執行在同一個Pod中。
- 它們透過分享卷進行資料交換。
- Sidecar容器負責資料的同步和更新。
討論
之前我們提到,容器映象就像類別,而容器就像物件導向程式設計(OOP)中的物件。如果我們繼續這個類別比,擴充套件容器的功能類別似於OOP中的繼承,而在Pod中讓多個容器協作則類別似於OOP中的組合。雖然這兩種方法都允許程式碼重用,但繼承涉及更緊密的耦合,表示容器之間是“is-a”的關係。
另一方面,Pod中的組合表示“has-a”的關係,並且更靈活,因為它不會在構建時將容器耦合在一起,讓你能夠在之後更改Pod定義中的容器。使用組合方法,你有多個容器(程式)在執行,被健康檢查、重啟,並且消耗資源,就像主應用程式容器一樣。現代的Sidecar容器通常很小,消耗的資源很少,但你需要決定是否值得這樣做。
Adapter 模式:統一異構系統介面
在容器化的分散式系統中,不同的團隊使用不同的技術和語言開發應用程式,導致系統具有異構性。這種異構性使得其他系統難以以統一的方式對待所有元件。Adapter 模式提供了一個解決方案,透過隱藏系統的複雜性,提供統一的存取介面。
問題
在分散式系統中,不同的服務可能使用不同的程式語言和函式庫,這些服務可能無法以統一的方式暴露指標給監控工具。監控工具需要一個統一的介面來收集和記錄指標。
解決方案
Adapter 模式透過在 Pod 中新增一個 Adapter 容器,將不同應用程式容器的指標轉換為統一的格式和協定。Adapter 容器知道如何讀取自定義的應用程式特定指標,並將其暴露在統一的格式中。
範例
假設我們有一個隨機數產生器應用程式,它將產生的隨機數和耗時寫入日誌檔案。我們希望使用 Prometheus 監控這個耗時,但日誌格式不符合 Prometheus 的預期。我們可以使用 Adapter 容器來解決這個問題。
Adapter 容器會啟動一個小型 HTTP 伺服器,每次請求都會讀取自定義的日誌檔案並將其轉換為 Prometheus 可理解的格式。下面的程式碼範例展示瞭如何組態 Deployment 以使用 Adapter 容器。
apiVersion: apps/v1
kind: Deployment
metadata:
name: random-generator
spec:
replicas: 1
selector:
matchLabels:
app: random-generator
template:
metadata:
labels:
app: random-generator
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
env:
- name: LOG_FILE
value: /logs/random.log
ports:
- containerPort: 8080
protocol: TCP
volumeMounts:
- mountPath: /logs
name: log-volume
#
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
-
---
-
- image: k8spatterns/random-generator-exporter
name: prometheus-adapter
env:
- name: LOG_FILE
value: /logs/random.log
ports:
- containerPort: 9889
內容解密:
- Deployment 組態:定義了一個名為
random-generator的 Deployment,包含一個主容器random-generator和一個 Adapter 容器prometheus-adapter。 - 環境變數:兩個容器分享同一個日誌檔案路徑
/logs/random.log,透過環境變數LOG_FILE設定。 - Adapter 容器:
prometheus-adapter容器負責讀取日誌檔案並將其轉換為 Prometheus 可理解的格式,透過 HTTP 端點暴露給 Prometheus。 - 統一介面:透過 Adapter 模式,Prometheus 可以統一地收集不同服務的指標,無需知道每個服務的內部實作細節。
大使者模式(Ambassador Pattern)在微服務架構中的應用
在大使者模式中,一個特殊的Sidecar容器被用來隱藏外部服務的複雜性,並為存取外部服務提供統一的介面。本章將探討大使者模式如何作為代理,解耦主容器與外部依賴之間的直接存取關係。
問題背景
容器化的服務通常需要存取其他服務,而這些外部服務可能因為動態變化、負載平衡需求、不可靠的協定或資料格式等原因而難以可靠地存取。理想情況下,容器應該是單一用途且可在不同情境中重複使用的。然而,如果一個容器同時提供業務功能並以特殊方式取用外部服務,那麼它將承擔多重責任。
解決方案
大使者模式透過引入一個大使者容器來隱藏存取外部服務的複雜性,並在localhost上為主應用程式容器提供簡化的存取檢視。例如,在開發環境中,存取本地快取可能是一個簡單的組態,但在生產環境中,可能需要能夠連線到不同快取分片的客戶端組態。
範例:使用大使者模式存取快取服務
apiVersion: v1
kind: Pod
metadata:
name: random-generator
labels:
app: random-generator
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: main
env:
- name: LOG_URL
value: http://localhost:9009
ports:
- containerPort: 8080
protocol: TCP
- image: k8spatterns/random-generator-log-ambassador
name: ambassador
volumeMounts:
- mountPath: /logs
name: log-volume
volumes:
- name: log-volume
emptyDir: {}
內容解密:
- 定義了一個名為
random-generator的Pod,包含兩個容器:main和ambassador。 main容器執行一個REST服務,用於生成隨機數,並透過環境變數LOG_URL指定日誌輸出到http://localhost:9009。ambassador容器監聽在9009埠,處理來自main容器的日誌資料,並可以將其轉發到日誌基礎設施。- 使用
emptyDir卷來分享日誌資料。
大使者模式的優勢
大使者模式與Sidecar模式類別似,但主要區別在於大使者不增強主應用程式的功能,而是作為一個智慧代理來存取外部服務。這使得應用程式容器可以專注於業務邏輯,而將存取外部服務的責任委託給專門的大使者容器。
外部組態模式:容器化應用的最佳實踐
在容器化應用開發中,組態管理是一項至關重要的任務。將組態儲存在原始碼中雖然簡單,但會導致程式碼與組態緊密耦合,不利於持續交付和佈署。因此,我們需要採用外部組態模式來實作程式碼與組態的分離。
環境變陣列態模式(EnvVar Configuration)
環境變陣列態模式是容器化應用中最簡單的組態方法。它利用環境變數來儲存組態資料,適用於小型組態集。環境變數具有跨平台、跨語言的特性,使得它們成為一種普遍適用的組態方式。
問題描述
每個非簡單的應用程式都需要一些組態來存取資料來源、外部服務或生產級調優。將組態寫死在應用程式中是一種不好的做法。相反,組態應該被外部化,以便在應用程式構建後仍可更改。
解決方案
十二要素應用程式宣言建議使用環境變數來儲存應用程式組態。這種方法簡單易行,適用於任何環境和平台。作業系統知道如何定義環境變數並將其傳遞給應用程式,而每種程式語言也允許輕鬆存取這些環境變數。
在 Docker 映像檔中,可以使用 ENV 指令直接定義環境變數。例如:
FROM openjdk:11
ENV PATTERN="EnvVar Configuration"
ENV LOG_FILE="/tmp/random.log"
ENV SEED="1349093094"
在 Kubernetes 中,可以在 Pod 規格中設定環境變數,例如:
apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
env:
- name: LOG_FILE
value: /tmp/random.log
- name: PATTERN
valueFrom:
configMapKeyRef:
name: random-generator-config
key: pattern
- name: SEED
valueFrom:
secretKeyRef:
name: random-generator-secret
key: seed
內容解密:
- 環境變數的定義:在 Docker 映像檔中,使用 ENV 指令定義環境變數。在 Kubernetes 中,則是在 Pod 規格中設定環境變數。
- 環境變數的存取:Java 應用程式可以透過 Java 標準庫存取環境變數,例如
System.getenv("SEED")。 - 環境變數的安全性:雖然環境變數很方便,但它們並不安全。將敏感資訊存放在環境變數中可能會導致資訊洩露。
程式碼解析
以下是一個 Java 程式碼範例,用於讀取環境變數:
public Random initRandom() {
long seed = Long.parseLong(System.getenv("SEED"));
return new Random(seed);
}
內容解密:
- 讀取環境變數:使用
System.getenv("SEED")讀取名為 SEED 的環境變數。 - 轉換資料型別:將讀取到的環境變數字串轉換為 long 型別。
- 初始化隨機數生成器:使用轉換後的 seed 值初始化隨機數生成器。