在 Kubernetes 環境中進行實驗佈署,可以選擇將實驗程式碼與生產程式碼整合在同一個二進位制檔案中,或是獨立佈署多個不同版本的服務。前者簡便易行,但實驗程式碼的錯誤可能影響生產環境穩定性;後者透過服務網格技術路由流量,更具彈性與穩健性,但也更為複雜。監控實驗服務的成功率至關重要,服務網格可以根據監控結果動態調整流量路由,將實驗失敗的影響降至最低。Kubernetes 的 Operator 模式簡化了應用程式的開發和佈署,Operator 將應用程式的操作知識嵌入其中,實作自動化管理和生命週期控制。Operator 特別適用於需要高度客製化和自動化的應用程式。
實驗佈署策略與最佳實踐
在軟體開發和服務營運中,實驗是瞭解變更對使用者經驗影響的重要手段。實驗的佈署方式對其實施效果和效率有著直接影響。本篇文章將探討實驗佈署的兩種主要方法:將實驗程式碼與生產程式碼放在同一個二進位制檔案中,以及佈署多個不同版本的服務。
實驗程式碼與生產程式碼整合的優缺點
將實驗程式碼與控制程式碼放在同一個二進位制檔案中的優點是簡單易行,便於將實驗功能推向生產環境。然而,這種方法的缺點在於,如果實驗程式碼不穩定並導致當機,也會影響生產流量。此外,由於任何變更都與服務的完整版本繫結,因此更新實驗或推出新實驗的速度較慢。
程式碼範例:簡單的實驗控制邏輯
import random
def route_request(request):
if random.random() < 0.1: # 10% 的流量進入實驗組
return experimental_handler(request)
else:
return production_handler(request)
def experimental_handler(request):
# 實驗處理邏輯
pass
def production_handler(request):
# 生產處理邏輯
pass
內容解密:
route_request函式根據隨機數決定請求是進入實驗組還是生產組。experimental_handler和production_handler分別代表實驗和生產環境下的請求處理邏輯。- 這種方法簡單易實作,但可能影響生產環境的穩定性。
獨立佈署實驗服務
另一種方法是佈署兩個或多個不同版本的服務。生產服務接收大部分流量,而實驗服務接收小部分流量。透過服務網格(Service Mesh)技術,可以將小部分流量路由到實驗服務。這種方法雖然實施起來更複雜,但比將實驗程式碼包含在生產二進位制檔案中更靈活、更穩健。
服務網格路由流量
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam rectangleBackgroundColor #F5F5F5
skinparam rectangleBorderColor #333333
skinparam arrowColor #333333
title 服務網格路由流量
rectangle "請求" as node1
rectangle "90% 流量" as node2
rectangle "10% 流量" as node3
node1 --> node2
node2 --> node3
@enduml
此圖示展示了服務網格如何將客戶端請求根據比例路由到生產服務和實驗服務。
監控與反饋
在使用獨立佈署的實驗服務時,監控實驗服務的成功率非常重要。如果實驗程式碼開始失敗,服務網格可以快速將其從流量路由中移除,以最小化對使用者的影響。為此,需要確保實驗基礎設施的監控獨立於標準生產監控,以便及時發現實驗中的失敗。
實施 Operator:擴充套件 Kubernetes 的關鍵
Kubernetes 的一個關鍵原則是其能夠透過 Operator 模式擴充套件核心 API。許多開發者認為這種可擴充套件性是 Kubernetes 在市場上佔據主導地位的一個重要因素。本章旨在介紹 Operator 的概念,並探討何時以及為什麼要在您的環境中實施 Operator。
Operator 模式的出現
2016 年,由 CoreOS(現為 Red Hat)長官的一組 Kubernetes 貢獻者提出了 Operator 模式,以簡化 Kubernetes 應用程式的開發和實施。Operator 模式定義了一種方式,用於封裝、佈署和維護與 Kubernetes API 和客戶端工具(如 kubectl)整合的應用程式。
Operator 的優勢
- 原生整合:Operator 可以原生地建立在 Kubernetes 上執行的應用程式,並與現有的 Kubernetes 流程整合。
- 知識嵌入:Operator 不僅可以佈署應用程式,還可以實作平滑升級、跨不同服務的協調、定製擴充套件流程,並將可觀察性嵌入複雜系統中。
何時實施 Operator
雖然本章不旨在教授如何編寫 Operator,但瞭解何時以及為什麼要實施 Operator 是非常重要的。Operator 特別適合於需要在 Kubernetes 環境中進行高度定製化和自動化的應用程式。
程式碼範例:簡單的 Operator 邏輯
func (r *ReconcileMyApp) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
// 取得 MyApp 物件
myapp := &myappv1.MyApp{}
err := r.Get(ctx, request.NamespacedName, myapp)
if err != nil {
// 處理錯誤
}
// 調整 MyApp 的狀態
// ...
return reconcile.Result{}, nil
}
內容解密:
ReconcileMyApp結構體實作了Reconcile方法,這是 Operator 的核心邏輯。- 在
Reconcile方法中,首先取得特定的MyApp物件。 - 然後根據
MyApp的規格調整其狀態,這可能涉及建立、更新或刪除底層資源。
Operator 關鍵元件
Operator Framework 是一個開源工具包,具有明確的軟體開發工具包(SDK)、生命週期管理和發布工具。一些專案圍繞 Operator 模式的概念進行構建,使社群更容易開發。Kubernetes 社群中的 API Machinery SIG 成員贊助了 kubebuilder 的開發,為 Operator 的兩個主要元件提供基礎 SDK:自定義資源定義(CRD)和控制器。由 Google 贊助作為社群的一部分,kubebuilder 正被定位為所有 Operator 和其他專案(如 KUDO、KubeOps 和 Kopf)的基礎 SDK。本章的範例將根據 kubebuilder 語法進行討論;然而,許多 Operator SDK 中的概念非常相似。
自定義資源定義(CRD)
通常,使用原生 Kubernetes 資源定義複雜的應用程式依賴關係和資源在實際操作中會變得具有挑戰性。平台工程師通常必須構建複雜的 yaml 範本,結合渲染管道和額外的資源,如作業和初始容器,以管理執行大型應用程式所需的大部分自定義。然而,自定義資源定義允許開發人員擴充套件 Kubernetes API 以提供新的資源型別,從而更好地以宣告方式表示應用程式的資源需求。
Kubernetes 允許使用 CustomResourceDefinition 介面動態註冊新資源,並將自動為您指定的版本註冊新的 RESTful 資源路徑。與 Kubernetes 中原生構建的許多資源不同,CRD 可以獨立維護並在需要時更新。CRD 將在 spec 欄位下定義資源的規範,並具有 spec.scope 定義,以確定從 CRD 建立的自定義資源是否為名稱空間或叢集範圍的資源。
Kubernetes API 物件、資源、版本、群組和種類別
Kubernetes 中的物件是實際儲存在系統中的實體,用於表示叢集的狀態。物件本身是典型的 CRUD 操作在叢集中作用的目標。物件將是整個資源定義的狀態,例如 Pod 或 PersistentVolume。
Kubernetes 資源是 API 中的端點,代表特定種類別的物件集合。因此,Pod 資源將包含 Pod 物件的集合。可以使用以下命令輕鬆在叢集中檢視:
kubectl api-versions
輸出結果如下:
NAME SHORTNAMES APIVERSION NAMESPACED KIND
bindings v1 true Binding
componentstatu... cs v1 false ComponentS...
configmaps cm v1 true ConfigMap
...
群組將相似關注點的物件聚集在一起。這種分組結合版本控制,允許同一群組內的物件單獨管理和更新。群組在物件的 apiVersion 欄位中的 RESTful 路徑中表示。在 Kubernetes 中,核心群組(也稱為舊版)將位於 /api/ REST 路徑下。通常在 Pod 規範或 Deployment yaml 中的 apiVersion 欄位中看到,基路徑被移除,如下所示:
kind: Deployment
apiVersion: apps/v1
metadata:
name: sample
spec:
selector:
matchLabels:
與任何良好的 API 一樣,Kubernetes API 是版本化的,並使用不同的 API 路徑支援多個版本。對於 Kubernetes 版本控制有相關,在版本控制自定義資源時也應使用相同的。API 也可以根據 API 的支援或穩定性分為不同的級別,因此經常會看到 Alpha、Beta 或 Stable API。在叢集中,例如,可能有 v1 和 v1beta1 用於相同的群組。
種類別(Kind)和資源(Resource)經常在相同的上下文中使用;然而,資源是種類別的具體實作。通常存在直接的種類別到資源關係,例如定義 kind: Pod 規範時,將在叢集中建立 Pod 資源。有時存在一對多的關係,例如 Scale 類別,可以由不同的資源(如 Deployment 或 ReplicaSet)傳回。這被稱為子資源。
建立我們的 API
自定義資源定義可以手動以 yaml 建立;然而,kubebuilder 和其他 Operator SDK 將根據提供的程式碼自動生成 API 定義。在 kubebuilder 中,可以在專案初始化後建立 API 的腳手架和所需的 Go 程式碼。要初始化專案,一旦滿足 kubebuilder 及其先決條件,就可以從包含專案檔案的新目錄中執行 init 命令:
$ kubebuilder init --domain platform.evillgenius.com --repo platform.evillgenius.com/platformapp --project-name=pe-app
這將建立一些基本檔案和預留的樣板程式碼:
$ tree
.
├── config
│ ├── default
│ │ ├── kustomization.yaml
...
內容解密:
上述指令和輸出結果展示瞭如何使用 kubebuilder 初始化一個新的 Operator 專案。其中,--domain、--repo 和 --project-name 引數用於指定專案的網域、儲存函式庫和名稱。初始化過程中,kubebuilder 將建立必要的檔案和目錄結構,包括 config 目錄下的 default 和 kustomization.yaml 檔案。
此步驟的重要性在於,它為建立和管理自定義資源定義(CRD)和相關的控制器提供了基礎結構。透過使用 kubebuilder,開發人員可以遵循最佳實踐,並利用社群支援的工具來簡化 Operator 的開發過程。
程式碼解析
在上述範例中,我們使用了 kubebuilder 工具來初始化一個新的 Operator 專案。下面是一些關鍵程式碼片段的解析:
// main.go
package main
import (
"flag"
"os"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
platformappv1 "platform.evillgenius.com/platformapp/api/v1"
// +kubebuilder:scaffold:imports
)
var (
scheme = runtime.NewScheme()
setupLog = ctrl.Log.WithName("setup")
)
func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(platformappv1.AddToScheme(scheme))
// +kubebuilder:scaffold:scheme
}
func main() {
...
}
內容解密:
上述 Go 程式碼是使用 kubebuilder 初始化 Operator 專案後生成的 main.go 檔案的一部分。其中,我們匯入了必要的套件,並初始化了 scheme,用於註冊自定義資源定義(CRD)。init 函式用於向 scheme 中新增 Kubernetes 的預設資源和我們的自定義資源。
此程式碼片段的重要性在於,它展示瞭如何使用 controller-runtime 套件來建立和管理 Kubernetes 資源。透過註冊必要的資源和設定日誌,我們可以為 Operator 的執行提供基礎設施。
Kubernetes Operator 開發:自定義 API 與資源定義
在開發 Kubernetes Operator 時,建立自定義 API 是關鍵步驟。本文將探討如何使用 Kubebuilder 建立和管理自定義資源定義(CRD),並詳細解析相關程式碼和組態。
建立自定義 API
首先,使用 Kubebuilder 建立新的 API:
$ kubebuilder create api --group egplatform --version v1alpha1 --kind EGApp
Create Resource [y/n] y
Create Controller [y/n] y
這將生成基本的 API 結構和相關檔案。
自定義資源規格(Spec)與狀態(Status)
接下來,修改 api/v1alpha1/egapp_types.go 檔案來定義 EGApp 資源的規格和狀態:
type EGAppSpec struct {
// AppId 是應用程式的唯一識別符號
AppId string `json:"appId,omitempty"`
// Framework 指定應用程式的框架(java、python 或 go)
// +kubebuilder:validation:Enum=java;python;go
Framework string `json:"framework"`
// InstanceType 定義應用程式的執行個體型別
// +kubebuilder:validation:Optional
// +kubebuilder:validation:Enum=lowMem;highMem;highCPU;balanced
// +kubebuilder:default="lowMem"
InstanceType string `json:"instanceType"`
// Environment 指定應用程式的執行環境(dev、stage 或 prod)
// +kubebuilder:validation:Enum=dev;stage;prod
Environment string `json:"environment"`
// ReplicaCount 定義應用程式的副本數量
// +kubebuilder:validation:Optional
// +kubebuilder:default:=1
ReplicaCount int32 `json:"replicaCount"`
}
type EGAppStatus struct {
// Pods 列出應用程式目前執行的 Pod 名稱
Pods []string `json:"pods"`
}
內容解密:
- AppId:用於與內部目錄系統比對的唯一應用程式 ID。
- Framework:指定應用程式的框架,限制為 java、python 或 go。
- InstanceType:定義執行個體型別,如低記憶體、高記憶體等,預設為 lowMem。
- Environment:指定執行環境(dev、stage 或 prod)。
- ReplicaCount:設定應用程式的副本數量,預設為 1。
- Pods:在狀態中記錄目前執行的 Pod 名稱。
生成 CRD 與相關資源
修改完成後,執行以下命令生成 CRD 和相關資源:
$ make generate
$ make manifests
這將根據 egapp_types.go 中的定義生成 CRD 和其他必要的 YAML 清單。
CRD 結構解析
生成的 CRD 類別似於以下結構:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: egapps.egplatform.platform.evillgenius.com
spec:
group: egplatform.platform.evillgenius.com
names:
kind: EGApp
plural: egapps
singular: egapp
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
properties:
spec:
properties:
appId:
type: string
environment:
enum:
- dev
- stage
- prod
type: string
framework:
enum:
- java
- python
- go
type: string
instanceType:
default: lowMem
enum:
- lowMem
- highMem
- highCPU
- balanced
type: string
replicaCount:
default: 1
format: int32
type: integer
內容解密:
- spec.group 與 spec.names:定義了資源的分組和名稱相關資訊。
- spec.versions.schema:描述了資源的結構和驗證規則。
- openAPIV3Schema.properties.spec.properties:詳細定義了
EGAppSpec的結構,包括各欄位的型別和驗證規則。