Kubernetes 對於應用程式佈署和管理至關重要,但開發者在 Kubernetes 上開發應用程式時常面臨挑戰。本文將探討如何建構開發叢集,以提升開發效率,並涵蓋開發者工作流程的三個階段:入門、開發和測試。文章將比較單一大叢集與多叢集的優缺點,並提供名稱空間管理、資源配額設定、自動化名稱空間回收以及 CI/CD 整合等實務技巧,讓開發者能快速迭代、除錯和測試應用程式。此外,文章也將探討如何透過 setup.sh 和 deploy.sh 等指令碼,簡化依賴項安裝和應用程式佈署流程,並說明如何與 npm 等工具整合,提升開發效率。
開發者工作流程
Kubernetes 的設計初衷是為了可靠地執行軟體。它簡化了應用程式的佈署與管理,透過應用導向的 API、自癒特性以及諸如 Deployments 等工具,實作了零停機的軟體滾動更新。儘管這些工具非常有用,但它們並沒有讓開發人員更容易地為 Kubernetes 開發應用程式。這就是開發者工作流程(developer workflows)發揮作用的地方。雖然許多叢集的設計目的是執行生產應用程式,因此很少被開發者工作流程存取,但啟用開發者工作流程以針對 Kubernetes 進行開發至關重要,這通常意味著需要一個叢集或至少部分叢集專門用於開發。建立這樣一個叢集以促進 Kubernetes 應用程式的輕鬆開發,對於確保 Kubernetes 的成功至關重要。如果沒有為叢集構建程式碼,那麼叢集本身就沒有實作太多功能。
目標
在描述構建開發叢集的最佳實踐之前,有必要闡明我們對這種叢集的目標。顯然,最終目標是使開發人員能夠快速、輕鬆地在 Kubernetes 上構建應用程式,但這在實踐中究竟意味著什麼,以及如何在開發叢集的實際功能中體現出來?
為了回答這個問題,讓我們首先識別開發人員與叢集互動的階段。
第一階段是入門(onboarding)。這是新開發人員加入團隊時的階段。此階段包括為使用者提供叢集登入以及使其熟悉第一個佈署。此階段的目標是在最短的時間內讓開發人員上手。您應該為此過程設定關鍵績效指標(KPI)目標。一個合理的目標是,使用者可以在不到半小時的時間內從零開始執行目前的應用程式。每當有新成員加入團隊時,都要測試您是否達到了這個目標。
第二階段是開發(developing)。這是開發人員的日常活動。此階段的目標是確保快速迭代和除錯。開發人員需要快速、重複地將程式碼推播到叢集。他們還需要能夠輕鬆測試程式碼並在程式碼執行不正常時進行除錯。此階段的 KPI 更具挑戰性,但您可以透過測量將 pull request(PR)或變更上傳並在叢集中執行所需的時間,或透過調查使用者感知的生產力來估計,或者兩者兼而有之。您還可以透過團隊的整體生產力來衡量這一點。
第三階段是測試(testing)。此階段與開發階段交織在一起,用於在提交和合併之前驗證程式碼。此階段的目標有兩個。首先,開發人員應該能夠在提交 PR 之前執行其環境的所有測試。其次,所有測試應該在程式碼合併到儲存函式庫之前自動執行。除了這些目標之外,您還應該為測試執行時間設定 KPI。隨著專案變得越來越複雜,越來越多的測試需要更長的時間。當這種情況發生時,識別一組較小的煙霧測試(smoke tests)可能會很有價值,開發人員可以在提交 PR 之前使用這些測試進行初步驗證。您還應該對測試的不穩定性(flakiness)設定非常嚴格的 KPI。不穩定的測試是指偶爾(或不那麼偶爾)失敗的測試。在任何合理活躍的專案中,每千次執行失敗超過一次的測試不穩定率將導致開發人員摩擦。您需要確保叢集環境不會導致測試不穩定。雖然有時測試不穩定是由於程式碼問題引起的,但也可能由於開發環境中的幹擾(例如,資源不足和吵鬧的鄰居)而發生。您應該透過測量測試不穩定性並迅速採取行動來修復它,以確保開發環境沒有這種問題。
構建開發叢集
當人們開始思考如何在 Kubernetes 上進行開發時,首先要做出的選擇之一是構建單個大型開發叢集還是為每個開發人員提供一個叢集。請注意,這種選擇只有在動態叢集建立很容易的環境中才有意義,例如公有雲。在物理環境中,可能只有一個大型叢集是唯一的選擇。
如果您有選擇,您應該考慮每個選項的優缺點。如果您選擇為每個使用者提供一個叢集,這種方法的一個重大缺點是它將更昂貴、效率更低,並且您將有很多不同的開發叢集需要管理。額外成本來自於每個叢集很可能被嚴重低估利用率的事實。此外,隨著開發人員建立不同的叢集,跟蹤和垃圾收集不再使用的資源變得更加困難。根據使用者的叢集方法的優勢在於簡單性:每個開發人員都可以自行管理自己的叢集,並且由於隔離,不同的開發人員很難相互幹擾。
另一方面,單個開發叢集將顯著更高效;您可能可以在分享叢集上維持相同數量的開發人員,成本降低到三分之一(或更低)。此外,您可以更輕鬆地安裝分享叢集服務,例如監控和日誌記錄,這使得生成一個對開發人員友好的叢集變得更加容易。分享開發叢集的缺點是使用者管理和潛在的開發人員之間的幹擾。由於向 Kubernetes 叢集新增新使用者和名稱空間的過程目前尚未簡化,因此您需要啟動一個流程來使新開發人員上手。儘管 Kubernetes 資源管理和根據角色的存取控制(RBAC)可以降低兩個開發人員發生衝突的機率,但始終存在使用者會消耗太多資源,從而導致其他應用程式和開發人員無法排程的可能性。此外,您仍然需要確保開發人員不會洩漏和忘記他們建立的資源。不過,這比每個開發人員建立自己的叢集的方法要容易一些。
儘管兩種方法都是可行的,但通常我們的建議是為所有開發人員提供一個大型叢集。儘管存在開發人員之間的幹擾挑戰,但這些挑戰是可以管理的,而且最終,成本效益和輕鬆地向叢集中新增組織範圍內的功能的能力超過了幹擾的風險。但是,您需要投資於使新開發人員上手、資源管理和垃圾收集的流程。我們的建議是首先嘗試使用單個大型叢集。隨著組織的發展(或者如果它已經很大),您可以考慮為每個團隊或群組(10 到 20 人)提供一個叢集,而不是為數百名使用者提供一個巨大的叢集。這可以使計費和管理變得更容易。轉向多個叢集可能會使確保一致性變得更加複雜,但是像車隊管理(fleet management)這樣的工具可以使管理多個叢集變得更加容易。
為多個開發人員設定分享叢集
在設定大型叢集時,主要目標是確保多個使用者可以同時使用該叢集,而不會相互幹擾。分隔不同開發人員的最明顯方法是使用 Kubernetes 名稱空間(namespaces)。名稱空間可以作為服務佈署的作用域,以便一個使用者的前端服務不會干擾另一個使用者的前端服務。名稱空間也是 RBAC 的作用域,確保一個開發人員無法意外刪除另一個開發人員的工作。因此,在分享叢集中,使用名稱空間是有意義的。
apiVersion: v1
kind: Namespace
metadata:
name: developer-namespace
內容解密:
apiVersion和kind定義了 Kubernetes 資源的版本和型別,在這裡我們建立了一個名稱空間。metadata部分包含了名稱空間的中繼資料,例如名稱。- 使用名稱空間可以幫助組織和管理 Kubernetes 叢集中的資源,並且可以與 RBAC 結合使用,以控制不同使用者對資源的存取許可權。
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: developer-rolebinding
namespace: developer-namespace
roleRef:
name: developer-role
kind: Role
subjects:
- kind: User
name: developer-user
apiGroup: rbac.authorization.k8s.io
內容解密:
- 這段 YAML 組態定義了一個
RoleBinding,它將developer-role角色繫結到developer-user使用者。 roleRef指定了要繫結的角色名稱和型別。subjects部分定義了被繫結到該角色的主體,在這裡是一個名為developer-user的使用者。- 這種組態確保了
developer-user在developer-namespace名稱空間中具有developer-role所定義的許可權。
在 Kubernetes 中設定分享叢集給多位開發者
使用者上線流程
在將使用者分配到某個名稱空間之前,必須先將該使用者上線至 Kubernetes 叢集。有兩種方法可以實作此目標:使用根據憑證的身份驗證,或組態叢集使用外部身份系統(如 Microsoft Entra ID 或 AWS Identity and Access Management [IAM])進行叢集存取。
使用外部身份系統的最佳實踐
使用外部身份系統是一種最佳實踐,因為它避免了維護兩個不同的身份來源。此外,大多數外部系統使用短期令牌而非長期憑證,因此令牌的意外洩露所造成的影響是有限的。
使用憑證的流程
如果無法使用外部身份系統,則需要使用憑證。以下是新增使用者至現有叢集的流程:
首先,需要產生憑證簽名請求(Certificate Signing Request, CSR)以生成新憑證。以下是一個簡單的 Go 程式來實作此功能:
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"os"
)
func main() {
name := os.Args[1]
user := os.Args[2]
key, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
panic(err)
}
// ...(以下省略部分程式碼)
}
內容解密:
此 Go 程式用於產生憑證簽名請求。首先,它生成一個 RSA 私鑰,然後建立一個憑證簽名請求,並將其編碼為 PEM 格式。程式需要兩個引數:輸出檔案名稱和使用者名稱。
執行此程式的命令如下:
go run csr-gen.go client <user-name>
這將產生 client-key.pem 和 client.csr 檔案。然後,可以執行以下指令碼來建立和下載新憑證:
#!/bin/bash
csr_name="my-client-csr"
name="${1:-my-user}"
csr="${2}"
# ...(以下省略部分指令碼)
內容解密:
此指令碼用於建立和下載新憑證。首先,它建立一個 CertificateSigningRequest 物件並提交給 Kubernetes 叢集。然後,它批准該請求並下載產生的憑證。最後,它輸出需要新增到 kubeconfig 檔案中的資訊。
建立和保護名稱空間
建立名稱空間的第一步是實際建立它,可以使用 kubectl create namespace my-namespace 命令。
為了保護名稱空間,需要確保可以將存取許可權授予特定使用者。這可以透過在名稱空間中建立 RoleBinding 物件來實作。RoleBinding 可能如下所示:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: example
namespace: my-namespace
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: edit
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: myuser
內容解密:
此 RoleBinding 物件將 edit ClusterRole 繫結到 myuser 使用者,使其在 my-namespace 名稱空間中具有編輯許可權。可以重複使用此繫結,只需更新名稱空間即可。如果確保使用者沒有其他角色繫結,則可以保證該名稱空間是叢集中唯一可存取的部分。
為了讓開發者能夠檢視其他人在做什麼,可以授予對整個叢集的讀取許可權。但是,這也包括對秘密資源的存取許可權,因此需要謹慎授予此許可權。
Kubernetes 中的角色繫結
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam rectangleBackgroundColor #F5F5F5
skinparam rectangleBorderColor #333333
skinparam arrowColor #333333
title Kubernetes 中的角色繫結
rectangle "bind" as node1
rectangle "reference" as node2
rectangle "grant" as node3
rectangle "apply to" as node4
node1 --> node2
node2 --> node3
node3 --> node4
@enduml此圖示說明瞭 Kubernetes 中的角色繫結流程,從使用者到名稱空間的許可權授予。
管理開發者工作流程與資源分配
在 Kubernetes 叢集中,為了有效管理開發者的工作流程並合理分配資源,需要對名稱空間(Namespace)進行有效管理。名稱空間作為開發者的工作空間,不僅能夠隔離不同開發者的資源,還能透過資源配額(ResourceQuota)限制資源使用,從而控制成本並確保資源公平分配。
使用 ResourceQuota 限制名稱空間資源
若要限制特定名稱空間的資源消耗,可以使用 ResourceQuota 物件來設定該名稱空間中資源使用的總限制。例如,以下是一個 ResourceQuota 的範例,限制名稱空間 my-namespace 中的 Pod 總共可使用 10 核心 CPU 和 100 GB 記憶體:
apiVersion: v1
kind: ResourceQuota
metadata:
name: limit-compute
namespace: my-namespace
spec:
hard:
requests.cpu: "10"
requests.memory: 100Gi
limits.cpu: 10
limits.memory: 100Gi
內容解密:
apiVersion和kind:定義了 Kubernetes 資源的 API 版本和型別,這裡是ResourceQuota。metadata:包含資源的後設資料,如名稱和所屬名稱空間。spec.hard:定義了資源的硬性限制,包括 CPU 和記憶體的請求(requests)和限制(limits)。requests.cpu和requests.memory:定義了 Pod 請求的 CPU 和記憶體資源總量。limits.cpu和limits.memory:定義了 Pod 可使用的 CPU 和記憶體資源總量的上限。
管理名稱空間的策略
在將開發者分配到特定名稱空間時,有兩種主要策略:
- 為每個開發者分配獨立的名稱空間:這種方式為開發者提供了專屬的工作空間,但可能導致資源浪費和垃圾回收困難。
- 動態分配具有生命週期限制(TTL)的名稱空間:這種方式鼓勵開發者將叢集資源視為臨時性,並且便於自動化管理和清理過期的名稱空間。
動態分配名稱空間的實作
可以開發簡單的工具或腳原本動態建立和分配名稱空間,並附加相關的中繼資料(如 TTL、開發者資訊、資源分配等),以便進行管理和會計核算。
簡單指令碼範例:
#!/bin/bash
# 建立新的名稱空間
kubectl create namespace $1
# 附加中繼資料
kubectl label namespace $1 ttl=24h owner=$2
內容解密:
- 指令碼功能:建立新的名稱空間並為其附加標籤(如 TTL 和所有者)。
kubectl create namespace $1:根據輸入引數建立名稱空間。kubectl label namespace $1 ttl=24h owner=$2:為新建立的名稱空間新增標籤,定義其 TTL 為 24 小時,並指定所有者。
自動化名稱空間回收
為了避免資源浪費,需要定期清理過期的名稱空間。可以透過一個簡單的指令碼檢查名稱空間的 TTL,並刪除過期的名稱空間。將此指令碼容器化並使用 ScheduledJob 定期執行,即可實作自動化的資源回收。
叢集層級服務的啟用
除了名稱空間管理外,啟用叢集層級服務(如日誌聚合服務)也非常重要。將應用程式日誌集中儲存到日誌服務(如 Elasticsearch),便於開發者搜尋和分析日誌,提升應用程式的開發和除錯效率。
開發者工作流程的最佳實踐
在 Kubernetes 環境中建立高效的開發者工作流程,需要在自動化和約定俗成之間取得平衡。以下將介紹一種實作此目標的方法,雖然這不是唯一的方法,但它為建立自己的工作流程提供了良好的參考。
初始設定
佈署應用程式的最大挑戰之一是安裝所有依賴項。在現代微服務架構中,開發單一微服務往往需要佈署多個依賴項,如資料函式庫或其他微服務。雖然佈署應用程式本身相對簡單,但識別和佈署所有依賴項以構建完整的應用程式卻常常令人沮喪,且需要試錯和參考不完整或過時的檔案。
為瞭解決這個問題,引入一種約定來描述和安裝依賴項是非常有價值的。這類別似於 npm install 命令,它安裝所有必要的 JavaScript 依賴項。雖然未來可能會出現類別似於 npm 的工具來為根據 Kubernetes 的應用程式提供此服務,但目前的最佳實踐是依靠團隊內部的約定。
使用 setup.sh 指令碼
一種約定是建立一個 setup.sh 指令碼,放在所有專案儲存函式庫的根目錄中。該指令碼負責在特定的名稱空間中建立所有依賴項,以確保應用程式的所有依賴項都正確建立。例如:
kubectl create -f my-service/database-stateful-set.yaml
kubectl create -f my-service/middle-tier.yaml
kubectl create -f my-service/configs.yaml
你可以將此指令碼與 npm 整合,在 package.json 檔案中新增以下內容:
{
"scripts": {
"setup": "./setup.sh"
}
}
這樣,新開發人員只需執行 npm run setup,就可以安裝叢集依賴項。
啟用活躍開發
設定好開發者工作空間後,下一步是讓開發者能夠快速迭代他們的應用程式。首先,需要能夠構建和推播容器映像。假設你已經設定好了這一步,如果沒有,可以參考其他線上資源和書籍。
使用 deploy.sh 指令碼
在構建和推播容器映像後,下一步是將其佈署到叢集中。與傳統的佈署不同,在開發者迭代的情況下,保持可用性並不是主要關心點。因此,最簡單的方法是刪除與先前佈署相關聯的 Deployment 物件,然後建立一個新的 Deployment 物件,指向新構建的映像。
一個示例 deploy.sh 指令碼可能如下所示:
kubectl delete -f ./my-service/deployment.yaml
perl -pi -e 's/${old_version}/${new_version}/' ./my-service/deployment.yaml
kubectl create -f ./my-service/deployment.yaml
內容解密:
kubectl delete -f ./my-service/deployment.yaml:刪除現有的 Deployment 物件,以清除舊版本的佈署。perl -pi -e 's/${old_version}/${new_version}/' ./my-service/deployment.yaml:使用 Perl 命令替換deployment.yaml檔案中的舊版本號為新版本號。kubectl create -f ./my-service/deployment.yaml:建立新的 Deployment 物件,使用更新後的deployment.yaml檔案。
與之前一樣,你可以將此指令碼與現有的程式語言工具整合,以便開發人員可以簡單地執行 npm run deploy 將新程式碼佈署到叢集中。
持續整合和交付(CI/CD)
在建立自動化的過程中,將其整合到 CI/CD 工具(如 GitHub Actions、Azure DevOps 或 Jenkins)中是非常有用的。這使得進一步的自動化(如合併開發人員的 PR 時自動佈署)變得更加容易。
內容解密:
- CI/CD 工具可以自動化測試、構建和佈署流程,提高開發效率和程式碼品質。
- 透過將
deploy.sh指令碼整合到 CI/CD 流程中,可以實作自動化佈署,減少手動操作的錯誤。