深入理解 Knative 的運作機制需要先認識其底層的硬體管理和叢集排程機制,這些機制為 Knative 提供了基礎執行環境,使其能夠有效地管理和排程資源。硬體管理階層負責處理節點的新增、移除、修復和升級,而叢集排程則將所有節點視為一個統一的計算資源池,並提供流程的排程和容錯移轉能力。Knative 使用 Kubernetes 作為底層排程系統,並利用 Envoy 作為高效能的 HTTP 負載平衡器。Knative Serving 則根據容器化應用程式,提供無狀態、提供 HTTP 服務的執行環境。
第三章:深入瞭解 Knative
在第二章中,我們使用 kn quickstart 命令快速體驗了 Knative,但我們的重點是讓 Knative 叢集執行起來,而不是探討其工作原理。雖然無伺服器架構的目標是讓開發者免於管理底層系統的日常細節,但瞭解一般的請求流程和模式有助於評估特定的模式是否適合使用。
不同的基礎設施供應商對特定的無伺服器細節實作方式不同,但許多主要的無伺服器平台在無伺服器運算和事件分發方面實作的功能相當相似。在本章中,我們將以 Knative Serving 為例來說明前者,以 Knative Eventing 為例來說明後者。Knative 是一個流行的開源無伺服器平台,建構於 Kubernetes 之上;它易於安裝,並且可以水平擴充套件以提高吞吐量和可靠性。
許多開源軟體中的原則可以直接應用於服務供應商提供的託管服務;我還將指出與流行的託管平台之間的一些特殊區別。Knative 的一個獨特之處在於 Serving 和 Eventing 專案是獨立的:你可以只使用 Knative Serving 來實作 REST API(例如),或者只使用 Knative Eventing 將事件傳遞給傳統的基礎設施元件,而無需安裝 Serving。
專注於開源軟體還允許你實際下載並重建原始碼,以探索不同的實作選擇。許多這些實作選擇也記錄在專案的 issue、檔案和 pull request(PR)歷史中,所有這些都可以免費在 GitHub 上取得。
基礎設施假設
直接探討 Knative 的工作原理很誘人,但首先有必要介紹一下執行 Knative(和其他無伺服器系統)的基礎設施背景。雖然可以從頭開始建立一個完全自訂的無伺服器執行環境,但通常將複雜系統分層建立會比較有幫助。對於無伺服器平台來說,這些層可能包括某種形式的對底層硬體、作業系統和程式執行層的抽象化,這樣排程和自動擴充套件等元件就可以抽象地操作,而不必糾結於核心升級、硬體修復等細節。
硬體和作業系統管理
在最基本的層面上,電腦硬體需要軟體來使其變得有用。底層硬體管理通常被稱為「作業系統」,但在現代資料中心伺服器(以下簡稱「節點」)中,可能有數十個相互協作的處理器,它們都有自己的軟體需要保持更新:網路、儲存和加速器的裝置韌體、BIOS 和核心補丁,以及像 Linux 這樣的作業系統來協調所有這些資源並將它們暴露給使用者空間程式。
Kubernetes 與 Envoy 的角色
Kubernetes 和 Envoy 提供了多種抽象化,使得 Knative 無需重新發明輪子。Envoy 是一個高效能的 HTTP 負載平衡器,由多個組態 API 驅動。這些 API 允許其他專案專注於暴露和整合負載平衡器功能,而無需實作這些功能。同樣,Kubernetes 提供了一個可擴充套件、可程式化的平台,用於執行電腦叢集。Kubernetes 具有高度的可插拔性,並且建立在通用 API 資源的概念之上,這些資源可以由外部元件管理,而不是預定義的內建工作流程。
Knative 的架構
Knative 使用 Kubernetes 作為底層排程系統,但也可以使用其他叢集排程系統,如 Apache Mesos 或 Google 的 Borg。同時也可以使用像 Apache 的 Hadoop 或 Flink 這樣的資料執行系統作為排程器,儘管即時效能可能會受到影響。
Knative架構圖
圖表翻譯: 此圖示展示了Knative的基本架構,包括使用者請求如何透過Kubernetes和Knative Serving到達應用程式,以及Knative Eventing如何處理事件。
Knative 技術深度解析:硬體管理與叢集排程
在探討 Knative 的運作原理之前,我們需要了解其背後的硬體管理和叢集排程機制。這些機制為 Knative 提供了基礎的執行環境,使其能夠有效地管理和排程資源。
硬體管理階層的重要性
實際的電腦硬體並非完美無缺,它們容易受到溫度、電源消耗以及零件磨損等因素的影響。隨著時間的推移,部分硬體會出現故障,或者需要升級以增加容量。因此,一個完善的硬體和作業系統管理階層必須考慮到這些需求,以便實作節點的新增、移除、修復和升級。
在某些環境中,虛擬化管理軟體可能是節點管理的一部分;而在其他情況下,「裸金屬」硬體會直接暴露給下一層的管理系統。無論採用何種方式,上層平台都不應直接接觸到用於管理、建立和刪除節點的工具;這些介面被隱藏在節點管理 API 之後。
叢集排程與「資料中心即電腦」的概念
當我們能夠統一管理各個節點時,我們就具備了基礎設施管理堆積疊的第一層。下一層是排程和流程管理 API,用於描述哪些應用程式應該在硬體叢集上執行。這一層的目標是將所有節點層級的資源視為一個統一的計算資源池,這種模式被稱為「資料中心即電腦」。
雖然個別流程仍會被排程到特定的節點上(並受到該節點可用資源的限制),但這種排程器提供了在第一個節點故障或需要停用時,在新節點上重新啟動流程的能力。
為叢集排程編寫的應用程式
為叢集層級的排程方案編寫的應用程式必須依賴於與為單一電腦編寫的應用程式不同的假設:應用程式必須標準化網路通訊,準備好應對隨時可能出現的一個或多個流程的故障,並處理當流程被檢測為故障時的應用程式狀態還原和修復。如今,這些能力通常被視為構建分散式系統的必要成本,但對於之前主要從事客戶端或大型主機應用程式開發的開發人員來說,這可能是一個相當大的學習曲線。
實作叢集排程
實作叢集排程通常需要兩個元件協同工作:節點層級的代理(如 Kubernetes 中的 kubelet)和叢集層級的排程器(它本身可能是一個分散式應用程式)。節點層級的代理負責監控節點的健康狀況,啟動排程到該節點的流程,並向叢集層級的排程器報告狀態。
節點層級代理與叢集層級排程器的工作流程
圖表翻譯: 此圖示展示了節點層級代理與叢集層級排程器的工作流程。節點層級代理負責監控節點健康狀況並啟動排程流程,而叢集層級排程器則負責分配網路位址和儲存資源,並提供 API 介面供應用程式使用。
Knative Serving:無伺服器程式碼執行執行時
Knative Serving 根據這樣的理念構建:許多應用程式可以實作為無狀態、提供 HTTP 服務的容器。這種設計建立在十二要素應用程式宣言以及早期 Google App Engine 和 Cloud Foundry 等平台的工作基礎上。這些平台主要專注於處理根據 Web 的應用程式。
隨著 OCI 容器作為一種封裝和執行格式的興起和標準化(由 Docker 和 Kubernetes 推動),容器成為跨不同實作封裝應用程式的可移植方式。
應用程式描述與控制平面
然而,僅有執行時的執行合約還不夠清楚如何向無伺服器平台描述應用程式。重要的資訊往往並未被捕捉在編譯後的工件中,例如「您希望使用什麼名稱來存取應用程式」或「一個程式需要多少記憶體」。這些關於應用程式的資訊,有時被稱為後設資料,是控制平面的一部分。
控制平面包含了所有管理執行應用程式意圖的高階系統,但不需要參與每個 HTTP 請求。根據 Kubernetes,Knative 使用 Kubernetes 自定義資源來定義圍繞應用程式容器的意圖。
Knative Serving 技術深度解析
Knative Serving 是 Knative 的核心元件之一,主要負責管理無伺服器應用的生命週期和流量控制。在探討 Knative Serving 的運作機制之前,我們需要了解其控制平面(Control Plane)和資料平面(Data Plane)的概念。
控制平面概念
Knative 的控制平面定義了多層級的物件,以實作對無伺服器堆積疊的不同層級管理。這些物件包括:
服務(Services)
服務提供了一個高階的無伺服器應用程式定義。它們為常見的使用案例提供了簡單的管理體驗。一個服務管理著兩個不同的底層資源:路由(Route)和組態(Configuration)。
路由(Routes)
路由管理入站請求的路由組態,將主機名稱對映到特定的應用程式版本。路由負責根據指定的流量百分比,將流量分配到不同的修訂版本(Revisions)。
組態(Configurations)
組態描述了應用程式的當前期望狀態,並作為修訂版本的範本。每次更新組態時,都會建立一個新的修訂版本,以捕捉應用程式的當前期望狀態。
修訂版本(Revisions)
修訂版本是應用程式組態的不可變檢查點。它們充當縮放單位和流量目標。Knative 自動管理修訂版本的建立和垃圾回收,使用者無需主動管理修訂版本。
Knative Serving 資源關係圖
圖表翻譯: 此圖示展示了 Knative Serving 中的資源關係,包括服務、路由、組態和修訂版本之間的關聯。
請求生命週期
當請求到達 Knative 時,會經過多個元件的處理。以下是請求生命週期的詳細解析:
- 負載平衡器: 請求首先到達 Kubernetes 叢集邊緣的負載平衡器。Knative 使用 HTTP 負載平衡器將流量路由到不同的修訂版本。
- Activator: 如果沒有可用的 Pod,請求會被轉發到 Activator 元件。Activator 負責緩衝請求,並向 Autoscaler 傳送訊號,以擴充套件或縮減 Pod。
- Queue-Proxy: 每個 Pod 中包含一個 Queue-Proxy 容器,用於與 Autoscaler 通訊並處理負載。
程式碼範例
from knative import Client
# 建立 Knative Client
client = Client()
# 定義服務
service = client.create_service(
name="example-service",
namespace="default",
traffic=[
{"revisionName": "example-revision", "percent": 100}
]
)
print(service)
內容解密:
此範例程式碼展示瞭如何使用 Knative Client 建立一個新的服務。其中,create_service 方法用於建立服務,並指定了流量分配規則,將 100% 的流量分配到 example-revision 修訂版本。
Knative Serving 技術深度解析
負載平衡器與叢集入口層
在 Knative Serving 架構中,叢集入口層(cluster ingress)扮演著至關重要的角色。該層不僅負責處理進入叢集的 HTTP 請求,還與其他非 Serverless 應用分享。Knative Serving 利用此入口層實作一般性和 Knative 特有的功能。大多數提供 HTTP 服務的 Kubernetes 叢集都會安裝入口提供者,原因如下:
- 提供 HTTP 協定版本轉換:從客戶端應用程式支援的 HTTP 版本轉換到伺服器支援的版本。較新的 HTTP 版本(如 HTTP/2 或 HTTP/3)實作了更複雜的流量控制機制,以提高在擁塞和高延遲網路鏈路上的效能。
- 實施最佳實踐並保護應用程式免受協定級攻擊:透過規範化請求標頭和 URL,以及實施針對協定級攻擊的防禦措施。
- 終止和管理 TLS 連線和憑證:以一致的方式管理 TLS 連線和憑證,使個別應用程式無需實作 TLS。
- 使用單一入口可以節省 IPv4 位址空間:透過使用單一路由器搭配多個憑證,並根據 Host 標頭路由請求,可以在單一 IP 位址後託管數百或數千個應用程式。
Knative 還利用一些進階路由器的能力來實作 Revision 之間的流量分離。具體做法是定義多個後端目標,並根據所需的流量比例分配權重,然後將流量分離決策編碼在自定義標頭中,以確保該決策只被執行一次。
內容解密:
- 叢集入口層分享:與其他非 Serverless 應用分享,提高資源利用率。
- HTTP 協定版本轉換:使客戶端與伺服器之間的通訊更有效率。
- 安全性增強:抵禦協定級攻擊,保護應用程式。
- TLS 管理:統一管理憑證和連線,提高安全性。
Activator(啟動器)元件
Knative Serving 的 Activator 是分享的代理池,作為 Revision 的容量緩衝區。當 Activator 接收到請求時,它會嘗試將請求路由到活躍的 Revision 例項。如果當前沒有可用的 Revision 例項,它會將請求加入佇列(讀取 HTTP 標頭但不轉發請求),並向 Autoscaler 發出訊號,表示有佇列中的請求等待處理。
Activator 的主要功能:
- 請求佇列管理:Activator 維護一個請求佇列,直到有可用的後端例項或請求超時。
- 動態擴縮減支援:當 Revision 容量不足時,Activator 被組態為目標,以緩衝額外的請求並支援從零例項開始擴充。
- 效能指標回報:Activator 回報即時和總請求數等指標,供 Autoscaler 使用。
程式碼範例:
import requests
def simulate_request_routing(revision_url, activator_url):
# 模擬請求路由到 Activator
response = requests.get(activator_url)
if response.status_code == 200:
# 請求成功轉發到 Revision
print("Request successfully routed to Revision.")
else:
# 請求失敗,可能需要擴充
print("Request failed, scaling may be needed.")
# 使用範例
simulate_request_routing("http://example-revision.com", "http://activator.knative-serving.svc")
內容解密:
simulate_request_routing函式:模擬將請求路由到 Activator,並檢查是否成功轉發到 Revision。- 錯誤處理:如果請求失敗,可能需要觸發擴充機制。
- 實際應用場景:可用於測試 Knative Serving 的流量管理和擴充功能。
Autoscaler(自動擴縮減器)元件
Autoscaler 負責根據 Activator 回報的指標動態調整 Revision 的例項數量。當有新的請求到達且當前例項無法處理時,Autoscaler 會啟動新的例項以滿足需求。
Knative 技術深度解析:Activator 與 Queue-Proxy 核心元件
Knative 是一個建構於 Kubernetes 之上的無伺服器(Serverless)平台,其設計核心在於提供高效、彈性的應用程式佈署與管理能力。在 Knative 的運作架構中,Activator 與 Queue-Proxy 是兩個不可或缺的關鍵元件,它們負責請求的路由、負載平衡、並發控制以及應用程式生命週期的管理。
Activator:動態調整請求路由
Activator 是 Knative 中的重要元件,主要負責在應用程式縮減至零例項(scale-to-zero)時接收請求,並將其轉發至適當的應用程式例項。當應用程式處於閒置狀態(即沒有例項執行)時,Activator 會接收進入的請求,並觸發應用程式的啟動。同時,它還負責在應用程式輕載時保持在請求路徑中,以最佳化負載平衡和減少入口控制平面的變更次數。
Activator 的優勢
- 當
Activator處於請求路徑中時,Kubernetes Service 會指向叢集中Activator的子集。這種子集化的設計使得連線被聚合到少數代理(proxies),從而能夠使用諸如「最小負載」(least-loaded)之類別的負載平衡演算法,而無需額外的協調開銷。經基準測試驗證,這種方法能夠減少在使用少量應用程式伺服器時的延遲變化。 - 重新程式設計入口控制平面(ingress control plane)可能相當昂貴。透過延遲後端變更,直到應用程式處於持續負載之下,
Autoscaler能夠減少入口控制平面的變動次數。
停用 Activator 的場景
在某些情況下,可以完全停用 Activator,例如:
- 當縮減至零並非必要時,可以透過將突發閾值(burst threshold)設為零並為每個修訂版本(Revision)設定最小例項數來停用
Activator。 - 這種組態適用於需要高用性的系統,即使在 Kubernetes 控制平面宕機或應用程式啟動時間較長的情況下。
Queue-Proxy:請求處理與並發控制
Queue-Proxy 是 Knative Serving 控制平面自動新增到應用程式容器旁邊的 sidecar 容器。它實作了例項級別的請求處理功能,包括生命週期管理、請求度量和並發執行控制。與 Activator 不同,Queue-Proxy 總是處於請求路徑中,即使在應用程式高負載下也是如此。
Queue-Proxy 的主要職責
- 報告請求度量:將請求度量報告給
Autoscaler和任何組態的監控系統。由於Queue-Proxy與應用程式容器位於同一 Pod 中,因此能夠測量應用程式延遲,而不受網路排隊延遲的影響。 - 實作硬性並發限制:如果需要,可以強制執行請求並發限制。由於
Queue-Proxy與應用程式容器是一對一的,因此能夠輕鬆跟蹤正在進行的並發請求數量。如果超出並發限制,Queue-Proxy可以將請求排隊,直到應用程式完成其他正在進行的請求。 - 快速檢測就緒狀態:在啟動時快速檢測應用程式的就緒狀態。預設的 Kubernetes 行為是最多每秒檢查一次應用程式的就緒狀態,這可能會增加冷啟動延遲。
Queue-Proxy實作了更積極的子秒級探測,直到就緒探測傳回成功,然後將探測頻率降低到請求的速率。 - 優雅關閉:使用「跛腳鴨」(lame duck)技術實作優雅關閉,即繼續處理現有請求直到完成,但拒絕新請求。這種技術確保了在 Kubernetes 中協調終止寬限期(terminationGracePeriodSeconds)引數和應用程式關閉程式碼,從而簡化了應用程式容器的離開流程。
Queue-Proxy 與 Activator 的協同工作
當 Activator 找到具有可用容量的修訂版本例項時,它會將請求轉發給 Queue-Proxy。後者位於應用程式容器旁邊,並執行最終級別的處理,然後將請求路由到應用程式。
@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle
title Knative 架構深入解析與請求生命週期
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圖表翻譯: 此圖示呈現了客戶端請求到達 Knative 系統後,如何經過 Ingress、Activator 和 Queue-Proxy 最終到達應用程式容器的流程。