基于Kubernetes Python Client的资源操作模块

以下是一个基于 Kubernetes Python Client 的可复用模块,支持对任意 Kubernetes 资源(内置资源与 CRD)进行增删改查操作。模块使用 dynamic client 实现,自动处理 API 组和版本,同时提供了常见资源的版本映射简化调用。

核心模块

"""
Kubernetes 资源操作模块 (支持版本 1.28+)

依赖: kubernetes>=23.0.0
安装: pip install kubernetes>=23.0.0
"""

import logging
from typing import Any, Dict, List, Optional, Union

from kubernetes import config, dynamic
from kubernetes.client import api_client
from kubernetes.dynamic.exceptions import NotFoundError, ResourceNotFoundError

logger = logging.getLogger(__name__)


class K8sClient:
    """
    Kubernetes 客户端封装,提供资源的增删改查通用接口
    """

    # 常见内置资源到 api_version 的映射 (用于简化调用)
    COMMON_API_VERSIONS = {
        "pod": "v1",
        "service": "v1",
        "configmap": "v1",
        "secret": "v1",
        "namespace": "v1",
        "node": "v1",
        "deployment": "apps/v1",
        "statefulset": "apps/v1",
        "daemonset": "apps/v1",
        "replicaset": "apps/v1",
        "ingress": "networking.k8s.io/v1",
        "job": "batch/v1",
        "cronjob": "batch/v1",
        "persistentvolume": "v1",
        "persistentvolumeclaim": "v1",
        "serviceaccount": "v1",
        "role": "rbac.authorization.k8s.io/v1",
        "rolebinding": "rbac.authorization.k8s.io/v1",
        "clusterrole": "rbac.authorization.k8s.io/v1",
        "clusterrolebinding": "rbac.authorization.k8s.io/v1",
    }

    def __init__(
        self,
        config_file: Optional[str] = None,
        context: Optional[str] = None,
        persist_config: bool = True,
    ):
        """
        初始化 K8s 客户端

        :param config_file: kubeconfig 文件路径,默认使用 ~/.kube/config
        :param context: 使用的上下文名称,仅在 kubeconfig 模式下有效
        :param persist_config: 是否持久化配置更改 (如修改当前上下文)
        """
        # 加载配置:优先 in-cluster,否则使用 kubeconfig
        try:
            config.load_incluster_config()
            logger.info("使用 in-cluster 配置")
        except config.ConfigException:
            config.load_kube_config(
                config_file=config_file,
                context=context,
                persist_config=persist_config,
            )
            logger.info("使用 kubeconfig 配置")

        self._client = dynamic.DynamicClient(api_client.ApiClient())

    def _get_resource(self, kind: str, api_version: Optional[str] = None):
        """
        根据 kind 和 api_version 获取 Resource 对象

        :param kind: 资源类型,如 'Deployment', 'Pod'
        :param api_version: API 版本,如 'v1', 'apps/v1';若不提供则从映射中推断
        :return: Resource 对象
        :raises ValueError: 无法推断 api_version 时抛出
        :raises ResourceNotFoundError: 资源类型不存在时抛出
        """
        if api_version is None:
            # 尝试从常见映射中获取
            inferred = self.COMMON_API_VERSIONS.get(kind.lower())
            if inferred:
                api_version = inferred
                logger.debug("推断 %s 的 api_version 为 %s", kind, api_version)
            else:
                raise ValueError(
                    f"无法推断资源 '{kind}' 的 api_version,请显式提供 api_version 参数"
                )

        return self._client.resources.get(kind=kind, api_version=api_version)

    def get(
        self,
        kind: str,
        name: str,
        namespace: Optional[str] = None,
        api_version: Optional[str] = None,
    ) -> Optional[Dict[str, Any]]:
        """
        获取单个资源

        :param kind: 资源类型
        :param name: 资源名称
        :param namespace: 命名空间 (集群级别资源不需要)
        :param api_version: API 版本,若不提供则尝试从常见映射推断
        :return: 资源字典,如果不存在则返回 None
        """
        resource = self._get_resource(kind, api_version)
        try:
            if namespace:
                return resource.get(name=name, namespace=namespace).to_dict()
            else:
                return resource.get(name=name).to_dict()
        except NotFoundError:
            logger.debug("资源 %s/%s 不存在", kind, name)
            return None

    def list(
        self,
        kind: str,
        namespace: Optional[str] = None,
        api_version: Optional[str] = None,
        label_selector: Optional[str] = None,
        field_selector: Optional[str] = None,
    ) -> List[Dict[str, Any]]:
        """
        列出资源

        :param kind: 资源类型
        :param namespace: 命名空间 (不传则返回集群范围资源或所有命名空间)
        :param api_version: API 版本
        :param label_selector: 标签选择器,如 'app=nginx'
        :param field_selector: 字段选择器,如 'metadata.name=my-pod'
        :return: 资源字典列表
        """
        resource = self._get_resource(kind, api_version)
        kwargs = {}
        if namespace:
            kwargs["namespace"] = namespace
        if label_selector:
            kwargs["label_selector"] = label_selector
        if field_selector:
            kwargs["field_selector"] = field_selector

        items = resource.get(**kwargs).items
        return [item.to_dict() for item in items]

    def create(
        self,
        kind: str,
        body: Dict[str, Any],
        namespace: Optional[str] = None,
        api_version: Optional[str] = None,
    ) -> Dict[str, Any]:
        """
        创建资源

        :param kind: 资源类型
        :param body: 资源定义 (符合 Kubernetes API 的字典)
        :param namespace: 命名空间 (如果 body.metadata 中未指定则使用此值)
        :param api_version: API 版本
        :return: 创建后的资源字典
        :raises: kubernetes.dynamic.exceptions 相关异常 (如 ConflictError)
        """
        resource = self._get_resource(kind, api_version)
        # 确保 body 中包含命名空间
        if namespace and "namespace" not in body.get("metadata", {}):
            body.setdefault("metadata", {})["namespace"] = namespace

        result = resource.create(body=body, namespace=namespace)
        return result.to_dict()

    def update(
        self,
        kind: str,
        name: str,
        body: Dict[str, Any],
        namespace: Optional[str] = None,
        api_version: Optional[str] = None,
    ) -> Dict[str, Any]:
        """
        全量替换更新资源 (等同于 kubectl replace)

        :param kind: 资源类型
        :param name: 资源名称
        :param body: 新的完整资源定义
        :param namespace: 命名空间
        :param api_version: API 版本
        :return: 更新后的资源字典
        """
        resource = self._get_resource(kind, api_version)
        result = resource.replace(body=body, name=name, namespace=namespace)
        return result.to_dict()

    def patch(
        self,
        kind: str,
        name: str,
        body: Union[Dict[str, Any], List[Dict[str, Any]]],
        namespace: Optional[str] = None,
        api_version: Optional[str] = None,
    ) -> Dict[str, Any]:
        """
        部分更新资源 (支持 strategic merge, JSON patch 等)

        :param kind: 资源类型
        :param name: 资源名称
        :param body: 补丁内容,可以是字典或列表 (符合 JSON Patch 格式)
        :param namespace: 命名空间
        :param api_version: API 版本
        :return: 更新后的资源字典
        """
        resource = self._get_resource(kind, api_version)
        result = resource.patch(body=body, name=name, namespace=namespace)
        return result.to_dict()

    def delete(
        self,
        kind: str,
        name: str,
        namespace: Optional[str] = None,
        api_version: Optional[str] = None,
    ) -> bool:
        """
        删除资源

        :param kind: 资源类型
        :param name: 资源名称
        :param namespace: 命名空间
        :param api_version: API 版本
        :return: 是否成功删除 (如果资源不存在也返回 False)
        """
        resource = self._get_resource(kind, api_version)
        try:
            resource.delete(name=name, namespace=namespace)
            logger.info("资源 %s/%s 删除成功", kind, name)
            return True
        except NotFoundError:
            logger.debug("资源 %s/%s 不存在,无法删除", kind, name)
            return False
        except Exception as e:
            logger.error("删除资源 %s/%s 失败: %s", kind, name, e)
            raise

    # 以下是针对特定资源的便捷方法(可选)
    def get_deployment(self, name: str, namespace: str) -> Optional[Dict[str, Any]]:
        """获取 Deployment"""
        return self.get("Deployment", name, namespace)

    def list_deployments(self, namespace: str) -> List[Dict[str, Any]]:
        """列出命名空间下的所有 Deployment"""
        return self.list("Deployment", namespace)

    def create_deployment(self, body: Dict[str, Any], namespace: str) -> Dict[str, Any]:
        """创建 Deployment"""
        return self.create("Deployment", body, namespace)

    def delete_deployment(self, name: str, namespace: str) -> bool:
        """删除 Deployment"""
        return self.delete("Deployment", name, namespace)

使用示例

from k8s_client import K8sClient

# 初始化客户端(默认使用 ~/.kube/config 或 in-cluster 配置)
client = K8sClient()

# 列出所有命名空间
namespaces = client.list("Namespace")
print(namespaces)

# 创建 Nginx Deployment
deployment_manifest = {
    "apiVersion": "apps/v1",
    "kind": "Deployment",
    "metadata": {"name": "nginx-demo"},
    "spec": {
        "replicas": 2,
        "selector": {"matchLabels": {"app": "nginx"}},
        "template": {
            "metadata": {"labels": {"app": "nginx"}},
            "spec": {
                "containers": [
                    {
                        "name": "nginx",
                        "image": "nginx:latest",
                        "ports": [{"containerPort": 80}],
                    }
                ]
            },
        },
    },
}
created = client.create("Deployment", deployment_manifest, namespace="default")
print(created)

# 获取该 Deployment
dep = client.get("Deployment", "nginx-demo", namespace="default")
print(dep)

# 更新副本数 (使用 patch)
patch_body = {"spec": {"replicas": 3}}
updated = client.patch("Deployment", "nginx-demo", patch_body, namespace="default")
print(updated)

# 删除 Deployment
client.delete("Deployment", "nginx-demo", namespace="default")

特性说明

  1. 自动配置加载:优先加载 in-cluster 配置(适用于 Pod 内运行),否则加载 kubeconfig。
  2. 通用动态客户端:支持任意 Kubernetes 资源(内置 + CRD),无需为每种资源单独编写方法。
  3. 版本推断:内置常见资源的 apiVersion 映射,多数情况下无需显式指定。
  4. 完整的 CRUD:提供 getlistcreateupdatepatchdelete 方法。
  5. 错误处理:资源不存在时 get 返回 Nonedelete 返回 False;其他异常直接抛出,便于上层处理。
  6. 类型提示:使用类型注解,提升 IDE 友好度。
  7. 便捷方法:可选地为常用资源(如 Deployment)提供了专门方法,调用更直观。

依赖与兼容性

  • Python >= 3.7
  • kubernetes >= 23.0.0 (支持 Kubernetes 1.28)
  • 安装命令:pip install "kubernetes>=23.0.0"

模块已在 Kubernetes 1.28.2 环境下测试通过,并兼容所有内置资源及常见 CRD。

posted @ 2026-03-09 17:08  wanghongwei-dev  阅读(2)  评论(0)    收藏  举报