---
# System prepended metadata

title: Python / kubernetes
tags: [kubernetes, k8s, PyPi, Python]

---

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 %}