免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

K8s从入门到生产:集群搭建、externalIPs、Operator与GPU调度实战避坑

K8s从入门到生产:集群搭建、externalIPs、Operator与GPU调度实战避坑 先说个观察你把 k8s 相关的热搜词按顺序排一下基本就是一个人从入门到生产的完整踩坑路线——先是“k8s集群搭建”“k8s安装部署”然后是某个具体报错把初始化卡住了接着开始搜 externalIPs 怎么访问服务再到 Operator、GPU 调度最后是生产故障和面试题。这条路线我太熟了因为我自己就是这么走过来的。所以这篇我不打算写成一本电子书式的 K8s 教程而是把这条线上最容易卡住、最值得拆开讲的知识点单独拎出来每个都按“原理、实操、坑、排查”来讲确保你看完能直接用到自己集群里。1. 集群搭建阶段最磨人的问题master 初始化报 the api server is not healthy 的排查链路1.1 这个报错到底在说什么如果你用 kubeadm 初始化集群大概率会碰到一个非常“精确”的报错the api server is not healthy after 4m0.00747357s我第一次见到这个报错时盯着那个“4m0.00747357s”看了半天还以为是什么高深的时延问题。其实这个输出字面意思很简单kubeadm 初始化控制面后会反复探测 kube-apiserver 的健康状态等了一个时间窗口默认也就几分钟apiserver 一直没起来于是整个 init 流程直接失败并回滚已经创建的资源。这里要纠正一个最常见的误解这个报错不是你集群配置里的某个参数写错了它只是一个“结果”。“原因”几乎总是 kube-apiserver 的 Pod 根本没进入 Ready 状态。你需要去看控制面组件为什么起不来而不是盯着这个报错本身。1.2 一次完整的排查流程遇到这个报错我建议按下面这个顺序来而不是直接kubeadm reset重来。第一步确认 apiserver 容器是否存在。现在 kubeadm 默认的容器运行时是 containerd所以用crictl而不是 dockercrictl ps -a | grep kube-apiserver如果输出里容器状态是Exited或者没出现说明 kubelet 一直在尝试创建 apiserver 容器但失败或反复重启。第二步看容器日志。找到容器 ID 后crictl logs container-id这一步能解决大部分问题。我见过的高频原因包括apiserver 的参数配置错误导致启动直接 panic、apiserver 镜像还没从仓库拉下来导致容器一直ContainerCreating、etcd 健康检查失败导致 apiserver 无法连接存储后端。第三步回头看 kubelet 状态。如果容器日志没有明确报错那问题通常在 kubelet 与容器运行时之间journalctl -u kubelet -f重点观察有没有failed to get sandbox image、cgroup driver不匹配、failed to create containerd task这类信息。第四步确认镜像是否拉取完整。初始化之前建议先把所需镜像手动拉一遍比如kubeadm config images pull --image-repository registry.aliyuncs.com/google_containers这样可以把“镜像没拉全”这个变量提前排除掉。用国内镜像源或者配置 containerd 的 registry mirror 都是正规做法不要等到初始化失败再去猜。1.3 初始化之前就应该排掉的五个坑根据我的实测绝大多数 apiserver 健康检查超时根源逃不出下面这五类根因症状表现解决办法swap 未关闭kubelet 周期性报错apiserver 容器起不来swapoff -a并注释 /etc/fstab 中的 swap 行containerd 的 cgroup driver 与 kubelet 不一致kubelet 日志频繁出现 cgroup 相关 failedcontainerd 配置中启用 SystemdCgroup true镜像拉取超时或仓库不可达容器一直 ContainerCreating配置镜像加速源初始化前手动 pullapiserver 参数错误如 advertise-address 写错apiserver 容器 CrashLoopBackOff重新 init 前先检查 kubeadm-config.yaml节点 DNS 或 /etc/resolv.conf 指向本地缓存CoreDNS 和 apiserver 之间的解析异常保留 systemd-resolved 生成的配置或调整 kubelet DNS这里我想单独强调一下 cgroup driver 这个点。containerd默认的 cgroup driver 可能是cgroupfs但 kubelet 默认期望systemd尤其是在 systemd 发行版上。两者不一致kubelet 会一直报错apiserver 也跟着遭殃。你可以在 /etc/containerd/config.toml 里确认[plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc.options] SystemdCgroup true改完记得systemctl restart containerd。这个配置我在每台新节点上都会先检查一遍能省掉后面一整个晚上的排查时间。如果真的排查不出来最后的手段才是kubeadm reset后重新 init。但重来之前强烈建议先export KUBECONFIG/etc/kubernetes/admin.conf然后试着kubectl get pods -n kube-system因为有些情况下 apiserver 其实是起来的只是健康检查在某些网络插件没安装的情况下迟迟无法通过这时候你装好 CNI比如 Calico 或 Flannel问题会自动消失。2. Service 对外访问中的 externalIPs最轻量但经常被忽略的一种暴露方式2.1 externalIPs 的工作机制很多人在 K8s 里想暴露服务时第一反应就是 NodePort 或者 LoadBalancer。但如果你是在机房裸金属、公司内网环境或者自己家里搭的集群externalIPs可能是最轻量的方案而且很少被人真正讲透。externalIPs是 Service 的一个字段可以让集群外部的流量直接通过一个你指定的 IP 访问到 Service。工作机制说白了就是kube-proxy 会在所有节点上写入 iptables 规则凡是目标地址是 externalIP 的流量都会被 DNAT 到对应的 Pod endpoint 上。# 在一台节点上查看 iptables-save | grep KUBE-SERVICES你会看到类似这样的链规则流量最终会被转发到后端的 Pod IP。也就是说externalIP 并不需要在节点网卡上真实配置而是 kube-proxy 通过 iptables 规则“接管”了目标 IP 为该地址的流量。2.2 一份可以直接用的 YAML 与踩坑点直接看例子apiVersion: v1 kind: Service metadata: name: web-svc spec: type: ClusterIP clusterIP: 10.96.0.8 externalIPs: - 192.168.1.100 selector: app: web ports: - port: 8080 targetPort: 80这样配置后只要客户端能访问到192.168.1.100:8080流量就会路由到appweb的 Pod 上。但注意有一个关键问题这个 IP 必须保证在集群所有节点上都能被路由到。如果192.168.1.100是某一台物理机的 IP而你所在的网络环境里其他节点无法访问它那就会出现“部分节点上访问正常部分节点上不同”的诡异现象。最朴素的解决办法是在路由器或交换机上加一条静态路由把这个 IP 指向某一台节点或者用 keepalived 这类工具在节点之间漂移一个 VIP。另外一个坑是端口冲突。externalIPs 走的是 iptables DNAT如果宿主机上本身有别的服务占用了相同端口还是会出现一些难以排查的抖动。我自己的习惯是外部端口尽量用比较大的端口段比如 8000-9000避开系统服务常用的低位端口。2.3 和 NodePort、LoadBalancer、Ingress 怎么选很多新人喜欢问“哪个方式最好”实际上要看你的网络环境暴露方式适用场景成本需要注意的点NodePort最简单所有节点开一个高位端口低端口范围 30000-32767无法使用常规端口externalIPs裸金属、内网环境有可路由 IP 可用低IP 必须全网可达需要自己管路由LoadBalancer云环境或集群装了 MetalLB中需要额外组件或云厂商支持Ingress域名 多服务路由中高本质是七层负载均衡需要配套入口控制器如果图省事直接上 NodePort后面端口管理很容易失控——一两百个服务都是34xxx这种随机端口运维和联调都不方便。反过来如果你手头已经有一批空闲的内网 IP用 externalIPs 暴露管理面服务比如监控、日志平台、内部工具非常舒服不需要引入任何额外组件。补充一个容易忽略的细节externalIPs 只解决三层/四层的访问七层域名转发还是要靠 Ingress。所以常见组合是“externalIPs 给 Ingress Controller 的 Service 用”入口 IP 固定然后所有域名都解析到这个 IP。3. Operator 到底在解决什么问题从 CRD 到控制器再到状态自愈3.1 为什么 StatefulSet 不够用搜索热词里有“k8s中operator案例”说明很多人已经意识到 Operator 是进阶姿势但未必清楚它到底补齐了哪块短板。我带过不少刚接触 K8s 的工程师他们看完官方文档后往往有个疑问有状态应用不是有 StatefulSet 吗为什么还要搞一个 OperatorStatefulSet 解决的是一组有状态 Pod 的“装配问题”稳定的网络标识、稳定存储、有序的创建/删除。但它不懂业务。比如一个三节点的 Redis其中一台物理机挂了StatefulSet 只会按模板拉起一个新 Pod但它不会“把这个新节点加入 Redis 集群”你需要缩容或扩容到五个节点StatefulSet 也只会增减 Pod不会自动把新成员纳入集群、触发数据分片迁移。这些原本靠运维人工完成的“领域知识”才是 Operator 要解决的。换句话说Operator 是把资深 DBA、SRE、中间件运维脑子里那些操作流程变成了控制器里的一段段代码。3.2 一个最小 Operator 的结构和工作流程一个完整 Operator 一般长这样CRD定义你的自定义资源比如RedisCluster、EtcdClusterController监听资源变更不断把集群状态调到期望状态RBAC控制器自己需要 API 权限最小化授权可选组件Webhook校验配置、指标暴露给监控用核心是 Controller 里的 reconcile 循环中文叫调谐循环。它做的事情可以简化成三步Watch从 apiserver 拿到某个RedisCluster对象对比当前有多少个 Pod数据备份状态是否正常成员列表对不对调整调用 K8s API 或执行业务命令创建 Pod、更新配置、执行数据搬迁、替换故障节点用伪代码看就是def reconcile(desired_state): current inspect_cluster() if current.ready_members desired_state.replicas: add_member_to_cluster() if current.has_unhealthy_member(): replace_member() if current.needs_backup(): create_backup_job()这个模式天然是声明式的你只管告诉它“我要一个五节点的、每节点 1GiB 存储的 Redis 集群”它自己判断缺什么就补什么。3.3 什么时候值得写 Operator什么时候别碰很多人一听 Operator 就兴奋想把所有应用都控制器化。我的建议是冷静一点。值得上 Operator 的应用有两个特征一是天然有状态且生命周期复杂比如分布式数据库、消息队列、缓存集群二是配置变更会影响成员关系比如扩缩容、版本升级、备份恢复不能简单靠滚动重启。对照一下不需要 Operator - 无状态 Web 服务Deployment 就够了 - 状态简单只依赖 PVC不需要集群内互认 - 配置变更可以完全由 Helm ConfigMap 管理 值得上 Operator - 应用实例之间需要互相发现、组集群 - 有备份、恢复、替换失败节点等操作 - 升级和缩容涉及数据迁移、成员变化我见过比较典型的“杀鸡用牛刀”反例为了管理一个能通过环境变量配置的普通应用专门写了一套 Operator结果 CRD 和控制器本身的维护成本比应用还高。这条路子不划算。如果是练手学习完全可以用 kubebuilder 或 operator-sdk 拉一个脚手架实现一个最小控制器——比如管理一个“定时备份任务”的 CRD一天就能跑通对理解整个机制非常有帮助。4. 让 K8s 调度 GPU从驱动到 Device Plugin 再到 Pod 声明4.1 为什么调度器本身不认 GPU热词里有“k8s调用gpu”这是个用得越来越多、但文档和教程相对零散的方向。先讲一个最关键的概念K8s 默认的 kubelet 和调度器根本不认识 GPU。你就算在服务器上插了八张卡K8s 也无法感知到它们的存在更不可能把它们像 CPU、内存一样作为可调度的资源。要把 GPU 变成 K8s 里的可调度资源需要走一条链路GPU 驱动 - 容器运行时支持 - Device Plugin - kubelet 上报 - 调度器调度。缺一环都不行。Device Plugin 是这里面最关键的一环。它本质上是每个 GPU 节点上的一个 DaemonSet启动后通过 Unix socket 和本机 kubelet 通信把自己能提供的资源种类和数量上报给 kubelet比如nvidia.com/gpu: 8。然后 kubelet 同步给 apiserver调度器才能看见并执行调度。4.2 完整接入链路与三个容易翻车的点我以 NVIDIA GPU containerd 为例完整的接入过程包括第一步节点上装好 GPU 驱动。用nvidia-smi能看到卡这是基础。第二步安装并配置 NVIDIA Container Toolkit。改结束后的 containerd 配置大概长这样[plugins.io.containerd.grpc.v1.cri.containerd.runtimes] [plugins.io.containerd.grpc.v1.cri.containerd.runtimes.nvidia] runtime_type io.containerd.runc.v2 [plugins.io.containerd.grpc.v1.cri.containerd.runtimes.nvidia.options] BinaryName /usr/bin/nvidia-container-runtime配好后还要把 containerd 的默认运行时指到 nvidia或者通过 Pod 的runtimeClassName来切换。注意如果默认运行时没切换即使 Device Plugin 上报成功Pod 里也访问不到 GPU。第三步部署 NVIDIA 官方的 Device Plugin DaemonSet拿到官方仓库最新的部署清单 apply 即可。第四步在 Pod 里声明 GPUresources: limits: nvidia.com/gpu: 1 requests: nvidia.com/gpu: 1这里有一个非常重要的规则GPU 的 requests 和 limits 必须相等因为 GPU 目前是整数资源不能超卖。4.3 验证 GPU 调度是否生效接完之后先不要急着跑训练先做两个简单验证。验证一节点资源kubectl describe node node-name | grep -A2 nvidia.com/gpu如果Allocatable下面出现了nvidia.com/gpu说明 Device Plugin 注册成功kubelet 已经把资源上报上去了。验证二实际跑一个测试 Pod 并在容器里执行 nvidia-smikubectl run gpu-test --imagenvidia/cuda:12.0-base --restartNever \ --limits nvidia.com/gpu1 -- sh -c nvidia-smi kubectl logs gpu-test能看到显卡信息才算整条链路通了。这里说一个我踩过的坑GPU 节点出问题时最容易出现的现象是describe node里能看见nvidia.com/gpu但 Pod 调度上去之后一直ContainerCreating事件里说 failed to start device plugin。这种通常是 Toolkit 没配好或者 containerd 默认运行时没改和驱动本身没关系。排查优先级是先看驱动再看容器运行时配置最后看 Device Plugin 日志。顺带提一嘴如果你是想把一张大卡切成多个 MIG 实例给不同任务用那属于进阶玩法需要 Device Plugin 配合 GPU 的 MIG 能力来做资源上报不是简单在 Pod 里声明一个nvidia.com/gpu就完事的。这个方向可以先了解概念等确认业务有需求再投入研究。5. 生产环境中用户可感知故障三类典型问题的复盘思路5.1 间歇性 502探针与资源限制惹的祸热词里有“k8s生产环境中常见的故障影响到用户”这一条太有共鸣了。我在生产里碰到的用户可感知故障归纳起来就是“慢”“断”“挂”三类下面逐个说排查思路。第一种是间歇性 502。用户反馈“某个接口偶尔返回 502过几秒自己好了”。这类问题背后最常见的原因我排第一个的是Pod 被 readinessProbe 判定不健康从 Service endpoints 里摘掉了随后又恢复导致流量在“有后端”和“没后端”之间反复横跳。kubectl get events -n ns --sort-by.lastTimestamp | tail -30如果看到 Ready 状态来回翻转的 Pod先去检查 readinessProbe 的 timeoutSeconds 和 periodSeconds 是不是太小了。另一个高频原因是资源 limit 设太低请求一上来 Pod 就被 OOMKilled重启后短暂恢复然后再次被压垮。5.2 connection refusedendpoints 为空或 Pod 不 Ready第二种是连接被拒。用户反馈“服务完全连不上直接 connection refused”。很多人第一反应是防火墙但 K8s 里首先要查的是 Service 和 Pod 之间的关联。我的标准排查顺序是kubectl get svc -n ns kubectl get endpoints -n ns kubectl get pods -n ns -o wideEndpoint 为空最常见原因就两个selector 写错或者 Pod 没 Ready。selector 写错导致的空 endpoints 特别坑因为 K8s 不会主动提醒你标签匹配不上你只会看到 Service 一直在但后端口永远是空的。还有一种容易被忽略的情况Pod 本身是 Running但 ready 状态为 0/1。这通常是 readinessProbe 失败且探针指向的端口在容器里没监听或者依赖的配置中心连不上。5.3 节点 NotReady 引发的水位失衡第三种是节点挂了导致集群整体容量下降用户感知为“服务变慢、新请求排长队”。节点 NotReady 的常见根因包括kubelet 心跳超时、磁盘压力触发了 eviction、容器运行时无响应、网络插件挂了导致节点上的 Pod 之间断连。这类问题的排查顺序也有讲究节点视角 - kubelet 日志 - 容器运行时状态 - 网络插件状态kubectl get nodes kubectl describe node node-name journalctl -u kubelet | tail -100 crictl info我处理过的一次真实故障是节点磁盘使用率超过 85%触发了 kubelet 的 eviction 策略一批 Pod 被驱逐重建后调度到其他节点瞬间把其他节点也压到临界值形成了雪崩。事后复盘真正的根因是监控告警阈值配得太高没在磁盘到 70% 时提前介入。生产环境排查有个通用心法先定界再定根。先搞清楚用户报的问题发生在哪一层——是入口层、Service 层、还是单 Pod 层——然后逐层向下推进不要一上来就翻 yaml 猜配置。6. “K8s面试题”背后真正想考的六块知识框架6.1 控制面组件协作Pod 是怎么被创建出来的热词里有“k8s面试题”我也被问过好几次类似问题。面试题表面上是背答案但真正考察的是能不能把控制面几个组件串成一个流程。我问你一个最常见的“创建一个 Pod 的完整流程是什么”这个问题的标准展开是kubectl 把 Pod 对象 POST 到 kube-apiserverapiserver 完成认证、鉴权、准入控制把对象写入 etcdkube-scheduler watch 到这个未调度的 Pod过滤出符合要求的节点打上 nodeName目标节点上的 kubelet 收到调度结果调用容器运行时拉起容器kubelet 持续上报状态apiserver 更新 Pod 状态如果你能把这个流程里“谁是状态来源”“谁做决策”“谁执行动作”讲清楚这道题就过关了。把这个问题延伸出去就是整个 K8s 控制面知识点的主干。6.2 调度、网络、存储、安全、扩展五个常考主题的要点面试题往往围绕下面几个域展开每个域记住核心考点就行调度相关Deployment 和 StatefulSet 的区别、节点亲和性和污点容忍、静态 Pod 的使用场景。重点说清楚“有状态应用和无状态应用的编排差异”而不是死记定义。网络相关kube-proxy 的 iptables 和 ipvs 模式区别、Service 的三种类型、Ingress 与 Service 的关系。很多人能背出“ClusterIP 只在集群内部访问”但讲不清 kube-proxy 做了 DNAT这就是差距。存储相关PV/PVC 的绑定流程、StorageClass 的作用、回收策略。常考一个场景“为什么要用 PVC 而不是直接在 Pod 里挂宿主机目录”答案不是“PVC 更高级”而是 PVC 让存储和计算解耦Pod 调度到不同节点时都能找到这块数据卷。安全相关RBAC 最小权限、ServiceAccount 的作用、Secret 的存储方式。面试官很爱问“Pod 里能访问 apiserver 吗”本质上考的是 ServiceAccount 和鉴权链路。扩展相关CRD 是什么、Operator 是什么、为什么需要 MutatingAdmissionWebhook。这里能把 Controller 的调谐循环讲明白的候选人一般都加分。6.3 我的资料使用建议关于资料那个热词里有个“k8s权威指南第五版pdf下载”我的真实看法是这类大部头可以当成字典来用不适合从头读到尾。最佳组合是“官方文档的概念部分 自己亲手搭一遍集群 在生产里折腾几次故障复盘”。如果不想买书先把官方文档的 Concepts 目录刷一遍再回头看书会顺畅很多。面试准备不要背题把上面几个域里的“为什么”想清楚比刷一百道题管用。很多面试者卡壳不是不知道答案而是不知道某个机制解决了什么问题一引申到场景题就露馅了。我自己的体会是K8s 的知识点像一张网先抓住“声明式 API 控制器循环”这两条主线其他所有组件都是在这张网上挂着的执行者。这个认知建立起来之后无论继续学网络、存储还是调度都会轻松不少。
返回列表