☰
Ubuntu上kubeadm搭建Kubernetes集群全流程与排错
2026/10/21 12:53:52 网站建设 项目流程

第一次看到 k8s 这个缩写的时候,很多人都会下意识去猜它到底是什么意思。其实就是 Kubernetes 中间隔了几个字母的简写,这个来自希腊语的单词原意是“舵手”或者“掌舵的人”,用来描述容器编排平台非常贴切。

我最近在测试环境里用 Ubuntu 22.04 LTS 加 kubeadm 手动搭了一套多节点 k8s 集群,从系统初始化到节点全部 Ready,前后折腾了整整一个周末。中间踩了不少坑,也把整个流程彻底理顺了。这篇内容把我在 Ubuntu 上搭建 k8s 环境的完整过程、关键选型逻辑和排错方法都写下来,适合两类人看:一类是刚接触容器编排、想弄清楚 k8s 到底是什么的初学者;另一类是准备在 Ubuntu 上从零搭一套能用的多节点集群、需要避开常见坑的实践者。整个过程完全基于物理机或虚拟机,不依赖云平台自带托管版,搭出来的集群结构更透明,也更适合深入学习。

1. 先搞清楚k8s到底在解决什么问题

1.1 从“部署几台机器”到“管理一堆容器”

早期部署一个应用,直接在一台物理机上装环境、跑进程就够了。后来服务多了,开始用虚拟机做隔离,一台机器上跑多个虚拟机,每个虚拟机里面放不同的应用。再往后容器技术普及,镜像打完包之后在任何环境里都能跑,部署这件事确实变轻了,但新的麻烦也跟着来了:几十个容器分散在多台机器上,怎么互相发现?某个容器挂了怎么自动起新的?流量多了以后如何横向扩展?发新版的时候怎么做到不中断服务?

这些问题单靠 Docker 命令已经回答不了,需要一个“调度和编排中枢”来统一管理。k8s 就是干这个的。它把服务器资源抽象成一个集群,帮你在里面安排容器的位置、维护副本数量、处理服务发现和负载均衡,还负责滚动更新和故障自愈。

1.2 k8s的核心工作原理

从架构上看,k8s 集群整体分成两部分:控制面和数据面。

控制面负责做决定,核心组件包括 API Server、etcd、Scheduler 和 Controller Manager。API Server 是所有请求的入口,相当于公司的前台;etcd 是集群状态的数据库,存着“当前有哪些节点、哪些 Pod、各自什么状态”;Scheduler 负责决定一个新的 Pod 应该放到哪台机器上;Controller Manager 负责持续维护期望状态,比如某个 Deployment 要 3 个副本,少了它就自动补。

数据面负责干活,每台节点机器上运行 kubelet、kube-proxy 和容器运行时。kubelet 是节点上的代理人,跟 API Server 通信,确保本机上的容器真实运行;kube-proxy 负责实现 Service 的网络转发规则;容器运行时就是真正跑容器的软件,常见的是 containerd。

用一个生活化的类比:整个集群就像一个酒店,客人是各种应用容器,客人想住什么房间、房间要几个,你只需告诉酒店前台(API Server),酒店会自己分配客房(节点)、安排入住(调度)、检查房间状态(kubelet 汇报),客人不需要自己去挑楼层。

1.3 什么样的场景才值得上k8s

k8s 看起来很强大,但并不是所有场景都该上。如果你的环境里只有两三个单体应用,用 Docker Compose 管理一台机器就够了,硬上一套集群反而会把简单问题复杂化。

比较适合上 k8s 的场景有这么几类:第一,应用需要多副本和高可用,机器故障时希望 Pod 自动迁移;第二,需要根据负载自动扩缩容;第三,微服务数量比较多,互相之间需要服务发现和负载均衡;第四,你想要有一套统一的发布流程,比如金丝雀发布、滚动更新。判断标准其实很简单:当单机方案让你频繁手工干涉部署和运维时,就轮到 k8s 登场了。

2. 动手搭建前,先确认选型和Ubuntu环境准备

2.1 版本选型:kubeadm加containerd

搭建 k8s 的方式不止一种。开发环境可以用 Minikube,一条命令就能拉起单节点;生产或接近生产的环境,最主流的手动方案是用 kubeadm。kubeadm 是官方提供的集群初始化工具,它不会帮你装容器运行时,也不会自动装网络插件,但会把控制面组件、证书、kubeconfig 这些基础工作全部自动化,是学习底层原理和搭建多节点集群的最佳起点。

容器运行时我选了 containerd 而不是 Docker。原因很简单:k8s 从 1.24 版本开始就已经不再内置对 Docker 的适配,而是通过 CRI 接口直接对接 containerd。containerd 更轻量,也更贴近 k8s 的原生运行模型。

这里要特别提醒一下:kubeadm、kubelet、kubectl 这三个工具的版本必须与目标 Kubernetes 版本保持一致。比如你计划搭 1.28 版本的集群,那这三个组件的版本也要选 1.28 对应的小版本,不能混搭,否则初始化的时候大概率会报版本不匹配的错误。

2.2 硬件配置与节点规划

在 Ubuntu 上搭集群之前,先把硬件账算清楚。控制面节点至少需要 2 核 CPU、2GB 内存,但真实使用下来,如果后续还要部署监控、管理界面这类组件,4GB 内存会更稳妥。工作节点的配置取决于你的业务负载,最小建议也是 2 核 2GB。磁盘建议预留 20GB 以上,系统盘和镜像存储放在一块也可以,但有条件的话最好给容器数据单独挂一块盘。

下面是搭建过程中比较常见的节点规划,可以直接参考:

角色主机名示例CPU内存磁盘系统版本
控制面k8s-master2核4GB40GBUbuntu 22.04 LTS
工作节点1k8s-node014核8GB80GBUbuntu 22.04 LTS
工作节点2k8s-node024核8GB80GBUbuntu 22.04 LTS

如果你手里只有一台机器,可以先用 kubeadm 搭一个单节点集群,再移除控制面节点的污点让普通 Pod 也能调度上去,这样学习是完全够用的。

网络方面有几个硬性要求:所有节点之间要能互通,建议提前配置静态 IP;DNS 最好统一用公司或局域网内稳定的 DNS;Etcd 的通信延迟要低,跨地域集群不在本次讨论范围内。另外所有节点的时间必须同步,建议安装 chrony 或 systemd-timesyncd,时间不同步会导致证书校验失败这种非常隐蔽的问题。

2.3 Ubuntu上必须提前处理的系统配置

Ubuntu 系统默认状态离 k8s 的运行要求还有一段距离,主要有三个地方必须处理。

第一,关闭 swap。k8s 的 kubelet 默认不允许使用交换分区,开着 swap 会导致节点状态异常。执行swapoff -a只是临时关闭,还要编辑/etc/fstab把 swap 那一行注释掉,保证重启后仍然关闭。

第二,加载内核模块。容器网络需要 overlay 和 br_netfilter 模块,这两个模块不加载,后续 Pod 之间的通信会出现莫名其妙的问题。

第三,防火墙和端口放行。对于学习环境,我建议直接把 ufw 停掉,省得排查网络问题的时候分不清是防火墙还是 CNI 插件的问题。生产环境则必须按端口清单精确放行,比如控制面节点的 6443、2379、2380、10250、10251、10252 等端口需要开放。

3. Ubuntu里的完整搭建流程:从初始化到集群正常

3.1 系统初始化这几步不能省

搭建流程的第一步是在所有节点上做统一的系统初始化。我按顺序做了以下操作,每一步都不想跳过:

先用hostnamectl set-hostname给每台节点设置主机名,控制面节点叫 k8s-master,另外两台叫 k8s-node01、k8s-node02。修改好之后把主机名和 IP 的对应关系写入/etc/hosts,避免集群内部通过主机名互相访问时解析失败。

然后执行 swap 关闭、内核模块加载和 sysctl 参数设置。这里的内核参数比较关键,需要让 iptables 能看到网桥流量:

swapoff -a sed -i '/swap/s/^/#/' /etc/fstab modprobe overlay modprobe br_netfilter cat > /etc/sysctl.d/k8s.conf <<EOF net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 net.ipv4.ip_forward = 1 EOF sysctl --system

设置完成后可以确认一下lsmod | grep br_netfilter是否有输出,有输出说明模块已经加载成功。

3.2 安装并配置containerd

容器运行时我装的是 containerd。Ubuntu 的软件源里通常自带 containerd 包,直接执行apt install -y containerd就可以装上。

装完先别急着用,必须生成并修改配置。containerd 默认配置里有两个地方会影响 k8s 运行:一是SystemdCgroup的设置,二是 sandbox 镜像的地址。

生成默认配置并编辑:

mkdir -p /etc/containerd containerd config default > /etc/containerd/config.toml

编辑config.toml,找到SystemdCgroup,默认是 false,改成 true。因为 Ubuntu 系统默认使用的就是 systemd 作为初始化进程,systemd 的 cgroup 驱动和容器运行时必须保持一致,否则 kubelet 会报 cgroup 驱动不匹配的错。

同时检查sandbox_image这一项,它指定了 k8s 每个 Pod 里都会用到的 pause 容器镜像,主要用于为 Pod 提供网络和命名空间基础设施。如果默认的镜像地址拉不回来,提前把它改成你环境中可访问的镜像仓库地址,避免后面沙箱一直创建不了。改完重启 containerd 并验证:

systemctl restart containerd systemctl enable containerd crictl version

3.3 安装kubeadm、kubelet和kubectl

接下来在所有节点上安装 k8s 三个核心工具。先在/etc/apt/sources.list.d/下添加 Kubernetes 软件源,然后执行:

apt update apt install -y kubeadm kubelet kubectl

装完之后一定要执行apt-mark hold kubeadm kubelet kubectl把这些工具锁定版本,防止系统升级时无意间把 kubelet 或 kubeadm 升级到不兼容的版本。k8s 集群组件版本一旦混搭,排查问题会非常痛苦。

3.4 初始化控制面节点

在控制面节点上执行kubeadm init是整套搭建过程中最关键的一步。我使用的命令是:

kubeadm init \ --apiserver-advertise-address=192.168.10.10 \ --pod-network-cidr=10.244.0.0/16

--apiserver-advertise-address要填控制面节点的内网 IP,--pod-network-cidr是 Pod 网段,这个地址段必须和你后面要装的 CNI 网络插件要求的段保持一致。这一步如果填错,后面网络全部要返工。

初始化过程一般要等几分钟,期间 kubeadm 会先做环境预检,然后生成证书、启动 etcd、部署控制面组件。看到结尾出现类似kubeadm join ...的命令时,把完整输出保存下来,这是工作节点加入集群的凭证,里面包含了 Token 和证书哈希。

初始化完成后还要配置 kubectl 的访问权限:

mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config

此时控制面节点已经初始化完成,但集群还是“半残废”状态,因为还没有网络插件,节点会一直处于 NotReady。

3.5 安装CNI网络插件并让节点Ready

初始化完成后,必须在集群里安装一个 CNI 网络插件。CNI 负责解决 Pod 之间的跨节点通信问题。可选方案有很多,Flannel、Calico 是我用得最多的两种。Flannel 配置简单,适合快速搭建学习环境;Calico 支持网络策略,生产环境更常用。

安装 CNI 的方式通常是下载对应的 YAML 清单,然后执行kubectl apply -f。在安装网络插件之前,我建议先确认清单文件里的 Pod 网段和你初始化时设定的--pod-network-cidr一致,不一致会导致 Pod 分配不到可用 IP。

安装完成后等两分钟左右,再执行kubectl get nodes,这时可以看到控制面节点的状态变成 Ready。如果一直 NotReady,可以参考后面第 4 节的排查流程。

3.6 工作节点加入集群

工作节点加入集群分为两步。第一步把第 3.1 到 3.3 节的系统初始化、containerd 安装、kubeadm 安装操作在每台工作节点上重复执行。第二步登录每台工作节点,执行你在控制面节点保存下来的 join 命令:

kubeadm join 192.168.10.10:6443 --token <你的token> \ --discovery-token-ca-cert-hash sha256:<证书哈希>

join 成功之后,回到控制面节点执行kubectl get nodes -o wide,应该能看到所有节点陆续出现在列表中。新加入的节点需要一些时间拉镜像、启动组件,状态会从 NotReady 慢慢变成 Ready。

3.7 验证集群能正常工作

集群 Ready 不代表一切都通了,我通常还会做一轮基础功能验证。先看所有核心组件是否正常:

kubectl get pods -A

重点关注 kube-system 命名空间下的 coredns,它必须是 Running 状态。接着启动一个临时测试 Pod,访问里面已有的服务或者直接在集群里创建一个 Deployment,检查 Pod 能否相互通信。

这里我习惯用两个测试 Pod,一个打另一个的服务 IP,能通说明 CNI 网络没问题,Service 的转发规则也生效了。跑完测试记得把临时资源删掉,保持集群干净。

4. 我搭k8s环境时踩过的坑和排查链路

搭建过程中我踩了好几个坑,有些问题光看错误提示完全摸不着头脑,必须结合日志一步步倒推。下面这四个坑是我认为最有代表性的。

4.1 坑一:初始化一直卡在等待kubelet启动

第一次执行kubeadm init的时候,命令停在[kubelet-check] It seems like the kubelet isn't running很久,最后直接超时。当时第一反应是 kubelet 服务出了问题,用systemctl status kubelet查看,服务确实一直在重启。

继续看日志journalctl -u kubelet -f,里面有很多关于 CRI 的报错,大意是 kubelet 无法通过 CRI 连接到容器运行时。问题出在我没有修改 containerd 的SystemdCgroup配置。kubelet 默认用 systemd 驱动,containerd 用的还是旧的 cgroupfs 驱动,两边对不上。

解决办法就是前面说的,把config.toml里的SystemdCgroup改成 true,然后彻底重启 containerd。改完之后建议用crictl ps随手验证一下,确保 containerd 能正常通信,再重新执行kubeadm reset后重新 init。

4.2 坑二:sandbox镜像拉取一直失败

执行 init 时发现控制面组件能起来,但 Pod 一直处于ContainerCreating。看 pod 的事件时,报的错是拉取 sandbox 镜像失败。

这个问题的根子在 containerd 配置里的sandbox_image默认地址。每个 Pod 在创建之前都要先起一个 pause 容器来共享命名空间,如果这个 pause 镜像在当前网络环境里拉不下来,所有 Pod 都会卡住。

方法很朴素:修改sandbox_image,把它换成环境里能正常拉取的镜像地址,然后重启 containerd。改完配置后强烈建议先手动执行crictl pull <镜像地址>验证一下是否能成功拉取,别等创建 Pod 的时候才被发现。

4.3 坑三:节点加入后一直是NotReady

工作节点成功 join 进集群,但状态迟迟不 Ready。用kubectl describe node k8s-node01可以看到节点上有一个条件叫Ready Unknown或者NetworkReady=false。

这种问题八成是网络插件没装好,或者工作节点上没有 CNI 插件所需的二进制文件。我检查了/opt/cni/bin目录,发现是空的,Flannel 的二进制根本没有被部署上去。CNI 插件清单一般通过 DaemonSet 方式运行,它会自动把二进制挂载到每个节点上,但如果节点上连/opt/cni/bin目录都不存在,挂载就会失败。

解决方法是确保每个节点上都有这个目录,或者手动安装containernetworking-plugins软件包补齐基础二进制。然后重新应用 CNI 清单,等待 Pod 在对应节点上启动,节点状态就会恢复。

4.4 坑四:CoreDNS一直处于Pending状态

初始化完成后,其他组件都 Running 了,唯独 coredns 一直 Pending。看事件信息写的是没有可用节点,看起来像是调度不上。

当时我搭的是单节点集群,控制面节点默认带有node-role.kubernetes.io/control-plane:NoSchedule污点,普通 Pod 不允许调度到控制面节点上。CoreDNS 虽然没有设置容忍度,但它属于集群必备组件,按理说应该能跑起来。真正的原因是节点 NotReady 导致调度器认为没有可用的工作节点。

先修好网络插件让节点变 Ready,再执行kubectl rollout restart deployment coredns -n kube-system,让它重新调度,这样 CoreDNS 就正常 Running 了。如果确实只有控制面节点可用,也可以给控制面节点去掉污点,但学习环境下我更建议先等网络恢复正常。

5. 集群搭完之后我建议先做的几件事

集群从 NotReady 变 Ready,很多人就觉得大功告成了,其实后面还有几件事建议尽快做,不然用起来很别扭。

5.1 安装一套管理和监控能力

裸 k8s 的 kubectl 只能看到状态,看不到资源使用情况。想执行kubectl top node看节点负载,必须提前安装 metrics-server。这是一个轻量组件,负责从 kubelet 收集指标数据。没装它之前,kubectl top会直接报错提示 Metrics API 不可用。

如果希望有一个可视化界面,可以考虑部署 Kubernetes Dashboard 或类似的管理界面。部署完以后,它默认创建的账号只有基础权限,正式使用前需要创建 ServiceAccount 并绑定 RBAC 权限。这里要特别小心,不要图省事给账号绑定 cluster-admin,权限敞开会很危险。

5.2 尽早规划命名空间和资源配额

测试集群阶段只用一个 default 命名空间没问题,但一旦多人共用,最好建几个独立命名空间,比如 dev、test、prod,给每个命名空间设置资源配额和限制范围。这一步比想象中更重要,因为 k8s 默认情况下不对资源做限制,某个测试任务可能把整台机器内存吃满,影响其他应用。

我之前遇到过同事在测试环境跑了一个没有资源限制的批处理任务,结果把节点的内存消耗到了临界点,其他 Pod 被 OOMKilled。后来给每个命名空间都加了 ResourceQuota 和 LimitRange,这类问题基本绝迹。

5.3 对etcd做备份并了解升级路径

k8s 集群里真正需要持续备份的是 etcd,它保存了集群几乎所有状态。如果没做备份,某天 etcd 数据损坏,整个集群可能直接瘫痪。

实际操作中可以直接使用 etcd 的 snapshot 功能,把快照文件保存到安全位置,并定时执行。建议在节点上写一个 cron 脚本定期备份,定期恢复测试也顺手做一次,确保备份真的能恢复。

升级方面要记住两条原则:一是版本升级不能跨级跳,要一步步来;二是升级顺序应该是先控制面节点,再工作节点,不能反着来。每升一个小版本之前,先执行kubeadm config images pull把新版本的镜像拉到本地,确认没问题再开始。

6. 在Ubuntu上折腾k8s,我最想告诉你的一些经验

整个流程走下来,我有一个很深的体会:手动搭一套 k8s 集群,真正困难的不是执行那些命令,而是在出错之后能不能有清晰的排查思路。所有的错误日志都有它的逻辑,kubelet 日志里写 CRI 连不上,就去检查容器运行时;Pod 事件里写网络不正常,就去检查 CNI 插件和内核模块。把“报错 -> 查日志 -> 定位假设 -> 验证修改”这条链路跑熟了,以后遇到任何组件问题都不会慌。

再分享一个特别实用的小技巧:执行kubeadm init或者kubeadm join之前,先手动跑一遍镜像预拉取,命令是kubeadm config images pull。这一下能提前暴露所有镜像拉取问题,避免在正式流程中途卡住。我当时就是靠这个把 sandbox 镜像的问题提前发现的,省了不少时间。

最后还有一句话想说:学习 k8s 最有效的路径不是看文档也不是看视频,而是自己在 Ubuntu 上从零搭一套集群,然后把集群故意弄坏几个地方,再自己修好。这个过程恍恍惚惚会让你觉得繁琐,但等你能不看参考就说出控制面各个组件是干什么的、Pod 启动的生命周期是怎样的,“k8s 是什么”这个问题的答案就不再是书上的一句话,而是你亲手验证过的经验了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询