我們最近在 OpenSauced 平台上發布了與 OpenSSF Scorecard 的整合。 OpenSSF Scorecard 是一個功能強大的 Go 命令列介面,任何人都可以使用它來開始了解其專案和依賴項的安全狀況。它會對危險的工作流程、CICD 最佳實踐、專案是否仍在維護等等進行多項檢查。這使得軟體開發者和消費者能夠了解他們的整體安全狀況,推斷專案是否可以安全使用,以及需要對安全實踐進行改進。
但我們將 OpenSSF 記分卡整合到 OpenSauced 平台的目標之一是使其可供更廣泛的開源生態系統使用。如果它是 GitHub 上的儲存庫,我們希望能夠顯示它的分數。這意味著將 Scorecard CLI 擴展到 GitHub 上的幾乎所有儲存庫。說起來容易做起來難!
在這篇文章中,讓我們深入了解如何使用 Kubernetes 做到這一點,以及我們在實現此整合時做出了哪些技術決策。
我們知道我們需要建立一個 cron 類型的微服務,它可以頻繁地更新無數儲存庫中的分數:真正的問題是我們如何做到這一點。臨時運行記分卡 CLI 是沒有意義的:該平台很容易被淹沒,我們希望能夠對整個開源生態系統的分數進行更深入的分析,即使 OpenSauced 存儲庫頁面尚未被訪問最近訪問過。最初,我們考慮使用 Scorecard Go 函式庫作為直接依賴程式碼,並在單一整體微服務中執行記分卡檢查。我們還考慮使用無伺服器作業來運行一次性記分卡容器,該容器將傳回各個儲存庫的結果。
我們最終採用的方法結合了簡單性、靈活性和強大功能,是大規模使用 Kubernetes 作業,所有這些都由「調度程式」Kubernetes 控制器微服務管理。執行一個 Kubernetes 作業無需與記分卡建立更深層的程式碼集成,它為我們提供了與使用無伺服器方法相同的好處,但成本更低,因為我們直接在 Kubernetes 叢集上管理這一切。作業的運作方式也提供了很大的靈活性:它們可以有很長的超時時間,可以使用磁碟,並且像任何其他 Kubernetes 範例一樣,它們可以讓多個 pod 執行不同的任務。
讓我們分解這個系統的各個組件,看看它們是如何深入工作的:
這個系統的第一個也是最大的部分是「scorecard-k8s-scheduler」;類似 Kubernetes 控制器的微服務,可在叢集上啟動新作業。雖然此微服務遵循建置傳統 Kubernetes 控制器或操作器時使用的許多原則、模式和方法,但它不會監視或改變叢集上的自訂資源。它的功能是簡單地啟動運行 Scorecard CLI 的 Kubernetes 作業並收集完成的作業結果。
我們先來看看Go程式碼中的主控制迴圈。此微服務使用 Kubernetes Client-Go 函式庫直接與執行微服務的叢集進行互動:這通常稱為叢集上配置和客戶端。在程式碼中,引導叢集上的客戶端後,我們輪詢資料庫中需要更新的儲存庫。一旦找到一些儲存庫,我們就會在各個工作「執行緒」上啟動 Kubernetes 作業,這些作業將等待每個作業完成。
// buffered channel, sort of like semaphores, for threaded working sem := make(chan bool, numConcurrentJobs) // continuous control loop for { // blocks on getting semaphore off buffered channel sem <- true go func() { // release the hold on the channel for this Go routine when done defer func() { <-sem }() // grab repo needing update, start scorecard Kubernetes Job on-cluster, // wait for results, etc. etc. // sleep the configured amount of time to relieve backpressure time.Sleep(backoff) }() }
這種具有緩衝通道的「無限控制循環」方法是 Go 中連續執行某些操作但僅使用配置數量的執行緒的常見方法。在任何給定時間運行的並發 Go 函數的數量取決於「numConcurrentJobs」變數的配置值。這將緩衝通道設定為充當工作池或信號量,表示在任何給定時間運行的並發 Go 函數的數量。由於緩衝通道是所有執行緒都可以使用和檢查的共享資源,因此我經常將其視為信號量:一種資源,很像互斥體,多個執行緒可以嘗試鎖定和存取。在我們的生產環境中,我們擴展了該調度程序中同時運行的執行緒數量。由於實際的調度程序的計算量不是很大,只會啟動作業並等待結果最終浮出水面,因此我們可以突破該調度程序可以管理的範圍。我們還有一個內建的退避系統,可以在需要時嘗試緩解壓力:如果出現錯誤或沒有找到可以計算分數的儲存庫,該系統將增加配置的「退避」值。這確保了我們不會不斷地用查詢猛擊我們的資料庫,並且記分卡調度程序本身可以保持在“等待”狀態,而不佔用集群上寶貴的計算資源。
在控制循環中,我們做了一些事情:首先,我們在資料庫中查詢需要更新記分卡的儲存庫。這是一個簡單的資料庫查詢,它基於我們監視的一些時間戳元資料並具有索引。自從計算儲存庫的最後一個分數以來,一旦經過配置的時間量,它將冒泡並由運行記分卡 CLI 的 Kubernetes 作業進行處理。
接下來,一旦我們有了可以取得分數的儲存庫,我們就使用「gcr.io/openssf/scorecard」鏡像啟動 Kubernetes 作業。使用 Client-Go 在 Go 程式碼中引導這項工作看起來與使用 yaml 非常相似,只需使用透過「k8s.io」導入提供的各種函式庫和 API 並以程式設計方式執行:
// defines the Kubernetes Job and its spec job := &batchv1.Job{ // structs and details for the actual Job // including metav1.ObjectMeta and batchv1.JobSpec } // create the actual Job on cluster // using the in-cluster config and client return s.clientset.BatchV1().Jobs(ScorecardNamespace).Create(ctx, job, metav1.CreateOptions{})
作業建立後,我們等待它發出已完成或出錯的訊號。與 kubectl 非常相似,Client-Go 提供了一種有用的方式來「監視」資源並觀察它們發生變化時的狀態:
// watch selector for the job name on cluster watch, err := s.clientset.BatchV1().Jobs(ScorecardNamespace).Watch(ctx, metav1.ListOptions{ FieldSelector: "metadata.name=" + jobName, }) // continuously pop off the watch results channel for job status for event := range watch.ResultChan() { // wait for job success, error, or other states }
最後,一旦我們成功完成作業,我們就可以從作業的 pod 日誌中取得結果,其中將包含來自記分卡 CLI 的實際 json 結果!獲得這些結果後,我們可以將分數更新插入資料庫並更改任何必要的元數據,以向我們的其他微服務或 OpenSauced API 發出訊號,表明有一個新分數!
如前所述,scorecard-k8s-scheduler 可以同時執行任意數量的並發作業:在我們的生產環境中,我們同時運行大量作業,所有作業均由該微服務管理。目的是能夠每兩週更新一次 GitHub 上所有儲存庫的分數。憑藉這種規模,我們希望能夠為任何開源維護者或消費者提供強大的工具和見解!
「調度程式」微服務最終成為整個系統的一小部分:任何熟悉 Kubernetes 控制器的人都知道,系統運作還需要額外的 Kubernetes 基礎架構。在我們的例子中,我們需要一些基於角色的存取控制(RBAC)來使我們的微服務能夠在叢集上建立作業。
首先,我們需要一個服務帳戶:這是調度程序將使用的帳戶,並具有與其綁定的存取控制:
apiVersion: v1 kind: ServiceAccount metadata: name: scorecard-sa namespace: scorecard-ns
我們將此服務帳戶放置在所有這些運行的「scorecard-ns」命名空間中。
接下來,我們需要為服務帳戶進行角色和角色綁定。這包括實際的存取控制(包括能夠建立作業、查看 Pod 日誌等)
apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: scorecard-scheduler-role namespace: scorecard-ns rules: - apiGroups: ["batch"] resources: ["jobs"] verbs: ["create", "delete", "get", "list", "watch", "patch", "update"] - apiGroups: [""] resources: ["pods", "pods/log"] verbs: ["get", "list", "watch"] — apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: scorecard-scheduler-role-binding namespace: scorecard-ns subjects: - kind: ServiceAccount name: scorecard-sa namespace: scorecard-ns roleRef: kind: Role name: scorecard-scheduler-role apiGroup: rbac.authorization.k8s.io
您可能會問自己「為什麼我需要授予此服務帳戶存取權限才能取得 pod 和 pod 日誌?這不是存取控制的過度延伸嗎?」記住!作業有 pod,為了取得包含記分卡 CLI 實際結果的 pod 日誌,我們必須能夠列出作業中的 pod,然後讀取它們的日誌!
第二部分“RoleBinding”,是我們實際將角色附加到服務帳戶的地方。然後可以在叢集上啟動新作業時使用此服務帳戶。
—
向 Alex Ellis 和他出色的運行作業控制器致敬:這是正確使用 Client-Go 與 Jobs 的巨大啟發和參考!
大家保持俏皮!
以上是我們如何使用 Kubernetes 作業來擴充 OpenSSF Scorecard的詳細內容。更多資訊請關注PHP中文網其他相關文章!