首页 / 资讯中心 / 文章详情

单节点Kubernetes部署实战:从kubeadm初始化到故障排查全解析

单节点Kubernetes部署实战:从kubeadm初始化到故障排查全解析 ★ FEATURED ARTICLE
一台服务器就搭Kubernetes这事听起来有点像拿屠龙刀杀鸡但真做起来的人并不少。我最近整理自己这套单节点部署流程时翻了不少过去的操作记录发现同一批问题反复踩镜像拉不下来、cgroup驱动对不上、节点一直NotReady。这篇就当一次学习总结把“只用一台服务器配置Kubernetes”这件事从方案选型、环境准备到kubeadm完整实操、问题排查全部过一遍。如果你是刚接触Kubernetes、手头只有一台4C8G的云主机或者一台旧电脑想低成本跑起一个接近生产形态的集群这篇文章正好对口。单机部署的思路很简单控制面组件、etcd、工作负载全部跑在同一台机器上集群里只有一个节点。这种方式不能谈高可用也不算大规模生产方案但它能让你用最小的资源密度把整个Kubernetes体系跑起来。下面的内容我会尽量按“先想清楚选型再准备环境然后动手部署最后排坑”的顺序来讲每一条命令和参数都注明为什么这么配方便你照着操作还能理解背后的逻辑。1. 先想清楚单机部署Kubernetes到底选哪条路1.1 为什么“一台服务器”这件事值得折腾很多人会觉得学习Kubernetes至少需要三台机器这是最常见的误区。生产环境确实需要多节点高可用但个人学习、功能验证、跑小型服务一台物理机完全足够。而且单机环境有个天然好处网络拓扑简单不存在跨节点通信问题排查问题的时候可以把范围缩小到“组件本身”而不是被多个节点之间的网络折腾。更实际的是成本问题。云服务器按年付费三台机器加起来不是小数目本地旧电脑虽然配置不算高但跑一个轻量的开发集群绰绰有余。我在实际使用中体会最深的一点是真正限制你学习Kubernetes的不是机器数量而是对组件之间关系的理解。单节点集群能覆盖大部分日常操作场景包括Deployment滚动更新、Service暴露、ConfigMap挂载、PVC持久化、HPA自动扩缩容这些核心概念和节点数没有强绑定。1.2 四种主流方案横向对比单机部署的方案大致可以分成四类minikube、kind、k3s/k0s等轻量发行版、以及最“原生”的kubeadm。它们的定位差别很大适合的人群也不同。方案部署复杂度资源占用与生产一致性适合场景minikube低中中初学者快速体验但依赖虚拟机驱动kind低低中CI环境、多节点模拟测试速度快k3s/k0s低极低中高边缘设备、树莓派、极简环境kubeadm高高高想真正理解Kubernetes组件结构的人minikube的优势是命令简单一条minikube start就能把集群拉起来但它默认会用虚拟机方式隔离环境资源开销并不小而且很多底层细节被封装掉了出问题后你甚至不知道去查哪个组件。kind的思路是用容器来跑Kubernetes节点创建非常快适合在CI流水线里做集成测试但它和真实物理机的资源隔离、内核参数环境还是有差异。k3s把一堆组件打包成单个二进制内存占用能压到几百兆对服务器配置要求极低但默认内置的traefik、local-path-provisioner这些“惊喜”会让初学者分不清什么是Kubernetes本身、什么是k3s自带的能力。1.3 我的选型结论如果你有充足时间并且想真正搞懂Kubernetes我强烈建议用kubeadm来搭单节点。原因很直接kubeadm是官方推荐的集群引导工具它不会替你做太多封装control plane每个组件都清清楚楚跑在系统服务或静态Pod里遇到问题可以直接看日志、看容器状态这个过程本身就是最好的学习。我在这篇文章后面也全部基于kubeadm来讲。如果你只是想快速有个环境跑业务或者服务器配置很低2C2G这种那k3s更适合你。我见过不少同事拿k3s当学习环境用起来确实舒服但后来让他们排查网络插件问题时就有点无从下手因为k3s自动整合了网络和数据存储组件出问题后的报错信息不够“标准”。这里没有绝对的好坏只看你的目标是什么。我的建议是学习的苦不能省道路上踩的坑都是未来的经验。2. 环境准备与关键决策2.1 硬件和系统的底线要求关于服务器配置我实测下来有一个比较舒服的参考线CPU至少2核内存至少4G磁盘至少40G。这只是能跑的程度如果你要同时跑control plane和多个业务Pod4G内存会在高负载时频繁触发OOM Killer建议8G起步。我在一台4C8G的机器上部署过calico网络插件、nginx应用、redis中间件和Prometheus监控整体运行还算稳但如果再加两个重量级服务内存就会吃紧。操作系统建议选择Ubuntu 22.04或Debian 11/12这样的主流Linux发行版内核版本不要太老。Kubernetes对内核有一定要求特别是使用eBPF类网络插件时内核版本直接决定功能是否可用。安装前先把系统更新到最新关闭不需要的图形桌面服务确保sshd能够稳定连接。服务器配置好后给机器设置一个固定的hostname我在做集群初始化之前都会先执行hostnamectl set-hostname k8s-single避免主机名随机变化导致节点标识混乱。2.2 容器运行时选择与cgroup驱动Kubernetes从1.24版本开始彻底移除了对Docker作为底层运行时的直接支持现在主流的运行时是containerd。很多人以为containerd是Docker的替代品其实它是Docker当年拆出来的底层容器运行时比Docker更适合Kubernetes直接对接因为接口更干净、资源占用更低。我在1.24之前的老资料上看到一堆配置Docker的操作现在完全可以跳过直接把containerd装好就行。需要特别注意的是cgroup驱动的一致性。kubelet默认使用systemd作为cgroup驱动containerd也应当配置成SystemdCgroup true否则kubelet启动时会报出“cgroup driver不匹配”之类的错误。这个错误是单机部署里最常见的拦路虎后面问题排查章节我会详细说。安装containerd后不要急着启动先去修改它的配置目录因为不同发行版默认的config.toml路径可能不同我习惯用containerd config default /etc/containerd/config.toml生成一份完整配置模板再改这样不容易漏掉关键字段。2.3 网络插件选型Kubernetes本身不提供Pod网络实现需要额外安装CNI插件。常见的选项有Flannel、Calico、Cilium三者的特点差异明显。Flannel最简单配置好之后一条命令就能让节点Ready适合学习和轻量场景但它的网络策略能力比较弱封装了VXLAN隧道性能不是最优。Calico功能全面支持丰富的NetworkPolicy跨网段路由能力好很多生产环境都在用缺点是组件多一些内存占用相对高。Cilium基于eBPF实现性能非常强还能做可观测性但对内核版本要求高单机学习用它会增加不少额外变量。我在单节点环境里更推荐Flannel或者Calico。如果你的服务器内存低于4G老老实实用Flannel如果想让环境和生产更贴近直接上Calico顺便把NetworkPolicy也一起学了。无论选哪个都必须在kubeadm init时指定对应的Pod网段Flannel默认是10.244.0.0/16Calico默认是192.168.0.0/16。这里只要记住Pod网段必须和CNI配置保持一致否则网络插件起来后会因为网段冲突反复重启。2.4 内核参数、Swap与防火墙的预设置安装开始前还有一批系统层面的配置要做。首先是加载内核模块overlay和br_netfilter这两个模块分别负责容器文件系统挂载和Linux网桥流量转发缺少任何一个都会导致容器网络异常。然后设置内核参数net.bridge.bridge-nf-call-iptables1让iptables规则能够作用于桥接流量否则节点间的Pod通信会断。这些操作写进/etc/rc.local或者systemd模块配置里重启后依然生效。Swap的处置是高危操作。kubeadm初始化时会检查swap是否开启如果开启会直接报错中止。生产环境我们倾向于禁用swap因为Kubernetes希望用严格的资源请求和限制来管理内存swap的存在会干扰这个机制。命令是swapoff -a并且要注释掉/etc/fstab里的swap行避免重启后自动挂载。如果你偏想保留swap也可以在初始化时加--ignore-preflight-errorsSwap参数跳过检查但我不建议这么做因为内存压力上来后系统会变得难以预测。防火墙方面如果开了ufw或firewalld至少需要放行6443、2379-2380、10250这几个端口但单机学习时最简单的方式是先停掉防火墙等熟悉后再逐步收紧规则。3. kubeadm单节点集群部署实操3.1 初始化准备主机名和内核模块现在开始完整实操。我先说明以下命令基于Ubuntu 22.04和Kubernetes 1.30版本其他地方版本不同时需要做少量调整。第一件事是设置主机名并把它写入/etc/hosts的映射里这样kubeadm在解析API Server地址时不会有歧义。hostnamectl set-hostname k8s-single echo 127.0.0.1 k8s-single /etc/hosts然后配置内核模块。我一般直接写一个配置文件到/etc/modules-load.d/k8s.conf内容只有两行overlay和br_netfilter。写完后执行modprobe overlay和modprobe br_netfilter手动加载再用lsmod确认。接下来是网络转发参数创建/etc/sysctl.d/k8s.conf写入net.bridge.bridge-nf-call-iptables1、net.bridge.bridge-nf-call-ip6tables1和net.ipv4.ip_forward1然后执行sysctl --system让配置立即生效。这一步做完可以顺手把swap关掉再配合fstab注释保证重启不恢复整个过程大概五分钟。3.2 安装kubeadm、kubelet、kubectl与容器运行时Kubernetes的安装包目前在官方软件源里有专门渠道不会进入发行版默认仓库。安装步骤比较简单先说containerd。装好containerd后生成默认配置并修改关键项重点是把SystemdCgroup设置为true同时把sandbox_image改成你本地区能够稳定访问的pause镜像地址。这里多说一句很多节点起不来的案例都是因为pause镜像拉不动所以在安装阶段就应该解决这个潜在问题。apt-get update apt-get install -y containerd mkdir -p /etc/containerd containerd config default /etc/containerd/config.toml改完配置重启containerd并且确认状态是active。接下来添加Kubernetes软件源导入GPG key和repository后安装三件套kubelet、kubeadm、kubectl。apt-get update apt-get install -y kubelet kubeadm kubectl安装完成后执行apt-mark hold kubelet kubeadm kubectl防止后续系统更新把这些核心组件升级到不兼容版本。这个习惯我在多次升级事故后养成的Kubernetes小版本升级还算平滑但如果被系统自动拉起来一个和CNI不兼容的新版本整个集群可能瞬间NotReady。3.3 初始化控制平面并处理单节点污点初始化命令是整个部署过程的核心也是最容易出错的一步。我使用的命令如下初始化时间取决于镜像拉取速度一般三到五分钟kubeadm init \ --pod-network-cidr192.168.0.0/16 \ --apiserver-advertise-address你的内网IP \ --cri-socketunix:///run/containerd/containerd.sockpod-network-cidr必须和后面安装的Calico默认网段保持一致这里我以Calico为例。apiserver-advertise-address可以省略不填时kubeadm会选择默认网卡IP但如果服务器有多块网卡最好手动指定。初始化成功后kubeadm会输出一个kubeconfig路径和一段worker节点加入命令单节点不需要worker所以我们只关心kubeconfig配置。mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config这时kubectl get nodes已经能看到master节点但状态肯定是NotReady因为网络插件还没装。我建议在装网络插件之前先做一件事查看节点上的污点。单节点集群只有一个控制面节点默认情况下普通Pod是不允许调度到这个节点上的如果不去掉污点后面部署业务应用时会一直Pending。执行kubectl taint nodes --all node-role.kubernetes.io/control-plane-把控制面污点移除单节点才能承载业务Pod。这个操作在生产环境要谨慎但在学习环境完全没问题。3.4 安装网络插件与验证集群网络插件的安装本质上就是应用一组YAML。Calico官方manifest内容比较多下载和apply时需要有耐心。网络插件启动需要先拉取镜像这一步耗时较长尤其在同一台机器上还会和control plane组件抢带宽需要等一会儿才能看到节点Ready。kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml如果服务器有多个网卡Calico可能选错网卡导致跨节点路由异常但单节点场景一般不会遇到这个问题。安装后持续观察命名空间状态watch kubectl get pods -n kube-system等所有Pod都进入Running再执行kubectl get nodes节点的STATUS会从NotReady变成Ready。如果超过两分钟还没Ready不要急着重新init先去看calico或kube-flannel的日志大多数情况都是镜像拉取不畅或者Pod网段不匹配。这个等待过程正好可以验证自己对Pod状态机、Deployment、DaemonSet这些概念的理解。3.5 部署一个Nginx示例应用验证全链路集群Ready后我习惯立即部署一个简单应用验证全链路这个动作非常重要很多坑会在“看似正常”的集群上潜伏几天才暴露。我用nginx做例子创建Deployment和Servicekubectl create deployment nginx-demo --imagenginx:latest kubectl expose deployment nginx-demo --port80 --typeNodePort kubectl get pods -o wide kubectl get svc确认Pod在Running状态后通过NodePort访问服务。如果机器没有外部网络访问条件用kubectl port-forward也可以验证。到这里一个功能完整的Kubernetes集群已经在单台服务器上跑起来了。后续你可以在上面继续挂各种工作负载但要注意这台机器的资源上限所有Deployment都应该配置requests和limits否则一个内存泄漏的服务就能拖垮整台机器。4. 高频问题与排查实录4.1 节点NotReady从检查kubelet开始NotReady是单机部署中出现频率最高的问题遇到它先别慌。第一步看kubelet服务状态systemctl status kubelet如果状态是failed立刻用journalctl -u kubelet -f查看实时日志。日志里可能有明确错误比如“failed to run kubelet”“misconfiguration”“connection refused”等。我遇到过几次都是因为containerd配置里SystemdCgroup没改成truekubelet和containerd的cgroup驱动不一致导致死循环。如果kubelet状态正常就去看kube-system命名空间里的Pod。网络插件Pod处于CrashLoopBackOff多半是镜像没拉到coredns处于Pending几乎可以断定是网络插件没准备好。这里要养成一个习惯遇到问题先describe而不是不停getkubectl describe pod xxx -n kube-system能告诉你事件记录、拉取镜像失败原因、探针失败时间点信息量远大于简略状态列表。不要反复重启机器多数问题重启解决不了。4.2 kubelet与containerd的cgroup driver不匹配这个错几乎是每个kubeadm部署者都会撞到的我把它单列出来。报错特征是在kubelet日志里看到“kubelet cgroup driver: systemd is different from docker cgroup driver: cgroupfs”或者类似表述。现在Kubernetes和containerd都推荐使用systemd驱动两者必须一致。修改/etc/containerd/config.toml找到SystemdCgroup这一项并改成true然后重启containerd和kubelet。改完后如果还不行检查kubelet是否被手动加过--cgroup-driver参数。kubelet配置文件在/var/lib/kubelet/config.yaml确保cgroupDriver字段是systemd。我见过有人在网上抄了一段把cgroup-driver改成cgroupfs的配置结果越改越乱。这里的原则很简单containerd配置和kubelet配置保持一致即可具体用systemd还是cgroupfs都可以跑但现代系统里默认值都是systemd你就顺着默认走。4.3 镜像拉取失败与registry mirror配置初始化过程中频繁出现“failed to pull image”的报错原因大多指向默认镜像源不可达。Kubernetes 1.24后默认镜像仓库地址是registry.k8s.io网络环境不理想时拉取会超时。解决办法是给containerd配置registry mirror把registry.k8s.io等域名映射到你本地能访问的镜像服务。在/etc/containerd/config.toml中仿照已有mirrors段落添加配置然后重启containerd。这里有两点很关键mirror不只是对集群镜像生效你部署业务应用时也同样受益配置写错一个引号都会导致containerd起不来改完文件最好用containerd config validate检查一遍。4.4 Pod一直Pending的常见原因单节点集群里Pod一直Pending最可能的原因就是控制面污点没有去掉。你用kubectl describe pod能看到事件里写着“0/1 nodes are available: 1 node(s) had untolerated taint”解决方式在前面已经讲过执行taint命令移除即可。如果不是污点问题就看资源是否充足Pod请求的内存量大于节点可分配量调度器不会把Pod放上来。单节点上没有其他节点可以调度所以排障就是彻底检查这一个节点为什么“容不下”。再看StorageClass。如果你部署的是StatefulSet或使用了PVCPod会一直Pending等待PV绑定。单机环境没有默认的动态存储供应器时需要先创建local PV或者部署一个local-path provisioner。这个问题在初学者里很常见因为他们觉得“磁盘明明有空间”但Kubernetes的存储抽象不会自动使用节点空闲磁盘你得显式告诉它哪里可以存数据。4.5 CrashLoopBackOff和镜像更新后遗症的排查Pod反复重启状态卡在CrashLoopBackOff通常有三类原因镜像名写错导致拉取失败、启动命令或探针配置错误、以及应用本身启动时崩溃。排查顺序是先kubectl logs 查看日志再看describe里的Events。如果应用是数据库或中间件往往容器起来几秒又挂日志里会有明确的退出原因。另外有一个容易忽略的坑如果部署deployment时用的是latest镜像而网络上又恰好存在多种平台架构的manifest在非amd64架构上启动时可能会拉到错误的镜像层导致启动失败。解决办法是给镜像补全tag或者用单架构镜像。这个算不上Kubernetes的坑但单机用户喜欢用旧电脑ARM架构的设备越来越多值得留意。5. 单节点集群的日常维护与使用心得5.1 资源限制与Pod调度策略单节点集群的最大风险是资源耗尽。一台机器同时运行系统进程、kubelet、容器运行时、control plane Pod和业务Pod任何一个进程异常都可能影响整台机器。我给所有工作负载设置requests和limits是基本操作requests用于调度决策limits用于保护节点。比如nginx的Deployment我会写成requests.memory128Milimits.memory256Mi这样调度器知道它最少需要多少资源宿主机也知道它最多能占用多少。如果你希望某些系统级服务稳定运行可以给kubelet配置system-reserved参数把一部分CPU和内存从可分配资源里预留出来。单节点本身没有隔离机制一旦节点内存耗尽Kubernetes会按QoS等级驱逐Pod但如果驱逐不及时系统可能先触发OOM Kill。我在同一台机器上跑监控和业务时发生过一次意外最终靠严格限制存储类中间件的内存解决了问题。这个教训给所有人节点资源规划的重要性不亚于集群高可用设计。5.2 etcd备份、日志轮转和定期清理很多人都觉得etcd是分布式组件单节点不需要备份这是严重的错误。单节点集群只有一份etcd数据磁盘故障意味着整个集群元数据全部丢失。etcd备份可以用etcdctl直接打快照备份文件里包含所有资源对象状态。执行备份时要用到etcd证书路径命令大致是ETCDCTL_API3 etcdctl \ --endpointshttps://127.0.0.1:2379 \ --cacert/etc/kubernetes/pki/etcd/ca.crt \ --cert/etc/kubernetes/pki/etcd/server.crt \ --key/etc/kubernetes/pki/etcd/server.key \ snapshot save /backup/etcd-snapshot-$(date %Y%m%d).db备份策略至少一天一次放到和系统盘不同的目录。日志方面容器日志默认会堆积在/var/log/containers下如果长时间不清理会占满磁盘建议配置logrotate按大小轮转Kubernetes的container日志文件本身也会被Docker式的JSON格式切割但轮转机制最好自己控制一下。还要注意定期清理不用了的镜像和悬空资源一条kubectl delete namespaces 比手动删一堆YAML高效多了。5.3 我的个人体会什么场景真正适合单节点实话说单节点集群不是生产环境的替代品但它是性价比最高的学习与开发环境。我在这台机器上做过Deployment回滚、Ingress配置、ConfigMap热更新、自定义CRD和Operator调试几乎没遇到过因为只有单节点而学不了的东西。真正需要多节点才能体会的是Pod被重新调度到另一台机器的过程、节点故障转移机制、以及etcd多节点选举这些场景单机永远模拟不了。如果你想在单节点上获得更接近生产的体验可以尝试把网络策略加进去、给Deployment配上PodAntiAffinity让调度器理解有些负载不适合共存这种方式能帮助你发现单节点下不容易暴露的资源竞争问题。最后分享一个小技巧我专门写了一组检查脚本定时查看节点NotReady状态、磁盘使用率、kube-system Pod健康度一旦异常就推送到自己的消息渠道。这种自动化巡检习惯比盯着kubectl get nodes更重要毕竟你不可能二十四小时盯着一个安静运行的学习集群。
阅读完成 · 觉得有帮助?
咨询建站