在 Kubernetes 環境中,API 閘道器扮演著流量入口的角色,而 Knative 則提供無伺服器應用程式的佈署和管理平台。本文將探討如何結合這兩者,構建更具彈性且可擴充套件的應用程式架構。透過 API 閘道器,我們可以將流量路由到不同的 Knative Service,實作服務的分割與組合。同時,微服務架構的引入,可以提升應用程式的可靠性和效能,並允許針對不同服務的需求進行精細化的資源組態和調整。此外,文章也將探討無伺服器架構中常用的互補服務,例如 Redis、Memcached 等鍵值儲存系統,以及 Amazon S3、MinIO 等物件儲存服務,說明它們如何與 Knative 協同工作,滿足應用程式的各種需求,例如資料快取、狀態儲存、非同步任務處理等。最後,我們將探討任務佇列和工作流程管理在無伺服器架構中的重要性,並提供實際程式碼範例,說明如何使用任務佇列延遲任務執行,以及如何設計工作流程以管理依賴任務。
API 閘道器與應用程式組成
在生產環境的 Kubernetes 佈署中,通常會使用 ingress 實作(如 Istio、nginx 或 Contour)來分割流量。由於我們使用的是 kn quickstart 的小型 Kubernetes 安裝,因此可以自行編寫 ingress。我們的設定將如圖 2-3 所示,每個形狀都是其自己的 Knative Service。
簡易 API 閘道器實作
最簡單的入門方法是佈署相同的容器兩次,但將流量分開,以便一個容器接收根和靜態 URL,另一個容器(即「API」服務)接收 /my/ 下的所有內容。繼續以 Python 為例,我們可以使用範例 2-14 中的程式作為 API 閘道器。
範例 2-14:gateway 容器的 main.py
from flask import Flask
from urllib import request
app = Flask(__name__, static_folder=None)
@app.route("/", defaults={"path":""})
@app.route("/<path:path>")
def route_to_static(path):
resp = request.urlopen(f"http://static-app.default.svc/{path}")
return (resp.read(), resp.status, resp.getheaders())
@app.route("/my/dashboard")
def route_to_api():
resp = request.urlopen("http://api-app.default.svc/my/dashboard")
return (resp.read(), resp.status, resp.getheaders())
內容解密:
- Flask 應用程式初始化:使用
Flask(__name__, static_folder=None)初始化應用程式,並停用靜態檔案資料夾。 - 路由設定:定義了兩個路由,一個用於靜態內容,另一個用於 API 請求。
route_to_static函式將請求轉發到http://static-app.default.svc/{path}。route_to_api函式將/my/dashboard請求轉發到http://api-app.default.svc/my/dashboard。
- 使用 urllib 傳送請求:
request.urlopen用於向指定的 URL 傳送請求,並傳回回應內容、狀態碼和標頭。
路由注意事項
由於 Knative Services 使用叢集範圍內的 ingress 基礎設施進行路由,因此需要在路由層設定 Host 標頭,以便 Knative 知道將請求路由到哪個 Service。使用 Python 的 urllib.request.urlopen 會自動設定 Host 標頭。
對於不同的 ingress 實作,需要使用不同的組態方法來設定 Host 標頭:
- Istio:使用
rewrite.authority引數。 - nginx:使用
nginx.ingress.kubernetes.io/upstream-vhost註解。 - Contour:使用
requestHeadersPolicy設定 Host 標頭。 - Gateway API:使用
filters.requestHeaderModifier提供標頭名稱和值的對映。
雖然我們的範例路由器支援簡單的路由,但專用的 HTTP 路由器(如 Envoy)將具有更好的效能和更多的功能。
將 API 分解為元件
目前的應用程式只有一個 API 端點,因此沒有太多需要分離的內容。然而,如果繼續使用目前的 API 設計,最終會得到一個單一的 API 端點,該端點從多個系統收集儀錶板方塊並將其聚合成單一的回應物件。
範例 2-15:將多個資料來源聚合成單一回應
@app.route("/my/dashboard")
def serve_content():
response = []
for function in dashboard_functions:
response.append(function())
return {"items": response}
內容解密:
- 聚合資料:遍歷
dashboard_functions列表中的每個函式,並將其結果新增到response列表中。 - 傳回回應:將聚合的資料以 JSON 格式傳回給客戶端。
這種設計存在一些問題:
- 可靠性:如果任何一個元件失敗或丟擲異常,可能會破壞整個應用程式。
- 效能:將所有後端呼叫捆綁在一起意味著所有後端資料都受限於最慢的後端資料。
解決方案是將 API 分解為不同的端點,並讓客戶端邏輯將它們組合在一起。最簡單的方法是更改 /my/dashboard URL 以傳回客戶端可以呼叫的“擴充套件 URL”列表,以取得單個儀錶板條目。
URL 路由結構
| URL | 後端服務 |
|---|---|
| /my/dashboard | Account |
| /weather/ | Weather |
| /openissues/ | GitHub issues |
| /news/ | News feed |
| /ai-art/ | AI-generated art |
此結構將工作分解為多個部分,可以應用 API 閘道器和無伺服器設計原則進行最佳化。
伺服器無狀態應用的設計優勢與互補服務
微服務架構的優勢
將大型服務拆分為多個微服務,不僅能提高系統的可靠性和降低延遲,還能更靈活地調整各個服務的操作設定。例如,GitHub 問題服務可能需要較長的回應逾時時間以適應 API 速率限制,而天氣服務的限制較低,而新聞推播服務則可能完全不需要速率限制。透過 Knative Service 規格中的 timeoutSeconds 值,我們可以根據這些需求進行調整。
此外,還可以使用 autoscaling.knative.dev/max-scale 註解將 GitHub 元件限制在三個副本以內,因為更多的副本只會觸及速率限制。同樣地,我們也可以根據元件的不同需求調整其他執行引數。例如,如果生成 AI 藝術是一個密集的過程,我們可以將 containerConcurrency 設定為較低的值,以限制同時生成的藝術品數量,並可能在資源請求中新增 GPU。
API 閘道器與伺服器無狀態應用的協同作用
API 閘道器可以透過 API 路由來建立合適的微服務並最佳化資源,從而提高伺服器無狀態應用的價值。同時,伺服器無狀態應用也可以透過提供自然且低摩擦的 API 擴充端點來提高 API 閘道器的價值。
擴充代理伺服器
API 閘道器通常支援多種內建功能,例如 API 金鑰速率限制、驗證身份驗證令牌以及將負載從一種格式轉換為另一種格式。然而,應用程式有時會有獨特的需求,無法滿足通用 API 閘道器的功能。例如,將資料轉換為某種特殊格式,或是根據後端裝置資料函式庫驗證授權令牌。
許多 API 閘道器提供了在明確的請求處理點進行擴充的機制,允許閘道器透過類別似 webhook 的請求呼叫外部服務,或是在閘道器程式中執行少量的專門程式碼。
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: webhook-service
spec:
template:
spec:
containers:
- image: webhook-image
ports:
- containerPort: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
內容解密:
此 YAML 檔案定義了一個 Knative Service 物件,用於佈署一個名為 webhook-service 的服務。該服務使用 webhook-image 映象,並指定了容器連線埠為 8080。同時,定義了資源請求和限制,包括 CPU 和記憶體的使用量。這樣的設定使得 webhook 服務能夠根據需求進行擴充套件,並且能夠有效地管理資源使用。
Knative 與 Webhook 的整合
像 Knative 這樣的無伺服器平台非常適合實作 webhook,因為它們專門用於回應無狀態的 HTTP 請求。使用 FaaS 模型,可以輕鬆地建立、迭代和佈署用於身份驗證、授權或負載轉換的 API 擴充 hook。
互補服務
大多數無伺服器執行時平台都是為無狀態應用程式設計的,這些應用程式將任何有意義的狀態儲存在外部資料儲存中。即使對於這些應用程式,也不能說它們完全不儲存任何狀態——在處理請求期間,會使用當前的請求上下文、呼叫堆積疊、載入的函式庫等。然而,無狀態應用程式避免依賴內部狀態,而是將狀態儲存在外部儲存系統中,例如資料函式庫或訊息佇列。
鍵值儲存
鍵值儲存系統相比傳統的資料函式庫系統具有相對簡單的介面。所有資料都透過應用程式定義的鍵來存取,這些鍵提供了對一個或多個與鍵相關的值的存取。透過要求所有對資料的存取都透過單一鍵進行,應用程式可以透過將每個鍵分配給特定的伺服器來分散對多個儲存伺服器的存取。這使得分散式鍵值儲存能夠擴充套件到高並發存取級別,支援無伺服器應用程式實作水平擴充套件。
import redis
# 建立 Redis 連線
client = redis.Redis(host='localhost', port=6379, db=0)
# 設定鍵值對
client.set('key', 'value')
# 取得鍵對應的值
value = client.get('key')
print(value.decode('utf-8')) # 輸出:value
內容解密:
這段 Python 程式碼展示瞭如何使用 Redis 鍵值儲存系統。首先,建立了一個到本地 Redis 伺服器的連線。然後,使用 set 方法設定了一個鍵值對,其中鍵為 'key',值為 'value'。接著,使用 get 方法檢索該鍵對應的值,並將其列印出來。這段程式碼演示了鍵值儲存的基本操作,包括寫入和讀取資料。
無伺服器架構中的補充服務
鑰匙值儲存系統的擴充套件優勢
在無伺服器架構中,後端服務的擴充套件性是關鍵挑戰之一。傳統的資料函式庫系統通常無法與應用伺服器同步擴充套件,因為它們需要建立大量的連線。例如,當前端系統動態擴充套件時,啟動200或500個例項可能會導致許多例項在初始化後端資料函式庫連線時失敗。這種「非彈性擴充套件」問題在伺服器less系統中尤其突出。
相對地,2000年以後發展的分散式儲存系統採用了分片技術(sharding),能夠像應用伺服器一樣水平擴充套件。分片技術將儲存系統中的鍵分配到不同的伺服器上,只要應用程式的存取模式均勻分佈在不同的鍵上,應用程式和儲存都可以隨著流量的增加而水平擴充套件,如圖2-5所示。
鑰匙值儲存的型別與應用
流行的鑰匙值儲存系統包括快速記憶體快取(如Redis、memcached)和永續性、複製資料儲存(如Cassandra、MongoDB)。無伺服器應用程式可能會根據資料的永續性和存取需求使用其中一種或兩種型別的儲存,有時還會結合外部連線等做法。
在我們的範例應用程式中,我們可以透過預取請求資料並將其快取在Redis等系統中來解決一些挑戰,同時設定過期時間。我們的儀錶板應用程式還可以使用更持久的鑰匙值儲存來記錄每個使用者的儀錶板設定或其他資訊,如功能或使用者憑證。
程式碼範例:使用Redis快取資料
import redis
# 連線到Redis伺服器
redis_client = redis.Redis(host='localhost', port=6379, db=0)
def cache_dashboard_data(query_string, data):
# 將儀錶板資料快取在Redis中
redis_client.set(query_string, data, ex=3600) # 快取1小時
def get_cached_dashboard_data(query_string):
# 從Redis取得快取的儀錶板資料
return redis_client.get(query_string)
#### 內容解密:
1. **連線到Redis伺服器**:使用`redis.Redis`函式連線到本地的Redis伺服器,指定主機、埠和資料函式庫。
2. **`cache_dashboard_data`函式**:將查詢字串和對應的資料快取在Redis中,並設定1小時的過期時間。
3. **`get_cached_dashboard_data`函式**:根據查詢字串從Redis取得快取的資料。
物件儲存:無伺服器架構中的關鍵元件
物件儲存(如Amazon S3、MinIO、Google Cloud Storage或Azure Blob Storage)是鑰匙值儲存的一種特殊形式,專注於儲存二進位制物件及其元資料。它們通常被組織成儲存桶,每個儲存桶包含多個物件,這些物件由字串路徑描述。與傳統檔案系統不同,物件路徑不是層次結構;每個物件都是儲存桶中的獨立條目。
物件儲存提供了一種可擴充套件的網路抽象,用於儲存檔案和物件資料。它們簡化了儲存API,避免了許多困難的邊緣案例。許多物件儲存系統還提供事件通知,用於建立和刪除操作,這些通知可用於觸發無伺服器工作流程。
Plantuml物件儲存架構
圖表翻譯: 此圖示展示了客戶端如何上傳物件到物件儲存服務,並觸發無伺服器函式進行資料處理,最終將處理結果存入資料函式庫。
計時器服務:排程任務的無伺服器解決方案
傳統應用程式通常使用背景程式中的計時器來週期性執行任務。然而,在無伺服器環境中,程式只在回應直接工作請求時執行,因此需要將計時和排程工作執行匯出到外部程式。計時器服務可以提交週期性的工作單元到無伺服器執行環境,從而實作按需擴充套件。
計時器服務可以表現為靜態的cron-like排程服務,也可以是更細粒度的未來排程通知服務。後者開始與任務佇列領域重疊。
任務佇列:管理延遲工作執行
任務佇列是一項服務,它接受工作訂單並在未來某個時刻執行,由佇列管理。雖然大多數無伺服器程式碼執行環境旨在透過水平擴充套件計算例項來盡快完成工作,但任務佇列卻透過延遲工作的時間維度來擴充套件工作負載。
程式碼範例:使用任務佇列延遲任務執行
from celery import Celery
app = Celery('tasks', broker='amqp://guest@localhost//')
@app.task
def delayed_task(data):
# 延遲執行的任務邏輯
print(f"Processing data: {data}")
# 延遲任務執行
delayed_task.apply_async(args=['some_data'], countdown=60) # 延遲1分鐘執行
#### 內容解密:
1. **建立Celery應使用案例項**:使用`Celery`類別建立一個名為`tasks`的應使用案例項,指定訊息代理(broker)為本地RabbitMQ服務。
2. **`delayed_task`函式**:定義一個延遲執行的任務,使用`@app.task`裝飾器將其註冊為Celery任務。
3. **延遲任務執行**:使用`apply_async`方法延遲執行`delayed_task`任務,傳遞引數並設定倒數計時(countdown)為60秒,即1分鐘後執行。
無伺服器架構中的任務佇列與工作流程管理
在設計無伺服器應用程式時,任務佇列(Task Queues)與工作流程(Workflows)是兩種至關重要的元件。它們能夠有效地管理非同步任務,確保應用程式的可擴充套件性和可靠性。
任務佇列:延遲執行與非同步處理
任務佇列允許開發者將工作延遲到未來執行,以維持特定的執行個體或處理中的預算。這種機制對於需要快速回應的延遲敏感型應用程式尤為重要,因為它可以將非交易性工作延遲執行,從而在完成延遲敏感請求後再進行處理。
任務佇列的實務應用
結合快取結果儲存在鍵值儲存(Key-Value Storage)中,任務佇列可以用來在背景中重新整理儀錶板結果,如下例所示:
@app.route("/weather/<zip:zip>")
def get_weather(zip):
weather = json.loads(redis.get(f"weather-{zip}"))
# JSON 物件包含過期時間和預報
if weather.expires < time.now() + time.duration(5, "minutes"):
# 在過期前重新整理天氣資料
queue.enqueue(f"/fetchweather/{zip}")
return weather.forecast
@app.route("/fetchweather/<zip:zip>")
def fetch_weather(zip):
# ... 從 NOAA 擷取天氣資料並儲存到 Redis
內容解密:
get_weather函式:首先從 Redis 中擷取指定郵遞區號的天氣資料。如果資料即將過期,則將重新擷取天氣資料的任務加入佇列。fetch_weather函式:負責從 NOAA 擷取最新的天氣資料並更新到 Redis 中。- 使用任務佇列的好處:能夠在不影響主要請求回應速度的情況下,非同步地更新資料。
工作流程:管理依賴任務
工作流程定義了一系列依賴的工作專案,後續工作專案的內容取決於先前工作專案的結果。某些系統允許使用者根據先前工作專案的結果定義邏輯步驟,或將一個工作專案的結果複製到後續的工作階段。
工作流程的特點
- 狀態機或流程圖定義:工作流程通常使用狀態機或流程圖來定義,而不是在應用程式碼中定義。
- 多語言支援:由於工作流程是透過狀態機或流程圖定義的,因此可以支援多種程式語言。
- 約束條件的執行:有助於執行諸如「工作負載必須終止」之類別的約束條件。
工作流程執行的範例
@startuml
skinparam backgroundColor #FEFEFE
skinparam sequenceArrowThickness 2
title Knative 無伺服器應用架構與 API 閘道器整合
actor "客戶端" as client
participant "API Gateway" as gateway
participant "認證服務" as auth
participant "業務服務" as service
database "資料庫" as db
queue "訊息佇列" as mq
client -> gateway : HTTP 請求
gateway -> auth : 驗證 Token
auth --> gateway : 認證結果
alt 認證成功
gateway -> service : 轉發請求
service -> db : 查詢/更新資料
db --> service : 回傳結果
service -> mq : 發送事件
service --> gateway : 回應資料
gateway --> client : HTTP 200 OK
else 認證失敗
gateway --> client : HTTP 401 Unauthorized
end
@enduml圖表翻譯: 此圖示展示了一個簡單的工作流程。首先檢查天氣資料是否過期,如果過期則重新擷取資料並更新到 Redis,最後傳回預報結果。