本文已超過一年。較舊的文章可能包含過時的內容。請檢查頁面中的資訊自發布以來是否已變得不正確。
使用許可控制器在執行階段偵測容器偏移

插圖作者:Munire Aireti
在 Box,我們使用 Kubernetes (K8s) 來管理數百個微服務,這些微服務讓 Box 能夠以 PB 級規模串流資料。在部署流程方面,我們執行 kube-applier 作為 GitOps 工作流程的一部分,具有宣告式組態和自動化部署。開發人員將他們的 K8s 應用程式清單宣告到 Git 儲存庫中,該儲存庫需要程式碼審查和自動檢查才能通過,然後任何變更才能合併並應用到我們的 K8s 叢集內部。然而,透過 kubectl exec
和其他類似的指令,開發人員可以直接與執行的容器互動,並從其部署狀態變更它們。這種互動可能會破壞在我們的 CI/CD 管道中強制執行的變更控制和程式碼審查流程。此外,它允許受影響的容器在生產環境中長期持續接收流量。
為了解決這個問題,我們開發了自己的 K8s 元件,稱為 kube-exec-controller,以及其對應的 kubectl 外掛程式。它們共同運作,以偵測和終止可能被變更的容器(由互動式 kubectl 指令引起),並將互動事件直接揭露給目標 Pod,以提高可見性。
用於互動式 kubectl 指令的 Admission Controller
一旦請求被傳送到 K8s,它需要由 API 伺服器進行身份驗證和授權才能繼續進行。此外,K8s 還有一個獨立的保護層,稱為 Admission Controller,它可以攔截請求,然後再將物件持久儲存在 etcd 中。API 伺服器二進位檔中編譯了各種預定義的 Admission Controller(例如,ResourceQuota 用於強制執行每個命名空間的硬性資源使用限制)。此外,還有兩個動態 Admission Controller,分別稱為 MutatingAdmissionWebhook 和 ValidatingAdmissionWebhook,分別用於變更或驗證 K8s 請求。我們採用後者來偵測由互動式 kubectl 指令引起的執行階段容器漂移。整個過程可以分為三個步驟,如下詳述。
1. 允許互動式 kubectl 指令請求
首先,我們需要啟用一個驗證 Webhook,將合格的請求傳送到 kube-exec-controller。為了新增適用於特定互動式 kubectl 指令的新驗證機制,我們將 Webhook 的規則配置為資源 [pods/exec, pods/attach]
,以及操作 CONNECT
。這些規則告訴叢集的 API 伺服器,所有 exec
和 attach
請求都應受到我們的 Admission Controller Webhook 的約束。在我們配置的 ValidatingAdmissionWebhook 中,我們指定了一個 service
參考(也可以替換為 url
,提供 Webhook 的位置)和 caBundle
,以允許驗證其 X.509 憑證,兩者都在 clientConfig
節點下。
以下是我們的 ValidatingWebhookConfiguration 物件的簡短範例
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: example-validating-webhook-config
webhooks:
- name: validate-pod-interaction.example.com
sideEffects: None
rules:
- apiGroups: ["*"]
apiVersions: ["*"]
operations: ["CONNECT"]
resources: ["pods/exec", "pods/attach"]
failurePolicy: Fail
clientConfig:
service:
# reference to kube-exec-controller service deployed inside the K8s cluster
name: example-service
namespace: kube-exec-controller
path: "/admit-pod-interaction"
caBundle: "{{VALUE}}" # PEM encoded CA bundle to validate kube-exec-controller's certificate
admissionReviewVersions: ["v1", "v1beta1"]
2. 使用可能被變更的容器標記目標 Pod
一旦收到 kubectl exec
的請求,kube-exec-controller 會進行內部記錄,以標記相關聯的 Pod。新增的標籤表示我們不僅可以查詢所有受影響的 Pod,還可以在控制器服務本身重新啟動時,啟用安全機制來檢索先前識別的 Pod。
Admission Controller 流程無法在其 Admission 回應中直接修改目標。這是因為 pods/exec
請求是針對 Pod API 的子資源,而該子資源的 API 種類是 PodExecOptions
。因此,kube-exec-controller 中有一個單獨的流程會非同步修補標籤。Admission Controller 始終允許 exec
請求,然後充當 K8s API 的用戶端來標記目標 Pod 並記錄相關事件。開發人員可以使用 kubectl
或類似工具檢查他們的 Pod 是否受到影響。例如
$ kubectl get pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
test-pod 1/1 Running 0 2s box.com/podInitialInteractionTimestamp=1632524400,box.com/podInteractorUsername=username-1,box.com/podTTLDuration=1h0m0s
$ kubectl describe pod test-pod
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning PodInteraction 5s admission-controller-service Pod was interacted with 'kubectl exec' command by user 'username-1' initially at time 2021-09-24 16:00:00 -0800 PST
Warning PodInteraction 5s admission-controller-service Pod will be evicted at time 2021-09-24 17:00:00 -0800 PST (in about 1h0m0s).
3. 在預定義的時間段後驅逐目標 Pod
如您在上述事件訊息中看到的,受影響的 Pod 不會立即被驅逐。有時,開發人員可能必須進入他們執行的容器中,以針對某些即時問題進行偵錯。因此,我們根據他們執行的叢集環境定義受影響 Pod 的存活時間 (TTL)。特別是,我們在開發叢集中允許更長的時間,因為在活躍開發中執行 kubectl exec
或其他互動式指令更為常見。
對於我們的生產叢集,我們指定了較低的時間限制,以避免受影響的 Pod 持續提供流量。kube-exec-controller 在內部為每個符合相關聯 TTL 的 Pod 設定並追蹤計時器。一旦計時器到期,控制器就會使用 K8s API 驅逐該 Pod。驅逐(而不是刪除)是為了確保服務可用性,因為叢集會遵守任何已配置的 PodDisruptionBudget (PDB)。假設使用者已將 x 個 Pod 定義為其 PDB 中的關鍵 Pod,則當目標工作負載運行的 Pod 少於 x 個時,驅逐(由 kube-exec-controller 請求)不會繼續進行。
以下是上述整個工作流程的順序圖
用於改善使用者體驗的全新 kubectl 外掛程式
我們的 Admission Controller 元件在解決我們平台上遇到的容器漂移問題方面表現出色。它還能夠將所有相關事件提交到受影響的目標 Pod。但是,K8s 叢集不會長時間保留事件(預設保留期為一小時)。我們需要為開發人員提供其他方式來取得他們的 Pod 互動活動。對於我們來說,kubectl 外掛程式 是公開此資訊的完美選擇。我們將外掛程式命名為 kubectl pi
(pod-interaction
的縮寫),並提供兩個子指令:get
和 extend
。
當呼叫 get
子指令時,外掛程式會檢查我們的 Admission Controller 附加的元數據,並將其轉換為人類可讀的資訊。以下是執行 kubectl pi get
的範例輸出
$ kubectl pi get test-pod
POD-NAME INTERACTOR POD-TTL EXTENSION EXTENSION-REQUESTER EVICTION-TIME
test-pod username-1 1h0m0s / / 2021-09-24 17:00:00 -0800 PST
此外掛程式還可用於延長標記為未來驅逐的 Pod 的 TTL。如果開發人員需要額外時間來偵錯正在發生的問題,這會很有用。為了實現這一點,開發人員可以使用 kubectl pi extend
子指令,其中外掛程式會修補給定 Pod 的相關 annotations。這些 annotations 包括持續時間和提出延長請求的使用者名稱,以提高透明度(顯示在從 kubectl pi get
指令傳回的表格中)。
相應地,kube-exec-controller 中定義了另一個 Webhook,它允許有效的 annotation 更新。一旦允許,這些更新就會依照請求重設目標 Pod 的驅逐計時器。開發人員端請求延長的範例可以是
$ kubectl pi extend test-pod --duration=30m
Successfully extended the termination time of pod/test-pod with a duration=30m
$ kubectl pi get test-pod
POD-NAME INTERACTOR POD-TTL EXTENSION EXTENSION-REQUESTER EVICTION-TIME
test-pod username-1 1h0m0s 30m username-2 2021-09-24 17:30:00 -0800 PST
未來改進
儘管我們的 Admission Controller 服務在處理對 Pod 的互動式請求方面表現出色,但它也可能在這些請求中的實際指令為空操作時驅逐 Pod。例如,開發人員有時僅僅為了檢查儲存在主機上的服務日誌而執行 kubectl exec
。儘管如此,目標 Pod 仍然會被彈出,即使它們的容器狀態完全沒有改變。此處的改進之一可能是新增區分傳遞給互動式請求的指令的能力,以便空操作指令不應總是強制 Pod 驅逐。但是,當開發人員取得執行中容器的 Shell 並在 Shell 內部執行指令時,這將變得具有挑戰性,因為它們將不再對我們的 Admission Controller 服務可見。
這裡值得指出的另一個項目是選擇使用 K8s labels 和 annotations。在我們的設計中,我們決定將所有不可變的元數據附加為 labels,以便更好地在我們的 Admission Controller 中強制執行不可變性。然而,其中一些元數據可能更適合作為 annotations。例如,我們有一個標籤,其鍵為 box.com/podInitialInteractionTimestamp
,用於列出 kube-exec-controller 程式碼中所有受影響的 Pod,儘管它的值不太可能用於查詢。作為 K8s 世界中更理想的設計,在我們的案例中,單個 label 可能更適合用於識別,而其他元數據則應用為 annotations。
總結
借助 Admission Controller 的強大功能,我們能夠透過偵測執行階段可能被變更的容器並驅逐其 Pod,同時不影響服務可用性,來保護我們的 K8s 叢集。我們還利用 kubectl 外掛程式來提供驅逐時間的彈性,從而為服務擁有者帶來更好、更自主的體驗。我們很榮幸地宣布,我們已將整個專案開源,供社群在其自己的 K8s 叢集中使用。非常歡迎和感謝任何貢獻。您可以在 GitHub 上找到託管的專案:https://github.com/box/kube-exec-controller
特別感謝 Ayush Sobti 和 Ethan Goldblum 在此專案上的技術指導。