Python Kubernetes客户端实战:从API调用链到集群自动化运维
做 Kubernetes 开发这几年我踩过最多的坑反而不是业务逻辑本身而是怎么和集群 API 打交道。明明 kubectl 敲一行命令就能搞定的事换到 Python 里写脚本却总是别扭——要么认证方式搞不明白要么返回值结构摸不透要么版本兼容性让人头大。后来我把官方 client-python 从底层到实战完整梳理了一遍把日常运维和自动化里最常用的 API 调用链全部串了起来才发现这东西用顺了以后比 kubectl 灵活得多。这篇教程就是把我整理的这套 Python Kubernetes 客户端用法完整写出来适合刚接触 K8s 二次开发的工程师也适合想把日常运维操作脚本化的朋友。1. 环境准备与基础认知1.1 客户端版本和集群版本的匹配问题很多人上手 Python Kubernetes 客户端kubernetes 包时第一件事就是pip install kubernetes装完就写代码结果跑起来全是版本不兼容的报错。这个库的版本号对应的是它内部封装的 Kubernetes API 版本比如kubernetes26.0.0对应的是 K8s v1.26.0 的 API 规范。为什么这个对齐这么重要因为 Kubernetes 的 API 会随着版本演进不断做增删改旧客户端调用新集群的接口可能拿不到新字段新客户端调用旧集群的接口可能请求了旧集群根本不存在的资源。我实际踩过的一个典型案例是集群还在 v1.23代码里用了 v1.24 才引入的ServerSideApply相关参数结果 API Server 返回 400排查了半天才意识到是版本错位。装客户端时建议用虚拟环境隔离直接一条命令搞定python3 -m venv k8s-env source k8s-env/bin/activate pip install kubernetes装完以后先跑一句验证python -c import kubernetes; print(kubernetes.__version__)我习惯把kubernetes.__version__打印出来和你集群的版本对比一下只要主版本号相差不超过 2 个版本绝大多数场景都没问题。要是你管理的是多个集群且版本跨度大那就得在代码里做适配层不要一个客户端版本跑天下。1.2 kubeconfig 加载方式文件、配置对象与集群内认证Python 客户端的认证方式主要分三种加载本地kubeconfig文件、直接构造Configuration对象、以及在 Pod 内部使用load_incluster_config()。这三种方式的适用场景差异很大我换集群操作时就经常用第一种部署在集群里的巡检脚本则用第三种。本地开发时最常见的是from kubernetes import config, client config.load_kube_config() v1 client.CoreV1Api()这段代码默认读取~/.kube/config如果你有多套集群配置可以指定文件路径config.load_kube_config(config_file/path/to/kubeconfig)这里有个很多人不知道的坑load_kube_config()加载的是当前 context 对应的集群如果你需要操作多个集群建议用config.load_kube_config(contextcluster-2)这种方式显式指定 context而不是每次都手动改 kubeconfig 文件。在 Pod 内部运行时就不能读 kubeconfig 了要用 ServiceAccount 挂载的 token 和 CA 证书from kubernetes import config config.load_incluster_config() v1 client.CoreV1Api()load_incluster_config()会自动读取/var/run/secrets/kubernetes.io/serviceaccount/下的 token 和 ca.crt。需要注意 RBAC 权限Pod 的 ServiceAccount 如果没绑定对应的 Role调 API 会直接 403 Forbidden这个我后面在常见问题里会详说。第三种方式是手动构造 Configuration适合那种从远程 API Server 连接且不方便用 kubeconfig 的场景比如从本机直连集群的 6443 端口。你需要拿 token 作为 Bearer Token 塞进请求头再加上 CA 证书做 TLS 校验。这种方式我一般只在调试阶段用生产环境还是优先 kubeconfig 和 in-cluster。2. 核心对象与基础 API 串讲2.1 CoreV1Api 与 AppsV1Api 的分工Python 客户端里 API 对象分层对应 Kubernetes 的 API Group。最常用的是两个CoreV1Api和AppsV1Api。CoreV1Api 管 Pod、Service、Namespace、ConfigMap、Secret 这些基础资源AppsV1Api 管 Deployment、StatefulSet、DaemonSet、ReplicaSet 这些工作负载资源。实际写代码的时候很多人上来就client.CoreV1Api()一把梭结果要操作 Deployment 时懵了——CoreV1Api 里根本没有 Deployment 的接口得切换到client.AppsV1Api()。这个分层的设计逻辑是跟 kubectl 的apiVersion对应的Deployment 的apiVersion是apps/v1Pod 是v1。所以你在 Python 里选择哪个 Api 对象本质上就是在决定往哪个 API Group 发请求。我整理了一张我日常开发中高频用到的资源与 Api 对象的对照表资源类型API 对象常用方法对应 apiVersionPodCoreV1Apilist_namespaced_pod, read_namespaced_pod, create_namespaced_podv1ServiceCoreV1Apilist_namespaced_service, create_namespaced_servicev1ConfigMapCoreV1Apiread_namespaced_config_map, create_namespaced_config_mapv1SecretCoreV1Apilist_namespaced_secret, create_namespaced_secretv1NamespaceCoreV1Apilist_namespace, create_namespacev1NodeCoreV1Apilist_node, read_nodev1DeploymentAppsV1Apilist_namespaced_deployment, create_namespaced_deploymentapps/v1StatefulSetAppsV1Apilist_namespaced_stateful_setapps/v1DaemonSetAppsV1Apilist_namespaced_daemon_setapps/v1IngressNetworkingV1Apilist_namespaced_ingressnetworking.k8s.io/v1这里有一个经验你写代码之前先想清楚你要操作的资源在哪个 API Group再去翻对应 Api 对象的方法效率会高很多。不要像我刚开始那样在 CoreV1Api 里翻半天想找一个 Deployment 的方法翻不到还以为是客户端版本问题。2.2 命名空间与标签选择器筛选逻辑的核心Kubernetes 集群里的资源量一大全量 list 回来再在内存里过滤是低效且不优雅的。正确的做法是把过滤条件直接下推到 API Server用label_selector和field_selector参数。标签选择器是运维脚本里最常用的筛选方式。比如你要列出所有带appnginx标签的 Podv1 client.CoreV1Api() pods v1.list_namespaced_pod(namespacedefault, label_selectorappnginx)多个标签条件可以用逗号组合语义是 ANDlabel_selectorappnginx,tierfrontend。这里有一个我踩过的坑label_selector的语法不是 Python 的表达式而是 K8s 规定的标签选择器语法比如environment in (production, staging)这种带操作符的写法也是合法的。如果你要用不等条件写成env!prod也是支持的但这种情况我一般不太推荐因为语义容易混淆。字段选择器则用来筛选资源本身的字段信息比如找到所有处于Failed状态的 Podpods v1.list_namespaced_pod(namespacedefault, field_selectorstatus.phaseFailed)字段选择器能过滤的字段远少于标签选择器不同的资源类型可用的字段也不同。Pod 常用的有metadata.name、metadata.namespace、status.phase、spec.nodeNameNode 资源可以用spec.nodeName但反过来 Pod 用spec.nodeName也能筛出调度到某个节点的 Pod。实际用的时候我不建议把字段选择器和标签选择器混用得太复杂因为一旦筛选条件写错排查的难度比遍历过滤还大。还有一个limit参数直接限制返回数量配合_continue参数做分页。大规模集群下 list 全量 Pod 是很重的操作分页能有效降低 API Server 的压力。这个用法我放在后面的分页章节具体说。2.3 创建与更新资源从 V1Deployment 对象说起Kubernetes 的 Python 客户端创建资源的方式和 kubectl apply 完全不同。kubectl 是读 YAML 然后交给服务端处理Python 客户端则需要你构造出对应的对象实例再传给 create 方法。这个编程模型对新手来说是最难适应的一点。比如创建一个 Nginx Deployment代码大概是这样的from kubernetes import client apps_v1 client.AppsV1Api() deployment client.V1Deployment( api_versionapps/v1, kindDeployment, metadataclient.V1ObjectMeta(namenginx-deployment, namespacedefault), specclient.V1DeploymentSpec( replicas3, selectorclient.V1LabelSelector(match_labels{app: nginx}), templateclient.V1PodTemplateSpec( metadataclient.V1ObjectMeta(labels{app: nginx}), specclient.V1PodSpec( containers[ client.V1Container( namenginx, imagenginx:1.25, ports[client.V1ContainerPort(container_port80)] ) ] ) ) ) ) apps_v1.create_namespaced_deployment(namespacedefault, bodydeployment)V1Deployment这个类会把你的参数序列化成 API Server 认识的 JSON 结构再通过 HTTP 请求发出去。这里的关键点在于spec.selector.match_labels必须与spec.template.metadata.labels匹配否则 Kubernetes 会拒绝这个 Deployment。实际经历过一次我只在 selector 里写了appnginxPod 模板里忘了加标签结果 API Server 直接报错spec.selector与spec.template.metadata.labels不匹配。这种错误很蠢但从另一个角度说明客户端代码里的字段对应关系是稳定的照着 API 规范写就不会错。更新资源的方法也值得注意。常用的两种方式一种是直接改对象再replace_namespaced_deployment一种是patch_namespaced_deployment做局部更新。replace 是整体替换调用前必须先read出来拿到最新的resource_version否则会因为版本冲突报 409。patch 则是做 Merge Patch只需要传你要修改的字段不需要先读原对象。apps_v1.patch_namespaced_deployment( namenginx-deployment, namespacedefault, body{spec: {replicas: 5}} )这种局部更新的方式在生产环境里最安全因为你不需要处理并发冲突比如多个脚本同时修改同一个 Deployment 的场景patch 只更新你指定的字段不容易把别人的改动覆盖掉。replace 我基本很少用了除了那些不支持 patch 的资源。3. 常用 API 调用串联实战3.1 从部署到验证完整串联一个发布流程我在帮团队做发布自动化脚本的时候把整个流程拆成了六个环节每次新需求上线就按这个链路走检查命名空间是否存在、清理旧版本资源、创建 Deployment、创建 Service、等待 Pod 就绪、验证访问入口。这套链路在 Python 里用客户端 API 串联起来非常直观。先把命名空间处理掉def ensure_namespace(name): core_v1 client.CoreV1Api() namespaces core_v1.list_namespace(field_selectorfmetadata.name{name}) if not namespaces.items: core_v1.create_namespace(client.V1Namespace(metadataclient.V1ObjectMeta(namename))) print(fnamespace {name} created) else: print(fnamespace {name} already exists)这里我用field_selector来查命名空间是否存在比list_namespace()全量返回再遍历判断要轻量得多。然后是清理旧的 Deployment。如果 Pod 模板参数变了Deployment 本来就会滚动更新但有时候你改了名字或者要强制重建就得先删除def delete_deployment_if_exists(name, namespace): try: apps_v1 client.AppsV1Api() apps_v1.delete_namespaced_deployment(namename, namespacenamespace) except client.exceptions.ApiException as e: if e.status ! 404: raise这里要说明一下delete_namespaced_deployment默认的propagation_policy是Background删除操作是异步的方法返回后资源可能还没完全消失。所以如果紧接着要创建同名 Deployment建议先轮询确认资源已终止再执行创建。我见过同事写脚本因为没等待删除完成结果创建时报AlreadyExists排查了很久才明白是异步删除的问题。接下来创建 Deployment 和 Service我这里用create_namespaced_deployment和create_namespaced_service把对象构造好一次性提交。注意 Service 的selector必须和 Deployment 的 Pod 标签一致这个也是新手最容易忽略的。service client.V1Service( api_versionv1, kindService, metadataclient.V1ObjectMeta(namenginx-service, namespacenamespace), specclient.V1ServiceSpec( selector{app: nginx}, ports[client.V1ServicePort(port80, target_port80)] ) ) core_v1.create_namespaced_service(namespacenamespace, bodyservice)这里要补充一个细节V1ServicePort里的port是 Service 对外暴露的端口target_port是转发到 Pod 内容器的端口。这两个值搞反了会导致 Service 不通而且不会有任何报错Pod 一切正常但访问就是超时。我调这个问题花了整整一个下午最后是靠手工curlPod IP 复现才定位的。3.2 等待 Pod 就绪轮询机制与超时控制的实现发布流程里最关键的一步是等 Pod 真正 Running 且 Ready而不是创建完 Deployment 就返回。客户端 API 提供的只是我收到了创建请求的确认而不是资源已就绪的通知。我的经验是写一个 wait 函数用轮询的方式读取 Pod 状态import time def wait_for_pods_ready(namespace, label_selector, timeout120): core_v1 client.CoreV1Api() start time.time() while time.time() - start timeout: pods core_v1.list_namespaced_pod(namespacenamespace, label_selectorlabel_selector) if pods.items: all_ready all( pod.status.phase Running and (pod.status.conditions and all(c.type ! Ready or c.status True for c in pod.status.conditions)) for pod in pods.items ) if all_ready: print(fall pods ready after {time.time() - start:.1f}s) return True time.sleep(3) raise TimeoutError(fpods not ready within {timeout}s)这段代码里有个细节pod.status.phase Running只代表容器被创建了不代表容器里的进程已经就绪。真正判断就绪必须看pod.status.conditions里type Ready且status True的那个条件。只判断 phase 的话经常出现状态是 Running 但应用还没起来的假阳性。轮询间隔time.sleep(3)我建议设成 3 到 5 秒太短会给 API Server 造成压力太长又会拖慢整个流程。另外超时时间要根据业务调整镜像下载慢的集群给 300 秒都不嫌多本地轻量级环境 60 秒就够了。3.3 读取日志与事件诊断 Pod 异常状态的核心手段当 Pod 一直没 Ready 或者 CrashLoopBackOff 的时候用客户端 API 拿日志和事件是比 kubectl 更灵活的排查方式。因为你可以把获取到的信息结构化地处理比如自动提取关键词、把日志归档到文件。读取 Pod 日志的代码很简单但参数要注意def get_pod_logs(namespace, pod_name, tail_lines100): core_v1 client.CoreV1Api() return core_v1.read_namespaced_pod_log( namepod_name, namespacenamespace, tail_linestail_lines, timestampsTrue )tail_lines参数对应kubectl logs --tail的语义只拿最后 N 行日志避免全量日志拉爆网络。timestampsTrue会给每条日志加上时间戳这对排查什么时候开始报错特别有用。还有一个followTrue的参数可以做流式日志类似kubectl logs -f在 Python 里返回的是生成器配合for line in stream就可以实时处理。事件的话用list_namespaced_event按时间排序后输出关键信息def list_events(namespace, involved_object_name): core_v1 client.CoreV1Api() events core_v1.list_namespaced_event( namespacenamespace, field_selectorfinvolvedObject.name{involved_object_name} ) for e in sorted(events.items, keylambda x: x.last_timestamp): print(f{e.last_timestamp} {e.type} {e.reason}: {e.message})事件里通常藏着最有价值的排查信息比如镜像拉取失败的具体原因、探针失败的超时细节。我处理 Pod 一直 Pending 的问题时第一反应就是拉事件看FailedScheduling的 message里面会直接告诉你0/3 nodes are available: 1 Insufficient cpu之类的原因比逐项检查节点资源快得多。4. 进阶玩法与性能调优4.1 Watch 机制实时监听资源变化前面介绍的 API 调用都是拉模式即主动请求、拿到快照。但很多自动化场景需要推模式——资源一变就通知你比如监控 Deployment 的扩容事件、监听 ConfigMap 变化触发配置热加载。Kubernetes 的 Watch 机制正是为这种场景设计的Python 客户端里通过watch.Watch()封装了这套能力。一个典型的监听 Deployment 变化的例子from kubernetes import watch, client def watch_deployment_changes(namespace): apps_v1 client.AppsV1Api() w watch.Watch() for event in w.stream(apps_v1.list_namespaced_deployment, namespacenamespace): deployment event[object] event_type event[type] print(f{event_type}: {deployment.metadata.name} replicas{deployment.spec.replicas}) if event_type DELETED: break这里实现了类似kubectl get deploy -w的流式监听。w.stream()返回一个生成器每个 event 里有typeADDED、MODIFIED、DELETED和object资源对象本身。你可以在循环里加自己的业务逻辑比如当副本数异常变化时发告警。Watch 机制有一个我特别想提醒的坑它靠长连接维持如果连接断了事件会暂时堆积。断线重连后服务端会用resource_version做增量恢复所以你的客户端代码要正确处理410 Gone这种旧版本失效的异常。我实际遇到的场景是开发环境集群版本从 v1.26 升级到 v1.28旧脚本的 watch 突然报错查下来就是 resource_version 过期了。应对方式是捕获异常后重新执行 watch 流。4.2 分页与性能控制list 全量大资源的正确姿势集群规模大了以后list_namespaced_pod()返回全部 Pod 对象的数据量非常可观尤其当你有几百个节点、上万 Pod 的时候一次全量拉取直接拖垮 API Server 和客户端内存。官方推荐的分页方式是配合limit和_continue。第一次调用时设置limit500API Server 会返回一页数据同时响应里的metadata._continue字段会带一个继续令牌把令牌传给下一次调用即可拿到下一页。def list_all_pods_paginated(namespace): core_v1 client.CoreV1Api() continue_token None while True: resp core_v1.list_namespaced_pod(namespacenamespace, limit500, _continuecontinue_token) for pod in resp.items: process_pod(pod) continue_token resp.metadata._continue if not continue_token: break我在生产环境用这个模式拉取全量 Pod 信息做资源统计耗时和内存占用都比全量 list 好很多。需要注意_continue令牌只在 API Server 预定的时间窗口内有效如果处理时间太长令牌过期会返回 410。所以拿到一批数据后应该尽快处理不要在里面做大量耗时操作。还有一种性能优化方案是用field_selectorstatus.phaseRunning这类条件缩小返回集只拿你关心的那一小部分数据。比如我只关心非 Running 状态的 Pod就把筛选条件下推给 API Server而不是全量拉回来再过滤。这样不仅减少网络 IO还降低了客户端内存压力。4.3 动态客户端和自定义资源Cover 自定义 CRD 场景管理 K8s 集群时除了官方内置资源你大概率会遇到自定义资源定义CRD。比如公司内部开发的发布平台、定时任务系统通常都会注册自己的 CRD。用 Python 客户端操作 CRD 有两种路径一种是直接定义动态客户端另一种是使用client.CustomObjectsApi。CustomObjectsApi的list_cluster_custom_object方法可以直接操作任意应用组下的自定义资源custom_api client.CustomObjectsApi() cronjobs custom_api.list_cluster_custom_object( groupstable.example.com, versionv1, pluralcrontabs ) for job in cronjobs[items]: print(job[metadata][name], job[spec].get(cronSpec))用CustomObjectsApi时返回的数据结构是字典而不是类型化对象字段都是动态的这对处理 CRD 来说反而灵活因为自定义资源的 schema 完全由你定义类型化对象反而没法做通用操作。动态客户端则更进一步连 API Group 和资源类型都可以在运行时动态指定。它的设计思路很像反射from kubernetes.dynamic import DynamicClient dyn_client DynamicClient(client.ApiClient()) resource dyn_client.resources.get(api_versionapps/v1, kindDeployment)这样写出来的代码可以通吃多种资源类型写通用运维平台的时候特别香。但代价是完全没有类型检查字段拼错了只能在运行时爆异常。我的建议是脚本化和明确类型场景用类型化客户端CoreV1Api、AppsV1Api写自动化平台这种通用框架时用动态客户端。5. 常见问题与排查技巧实录5.1 401 Unauthorized 与 403 Forbidden 的区分和处理用 Python 客户端访问集群报错时最常见的两类异常是 401 和 403。很多刚上手的人把这两个混为一谈其实它们的排查路径完全不同。401 Unauthorized 表示你没有通过服务端的身份认证换句话说服务端不认识你是谁。在 Kubernetes 的语境下通常是 kubeconfig 里的 token 失效或者证书过期。一个典型场景是ServiceAccount 的 token 有有效期比如绑定了一段时间后轮换你在 Pod 里运行的脚本拿到的是旧 token请求就被拒了。排查时先确认 kubeconfig 里的 token 还能不能用kubectl auth whoami如果这条命令报 401说明 kubeconfig 本身就有问题。Python 客户端里报 401 时可以打印异常响应的 body 看详细提示from kubernetes.client.exceptions import ApiException try: v1 client.CoreV1Api() v1.list_namespaced_pod(namespacedefault) except ApiException as e: print(fstatus: {e.status}) print(freason: {e.reason}) print(fbody: {e.body})401 错误时的响应体里通常会带WWW-Authenticate头或具体错误描述顺着这个信息去查 token 状态就行。403 Forbidden 就完全不同了它是认证通过但没有操作权限。这种情况几乎都指向 RBAC 配置问题。比如你在 Pod 里跑脚本时只是默认挂载了defaultServiceAccount而default在大部分集群里只有很有限的权限一调用 list Pods 直接 403。解决 403 的标准姿势是创建专用的 ServiceAccount 并绑定 Rolekubectl create serviceaccount my-bot -n default kubectl create rolebinding my-bot-admin \ --clusterroleadmin \ --serviceaccountdefault:my-bot \ -n default绑定后 Pod 里挂载的 token 就有对应权限了。这里我特别提一下业务脚本的 RBAC 权限应该遵循最小权限原则不要图方便直接绑cluster-admin因为一旦脚本被爆破整个集群就裸奔了。我见过好几个团队因为图省事绑了 admin结果脚本里一个 bug 误删了所有命名空间血泪教训。5.2 连接超时与 TLS 证书报错的处理Python 客户端访问集群时连接层的报错往往比 API 层的报错更让人头大。我遇到过两类典型问题一类是ConnectionTimeout另一类是 SSL 证书校验失败。连接超时先检查 API Server 的地址在你当前网络环境下是否可达curl -k https://api-server:6443/version注意这里用-k跳过证书校验只是用来判断网络通不通。如果 curl 能通但 Python 客户端超时多半是域名解析或代理问题检查一下环境变量里有没有设HTTP_PROXY或HTTPS_PROXY这类代理变量会把访问集群的请求也转发出去导致连接卡死。SSL 证书报错的表现是类似[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed这个报错有两种可能一是 kubeconfig 里的certificate-authority-data和集群实际 CA 不匹配二是你手动用了 IP 访问而证书里没有对应的 SAN。第一种情况重新获取集群 CA 即可第二种情况如果是测试环境可以在Configuration里临时关闭校验configuration client.Configuration() configuration.host https://api-server:6443 configuration.verify_ssl False configuration.ssl_ca_cert None client.Configuration.set_default(configuration)但生产环境严禁这样做关闭 TLS 校验等于把集群的访问凭证暴露在明文 HTTP 下。正确的方案是把这个集群的 CA 证书下载下来配置进 kubeconfig 或者 Python 客户端的ssl_ca_cert路径。5.3 并发调用时的线程安全问题Python 客户端在并发场景下的线程安全性是很多人忽略的问题。kubernetes库底层用的是 OpenAPI 生成的 ApiClient默认情况下每个 ApiClient 实例维护一个 HTTP 连接池。如果你在多线程里同时用同一个CoreV1Api实例去调用接口可能会碰到连接复用的异常或者不确定的超时行为。我的做法是一个线程一个 ApiClient 实例不要共享。具体实现上是把 ApiClient 和对应的 Api 对象放进线程本地存储import threading _thread_local threading.local() def get_core_v1(): if not hasattr(_thread_local, core_v1): api_client client.ApiClient() _thread_local.core_v1 client.CoreV1Api(api_client) return _thread_local.core_v1这样每个线程都有独立的 HTTP 连接池不会互相抢连接或者写坏共享状态。实际上大多数运维脚本的并发量不大单线程跑顺序也够用但如果你要批量处理几百个 Deployment 的伸缩操作用线程池并发是常见优化手段。这时候线程隔离的 ApiClient 就是必需品。另外API Server 侧也有并发限制大量并发写请求会触发APIService的max-in-flight限流客户端表现为 429 Too Many Requests。遇到这种报错要加退避重试不要一味的加大并发不然集群的资源管理器会先被打挂。5.4 问题排查速查表结合上面讲的这些我把遇到过的典型异常整理成了速查表可以直接对着排查异常表现可能原因排查思路解决方式401 Unauthorizedtoken 过期 / 证书失效kubectl auth whoami验证刷新 kubeconfig 或重建 ServiceAccount token403 ForbiddenRBAC 权限不足检查 ServiceAccount 的 Role/RoleBinding创建最小权限的 Role 并绑定ConnectionTimeout网络不通 / 代理影响curl 测试 API Server 端口关代理、检查防火墙SSL CERTIFICATE_VERIFY_FAILEDCA 不匹配 / 手动 IP 访问核对 kubeconfig CA下载正确 CA 或临时跳过验证测试环境409 ConflictresourceVersion 冲突检查 replace 前是否读取最新版本改用 patch 方法做局部更新404 NotFound资源不存在或未同步确认命名空间、资源名拼写创建前先 list 检查429 Too Many RequestsAPI Server 限流检查 client 并发量降低 QPS、加退避重试410 GoneresourceVersion 过期watch 长连接中断时间过长捕获异常后重建 watch 流AlreadyExists异步删除未完成确认旧资源是否已终止轮询等待删除完成再创建ApiException 0底层网络异常检查客户端版本和连接配置升级客户端、检查连接池配置6. 综合实战写一个集群巡检脚本6.1 脚本设计思路把前面拆开讲的这些 API 串起来最有代表性的综合场景就是写一个集群巡检脚本自动检查所有节点状态、异常 Pod、版本信息然后把结果整理成一个报告输出。这个脚本我放在团队内部用每次发布前跑一遍能提前发现很多隐形问题。脚本的设计思路是这样按模块拆分采集函数每个函数负责一类资源的检测。核心采集点有三个——节点状态、工作负载健康度、集群版本与组件状态。最后统一汇总打印报告。6.2 核心代码与输出示例节点状态采集最核心的是看每个 Node 的conditions特别关注Ready条件是否为True以及节点上的allocatable和capacity资源余量def check_nodes(): core_v1 client.CoreV1Api() nodes core_v1.list_node() unhealthy_nodes [] for node in nodes.items: ready False for condition in node.status.conditions: if condition.type Ready: ready condition.status True if not ready: unhealthy_nodes.append(node.metadata.name) return unhealthy_nodesPod 健康检查的核心是找出所有非 Running、Completed、Succeeded 状态的 Pod以及那些运行中但 Ready 条件不是 True 的def check_pods(namespacedefault): core_v1 client.CoreV1Api() pods core_v1.list_namespaced_pod(namespacenamespace) abnormal_pods [] for pod in pods.items: if pod.status.phase not in [Running, Succeeded]: abnormal_pods.append((pod.metadata.name, pod.status.phase)) continue if pod.status.phase Running: ready False for condition in pod.status.conditions or []: if condition.type Ready: ready condition.status True if not ready: abnormal_pods.append((pod.metadata.name, RunningNotReady)) return abnormal_pods版本信息与组件状态用version_api.get_code()拿到集群版本再通过 list 节点上的 kubelet 版本核对建康def check_cluster_version(): version_api client.VersionApi() version version_api.get_code() print(fcluster version: {version.git_version}) return version.git_version跑完以后脚本输出我一般设计成 Markdown 表格格式方便直接贴到工作群里。实际运行中这个脚本帮我抓到过几次节点 NotReady、几个 Pod 一直 ImagePullBackOff 的问题比手动敲 kubectl 一个个查效率高太多了。6.3 脚本的调度与集成建议巡检脚本写完之后要真正发挥价值就得定时跑。我建议两种方式一种是本地 cron 定时跑简单直接另一种是打包成镜像作为 CronJob 部署在集群内部这样巡检脚本本身就在利用 Kubernetes 的调度能力。CronJob 部署时要注意 RBAC 权限配置给脚本创建一个专门的 ServiceAccount绑一个只有只读权限的 RoleapiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: cluster-reader rules: - apiGroups: [] resources: [nodes, pods, services, configmaps, secrets] verbs: [get, list, watch]这样巡检脚本只有读取权限即使出 bug 也不会误删集群资源。部署为 CronJob 后脚本的输出通过容器的 stdout 就能被日志系统收集不需要单独的文件存储。这也是我目前团队里在用的方案稳定跑了小半年几乎没出过问题。我个人在反复写这类脚本过程中的体会是Kubernetes 的 Python 客户端最大的价值不在于替代 kubectl而在于把 Kubernetes 变成你应用系统的一部分。你可以用代码去编排发布流程、自动化巡检集群状态、监听资源变化触发业务逻辑甚至结合 Webhook 做审批流。这些能力是 kubectl 命令做不到的。建议你按这篇文章的路径先熟悉 CoreV1Api 和 AppsV1Api 的常用方法理解资源对象的构造逻辑然后从一个小的自动化场景切入比如自动清理异常 Pod或者自动同步 ConfigMap。用顺手了以后你会发现这个库的性能和灵活性完全够用而且 Python 的生态还能帮你把采集到的数据接进监控、告警、报表形成一条完整的自动化链路。最后提醒一句所有跑在集群里的脚本务必做好 RBAC 权限控制能用只读绝不用写权限权限越大出事故的半径越大。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →