# knative 和 kustomization 介紹 先來個白話介紹, - 若將 k8s 想成是一個設備超齊全的中央廚房 (功能強大但操作複雜) - 那 Knative 就像是安裝在這個中央廚房中的智能管理系統,開發者只要提供一部分程式碼,系統就會幫你處理好一切 --- ## Knative Serving ![](https://line-objects-internal.com/notes-line-dev-files/uploads/4ba200fb-5e1b-4e7e-a2ab-5d6febbe87b9.png) 這張圖展示了 Knative Serving 宣告式 API 的核心,也就是開發者主要接觸的四種 Kubernetes 自訂資源定義 (CRD)。 1. **Service (`service.serving.knative.dev`)**: * **角色**: 這是最高層級的抽象,是使用者與 Knative 互動的主要入口點。一個 `Service` 物件旨在管理一個無伺服器工作負載的完整生命週期。 * **職責**: 當您建立一個 `Service` 物件時,Knative 的控制器會自動為您生成並管理對應的 `Configuration` 和 `Route` 子物件。它的目標是將一個應用的部署、網路配置和版本管理濃縮在單一的 YAML 檔案中,大幅簡化操作。 2. **Configuration (`configuration.serving.knative.dev`)**: * **角色**: 定義了您工作負載的「期望狀態 (Desired State)」。它是一個範本,包含了部署所需的所有設定,例如容器映像檔 (`image`)、環境變數、資源請求/限制等。 * **職責**: 它的核心職責是實現「程式碼與配置分離」。當此物件的 `spec.template` 部分發生任何變更時(例如更新了容器映像檔),它不會修改現有狀態,而是會觸發建立一個新的、不可變的 `Revision`。 3. **Revision (`revision.serving.knative.dev`)**: * **角色**: 一個 **不可變 (Immutable) 的時間點快照 (Point-in-time Snapshot)**。它捕捉了 `Configuration` 在某一特定時刻的程式碼和配置。 * **職責**: 不可變性是其關鍵特性。這保證了每次部署都是可預測且穩定的。由於每個 `Revision` 都是一個獨立的、可運行的實體,這使得版本追蹤、灰度發布 (Canary Release) 和即時回滾 (Instant Rollbacks) 變得極其簡單和安全。每個 `Revision` 都可以獨立地進行自動縮放。 4. **Route (`route.serving.knative.dev`)**: * **角色**: 負責網路流量管理。它將一個公開的網路端點 (URL) 映射到一個或多個 `Revision`。 * **職責**: `Route` 的主要工作是根據您設定的規則來分配流量。您可以將 100% 的流量指向最新的 `Revision`(預設行為),也可以精確地切分流量,例如將 90% 流量導向穩定的 v1 版本,10% 流量導向新的 v2 版本進行 A/B 測試或灰度驗證。它會將這些規則轉譯給底層的網路層 (如 Istio, Contour) 來執行。 ### Knative Serving Architecture ![](https://line-objects-internal.com/notes-line-dev-files/uploads/1e274031-f5c9-462e-86c3-f16d4a67c9ae.png) 這張圖展示了 Knative 的控制平面 (Control Plane) 和資料平面 (Data Plane) 的關鍵組件及其互動關係。 #### **控制平面 (Control Plane) - "大腦"** 控制平面負責監控和管理叢集狀態,確保實際狀態與您的期望狀態一致。 * **Controller**: Knative 的核心控制器,它不斷地監聽 (watch) Knative 資源 (如 `Service`) 的變化。當偵測到變更時,它會執行協調迴圈 (Reconciliation Loop),建立或更新依賴的資源 (如 `Configuration`, `Route`, Kubernetes `Deployment` 等)。 * **Autoscaler**: 這是實現自動伸縮的核心。它持續從 `Activator` 和 `Queue-Proxy` 收集流量指標 (如請求併發數),並根據這些指標和使用者定義的策略,動態調整每個 `Revision` 對應的 Pod 副本數量。 * **Webhooks**: Kubernetes 的准入控制器 (Admission Controller),在資源被寫入 etcd 之前進行攔截。它負責驗證 (validate) 您的 YAML 是否合法,以及變更/注入 (mutate) 預設值,確保系統的穩定性。 * **DomainMapping**: 允許您將自訂的網域名稱對應到一個 Knative `Service`,而非使用 Knative 自動產生的 URL。 #### **資料平面 (Data Plane) - "交通系統"** 資料平面是實際處理和轉發使用者請求的組件。 * **Ingress Gateway**: 所有外部和內部流量的統一入口。它是一個可插拔的網路層 (如 Istio, Contour, Kourier),根據 `Route` 資源設定的規則,將請求導向後端。如圖所示,它有兩種工作模式: * **Proxy Mode**: 將流量轉發給 `Activator`。此模式用於服務縮減至零或流量突發的場景。 * **Serve Mode**: 將流量直接轉發給應用的 Pod (`Queue-Proxy`)。此模式用於服務已有可用 Pod 的高流量場景。 * **Activator**: 在資料路徑中扮演關鍵的緩衝和喚醒角色。當一個 `Revision` 的副本數為零時,`Activator` 會接收並暫存 (buffer) 傳入的請求,同時 "Poke" (戳一下) `Autoscaler` 觸發擴容。一旦有 Pod 可用,它便會將請求轉發過去。 * **Queue-Proxy**: 一個強制注入到每個使用者 Pod 中的 **邊車容器 (Sidecar Container)**。它的職責包括: 1. 精確測量進入使用者容器的併發請求數 (`containerConcurrency`),並將指標上報給 `Autoscaler`。 2. 當請求超過設定的併發上限時,可以作為請求佇列。 3. 管理 Pod 的優雅終止 (graceful shutdown)。 * **User-Container**: 這就是您自己開發的應用程式容器。 ### HTTP Request Flows ![](https://line-objects-internal.com/notes-line-dev-files/uploads/6ecfe35f-8b8f-4eab-8c13-ebe6d3a9a6f7.png) 這張圖詳細描繪了一個 HTTP 請求從進入到被處理的完整路徑。 #### **場景 A: 從零擴容 (Cold Start / Proxy Mode)** 當一個 `Revision` 沒有任何運行的 Pod 時 (scaled to zero): 1. **請求到達**: 使用者的請求首先到達 `HTTP Router` (即 Ingress Gateway)。 2. **路由至 Activator**: `Router` 根據路由表發現目標 `Revision` 沒有可用的 Pod IP,於是將請求轉發到共享的 `Activator`。 3. **觸發擴容**: `Activator` 接收請求,並立即通知 `Autoscaler` 需要資源。`Autoscaler` 隨即與 `Kubernetes apiserver` 通信,將該 `Revision` 對應的 `ReplicaSet` (或 `Deployment`) 的副本數從 0 調整為 1。 4. **Pod 創建**: Kubernetes 排程器創建一個新的 Pod。此 Pod 內含 `Queue-Proxy` 和您的 `User container`。 5. **請求轉發**: 一旦 Pod 啟動並回報為 Ready 狀態,`Activator` 就會將其暫存的請求轉發到新 Pod 的 `Queue-Proxy`,最終由您的 `User container` 處理。從步驟 1 到 5 的延遲就是「冷啟動」時間。 #### **場景 B: 高流量 (Serve Mode)** 當一個 `Revision` 已經有至少一個運行的 Pod 時: 1. **請求到達**: 使用者的請求到達 `HTTP Router`。 2. **直接路由**: `Router` 的路由表此時已包含該 `Revision` 的 Pod IP 位址。因此,請求會繞過 `Activator`,被直接轉發到其中一個健康 Pod 的 `Queue-Proxy`。 3. **請求處理**: `Queue-Proxy` 接收請求,在檢查併發數後,將其傳遞給 `User container` 進行處理。同時,`Queue-Proxy` 會持續上報流量指標給 `Autoscaler`,以便後者進行更精準的擴縮容決策。 這個路徑移除了 `Activator` 這個中間躍點,提供了最低的請求延遲。Knative 會根據流量動態地在這兩種模式之間切換,以兼顧成本效益和效能。 --- 理論結合實踐是最好的學習方式。 接下來我們將透過幾個由淺入深的 YAML 範例,來實際教學如何撰寫 Knative 設定檔。 --- ### 範例一:部署第一個 Knative 應用 (All-in-One Service) 對於 90% 的場景,您只需要撰寫一個 `Service` 物件,Knative 就會為您處理一切。這是最常見且最推薦的入門方式。 假設我們要部署一個簡單的 "Hello World" 應用。 **`service.yaml`** ```yaml # API 版本宣告,使用 Knative Serving v1 apiVersion: serving.knative.dev/v1 # 資源類型為 Service kind: Service metadata: # 此服務的名稱,將會成為 URL 的一部分 name: helloworld-go spec: # 'template' 區塊定義了 Revision 的模板。 # 未來所有基於此設定的 Revision 都會依此建立。 template: spec: containers: # 指定您的容器映像檔 - image: gcr.io/knative-samples/helloworld-go # 設定環境變數 env: - name: TARGET value: "World" ``` **解說:** * 這個單一的 `Service` 檔案已經包含了 `Configuration` 和 `Route` 的所有必要資訊。 * 當您透過 `kubectl apply -f service.yaml` 部署它時,Knative 會: 1. 建立一個名為 `helloworld-go` 的 `Configuration`。 2. 基於 `spec.template` 的內容,立即建立第一個 `Revision` (例如 `helloworld-go-00001-xyz`)。 3. 建立一個 `Route`,將 100% 的流量都導向這個剛剛建立的 `Revision`。 4. 提供一個公開的 URL,例如 `http://helloworld-go.default.example.com`。 ----- ### 範例二:更新應用與版本管理 現在,假設我們要更新這個應用,將它回應的文字從 "Hello World\!" 改為 "Hello Knative\!"。我們只需修改環境變數並重新套用即可。 **`service-updated.yaml`** ```yaml apiVersion: serving.knative.dev/v1 kind: Service metadata: name: helloworld-go spec: template: spec: containers: - image: gcr.io/knative-samples/helloworld-go env: - name: TARGET # 這一行是我們修改的地方 value: "Knative" ``` **解說:** * 當您套用這個更新後的檔案時,Knative 偵測到 `spec.template` 發生了變化。 * 它**不會**修改舊的 `Revision`,而是會: 1. 基於新的設定,建立一個**全新的、不可變的 `Revision`** (例如 `helloworld-go-00002-abc`)。 2. 預設情況下,`Route` 會被自動更新,將 **100% 的流量**無縫切換到這個新的 `Revision` 上。 * 舊的 `Revision` (`...-00001-xyz`) 依然存在,只是沒有流量導入。這意味著如果您發現新版本有問題,可以隨時快速回滾。 ----- ### 範例三:進階應用 - 灰度發布 (Canary Release) 假設新版本 (`...-00002-abc`) 存在風險,我們不想一次性將所有使用者都切換過去。我們希望先導入 10% 的流量到新版本,觀察其穩定性。這時我們就需要手動控制 `traffic` 區塊。 首先,您需要知道舊版和新版 `Revision` 的確切名稱。您可以透過以下指令取得: `kubectl get revisions` 假設我們得到的兩個版本名稱分別是: * 穩定版 (v1): `helloworld-go-00001-xyz` * 新候選版 (v2): `helloworld-go-00002-abc` 現在我們可以撰寫一個新的 YAML 來精確控制流量分配。 **`service-canary.yaml`** ```yaml apiVersion: serving.knative.dev/v1 kind: Service metadata: name: helloworld-go spec: # 注意:即使手動控制流量,template 區塊依然是必需的。 # 它定義了 "下一次" 更新時 Revision 的樣板。 template: spec: containers: - image: gcr.io/knative-samples/helloworld-go env: - name: TARGET value: "Knative" # 這是控制流量分配的核心區塊 traffic: # 第一個流量目標 - tag: stable-version # 為此流量目標設定一個標籤,方便識別 revisionName: helloworld-go-00001-xyz # 指向舊的、穩定的 Revision percent: 90 # 分配 90% 的流量 # 第二個流量目標 - tag: candidate-version # 為新版本設定標籤 revisionName: helloworld-go-00002-abc # 指向新的、待驗證的 Revision percent: 10 # 分配 10% 的流量 # 您也可以使用 latestRevision: true 來自動指向最新的 Revision ``` **解說:** * 這個設定明確地告訴 Knative 的 `Route` 物件: * 將 90% 的請求發送到 `helloworld-go-00001-xyz`。 * 將剩餘 10% 的請求發送到 `helloworld-go-00002-abc`。 * `tag` 會產生對應的獨立 URL (例如 `stable-version-helloworld-go...` 和 `candidate-version-helloworld-go...`),讓您可以單獨對某個版本進行測試,非常方便。 * 當您確認新版本穩定後,只需修改此檔案,將 `percent` 調整為 0 和 100,然後再次套用,即可完成發布。 ### 總結 從這些範例中可以看出,Knative 的設計哲學是透過簡單、聲明式的 YAML,來管理複雜的應用生命週期和部署策略。開發者可以從一個極簡的 `Service` 開始,並在需要時逐步引入如 `traffic` 這樣的進階設定,而無需關心底層是如何創建 `Deployment`、`Service`、`Ingress` 以及如何協調它們的。 --- ### Kustomize 是什麼?為什麼它和 Knative 是絕配? ![](https://line-objects-internal.com/notes-line-dev-files/uploads/930d91d5-86e0-40fd-bd0f-68c25a4ab354.png) source: https://razorops.com/blog/kustomize 簡單來說,**Kustomize 是一個無需模板、原生於 Kubernetes 的組態管理工具**。它的核心哲學是「客製化」而非「模板化」。 想像一下您在用樂高積木蓋房子: * **基礎 YAML 檔案 (如 Knative 的 `service.yaml`)**:這是一套通用的樂高積木組,定義了房子的核心結構(`base`)。 * **不同環境的需求 (開發、測試、生產)**:您想為這棟房子打造不同的「主題外觀」。開發版可能想漆成藍色,加個小花園;生產版則要漆成紅色,並蓋上堅固的屋頂。這些就是「疊加層」(`overlay`)。 傳統作法是為每個環境複製整套積木(YAML),然後手動修改,這非常沒效率且容易出錯。而 Kustomize 就像一個**智慧的樂高裝飾師**,它允許您: 1. **保留一套基礎積木 (Base)**:您只需維護一份核心的 YAML 檔案。 2. **建立不同的裝飾方案 (Overlay)**:針對每個環境,您只需定義「差異」的部分,例如「把所有牆壁變成藍色」、「在屋頂加上一個天線」等。 當您需要某個環境的最終設計圖時,Kustomize 會聰明地將「基礎積木」和您指定的「裝飾方案」合併,產生出最終的、完整的 YAML 組態。 **它與 Knative 是絕配的原因:** Knative 應用程式在不同環境中通常需要不同的設定,例如: * **開發環境**:使用開發用的資料庫環境變數,Pod 資源需求較低。 * **測試環境**:需要開啟特定的偵錯標籤 (label)。 * **生產環境**:使用正式的容器映像檔標籤 (image tag),設定更高的 Pod 副本數,並配置更嚴格的資源限制。 Kustomize 讓您能優雅地管理這些差異,而不需要為每個環境複製和維護幾乎一模一樣的 Knative `Service` YAML 檔案。 ----- ### Kustomize 的核心概念 在深入了解 Built-ins 之前,先掌握這幾個關鍵詞彙: * **`kustomization.yaml`**:Kustomize 的核心檔案,如同一個「說明書」或「配方」,告訴 Kustomize 要讀取哪些基礎資源,以及要對它們執行哪些修改。 * **`base`**:一個基礎目錄,包含了通用的、不分環境的 Kubernetes YAML 檔案。 * **`overlay`**:一個疊加層目錄,它會引用一個 `base`,並在其之上應用特定的客製化修改。我們通常會為 `development`, `staging`, `production` 各建立一個 overlay。 一個典型的專案結構如下: ``` my-app/ ├── base/ │ ├── kustomization.yaml │ └── service.yaml # 基礎的 Knative Service └── overlays/ ├── staging/ │ ├── kustomization.yaml │ └── patch-env.yaml # 專用於 staging 的補丁 └── production/ ├── kustomization.yaml └── patch-resources.yaml # 專用於 production 的補丁 ``` 要建構生產環境的設定,您只需執行 `kustomize build overlays/production`。 ----- ### Kustomize Built-Ins 詳解 (採用現代語法) Kustomize 的強大之處在於其內建的「產生器 (Generators)」和「轉換器 (Transformers)」。它們是 `kustomization.yaml` 中用來描述「如何修改」的關鍵欄位。 #### 1\. 資源產生器 (Generators) - 無中生有 它們負責在建構過程中建立新的 Kubernetes 資源。 * **`configMapGenerator`**: 從檔案或字面值產生 `ConfigMap`。 * **用途**:將設定檔或環境變數與應用程式容器分離,實現更好的配置管理。 * **常用選項**: `name`, `files`, `literals`, `options` (可在此單獨設定標籤/註解,或用 `disableNameSuffixHash: true` 關閉名稱的 hash 後綴)。 * **範例**: ```yaml configMapGenerator: - name: my-app-config files: - config/app-settings.ini literals: - LOG_LEVEL=info - FEATURE_FLAG=true ``` * **`secretGenerator`**: 與 `configMapGenerator` 類似,但用於產生 `Secret`。 * **用途**:安全地管理敏感資料,如 TLS 憑證、API 金鑰、資料庫密碼等。 * **範例**: ```yaml secretGenerator: - name: my-db-credentials literals: - a_secret_key=s3cr3t-v4lu3 files: - tls.crt - tls.key type: "kubernetes.io/tls" ``` #### 2\. 全域轉換器 (Global Transformers) - 全面修改 它們會將修改應用到**所有**的資源上。 * **`namePrefix` / `nameSuffix`**: 為所有資源的名稱加上前綴或後綴。 * **用途**:在多租戶或多環境的叢集中,避免資源名稱衝突。 * **範例**:`namePrefix: staging-` 會將 `my-app-svc` 變成 `staging-my-app-svc`。 * **`namespace`**: 將所有資源設定到指定的命名空間。 * **用途**:確保整個應用被部署到正確的隔離環境中。 * **`commonLabels` / `commonAnnotations`**: 為所有資源(包括 Pod 範本中的選擇器)加上相同的標籤或註解。 * **用途**:方便統一追蹤、管理和篩選,例如標示應用負責團隊 `owner: devops-team`。 #### 3\. 特定目標轉換器 (Targeted Transformers) - 精準打擊 它們只會修改符合特定條件的資源或欄位。 * **`images`**: 修改容器映像檔的名稱、標籤 (tag) 或摘要 (digest)。 * **用途**:**這是 Kustomize 最常用的功能之一**。在不同環境中使用不同的映像檔版本。 * **範例**: ```yaml images: - name: my-app-image # 原始映像檔名稱 newName: gcr.io/my-repo/my-app # 可選,修改映像檔路徑 newTag: v1.2.0-stable # 新的標籤 ``` * **`replicas`**: 修改 Deployment, StatefulSet 等資源的副本數量。 * **用途**:在生產環境增加副本數以應對高流量。 * **範例**: ```yaml replicas: - name: my-app-deployment # 目標 Deployment 的名稱 count: 5 # 設定副本數為 5 ``` * **`patches`**: **這是 Kustomize 的終極武器**,也是推薦使用的現代補丁語法。 * **原理**:一個統一的欄位,可同時處理「策略性合併補丁」和「JSON 補丁」,並透過 `target` 欄位精準鎖定要修改的資源。 * **用途**:進行任何形式的修改,從簡單的欄位值變更到複雜的列表元素操作。 * **範例 1:策略性合併 (Strategic Merge Patch)** 修改名為 `my-app` 的 Deployment,增加 CPU 請求。 `kustomization.yaml`: ```yaml patches: - path: increase-cpu.yaml # 補丁檔案路徑 target: kind: Deployment name: my-app ``` `increase-cpu.yaml`: ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-app spec: template: spec: containers: - name: my-app-container resources: requests: cpu: "250m" ``` * **範例 2:JSON 補丁 (JSON Patch) - 使用行內補丁** 修改 `my-app` 的 Deployment,替換第一個容器的映像檔。 `kustomization.yaml`: ```yaml patches: - target: kind: Deployment name: my-app patch: |- # 這是一種標準化的操作語法 - op: replace path: /spec/template/spec/containers/0/image value: nginx:stable ``` ----- ### 綜合實戰範例:Kustomize + Knative 多環境管理 讓我們用現代語法重新實作一個更完整的 Knative 應用場景。 **專案結構:** ``` knative-app/ ├── base/ │ ├── kustomization.yaml │ └── service.yaml └── overlays/ ├── staging/ │ └── kustomization.yaml └── production/ ├── kustomization.yaml └── patch-production-resources.yaml ``` **1. Base: 基礎 Knative Service** `base/service.yaml`: ```yaml apiVersion: serving.knative.dev/v1 kind: Service metadata: name: knative-greeter spec: template: spec: containers: - image: gcr.io/knative-samples/helloworld-go env: - name: TARGET value: "World" ``` `base/kustomization.yaml`: ```yaml resources: - service.yaml ``` **2. Staging Overlay: 測試環境** 我們希望測試環境: * 有 `staging-` 名稱前綴以避免衝突。 * 使用 `dev` 版本的映像檔。 * 將 `TARGET` 環境變數改為 `Staging`。 * 加上 `env: staging` 標籤。 `overlays/staging/kustomization.yaml`: ```yaml resources: - ../../base namePrefix: staging- commonLabels: env: staging images: - name: gcr.io/knative-samples/helloworld-go newTag: dev patches: - target: kind: Service name: knative-greeter patch: |- - op: replace path: /spec/template/spec/containers/0/env/0/value value: "Staging" ``` **3. Production Overlay: 生產環境** 我們希望生產環境: * 使用 `stable` 版本的映像檔。 * 設定更高的資源請求與限制。 * 將 `TARGET` 環境變數改為 `Production`。 `overlays/production/patch-production-resources.yaml`: ```yaml apiVersion: serving.knative.dev/v1 kind: Service metadata: name: knative-greeter spec: template: spec: containers: - name: helloworld-go env: - name: TARGET value: "Production" resources: requests: cpu: 200m memory: 256Mi limits: cpu: 500m memory: 512Mi ``` `overlays/production/kustomization.yaml`: ```yaml resources: - ../../base images: - name: gcr.io/knative-samples/helloworld-go newTag: stable patches: - path: patch-production-resources.yaml target: kind: Service name: knative-greeter ``` **如何使用?** 當您要部署到測試環境,只需執行:`kustomize build overlays/staging | kubectl apply -f -`。 當您要部署到生產環境,只需執行:`kustomize build overlays/production | kubectl apply -f -`。 透過這種方式,您用一套 `base` 和幾個描述差異的 `overlay`,清晰、可維護地管理了複雜的多環境應用配置。 ## 參考文件: - Knative: https://knative.dev/docs/serving/ - kustomization: https://kubectl.docs.kubernetes.io/references/kustomize/