前阵子想把kubernetes系统学一遍翻了不少资料发现几乎所有教程的开头都是同一个套路准备三台服务器一台控制面、两台工作节点。这个前提直接把我卡住了——我手头只有一台2C4G的服务器总不能为了学K8s先买几台机器吧。后来我花了两三个周末在这台机器上把原生Kubernetes集群跑通了前前后后重装过三次系统也踩了不少坑。这篇学习总结不打算讲太多空理论重点是把“一台服务器配置kubernetes”这条路彻底走通包括方案选型、kubeadm初始化参数、网络插件坑点、单机运维恢复思路。适合手里只有一台机器、又想从原理层面理解K8s的读者尤其是那些不想用minikube一带而过、想真实感受集群组件怎么协作的人。1. 一台服务器的K8s到底能干什么先把目标定清楚1.1 单机集群的两个典型用途很多人把Kubernetes描述成“多机分布式平台”这导致单机方案经常被忽略。实际上单机集群有两个很典型的使用场景学习实验型和轻量生产型。学习实验型很好理解用它熟悉Pod、Deployment、Service、ConfigMap这些核心对象的定义方式观察控制面组件如何协作练习kubectl操作这些在单机上都能完整复现。轻量生产型也不少见个人项目、几个Web服务、定时任务配上Containerd和K8s原生机制比一堆Shell脚本加系统服务管理要清晰得多。Kubernetes本质上是一套“声明式编排系统”它不关心底下是一台还是十台机器。只要你把资源请求、标签、健康检查配置正确它就会把工作负载放到能用的节点上。所以一台服务器也能成立前提是你必须接受一个现实节点挂了编排系统再努力也没办法单机天然没有故障转移到别处的能力。1.2 单机与多机的本质差异在哪里多机部署时控制面和工作负载通常分开各节点角色清晰。单机则是控制面组件和工作负载Pod共用同一台机器的CPU、内存、磁盘。集群启动后这台机器上会同时跑着一堆进程kube-apiserver、kube-controller-manager、kube-scheduler、etcd再加上kubelet和containerd全部挤在一起。单机部署最容易犯的错误是“我只需要关注Pod”实际上你还得时刻关心里面的系统资源。尤其etcd是单点它的磁盘延迟直接影响整个集群的稳定性因为每一次资源创建、更新都要经过etcd的持久化确认。理解了这一点后面给机器做优化时才知道该把注意力放在哪里。1.3 最低配置参考结合我自己的实测2核4G算是底线配置能完整跑通集群部署一两个小型应用但内存会非常紧张系统负载偏高。4核8G会舒服很多跑控制面加几个业务Pod游刃有余。磁盘建议至少预留20GB因为Kubernetes安装包、镜像缓存、容器日志很容易把空间吃满这个我后面专门踩过坑。2. 单机方案四选一minikube、kind、k3s、kubeadm我的选型心得2.1 四种常见方案的横向对比单机部署Kubernetes市面上方案不少但本质差异很大。我把常见的四种放在一起对比结论会更直观方案集群形态资源占用适合场景对学习的价值minikube本机单节点封装了驱动中快速体验、本机开发抽象较多很多细节被隐藏kind容器内跑Node可模拟多节点低CI、本地联调和真实节点差异较大k3s轻量发行版极低边缘计算、受限环境组件做了合并处理容易混淆kubeadm原生标准集群中高深入学习、生产环境扩展暴露组件细节最多2.2 kubeadm为什么最值得花时间我的观点很明确如果你想学“Kubernetes本身”而不是去学“某个Kubernetes发行版”kubeadm是唯一推荐的主线方案。它的初始化命令直接对应官方文档控制面组件以静态Pod方式运行排错思路和生产环境完全一致。minikube和k3s我并不是说不好我自己也用过它们解决的是“快速得到可用的集群”这个问题。比如k3s把很多组件合并成单进程看起来启动很快但你在学习时看日志都不知道去哪看学习价值反而折损。kubeadm把控制面组件直接放在/etc/kubernetes/manifests下面一目了然哪个组件挂了可以直接看它的静态Pod状态这种透明感在排障时特别宝贵。2.3 环境准备阶段最容易被忽略的几个点我用kubeadm初始化三次三次都失败过事后复盘发现很多问题不是出在kubeadm命令本身而是系统底层环境没准备好。第一个是swap必须关闭。kubelet默认对swap很敏感开着swap它可能直接拒绝启动或者报一堆奇怪的错。第二个是内核模块必须加载overlay和br_netfilter尤其是br_netfilter不加载的话网络插件起来后流量转发会有问题。第三个是containerd的cgroup driver必须和kubelet保持一致通常都改成systemd。这些点单独看都很小但任何一个没做对后面都会以很隐蔽的方式报错。3. kubeadm单控制面集群搭建三次初始化后的完整步骤与坑点3.1 操作系统层初始化我建议所有操作都在root或者具备sudo的普通用户下进行。第一步先处理swapsudo swapoff -a sudo sed -i / swap / s/^/#/ /etc/fstab注意swapoff -a只对当前生效重启之后会恢复所以一定要把/etc/fstab里的swap行注释掉不然下次重启又要踩一遍坑。然后加载内核模块这一步很多人会跳过cat EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter接着配置内核参数cat EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables 1 net.bridge.bridge-nf-call-ip6tables 1 net.ipv4.ip_forward 1 EOF sudo sysctl --systemnet.ipv4.ip_forward经常被忘记而这个参数不开启的话Flannel这类网络插件会出现CrashLoopBackOff因为容器间的流量转发根本走不通。接下来是containerd的配置。安装好containerd后先导出默认配置再修改containerd config default | sudo tee /etc/containerd/config.toml打开这个文件找到SystemdCgroup那一行把false改成true原因是当前环境多数使用systemd作为init进程而Kubernetes也会把cgroup driver设为systemd两边的driver必须一致。改完重启containerdsudo systemctl restart containerd建议顺手配一下crictl后面排查容器状态会很方便cat EOF | sudo tee /etc/crictl.yaml runtime-endpoint: unix:///run/containerd/containerd.sock image-endpoint: unix:///run/containerd/containerd.sock EOF3.2 kubeadm init参数设计与验证安装kubelet、kubeadm、kubectl后就可以初始化了。官方软件仓库的配置这里不展开按对应Linux发行版的文档配置好即可。初始化命令最关键的是两个参数--apiserver-advertise-address和--pod-network-cidr。sudo kubeadm init \ --apiserver-advertise-address192.168.1.10 \ --pod-network-cidr10.244.0.0/16 \ --node-namek8s-single--apiserver-advertise-address要填这台服务器实际的内网IP不要填127.0.0.1否则之后kubectl和外部组件访问API Server都会出问题。--pod-network-cidr必须和你选择的网络插件匹配我这里用的是FlannelFlannel默认使用10.244.0.0/16这个网段。如果你用Calico一般会改用192.168.0.0/16这个网段如果和现有网络冲突还要额外调整。初始化过程中kubeadm会拉取一系列镜像比如kube-apiserver、kube-controller-manager、kube-scheduler、etcd、pause。如果你的环境访问默认镜像仓库比较慢可以先执行kubeadm config images list看一眼需要哪些镜像再通过kubeadm config images pull --image-repository你的仓库地址提前拉好。网络受限的情况下这一步非常关键我遇到过初始化命令卡在拉取pause镜像超过五分钟没反应的情况后来换用当前环境可访问的镜像仓库才解决。初始化成功后kubeadm会输出两段关键信息一段是如何配置kubectl另一段是其他节点如何加入集群。先配置kubectlmkdir -p $HOME/.kube sudo cp /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config如果不配置kubeconfig执行kubectl get nodes会直接报权限错误这个错很常见。3.3 网络插件从NotReady到Ready的排查逻辑初始化完成后节点状态一定是NotReady这是正常的因为还没有安装CNI网络插件。网络插件在Kubernetes里属于“基础设施”CoreDNS等组件都要等它就绪之后才能正常运行。单机环境建议从Flannel这种轻量级插件开始。从Flannel项目的官方发布渠道获取kube-flannel.yml清单文件然后执行kubectl apply -f kube-flannel.yml很多教程执行完这条命令就直接进入下一步了但我建议你先等一分钟然后看Pod状态kubectl get pods -n kube-system如果看到Flannel的Pod处于CrashLoopBackOff状态不要慌。排查顺序是这样先看Pod日志kubectl logs它为什么崩溃大概率是镜像拉取失败或者网络插件依赖的内核参数没开启。我最开始就是漏掉了net.ipv4.ip_forwardFlannel起来后马上又退出反复重启。还有个隐蔽问题安装网络插件前如果Node长时间处于NotReady可以用kubectl describe node看节点状态里的Conditions里面会明确写“network plugin is not ready: cni config uninitialized”这类信息。看到这个提示基本就是CNI没有成功写入配置回到Flannel Pod日志继续查。3.4 解除控制面污点并验证集群可用默认情况下控制面节点带有node-role.kubernetes.io/control-plane:NoSchedule污点意思是普通工作负载Pod不允许调度到这个节点上。单节点集群如果不处理这个污点你会发现所有Deployment创建的Pod都卡在Pending状态。解决方案是把所有节点上的这个污点移除kubectl taint nodes --all node-role.kubernetes.io/control-plane-如果你用的是较早版本污点的Key可能还是node-role.kubernetes.io/master-两条命令都试一下同时也用kubectl describe node确认污点是否真的去掉了。执行成功后节点才能承载业务Pod。然后快速验证一下集群是否可用kubectl create deployment nginx --imagenginx --replicas2 kubectl expose deployment nginx --port80 --typeNodePort kubectl get svc nginx如果Pod能在几十秒内进入Running状态说明控制面、调度、kubelet、kube-proxy、CNI网络这一整条链路都已经打通。后面再挂Ingress、配置存储、部署Dashboard都是在验证过的地基上做扩展。3.5 初始化阶段典型报错速查表把这几天遇到的坑整理成一张表遇到类似问题可以直接照方抓药现象最可能的根因排查方向kubeadm init一直卡住镜像拉取慢或apiserver启动失败换可访问镜像仓库查kubelet日志kubelet报cgroup driver错误containerd与kubelet配置不一致修改config.toml的SystemdCgroupCoreDNS一直PendingCNI网络插件未安装检查kube-system下网络插件PodFlannel反复CrashLoopBackOff内核模块或ip_forward未开启检查modules-load和sysctl配置kubectl连不上集群kubeconfig未配置或地址错误重新复制admin.conf到~/.kube4. 单机集群日常运维资源临界值、重启恢复和日志定位的实操笔记4.1 两条观察线系统层与控制面层单机集群的运维比多机更依赖系统层面的监控。我习惯同时开两条观察线一条是系统资源比如free -h看内存、htop看CPU、df -h看磁盘另一条是集群状态kubectl get nodes -o wide看节点状态kubectl get pods -A看所有Pod分布。单机的风险在于一个业务Pod如果出现内存泄漏不仅影响它自己还会连累kubelet和etcd。我遇到过最典型的场景是某个Java应用堆内存配置过高系统内存被吃光后kubelet因为无法正常心跳导致节点短暂NotReady随后所有Pod都被重新调度。等机器恢复后虽然没有数据损失但服务中断了几分钟。所以如果你要在单机上跑生产负载建议给系统预留一部分资源水位。以4G内存为例空闲内存低于1G就要开始告警低于500M就非常危险了。控制面组件一旦被系统OOM杀掉集群会直接失去响应。4.2 宿主机重启后集群如何自动恢复单机集群日常最容易遇到的运维动作其实是重启服务器。重启之后集群能不能自动起来取决于两个关键服务是否设置成了开机自启sudo systemctl enable --now containerd sudo systemctl enable --now kubeletKubernetes控制面组件kube-apiserver、kube-controller-manager、kube-scheduler、etcd都是静态Pod由kubelet直接拉起所以只要kubelet和containerd先启动完成整个控制面理论上会自己恢复。但这不代表立即就能用我实测下来重启后等三到五分钟再执行kubectl get nodes更稳妥因为etcd和apiserver之间有一个初始化协商和恢复的窗口期。有一种意外情况由于非正常关机CNI的网桥接口状态异常导致Flannel Pod启动后网络不通。这时候重启containerd和kubelet有时能解决如果还不行可以查看Flannel日志按网络的错误提示处理不要一上来就重装集群。4.3 磁盘空间镜像、日志与本地数据单机集群磁盘爆掉是迟早会遇到的事除非你严格控制镜像拉取。最占空间的几个目录分别是/var/lib/containerd镜像和容器层数据、/var/log系统日志和容器日志、/var/lib/kubeletPod本地数据。排查命令很简单sudo du -sh /var/lib/containerd /var/log /var/lib/kubelet找到空间大户之后清理思路也不同。镜像缓存可以用crictl清理sudo crictl rmi --prune容器日志如果交给了journald管理可以按时间裁剪sudo journalctl --vacuum-time3d另外要特别注意日志驱动和日志轮转最好在containerd配置里提前设置不然某个Pod突然疯狂打印日志两三个小时就能把磁盘写满到时候连kubectl都会因为etcd空间不足而报错。5. 从单机到多节点之前至少要正视的四个差异5.1 网络链路差异单机环境下所有Pod都在同一个节点上IP分配比较简单流量转发都经过本机的CNI网桥。多节点环境下每个节点会分配一个独立的PodCIDR网段Pod IP要跨主机通信底层依赖CNI插件在每台机器上建立隧道或路由规则。我在单机上用了很久觉得Service转发挺自然的后来在研究多节点方案时才发现很多网络策略、跨节点访问控制、Ingress流量入口设计单机根本复现不了。单机玩得再熟这部分知识也要补。5.2 高可用差异单机集群的控制面只有一份etcd也只有一个副本。这意味着它没有故障转移能力也没有集群自愈的余地。很多人会误以为“单机K8s能跑就说明多机只是加几台机器的事”这个认知很危险。多节点控制面的核心区别在于API Server可以多副本负载均衡etcd可以组成quorum容忍部分节点故障controller-manager和scheduler通过leader选举保证同一时刻只有一个实例在工作。这些机制在单机上基本看不到需要单独学习。5.3 调度特性的可观察性差异在单机上所有Pod都落在同一个节点Kubernetes调度器的存在感很弱。你会发现节点选择、资源打分、Affinity这些机制好像没什么用。实际上调度器在多节点集群里会经过“预选、优选、打分”几个阶段单机上这些过程因为只有一个候选节点直接被简化了。学习Kubernetes调度我建议至少用两个节点的环境才能直观看到nodeSelector和affinity怎么影响Pod落点。单机更适合学习“Pod生命周期”和“服务暴露”不适合学调度细节。5.4 从单机到多机的务实练习路线如果后续有条件几条路径可以尝试最低成本的办法是准备一台控制面加一台工作节点用kubeadm生成的kubeadm join命令把第二个节点加进来观察证书、token和集群发现的完整流程想进一步理解高可用再扩展到三台控制面加多台工作节点。实在没有多台物理机时也可以先用容器方式模拟多节点但心里要清楚这终究是模拟网络和存储层面的行为跟物理机有差异。路线上我建议先把单机上的kubelet配置、容器运行时、网络插件机制吃透再上多节点排错时思路会清晰得多。最后说点个人体会。单机K8s这套折腾下来我最大的收获不是能熟练敲kubectl而是被迫学会了看日志、查状态。任何组件出问题第一反应不再是重装而是按顺序查journalctl -u kubelet、kubectl describe pod、kubectl logs。这种排错习惯放到多节点集群里一样通用。如果你手里也只有一台机器别觉得学不了Kubernetes反而应该把这一台机器上的每个细节都琢磨明白。再分享一个小技巧尽早把kubectl get events --all-namespaces --watch用熟它比很多花哨的监控面板都直观集群里发生的大小异常最先都会以Event的形式暴露出来。