Python / kubernetes === ###### tags: `Python` ###### tags: `Python`, `PyPi`, `kubernetes`, `k8s` <br> [TOC] <br> ## kube config ### 使用本機端 kube config ```python from kubernetes import config config.load_kube_config() ``` <br> <hr> <br> ## client ### API 分類說明 - ### `client.CoreV1Api()` - **用途**:用來存取 Kubernetes 核心 API(Core API),包含 Pod、Service、ConfigMap、Secret、Namespace 等基本資源。 - **範例**:可以用它來取得某個 Namespace 下的所有 Pod,或建立、刪除 Service 等。 - ### `client.AppsV1Api()` - **用途**:主要針對應用程式部署相關的資源,屬於 apps API 群組。 - **範例**:用來操作 Deployment、StatefulSet、DaemonSet、ReplicaSet 等資源,管理應用程式的部署與擴展。 - ### `client.ApiextensionsV1Api()` - **用途**:用於管理 Kubernetes 的擴充資源,特別是 CustomResourceDefinition (CRD)。 - **範例**:可以用來建立、更新或刪除 CRD,進而定義新的自訂資源類型,讓 Kubernetes API 擴充出新的資源。 - ### `client.CustomObjectsApi()` - **用途**:專門用來操作由 CRD 定義的自訂資源(Custom Resources, CR)。 - **範例**:使用它可以對自訂資源進行 CRUD(建立、讀取、更新、刪除)操作,例如列出某個 CRD 定義下的所有自訂資源實例、更新 status 子資源等。 <br> ## pod ### 列出所有 pod ```python= from kubernetes import config, client config.load_kube_config() # load ~/.kube/config core_api = client.CoreV1Api() pod_list = core_api.list_pod_for_all_namespaces().items print('#len(pod_list) =', len(pod_list)) for pod in pod_list: print(pod.metadata.namespace, '\t', pod.metadata.name) ``` - **執行範例** ``` #len(pod_list) = 12 default empty-pod ingress-nginx ingress-nginx-admission-create-wlg6k ingress-nginx ingress-nginx-admission-patch-rvzd8 ingress-nginx ingress-nginx-controller-65496f9567-jxb9l kube-system coredns-76f75df574-gjlbs kube-system etcd-mymini kube-system kube-apiserver-mymini kube-system kube-controller-manager-mymini kube-system kube-proxy-54vxp kube-system kube-scheduler-mymini kube-system storage-provisioner local-path-storage local-path-provisioner-844bd8758f-2ns2x ``` <br> ## CRD ### 列出所有 CRD - ### 透過 `api_ext.list_custom_resource_definition()` ```python= from kubernetes import config, client config.load_kube_config() # load ~/.kube/config api_ext = client.ApiextensionsV1Api() crd_list = api_ext.list_custom_resource_definition().items print('#len(crd_list) =', len(crd_list)) for crd in crd_list: print(crd.metadata.name, '|', crd.spec.group) ``` - ### 透過 `custom_api.list_cluster_custom_object()` ```python= from kubernetes import config, client config.load_kube_config() api_ext = client.ApiextensionsV1Api() custom_api = client.CustomObjectsApi() crd_list = custom_api.list_cluster_custom_object( group='apiextensions.k8s.io', version='v1', plural='customresourcedefinitions') # keys(): ['kind', 'apiVersion', 'metadata', 'items'] # - 'kind': 'CustomResourceDefinitionList' # - 'apiVersion': 'apiextensions.k8s.io/v1' print('#len(crd_list) =', len(crd_list['items'])) for crd in crd_list['items']: # crd['metadata']['annotations']['kubectl.kubernetes.io/last-applied-configuration'] # the original yaml print(crd['metadata']['name'], '|', crd['spec']['group']) ``` - **錯誤寫法**: ```python= from kubernetes import config, client config.load_kube_config() custom_api = client.CustomObjectsApi() custom_api.list_namespaced_custom_object( group='apiextensions.k8s.io', version='v1', namespace=None, plural='customresourcedefinitions') ``` CustomResourceDefinition (CRD) 是 cluster-scoped 的資源,因此不能使用 `list_namespaced_custom_object` 來撈取,要改用 `list_cluster_custom_object`。 <br> ### 列出特定 CRD ```python= from kubernetes import config, client config.load_kube_config() # load ~/.kube/config api_ext = client.ApiextensionsV1Api() crd_list = api_ext.list_custom_resource_definition().items print('#len(crd) =', len(crd_list)) group = 'example.com' # 特定 group group_crd_list = [crd for crd in crd_list if crd.spec.group == group] ``` <br> ### 建立 CRD ```python= from kubernetes import client, config def main(): config.load_kube_config() # 定義 CRD 的內容,必須與 YAML 中的內容一致 crd_manifest = { "apiVersion": "apiextensions.k8s.io/v1", "kind": "CustomResourceDefinition", "metadata": { "name": "products.example.com" }, "spec": { "group": "genai.example.com", "versions": [ { "name": "v1", "served": True, "storage": True, "schema": { "openAPIV3Schema": { "type": "object", "properties": { "spec": { "type": "object", "properties": { "title": {"type": "string"}, "author": {"type": "string"}, "publicationDate": {"type": "string", "format": "date"}, "edition": {"type": "integer"} }, "required": ["title", "author", "publicationDate", "edition"] } } } }, "subresources": { "status": {} } } ], "scope": "Namespaced", "names": { "plural": "products", "singular": "product", "kind": "Product", "shortNames": ["pd"] } } } api_instance = client.ApiextensionsV1Api() try: api_response = api_instance.create_custom_resource_definition(body=crd_manifest) print("CRD 建立成功,狀態:", api_response.status) except client.exceptions.ApiException as e: print("建立 CRD 時發生錯誤:\n%s" % e) if __name__ == '__main__': main() ``` - 權限與連線:確保你的 kubeconfig 設定正確且有足夠的權限來建立 CRD - 錯誤處理:如果 CRD 已存在,API 可能會回傳 409 衝突錯誤,此時你可以根據需求進行更新或忽略該錯誤。 <br> ### 建立 CR ```python= from kubernetes import client, config from kubernetes.client.rest import ApiException def main(): # 載入 kube config,如果是在 cluster 內執行請改用 config.load_incluster_config() config.load_kube_config() # 建立 CustomObjectsApi 實例 api = client.CustomObjectsApi() # CRD 定義的 group、version、plural 名稱 group = "example.com" version = "v1" namespace = "default" plural = "products" # 根據 CRD 定義中的 plural # 定義 CR 的內容 cr_body = { "apiVersion": "example.com/v1", "kind": "Product", "metadata": { "name": "great-product" }, "spec": { "title": "The Great Product", "author": "John Doe", "publicationDate": "2021-01-01", "edition": 1 } } try: # 嘗試建立 CR api.create_namespaced_custom_object( group=group, version=version, namespace=namespace, plural=plural, body=cr_body ) print("Custom Resource 建立成功!") except ApiException as e: if e.status == 409: # 如果已存在(409 Conflict),則可以使用 patch 更新 CR print("Custom Resource 已存在,將進行更新...") try: api.patch_namespaced_custom_object( group=group, version=version, namespace=namespace, plural=plural, name=cr_body["metadata"]["name"], body=cr_body ) print("Custom Resource 更新成功!") except ApiException as patch_e: print("更新 CR 時發生錯誤: %s" % patch_e) else: print("建立 CR 時發生錯誤: %s" % e) if __name__ == "__main__": main() ``` - **載入設定**:程式中使用 `config.load_kube_config()` 來載入 kubeconfig(若在 Pod 內部運行則請使用 `config.load_incluster_config()`)。 - **CustomObjectsApi**:透過 `client.CustomObjectsApi()` 取得處理自定義資源的 API 實例。 - **建立 CR**:使用 `create_namespaced_custom_object()` 來建立 CR,若 CR 已存在則會回傳 409 衝突錯誤。 - **更新 CR**:捕獲 409 錯誤後,可以使用 `patch_namespaced_custom_object()` 進行更新。根據需求,你也可以使用 `replace_namespaced_custom_object()`。 <br> ## node ### 打標籤 + search - ### 叢集裡符合 v100-1gpu 規格的節點打標籤 ``` kubectl label node <NODE_NAME> resourceFlavor=v100-1gpu ``` - ### Python 範例: ```python===== from kubernetes import config, client # 載入 kubeconfig(或在 Pod 裡可改用 load_incluster_config()) config.load_kube_config() v1 = client.CoreV1Api() def get_nodes_by_profile(profile: str): # 直接以 label_selector 過濾 label_selector = f"resourceFlavor={profile}" resp = v1.list_node(label_selector=label_selector) return resp.items if __name__ == "__main__": nodes = get_nodes_by_profile("v100-1gpu") for node in nodes: print(f"Matched node: {node.metadata.name}") ``` - ### [node 上的其他資訊] 動態解析節點的 capacity/metadata.labels > 如果無法事先打標籤,或想要更彈性地依照 GPU 型號與數量來篩選,可以直接看 node.status.capacity 和廠商標籤: - ### `labels = node.metadata.labels or {}` ```yaml metadata: labels: ... nvidia.com/gpu.count: "8" nvidia.com/gpu.family: ampere nvidia.com/gpu.machine: ESC8000 nvidia.com/gpu.memory: "49140" nvidia.com/gpu.product: NVIDIA-A40 ``` - 從 label 取得 GPU 數量 ```python= gpu_count = int(capacity.get("nvidia.com/gpu", "0")) ``` - ### `capacity = node.status.capacity or {}` ```yaml status: ... capacity: cpu: "128" ephemeral-storage: 1842730644Ki hugepages-1Gi: "0" hugepages-2Mi: "0" memory: 528019792Ki nvidia.com/gpu: "8" pods: "110" ``` - 從 status 取得 GPU 型號 ```python= prod_lbl = labels.get("nvidia.com/gpu.product", "") \ or labels.get("cloud.google.com/gke-accelerator", "") ``` <br> ## 透過 ApiClient - 程式碼片段 ```python # Create an instance of the low-level API client api_client = client.ApiClient() # Construct the URL path for the custom resource. # Example: /apis/genai.asus.com/v1/namespaces/default/products/great-product path = f"/apis/{group}/{version}/namespaces/{namespace}/{plural}/{name}" ``` <br> ## Exception <br> ## 討論 ### `load_kube_config()` vs `load_incluster_config()` - ### Question `config.load_incluster_config()` `config.load_kube_config()` 兩者用法差在哪裡? - ### gpt-o1 (2025/02/04) > Python Kubernetes 客戶端:`load_kube_config()` vs `load_incluster_config()` 在使用 Python Kubernetes 客戶端套件時,`load_kube_config()` 和 `load_incluster_config()` 的主要差異在於它們載入認證與集群連線設定的方式不同,通常用於不同的執行環境: --- ### `config.load_kube_config()` - **用於在叢集之外執行程式時**,從本機的 kubeconfig 檔(預設為 `~/.kube/config`)載入 Kubernetes API 的連線與認證資訊。 - 適用情境: - 你在本地機器上執行 Python 腳本,希望連到遠端的 Kubernetes 叢集。 - 你的開發環境安裝了 `kubectl` 並可以正常操作叢集,且有對應的 kubeconfig 檔案。 - 若未指定路徑,預設會去讀取 `~/.kube/config` 檔。如果 kubeconfig 檔不在該路徑,或是有多個 kubeconfig,要自行指定檔案路徑。 - 示範程式碼: ```python from kubernetes import client, config # 從本機的 ~/.kube/config 讀取設定 config.load_kube_config() # 後續可直接呼叫 client 建立連線 v1 = client.CoreV1Api() print(v1.list_namespace()) ``` --- ### `config.load_incluster_config()` - **用於程式已經部署在 Kubernetes 叢集內部**並透過 Pod 預設掛載的 ServiceAccount 來做權限驗證時,自動讀取該 Pod 中預設的認證檔(通常在 `/var/run/secrets/kubernetes.io/serviceaccount`)。 - 適用情境: - 你撰寫的 Python 應用程式已打包成容器,並部署到 Kubernetes 叢集內的某個 Pod 中執行。 - Pod 透過指定的 ServiceAccount 或預設的 ServiceAccount (default) 授予所需權限後,就能直接使用這個方法與 Kubernetes 互動。 - 示範程式碼: ```python from kubernetes import client, config # 讀取叢集內部預設的設定與憑證 config.load_incluster_config() # 後續可直接呼叫 client 建立連線 v1 = client.CoreV1Api() print(v1.list_namespace()) ``` --- ### 結論 - 如果是在本地(或其他「叢集外部」主機)執行程式,需要使用自己的 kubeconfig 檔案連到叢集,請使用 **`load_kube_config()`**。 - 如果程式已經部署在 Kubernetes 叢集內部的容器(Pod)執行,並想要透過該 Pod 的 ServiceAccount 連線管理 Kubernetes,請使用 **`load_incluster_config()`**。 - ### gpt-o3-mini-high (2025/02/26) > Python Kubernetes 配置方法比較 在 Python 的 Kubernetes 客戶端中,這兩個方法主要用於不同的執行環境,具體區別如下: ### config.load_kube_config() - **用途:** 用於本地開發或叢集外的應用程式。這個方法會從使用者本地的 kubeconfig 檔案(通常位於 `~/.kube/config`)中載入連線資訊和認證資料。 - **適用情境:** 當你在開發環境或測試環境中,想要從你的工作站或筆記型電腦連接到 Kubernetes 叢集時,應使用這個方法。 ### config.load_incluster_config() - **用途:** 用於在 Kubernetes 叢集內部運行的應用程式。這個方法會自動讀取 Pod 內部掛載的服務帳戶相關資訊(如 token、CA 憑證),並依據預設的環境變數(例如 `KUBERNETES_SERVICE_HOST` 與 `KUBERNETES_SERVICE_PORT`)來建立連線。 - **適用情境:** 當你的應用程式部署在 Kubernetes 叢集內的 Pod 中時,應使用這個方法來獲取必要的認證資訊,避免手動配置 kubeconfig。 ### 總結 - **來源差異:** - **load_kube_config()** 從本地檔案讀取設定。 - **load_incluster_config()** 從叢集內部的環境與掛載的憑證讀取設定。 - **使用場景:** - 叢集外部(如開發、管理工具) → 使用 `load_kube_config()` - 叢集內部(如容器化應用程式) → 使用 `load_incluster_config()` 這樣的設計讓 Kubernetes 客戶端能夠根據不同的執行環境自動選擇合適的認證及配置方式,確保應用程式能順利連接到叢集。 <br> ### 列出 group='example.com' 的所有 CR - ### gpt-o3-mini-high (2025/02/04) 可以,但必須知道你要查詢的自訂資源(CR)的種類(即 CRD 的 plural 名稱)以及 API 版本,因為 Kubernetes API 並沒有提供一個單一的端點可以直接「列出某個 group 下所有 CR」的功能。你必須分別列出每個 CR 的實例。 以下提供兩種情境的範例: ## 情境 1:列出特定 CR 類型的所有實例 假設你的 CRD 定義如下: - **Group**: `example.com` - **Version**: `v1` - **Plural**: `books` - **命名空間**: 例如 `default` 下面的程式碼示範如何列出 `default` 命名空間中所有 `books` 類型的 Custom Resource 實例: ```python= from kubernetes import client, config def list_books_in_namespace(namespace="default"): # 載入 kubeconfig 設定;如果在集群內運行,請改用 config.load_incluster_config() config.load_kube_config() # 建立 CustomObjectsApi 實例 custom_api = client.CustomObjectsApi() # 使用 list_namespaced_custom_object 來列出指定命名空間中的所有 books books = custom_api.list_namespaced_custom_object( group="example.com", version="v1", namespace=namespace, plural="books" ) return books if __name__ == "__main__": books = list_books_in_namespace("default") print(books) ``` ## 情境 2:列出某個 Group 下所有不同 CRD 的實例 如果你想要列出某個 group(例如 `example.com`)下所有不同 CRD 的實例,可以依照以下步驟進行: 1. **列出該 group 下所有 CRD 定義** 利用 `ApiextensionsV1Api` 來取得所有 CRD,並過濾出 group 為 `example.com` 的 CRD。 2. **對於每個 CRD,根據其 plural 與版本列出對應的實例** 若 CRD 為 namespaced,則使用 `list_namespaced_custom_object`,否則使用 `list_cluster_custom_object`。 以下是完整的程式碼範例: ```python= from kubernetes import client, config def list_crds_in_group(group="example.com"): config.load_kube_config() api_ext = client.ApiextensionsV1Api() crds = api_ext.list_custom_resource_definition().items # 過濾出 group 為 example.com 的 CRD filtered = [crd for crd in crds if crd.spec.group == group] return filtered def list_all_custom_resources_in_group(group="example.com", namespace="default"): config.load_kube_config() custom_api = client.CustomObjectsApi() api_ext = client.ApiextensionsV1Api() # 先取得該 group 下所有 CRD 定義 crds = api_ext.list_custom_resource_definition().items group_crds = [crd for crd in crds if crd.spec.group == group] all_resources = {} for crd in group_crds: plural = crd.spec.names.plural # 假設使用第一個版本,實際情況可根據需求調整 version = crd.spec.versions[0].name try: # 如果 CRD 是 namespaced,使用 list_namespaced_custom_object resources = custom_api.list_namespaced_custom_object( group=group, version=version, namespace=namespace, plural=plural ) except client.rest.ApiException as e: # 如果 CRD 是 cluster-scoped 則改用 list_cluster_custom_object resources = custom_api.list_cluster_custom_object( group=group, version=version, plural=plural ) all_resources[plural] = resources return all_resources if __name__ == "__main__": resources = list_all_custom_resources_in_group("example.com", "default") for plural, res in resources.items(): print(f"Custom Resource Type: {plural}") print(res) ``` ## 結論 上述範例分別展示了如何: - 列出特定 CR 類型(例如 `books`)的所有實例 - 列出某個 API group 下所有 CRD 定義對應的 Custom Resource 實例 請根據你的實際需求調整 namespace、版本或錯誤處理機制。這些程式碼能夠幫助你更靈活地管理與查詢 Kubernetes 中的自訂資源。 <br> ### CRD 中的 `served: true` & `storage: true`,兩者差在哪裡? - ### gpt-o3-mini-high (2025/02/10) > CRD 版本欄位: served 與 storage 在 CRD (CustomResourceDefinition) 的定義中,每個版本包含兩個重要屬性: ### served: true - 表示該版本 **被 API server 提供**,也就是說你可以使用這個版本來存取(查詢、新增、修改、刪除) CRD 物件。 - 一個 CRD 可以同時支援多個 served 版本,讓使用者可以依照需求選擇不同版本進行操作,API server 會自動處理版本轉換。 ### storage: true - 表示該版本作為 **儲存版本**,也就是說所有 CRD 物件實際上在 etcd 中是以此版本的格式儲存。 - 在多個 served 版本中,只有一個版本會被標記為 storage。當你透過其他 served 版本來查詢或修改物件時,Kubernetes 會自動將儲存版本的資料轉換成你所請求的版本格式。 **總結:** - **served**:決定了哪些版本可以用來進行 API 操作(讀取、寫入等)。 - **storage**:決定了資料在 etcd 中實際儲存時所使用的格式。 例如,假設你的 CRD 定義了 `v1` 與 `v2` 兩個版本,且兩者都設定為 served,但只有 `v2` 被標記為 storage,那麼: - 當你使用 `v1` 或 `v2` 存取資源時,API server 都可以處理你的請求; - 但實際上,所有資源都是以 `v2` 格式儲存在 etcd 中; - 當你使用 `v1` 查詢時,Kubernetes 會自動將 `v2` 的資料轉換成 `v1` 格式再返回。 > **注意:** > 當在 Markdown 檔案中包含程式碼區塊時,如果需要避免巢狀的 markdown 顯示,可以使用三個雙引號 (`"""`) 來表示程式碼區塊,而不是使用三個反引號 (```)。 > 例如: <br> ### 關於 CRD,如果 v1 和 v2 有不同的資料欄位,那能自動轉換資料?還是資料會有遺失 - ### gpt-o3-mini-high (2025/02/10) > CRD 版本間的自動轉換 當你有多個 CRD 版本(例如,`v1` 和 `v2`),且它們的 schema 不同時,Kubernetes 會根據 CRD 中所定義的轉換策略來執行版本間的轉換。不過,這個轉換有其限制: ### 預設轉換行為 - **基本欄位對應:** 如果 `v1` 與 `v2` 之間共享具有相容類型的欄位,API server 會自動將這些欄位轉換。 - **缺少欄位:** 如果某個版本中存在的欄位在另一個版本中未被定義,那麼在版本轉換時,該欄位的資料將不會被轉換。也就是說,當一個欄位僅存在於儲存版本中但在 served 版本中缺少時,轉換後該欄位的資料可能會遺失。 ### 自訂轉換 Webhook - **目的:** 當兩個版本的 schema 差異較大時,你可以實作一個 **轉換 webhook**,以自訂欄位間的轉換邏輯。 - **優點:** 透過轉換 webhook,你可以明確定義如何將一個版本的資料轉換到另一個版本,確保重要的欄位不會因自動轉換而遺失。 - **何時使用:** 若 `v1` 和 `v2` 之間存在明顯的 schema 差異,自動轉換可能不足以完整保留所有必要的資料,此時實作轉換 webhook 就顯得非常必要。 ## 總結 - **自動轉換可處理:** 欄位相容的基本轉換。 - **資料遺失風險:** 如果某個版本中存在而另一版本中缺少的欄位,未透過自訂邏輯進行轉換時,該欄位資料可能會遺失。 - **轉換 Webhook:** 能夠讓你自訂轉換邏輯,確保在版本轉換時完整保留所需資料。 --- **結論:** 若 `v1` 和 `v2` 擁有不同的資料欄位,僅依賴 Kubernetes 的自動轉換功能可能會導致部分資料遺失。為了確保資料在版本之間正確轉換,建議根據實際需求實作自訂的轉換 webhook。 <br> ### 關於 CRD 的定義,spec.scope 有一個屬性是 Namespaced,這個屬性用途為何?怎麼使用? - ### gpt-o3-mini-high (2025/02/21) > CRD 定義中的 `spec.scope: Namespaced` 的用途及使用方式 在 CRD 定義中,`spec.scope` 用來指定該自定義資源(CR)的生命週期範圍,也就是它是「Namespaced」還是「Cluster」級別的資源。 ### Namespaced 當你設定為 `Namespaced` 時,表示每個該 CR 的實例都必須屬於一個特定的 namespace。這意味著: - **資源隔離:** 每個資源只存在於各自的 namespace 中,可以實現細粒度的資源隔離。 - **RBAC 管理:** 可以利用基於 namespace 的權限控制,針對不同的團隊或應用分配權限。 - **操作方式:** 在建立這類資源的 YAML 定義或 API 請求中,必須在 `metadata` 區塊指定 `namespace`。使用 `kubectl` 操作時,也需透過 `--namespace` 參數來指定。 ### Cluster 相對地,若將 `spec.scope` 設定為 `Cluster`,則該 CR 的實例為全域性資源,無需指定 namespace,且在整個集群中唯一。這類資源常用於描述集群級別的設定或狀態。 ### YAML 範例 假設 CRD 的 `spec.scope` 已設定為 `Namespaced`,那麼在創建一個 Book 實例時需要指定 namespace,例如 `default`: ```yaml= apiVersion: genai.asus.com/v1 kind: Book metadata: name: my-book namespace: default spec: title: "Kubernetes 實戰" author: "某某作者" publicationDate: "2023-01-01" edition: 1 ``` ### Python 使用範例 在使用 Python 的 Kubernetes 客戶端操作 namespaced 資源時,可以使用 `get_namespaced_custom_object` 或 `list_namespaced_custom_object` 方法,並指定 namespace。例如: ```python= from kubernetes import config, client # 載入 kube config (假設在本機上運行) config.load_kube_config() # 建立 CustomObjectsApi 實例 cust_api = client.CustomObjectsApi() # 定義 CRD 的相關參數 group = "genai.asus.com" version = "v1" namespace = "default" # 針對 namespaced 資源必須指定 namespace plural = "books" name = "my-book" # 取得特定 namespace 中的 Book 資源 book_instance = cust_api.get_namespaced_custom_object( group=group, version=version, namespace=namespace, plural=plural, name=name ) print(book_instance) ``` ### 總結 `spec.scope: Namespaced` 定義了 CRD 的資源實例必須存在於特定的 namespace 中。這使得資源可以利用 namespace 隔離進行組織和權限管理。如果 CRD 定義為 `Cluster` 則無需指定 namespace,資源為全域唯一。 <br> ### 將一座 K8s 的 CRD & CR instances 同步到另一座 K8s,讓兩座 k8s 有相同的 CRD & CR instances,這要如何實作? - ### gpt-o3-mini-high (2025/02/21) > 利用 Python 與 Kubernetes 套件實作 K8s CRD 與 CR instance 同步 你可以使用 Python 的 Kubernetes 客戶端(官方套件 [kubernetes](https://pypi.org/project/kubernetes/))來連接兩個不同的 K8s 叢集,分別讀取來源叢集中的 CRD 與 CR 實例,並在目標叢集中進行建立或更新。基本思路如下: 1. **載入不同的 kubeconfig** 透過 `config.load_kube_config()` 並指定不同的 context 或使用不同的 config 檔,連接來源叢集和目標叢集。 2. **同步 CRD** - 使用 `ApiextensionsV1Api` 在來源叢集中列出所有 CRD。 - 對每個 CRD,先在目標叢集中嘗試讀取。如果存在則進行更新(replace),若不存在則進行建立(create)。 3. **同步 CR instance** - 使用 `CustomObjectsApi` 列出來源叢集中各個 namespace(或全域)的自定義資源(CR)。 - 對每個 CR,同樣在目標叢集中先查詢是否已存在,存在則更新,不存在則建立。 4. **錯誤處理與同步機制** - 當同步操作遇到 API 例外(例如 404 表示資源不存在)時,進行相應的建立操作。 - 如果希望實現持續同步,可以利用 Kubernetes 的 watch 功能,監控來源叢集的變化,並及時將變更應用到目標叢集。 ### 程式碼範例 以下是一個簡單的 Python 程式碼示例: ```python= from kubernetes import client, config from kubernetes.client.rest import ApiException # 連接來源叢集(例如透過 context 名稱) config.load_kube_config(context="source-cluster") source_crd_api = client.ApiextensionsV1Api() source_custom_api = client.CustomObjectsApi() # 連接目標叢集 config.load_kube_config(context="destination-cluster") dest_crd_api = client.ApiextensionsV1Api() dest_custom_api = client.CustomObjectsApi() # 同步 CRD try: source_crd_list = source_crd_api.list_custom_resource_definition().items for crd in source_crd_list: crd_name = crd.metadata.name try: # 嘗試讀取目標叢集中的 CRD dest_crd_api.read_custom_resource_definition(crd_name) # 存在則更新 CRD print(f"Updating CRD: {crd_name}") dest_crd_api.replace_custom_resource_definition(crd_name, crd) except ApiException as e: if e.status == 404: # 不存在則建立 CRD print(f"Creating CRD: {crd_name}") dest_crd_api.create_custom_resource_definition(crd) else: raise except ApiException as e: print("Error synchronizing CRD:", e) # 同步 CR instance(假設你要同步的 CR 位於某個特定的 group、version、namespace、plural) # 例如: group = "example.com" # 自定義資源群組 version = "v1" # 版本 namespace = "default" # namespace plural = "myresources" # 自定義資源 plural try: source_crs = source_custom_api.list_namespaced_custom_object( group=group, version=version, namespace=namespace, plural=plural ) for cr in source_crs.get('items', []): name = cr['metadata']['name'] try: # 嘗試在目標叢集中取得 CR dest_custom_api.get_namespaced_custom_object( group=group, version=version, namespace=namespace, plural=plural, name=name ) # 如果存在則更新 CR print(f"Updating CR: {name}") dest_custom_api.replace_namespaced_custom_object( group=group, version=version, namespace=namespace, plural=plural, name=name, body=cr ) except ApiException as e: if e.status == 404: # 不存在則建立 CR print(f"Creating CR: {name}") dest_custom_api.create_namespaced_custom_object( group=group, version=version, namespace=namespace, plural=plural, body=cr ) else: raise except ApiException as e: print("Error synchronizing CR instance:", e) ``` ## 說明 - **多叢集連線**:透過在同一程式中分別載入不同的 kubeconfig context(或使用不同的 config 檔案),可以同時操作來源和目標叢集。 - **API 操作**:對於 CRD 使用 `ApiextensionsV1Api`,而對於 CR 則使用 `CustomObjectsApi`。 - **例外處理**:根據 API 返回的錯誤狀態碼(如 404),進行相應的建立或更新操作。 - **持續同步**:如果需要實現持續同步,可利用 Kubernetes 提供的 `watch` 來監控資源變化,進而觸發同步邏輯。 以上方法可以確保兩個叢集中的 CRD 與 CR instance 維持一致,不論是在初次同步或之後的更新操作,都可以透過程式化管理來達成。 希望這份文件能幫助你理解如何使用 Python 與 Kubernetes 套件實作跨叢集同步。 <br> ### 在同一程式中分別載入不同的 kubeconfig context,這邊的 context 是指什麼?資訊怎麼取得? - ### gpt-o3-mini-high (2025/02/21) > Kubernetes Kubeconfig Context 設定 在 Kubernetes 的 kubeconfig 檔案中,**context** 是一個命名的設定,它將特定的 cluster(集群)、user(使用者認證)和(可選的)namespace(命名空間)綁定在一起。也就是說,透過一個 context,你可以同時指定要連接哪個集群、使用哪組認證,還有預設操作哪個命名空間。 ### 如何取得 Context 資訊 #### 1. 檢視 kubeconfig 檔案 kubeconfig 檔案通常位於 `~/.kube/config`。這個 YAML 格式的檔案中有一個 `contexts` 欄位,裡面列出了所有定義好的 context。例如: ```yaml contexts: - name: my-cluster-context context: cluster: my-cluster user: my-user namespace: default ``` 這裡的 `name` 就是 context 的名稱,而 `context` 區塊中則指定了對應的 cluster、user 以及預設的 namespace。 #### 2. 使用 kubectl 命令 你可以透過終端機執行以下命令來列出所有的 context: ```bash kubectl config get-contexts ``` 該命令會顯示所有已設定的 context,並標記目前使用中的 context。 #### 3. 在程式中載入不同的 Context 利用 Python 的 Kubernetes 客戶端,你可以指定 context 名稱來載入對應的設定: ```python from kubernetes import config # 載入特定 context 的 kubeconfig 設定 config.load_kube_config(context="my-cluster-context") ``` 這樣可以讓你在同一程式中根據需要連接到不同的集群。 透過這些方式,你可以輕鬆取得和管理 kubeconfig 中的 context 設定,進而在程式中同時操作多個 Kubernetes 叢集。 <br> ### kubernetes.client.exceptions.ApiException: (403) - ### Question 目前在 K8s pod 中,並使用 load_incluster_config() 可以正常讀取 ```python from kubernetes import config, client config.load_incluster_config() custom_api = client.CustomObjectsApi() custom_api.list_namespaced_custom_object( group='example.com', version='v1', namespace='default', plural='books') ``` 遇到底下錯誤: `kubernetes.client.exceptions.ApiException: (403) Reason: Forbidden HTTP response headers: HTTPHeaderDict({'Audit-Id': 'be50111d-88a6-4916-befe-952536d6e9e6', 'Cache-Control': 'no-cache, private', 'Content-Type': 'application/json', 'X-Content-Type-Options': 'nosniff', 'X-Kubernetes-Pf-Flowschema-Uid': '61d8d686-8d79-4b63-801d-5a000c541458', 'X-Kubernetes-Pf-Prioritylevel-Uid': '476683c1-304f-41c1-862b-47dafbd7363a', 'Date': 'Wed, 26 Feb 2025 01:53:39 GMT', 'Content-Length': '392'}) HTTP response body: {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"books.example.com is forbidden: User \"system:serviceaccount:default:books-demo-sa\" cannot list resource \"books\" in API group \"example.com\" in the namespace \"default\"","reason":"Forbidden","details":{"group":"example.com","kind":"books"},"code":403}` 要怎麼解決? - ### gpt-o3-mini-high (2025/02/26) > 解決 403 Forbidden 錯誤的步驟 當你在 Pod 中使用 `config.load_incluster_config()` 並調用 CustomObjectsApi 時,出現以下錯誤訊息: ``` kubernetes.client.exceptions.ApiException: (403) Reason: Forbidden HTTP response body: {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"modelartifacts.genai.asus.com is forbidden: User \"system:serviceaccount:default:books-demo-sa\" cannot list resource \"modelartifacts\" in API group \"genai.asus.com\" in the namespace \"default\"","reason":"Forbidden", ... } ``` 這表示目前 Pod 使用的 ServiceAccount **(books-demo-sa)** 沒有存取自訂資源 (Custom Resource) `modelartifacts` 的權限。解決這個問題主要需依照以下步驟進行: ### 1. 檢查並建立 RBAC 權限 你需要為該 ServiceAccount 配置適當的 RBAC 許可權,讓它能夠存取 `modelartifacts` 自訂資源。 #### a. 建立 ClusterRole 如果你希望 ServiceAccount 在整個叢集中存取該自訂資源,可以使用 ClusterRole。例如: ```yaml apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: books-reader rules: - apiGroups: ["example.com"] resources: ["books"] verbs: ["get", "list", "watch"] ``` ### b. 建立 ClusterRoleBinding 將剛才建立的 ClusterRole 綁定到相應的 ServiceAccount: ```yaml apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: books-reader-binding subjects: - kind: ServiceAccount name: books-demo-sa namespace: default roleRef: kind: ClusterRole name: book-reader apiGroup: rbac.authorization.k8s.io ``` - 備註: - 在這個 YAML 配置中,`subject` 表示被授予該 ClusterRole 權限的「對象」或「主體」。 - 具體來說,它定義了哪些實體(例如使用者、群組或服務帳戶)將擁有該角色的權限。 - 在上面的範例中,`subject` 指的是位於 `default` 命名空間中的一個 **ServiceAccount**,名稱為 `vllm-llama33-70b-inst-32k-v100-sa`。 > **注意:** 如果只需要在特定 namespace 中使用權限,可以考慮使用 Role 與 RoleBinding,僅限制在該 namespace 生效。 ## 2. 套用 RBAC 設定 將上述 YAML 檔案內容保存後,執行以下指令套用: ```bash kubectl apply -f your-rbac-config.yaml ``` 這樣,ServiceAccount 就會被授予對 `books` 自訂資源的存取權限,應該可以解決 403 Forbidden 的錯誤。 ## 3. 驗證 重新啟動或重新部署 Pod,並確認應用程式是否能成功讀取該資源。如果問題仍存在,請檢查以下項目: - YAML 配置中的 API group、resource 名稱是否正確 - Pod 是否正確使用你指定的 ServiceAccount - 叢集中是否已正確套用 RBAC 規則 <br> ### books-reader-binding 這個資源,如果 namespace 不限於 default ,要如何設計? - ### gpt-o3-mini-high (2025/02/26) > 如何設計適用於所有命名空間的 books-reader-binding 在 Kubernetes 中,RoleBinding 是針對特定命名空間有效的,而 ClusterRoleBinding 則是集群範圍內有效。 若你的需求是讓「books-reader-binding」這個資源不侷限於 default 命名空間,可以考慮以下兩種設計方法: ### 1. 使用 ClusterRole 與 ClusterRoleBinding 這種方式適用於當你希望對整個集群的所有命名空間都授予權限時。 例如,可以先建立一個 ClusterRole,定義好對特定資源(例如 books)的操作權限: ```yaml= apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: books-reader rules: - apiGroups: ["books.example.com"] resources: ["books"] verbs: ["get", "list", "watch"] ``` 接著,建立 ClusterRoleBinding。 如果你想讓所有命名空間中的 ServiceAccount 都有權限,可以將 subject 設定為全局群組 `system:serviceaccounts`: ```yaml= apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: books-reader-binding subjects: - kind: Group name: system:serviceaccounts apiGroup: rbac.authorization.k8s.io roleRef: kind: ClusterRole name: books-reader apiGroup: rbac.authorization.k8s.io ``` 這樣,所有命名空間中的 ServiceAccount 都會因屬於 `system:serviceaccounts` 群組,而擁有讀取 books 資源的權限。 ### 2. 為每個命名空間建立 RoleBinding 如果你不希望過於開放權限,而是希望在特定命名空間中進行細部控制,可以在每個需要的命名空間中建立 RoleBinding,將同一個 ClusterRole 或 Role 綁定到該命名空間中的 ServiceAccount。 例如,在某個命名空間(例如 namespace1)中,你可以這樣設計: ```yaml= apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: books-reader-binding namespace: namespace1 subjects: - kind: ServiceAccount name: {pod_name}-sa namespace: namespace1 roleRef: kind: ClusterRole name: books-reader apiGroup: rbac.authorization.k8s.io ``` - **備註** - 在這個 YAML 配置中,`subject` 表示被授予該 ClusterRole 權限的「對象」或「主體」。具體來說,它定義了哪些實體(例如使用者、群組或服務帳戶)將擁有該角色的權限。在上面的範例中,`subject` 指的是位於 `namespace1` 命名空間中的一個 **ServiceAccount**,名稱為 `vllm-llama33-70b-inst-32k-v100-sa`。 - `{pod_name}-sa` 可以從 error 訊息得知: ``` HTTP response body: { "kind":"Status", "apiVersion":"v1", "metadata":{}, "status":"Failure", "message":"books.example.com is forbidden: \ User \"system:serviceaccount:default:book-demo-sa\" cannot \ list resource \"books\" in API group \"example.com\" \ in the namespace \"default\": \ RBAC: clusterrole.rbac.authorization.k8s.io \"example.com-auth\" not found", "reason":"Forbidden", "details":{"group":"example.com","kind":"books"}, "code":403 } ``` 這種方式讓你可以精準地控制哪些命名空間的哪些 ServiceAccount 具有對 books 資源的存取權。 ### 小結 - **ClusterRoleBinding**:適用於需要跨所有命名空間授權的情況,可以利用全局群組(如 `system:serviceaccounts`)來統一管理權限。 - **RoleBinding**:適用於需要精細控制每個命名空間中權限的情況,需要在每個命名空間分別建立 RoleBinding。 根據你的實際需求來選擇合適的方法,使得 books-reader-binding 的權限設定不侷限於 default 命名空間即可。 <br> ### Python 的 kubernetes 套件是否能 watch CR 的新增或變更? - ### gpt-o3-mini-high (2025/03/11) 是的,Python 官方的 Kubernetes Client(`kubernetes` 套件)可以監控(watch)自訂資源(CR)的新增、變更與刪除。大致上有兩種常見做法: 1. **使用 `CustomObjectsApi` + `watch.Watch()`** 2. **使用 Dynamic Client 進行監控** --- ### 1. 使用 `CustomObjectsApi` + `watch.Watch()` 在 Python Kubernetes Client 中,可以透過 `CustomObjectsApi` 搭配 `watch.Watch()` 來監控特定 Custom Resource 的事件。範例程式碼如下: ```python= from kubernetes import client, config, watch def watch_custom_resource(): # 讀取 Kubeconfig,或使用 config.load_incluster_config() 在 Pod 內部執行 config.load_kube_config() # 建立 CustomObjectsApi 物件 custom_api = client.CustomObjectsApi() # 建立 watch 物件 w = watch.Watch() # 透過 stream() 監控指定群組/版本/命名空間/複數名稱(plural) 的資源 for event in w.stream( func=custom_api.list_namespaced_custom_object, group="mygroup.example.com", # CRD 的 group version="v1", # CRD 的 version namespace="default", # 要監控的命名空間 plural="mycustomresources", # CRD 的 plural 名稱 timeout_seconds=60 ): # event: dict_keys(['type', 'object', 'raw_object']) # event['type'] 可能是 ADDED、MODIFIED 或 DELETED # event['object'] 是實際的 CR 物件 (dict) event_type = event["type"] cr_object = event["object"] print(f"Event Type: {event_type}") print(f"CR Content: {cr_object}") # 可以根據事件類型進行相應處理 if __name__ == "__main__": watch_custom_resource() ``` 在此範例中: - `list_namespaced_custom_object` 是一次性列出命名空間內的 CR,但透過 `watch.Watch().stream()` 可以持續監控後續事件。 - 你可以改用 `list_cluster_custom_object` 來監控整個 cluster scope 的 CR。 - `timeout_seconds` 參數會在逾時後結束監控,需要自行處理重新連線或其他流程。 ### 備註 - ### 備註1:上面程式碼的 func 和相關參數,可以獨立呼叫測試。確定沒問題,就可以帶入 `stream(...)` - code ``` custom_api.list_namespaced_custom_object( group="mygroup.example.com", # CRD 的 group version="v1", # CRD 的 version namespace="default", # 要監控的命名空間 plural="mycustomresources", # CRD 的 plural 名稱 timeout_seconds=60) ``` - 結果 ``` {'apiVersion': 'mygroup.example.com/v1', 'items': [], 'kind': 'MyCustomResourcesList', 'metadata': {'continue': '', 'resourceVersion': 'xxxx'}} ``` - ### 備註2:鍵值 `'object'` & `'raw_object'`,目前沒有發現任何差異... --- ### 2. 使用 Dynamic Client Kubernetes Python Client 也提供了 **Dynamic Client**,可以更彈性地存取或監控 CR 而不需要先定義固定的 API 類別。使用方式如下: ```python= from kubernetes import config, watch from kubernetes.dynamic import DynamicClient def watch_cr_dynamic(): config.load_kube_config() # 建立 dynamic client dynamic_client = DynamicClient( client.ApiClient() ) # 取得想要監控的資源 resource = dynamic_client.resources.get( api_version="mygroup.example.com/v1", kind="MyCustomResource" # CRD 中定義的 kind ) w = watch.Watch() for event in w.stream( func=resource.list_namespaced, namespace="default", timeout_seconds=60 ): print(event) if __name__ == "__main__": watch_cr_dynamic() ``` - `dynamic_client.resources.get()` 會根據 `api_version` 與 `kind` 找到對應的資源存取方式。 - 後續的 `.list_namespaced()`、`.list()`、或 `.get()` 都可以加上 `watch=True`,或直接用 <br> {%hackmd vaaMgNRPS4KGJDSFG0ZE0w %}