Python k8s操作示例

1.安装kubernetes

pip install kubernetes

2.示例

import json
import os
from datetime import datetime
from typing import List, Optional, Tuple

import yaml
from kubernetes import client, config, stream
from kubernetes.client.rest import ApiException
from loguru import logger
from pydantic import BaseModel


class PodInfo(BaseModel):
    """
    pod信息模板
    """

    namespace: str  # 所在命名空间
    name: str  # 名称
    status: str  # pod状态
    ready: Tuple[int, int]  # pod内container就绪状态,(已就绪,pod预设总数)
    restart_count: int = 0  # pod内container重启总数
    age: int = 0  # pod创建至今的时间
    ip: str = ""  # pod的局域网ip
    node: str = ""  # pod所在node名称
    nominated_node: str = ""  # pod调度指定的节点名称
    readiness_gates: str = ""  # 就绪门控


class Utils:
    """
    配合KubectlPy的工具
    """

    @staticmethod
    def yaml_file_check(yaml_file_path: str):
        """
        检查yaml文件
        检查文件拓展名和文件是否存在
        """
        if not yaml_file_path.endswith(".yaml") and not yaml_file_path.endswith(".yml"):
            raise Exception("yaml文件需是yaml或yml文件")
        if not os.path.exists(yaml_file_path):
            raise Exception("yaml文件不存在")

    @staticmethod
    def get_pod_restarts(pod_status) -> int:
        """
        统计pod内container重启次数
        """
        restart_count = 0
        if pod_status.container_statuses:
            for container in pod_status.container_statuses:
                restart_count += container.restart_count
        return restart_count

    @staticmethod
    def get_pod_age(pod_status) -> int:
        """
        统计pod运行时间
        """
        pod_start_time = pod_status.start_time
        if pod_start_time:
            # tzinfo=None表示移除时区信息
            start_dt = pod_start_time.replace(tzinfo=None)
            now_dt = datetime.utcnow()
            run_time_seconds = (now_dt - start_dt).total_seconds()
            return int(run_time_seconds)
        else:
            return 0

    @staticmethod
    def get_pod_readiness_gates(pod_spec, pod_status) -> str:
        """
        提取就绪门控
        默认情况下,Kubernetes 通过检查容器的就绪探针(Readiness Probes)来判断 Pod 是否就绪。
        就绪门控(Readiness Gates) 允许 Pod 定义一个自定义的就绪条件,只有满足自定义条件时 Pod 才会被视为就绪。
        """
        readiness_gates = []
        # 获取 Pod 定义中的就绪门控条件类型
        if pod_spec.readiness_gates:
            gate_types = [gate.condition_type for gate in pod_spec.readiness_gates]
            if pod_status.conditions:
                for condition in pod_status.conditions:
                    if condition.type in gate_types:
                        readiness_gates.append(f"{condition.type}: {condition.status}")
        readiness_gates_str = ",".join(readiness_gates) if readiness_gates else ""
        return readiness_gates_str

    @staticmethod
    def get_pod_ready_status(pod_status) -> Tuple[int, int]:
        """
        获取pod内container就绪状态
        """
        ready_count = 0
        total_count = 0
        if pod_status.container_statuses:
            total_count = len(pod_status.container_statuses)
            for container in pod_status.container_statuses:
                if container.ready:
                    ready_count += 1
        return ready_count, total_count

    def parse_one_pod_info(self, pod_object) -> PodInfo:
        """
        解析一个pod的信息
        """
        pod_metadata = pod_object.metadata
        pod_status = pod_object.status
        pod_spec = pod_object.spec
        item = {
            "namespace": pod_metadata.namespace,
            "name": pod_metadata.name,
            "status": pod_status.phase,
            "ready": self.get_pod_ready_status(pod_status),
            "restart_count": self.get_pod_restarts(pod_status),
            "age": self.get_pod_age(pod_status),
            "ip": pod_status.pod_ip if pod_status.pod_ip else "",
            "node": pod_spec.node_name if pod_spec.node_name else "",
            "nominated_node": (
                pod_status.nominated_node_name if pod_status.nominated_node_name else ""
            ),
            "readiness_gates": self.get_pod_readiness_gates(pod_spec, pod_status),
        }
        return PodInfo(**item)


class KubectlPy:
    """
    apiVersion: v1

    - list_namespaces: 列举集群中的命名空间名称
    - list_pods: 列举指定命名空间下的Pod信息
    - get_one_pod_info: 获取指定命名空间下的指定Pod信息
    - exec_command_in_pod:: 在Pod中执行命令
    - add_deployment_model: 添加Deployment参数模版
    - show_deployment_model: 显示Deployment参数模版
    - create_deployment: 创建Deployment,必须先添加Deployment参数模版,并且调用时需传入模板替换参数
    - delete_deployment: 删除Deployment
    """

    def __init__(self, config_path: Optional[str] = None):
        self._utils = Utils()
        if isinstance(config_path, str):
            self._utils.yaml_file_check(config_path)
        self.config_path = config_path
        # config_path不填,则从环境变量KUBECONFIG中获取,还没有,就找"~/.kube/config"
        config.load_kube_config(config_file=config_path)
        # 操作核心 API 组里的资源,Pod、Service、Namespace、ConfigMap 等.
        self._core_v1_client = client.CoreV1Api()
        # 操作apps/v1 API 组的资源, Deployment、StatefulSet、DaemonSet 等.
        self._apps_v1_client = client.AppsV1Api()
        # 底层客户端,提供基础功能,像序列化、反序列化、HTTP 请求发送等.
        self._api_client = client.ApiClient()
        logger.info("kubectl-py成功连接k8s集群")
        self._deployment_model: Optional[str] = None

    def list_namespaces(self) -> List[str]:
        """
        列举集群中的命名空间的名称
        """
        names = []
        try:
            namespaces = self._core_v1_client.list_namespace()
        except ApiException as e:
            raise Exception(
                f"获取命名空间名称列表失败,状态码:{e.status},错误信息:{e.body}"
            )
        except Exception as e:
            raise Exception(f"获取命名空间名称列表失败,错误信息:{e}")
        for ns in namespaces.items:
            names.append(ns.metadata.name)
        return names

    def list_pods(self, namespace: str = "default") -> List[PodInfo]:
        """
        列举命名空间中的pod信息
        """
        result = []
        try:
            pods = self._core_v1_client.list_namespaced_pod(namespace)
        except ApiException as e:
            raise Exception(
                f"获取命名空间{namespace}的pod信息列表失败,状态码:{e.status},错误信息:{e.body}"
            )
        except Exception as e:
            raise Exception(f"获取命名空间{namespace}的pod信息列表失败,错误信息:{e}")
        for p in pods.items:
            item = self._utils.parse_one_pod_info(p)
            result.append(item)
        return result

    def add_deployment_model(self, model_file_path: str):
        """
        通过yaml文件添加创建deployment的模板数据
        yaml -> dict
        需要替换的字段用"$<标识>$"表示
        """
        self._utils.yaml_file_check(model_file_path)
        with open(model_file_path, "r") as file:
            model_dic: dict = yaml.safe_load(file)
            model_json = json.dumps(model_dic, ensure_ascii=False)
            if not ("$<" in model_json and ">$" in model_json):
                raise Exception("创建deployment的模板文件中,未设置用于替换的标识")
            self._deployment_model = model_json

    def show_deployment_model(self) -> Optional[dict]:
        """
        展示创建deployment的模版数据(字典)
        """
        if not self._deployment_model:
            return dict()
        return json.loads(self._deployment_model)

    def create_deployment(self, **model_replace_map):
        """
        创建deployment
        """
        if not self._deployment_model:
            raise Exception("要创建deployment,请先添加deployment模板文件")

        if not model_replace_map:
            raise Exception("要创建deployment,请提供用于替换的参数")

        temp_model_json = self._deployment_model
        for k, v in model_replace_map.items():
            assert k.startswith("$<") and k.endswith(">$"), "存在非法的替换标识"
            assert k in temp_model_json, f"deployment模板文件中,未设置{k}替换标识"
            temp_model_json = temp_model_json.replace(k, str(v))
        assert "$<" not in temp_model_json, "存在没有完成替换的标识"
        model_dic = json.loads(temp_model_json)
        sanitized_body = self._api_client.sanitize_for_serialization(model_dic)
        namespace = model_dic.get("metadata", dict()).get("namespace", "default")
        dp_name = model_dic.get("metadata", dict()).get("name", "")
        try:
            self._apps_v1_client.create_namespaced_deployment(
                namespace=namespace,
                body=sanitized_body,
            )
        except ApiException as e:
            raise Exception(
                f"Deployment{dp_name}创建失败,状态码:{e.status},错误信息:{e.body}"
            )
        except Exception as e:
            raise Exception(f"Deployment{dp_name}创建失败,错误信息:{e}")

    def delete_deployment(self, name: str, namespace: str = "default"):
        """
        删除deployment
        """
        delete_options = client.V1DeleteOptions()
        try:
            self._apps_v1_client.delete_namespaced_deployment(
                name=name,
                namespace=namespace,
                body=delete_options,
            )
        except ApiException as e:
            raise Exception(
                f"删除Deployment{name}失败,状态码:{e.status},错误信息:{e.body}"
            )
        except Exception as e:
            raise Exception(f"删除Deployment{name}失败,错误信息:{e}")

    def exec_command_in_pod(
        self,
        pod_name: str,
        command: str,
        container: Optional[str] = None,
        namespace: str = "default",
        shell_type: str = "bash",
    ):
        """
        到Pod里执行命令
        """
        if shell_type == "bash":
            exec_command = ["/bin/bash", "-c", command]
        elif shell_type == "sh":
            exec_command = ["/bin/sh", "-c", command]
        else:
            raise Exception(f"不支持的shell_type:{shell_type}")
        try:
            stream.stream(
                self._core_v1_client.connect_get_namespaced_pod_exec,
                pod_name,
                namespace,
                command=exec_command,
                container=container,
                stderr=True,  # 捕获错误输出
                stdin=False,  # 不提供命令输入
                stdout=True,  # 捕获正确输出
                tty=False,  # 不分配伪终端
            )
        except ApiException as e:
            raise Exception(
                f'执行命令"{command}"失败,状态码:{e.status},错误信息:{e.body}'
            )
        except Exception as e:
            raise Exception(f'执行命令"{command}"失败,错误信息:{e}')

    def get_one_pod_info(self, pod_name: str, namespace: str = "default") -> PodInfo:
        """
        获取单个pod信息
        """
        try:
            pod = self._core_v1_client.read_namespaced_pod(
                name=pod_name,
                namespace=namespace,
            )
        except ApiException as e:
            raise Exception(
                f"获取Pod{pod_name}信息失败,状态码:{e.status},错误信息:{e.body}"
            )
        except Exception as e:
            raise Exception(f"获取Pod{pod_name}信息失败,错误信息:{e}")
        return self._utils.parse_one_pod_info(pod)


if __name__ == "__main__":
    test = KubectlPy(config_path="kubeconfig.yaml路径")
    print(test.list_pods())

 

posted @ 2025-08-22 10:27  CJTARRR  阅读(33)  评论(0)    收藏  举报