做运维这行,绕不开 Kubernetes;学 Kubernetes,绕不开从零装集群。网上关于 K8s Cluster 详细安装步骤的文章非常多,可真正照着装的时候,很多人会栽在同一个地方:版本没对齐、镜像拉不下来、CNI 网段冲突、节点加入后一直 NotReady。这篇文章我就按实际动手的顺序,从版本规划一直写到排障,把每一步为什么要这么做的原因也一并说清楚。内容会比较多,但跟着走完,你应该能获得一套可用的 Kubernetes 集群。适合想用 kubeadm 搭建测试环境或小型生产集群的读者。
1. 动手前先做的三件事:版本规划、节点规划与运行时选型
1.1 版本规划:小版本一致比追新更重要
Kubernetes 的版本号是 x.y.z,比如 1.28.3。x 是主版本,y 是次版本,z 是补丁版。kubeadm 对组件版本有兼容性要求:master 和 worker 上的 kubelet 大版本允许差一个小版本,但最好保持一致。我见过的翻车场面,多半是 master 用 1.28,某个 worker 用 1.25,节点加入时 apiserver 直接拒绝证书或版本相关请求。所以动手前先定一个版本,比如 1.28.x,所有节点都装 1.28 对应的版本。
怎么挑选具体版本?不建议盲目追最新。选择一个当前仍在维护期内的稳定版本即可,比如 1.28 或 1.29 这样的小版本。选好后,把 kubeadm、kubelet、kubectl 三个组件锁在同一个版本上,比如用 apt 安装时指定版本号,而不是直接install kubeadm kubelet kubectl。不然系统源一更新,你会无脑升到最新版本,测试环境问题不大,生产环境可能引入不必要的变化。
1.2 节点规划与硬件最低要求
如果你只是搭测试环境,用三台虚拟机就够了:一台控制面节点(master),两台工作节点(worker)。如果讲究高可用,控制面至少三个节点起步,但这里先按单控制面讲。控制面节点建议不低于 2C4G,worker 节点建议 2C4G 起。磁盘方面,控制面节点的/var/lib/etcd尽量使用 SSD,etcd 对磁盘 IO 延迟比较敏感。
网络方面,需要给控制面节点一个固定 IP,后面 kubeadm join 会用这个 IP 或域名作为 API Server 地址。还要提前规划两段不会与其他网段冲突的虚拟网段:Pod 网段和 Service 网段。kubeadm 默认的 Service 网段是 10.96.0.0/12,Pod 网段初始化时不指定,由 CNI 插件决定,比如 Calico 默认 192.168.0.0/16。这两段网段必须避开你的物理内网网段,否则后面路由会非常混乱。
| 节点角色 | CPU 最低 | 内存最低 | 磁盘要求 | 网络要求 |
|---|---|---|---|---|
| 控制面 master | 2C | 4G | 系统盘 + etcd 数据盘建议 SSD | 固定 IP,6443 端口可达 |
| 工作节点 worker | 2C | 4G | 系统盘,容器镜像存储空间 | 与 master 网络互通,内网互通 |
| 测试环境 | 2C | 2G | 可凑合,但不建议生产 | 单机或虚拟机均可 |
1.3 容器运行时选型:为什么我推荐 containerd
Kubernetes 自己不直接启动容器,它通过容器运行时去拉镜像、启停容器、管理挂载。早期版本流行 Docker + cri-dockerd 的方式,但 Docker 并不原生实现 CRI(Container Runtime Interface),需要多一层适配。现在 kubeadm 默认推荐 containerd,它把 CRI 插件直接内置了,组件更少,部署更轻,踩坑的概率反而更低。
containerd 也不是唯一选择,CRI-O 也可以,但 containerd 是 kubeadm 默认集成、绝大多数 Kubernetes 发行版默认使用的运行时,相关文档和排障案例最多。对于自建集群,我建议优先 containerd。后面所有步骤也按 containerd 来写。
2. 基础环境配置:内核参数、主机名和 DNS 是翻车重灾区
2.1 关闭 Swap:不是玄学,是 kubelet 的硬性要求
Kubernetes 的 kubelet 默认拒绝在开启 Swap 的节点上工作。K8s 希望节点内存资源按容器的 requests/limits 管理,如果依赖 Swap,容器会使用远超 limits 的内存,影响调度和 QoS 判断。所以开节点后第一件事就是把 swap 关掉。
临时关闭命令是swapoff -a,但这只是临时的,重启后/etc/fstab里的 swap 配置会重新激活它。你需要注释掉/etc/fstab中 swap 那一行。部分发行版上,Swapon 由 systemd 管理,最好顺手执行systemctl mask swap.target(如果存在),避免开机时被激活。注意这些操作要在所有节点上做,不只是 master。
2.2 加载内核模块和设置 sysctl 参数
为了让 iptables 能看到桥接流量、让容器能够转发流量,需要调整几个内核参数。大多数容器方案都依赖 overlay 文件系统,所以先加载两个模块:
cat <<EOF | tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF modprobe overlay modprobe br_netfilter然后写入 sysctl 配置:
cat <<EOF | 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 sysctl --systemnet.bridge.bridge-nf-call-iptables让 bridge 设备上的流量经过 iptables 规则处理,K8s 的 Service 转发依赖它;net.ipv4.ip_forward打开内核 IP 转发,Pod 跨节点通信和 Service 代理都依赖它。这几行配置在多数 Linux 发行版上,比如 CentOS、Ubuntu,都可以直接用,但轻量级系统可能默认没有启用,务必配置后确认已生效。
2.3 主机名、hosts 解析与时间同步
Kubeadm 初始化时会用主机名生成证书,证书里的节点名会和 kubelet 注册名对应,所以主机名必须全局唯一,不能用 localhost.localdomain 之类的默认值。建议统一小写:
hostnamectl set-hostname k8s-master然后修改每台节点的/etc/hosts,把所有节点 IP 和主机名都写进去:
192.168.1.10 k8s-master 192.168.1.11 k8s-worker-1 192.168.1.12 k8s-worker-2时间同步看起来不起眼,但集群证书签发和 kubelet 心跳都依赖时间。如果节点时钟偏差过大,初始化时生成证书、验证身份可能直接报错。简单起见,用 chrony 或 systemd-timesyncd 即可:
apt install chrony -y systemctl enable --now chrony检查时间同步状态可以用chronyc tracking或timedatectl。生产环境最好统一时间源,不走这一步后面证书相关错误会很难查。
3. 安装 containerd:这一步没配好,后面全是泪
3.1 安装 containerd 与基础验证
containerd 的安装包一般来自容器运行时软件源,也可以用发行版仓库自带的 containerd,不过版本可能偏旧。建议使用较新的稳定包,安装后启动服务:
apt install containerd -y systemctl enable --now containerd大多数系统安装后不会生成/etc/containerd/config.toml,需要手动生成默认配置:
mkdir -p /etc/containerd containerd config default > /etc/containerd/config.toml用ctr version可以确认安装成功。但 ctr 命令走的是 containerd 原生接口,和 CRI 插件还不完全等价,后续最好用 crictl 验证 CRI 接口。
3.2 关键配置项:Cgroup Driver、镜像加速和 Pause 镜像
拿到默认 config.toml 后,我们主要改三处。第一处是 Cgroup Driver。为了和 kubelet 保持一致,通常将SystemdCgroup设为true。找到[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options],把SystemdCgroup = false改成SystemdCgroup = true,也可以直接替换:
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml第二处是 sandbox_image,也就是 pause 镜像地址。默认值指向社区维护的仓库,如果你的网络访问不到,就会在 kubeadm init 拉取镜像阶段卡死。你可以把 sandbox_image 改成你所在网络环境可访问的仓库地址,例如:
sandbox_image = "registry.example.com/pause:3.9"第三处是镜像加速配置。可以在[plugins."io.containerd.grpc.v1.cri".registry.mirrors]下给 docker.io 配置一个可用的镜像源,具体写法由你的镜像仓库决定。配置完成后重启 containerd:
systemctl restart containerd3.3 修改后验证:为什么用 crictl 而不是 docker
如果你没有安装 Docker,crictl 才是 CRI 的标准客户端。安装 crictl 后,先设置 endpoint:
crictl config runtime-endpoint unix:///run/containerd/containerd.sock crictl config image-endpoint unix:///run/containerd/containerd.sock然后拉一个测试镜像:
crictl pull busybox:1.28 crictl images如果这一步能成功,说明 containerd 的 CRI 接口和镜像仓库都正常。很多人会跳过验证,等到 kubeadm init 才发现 image pull 报错,回头再改 containerd,白白多等一轮。建议这里多花两分钟。
4. kubeadm 初始化控制面:一条命令背后的参数逻辑
4.1 安装 kubeadm、kubelet、kubectl
在所有节点上安装 Kubernetes 组件,安装方式取决于发行版。一般流程是添加 Kubernetes 软件源,然后指定版本安装:
apt-cache madison kubeadm | head -10 apt install -y kubeadm=1.28.x-0 kubelet=1.28.x-0 kubectl=1.28.x-0安装完成后为了让 kubectl 好用,可以加自动补全:
echo 'source <(kubectl completion bash)' >> ~/.bashrc source ~/.bashrc注意:kubelet 安装后不要立刻启动。kubelet 在加入集群之前会因为找不到集群配置而不断报错,这很正常。等 kubeadm init 或 join 后再让 systemd 管理它。
4.2 生成初始化配置文件:别只记一条长命令
网上最常见的做法是直接kubeadm init --apiserver-advertise-address=... --pod-network-cidr=...,但我建议先生成一份配置再初始化,便于后续维护和排查。执行:
kubeadm config print init-defaults --component-configs KubeletConfiguration > kubeadm-init.yaml打开文件,重点修改这几块:
localAPIEndpoint.advertiseAddress:改为控制面节点的固定 IP。nodeName:改为当前控制面节点的主机名。imageRepository:如果你的环境需要镜像加速,改成可访问的仓库前缀。kubernetesVersion:改成你安装的版本,如 1.28.x。networking.podSubnet:设置成和后续 CNI 匹配的网段,比如 192.168.0.0/16 或 10.244.0.0/16。
如果希望用域名访问 API Server,还要在apiServer下的certSANs里添加域名和备用 IP:
apiServer: certSANs: - "k8s-api.example.com" - "192.168.1.10"为什么搞成文件?因为 kubeadm init 执行后,生成的各种证书和配置都基于这份输入。后续如果证书要续期或重建控制面,还能照着原始配置来。直接敲长命令也能跑,但配置不会备份,等出问题再想当时填了什么,只能抓瞎。
4.3 执行初始化:输出里藏着三块重要信息
初始化命令很简单:
kubeadm init --config kubeadm-init.yaml执行时会先检查环境,然后拉取所需镜像,最后生成证书、配置、kubeconfig,并启动控制面组件。如果卡在拉镜像步骤,回头检查第 3 节的 containerd 配置。
成功的输出末尾会包含几块信息:
- kubeconfig 文件路径,通常让你拷贝
/etc/kubernetes/admin.conf。 - 一个
kubeadm join命令,里面有--token和--discovery-token-ca-cert-hash。 - 提示你是否需要部署网络插件。
我强烈建议把这段输出复制保存到本地文件,后面 Worker 加集群和排障都要用。输出的 Token 默认有效期 24 小时,过期也不用重新初始化,用kubeadm token create --print-join-command重新生成即可,这点在第 6 节再讲。
4.4 配置 kubectl 访问集群
初始化完成后,需要用管理员证书配置 kubectl:
mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config然后执行:
kubectl get nodes你会看到控制面节点处于 NotReady 状态,这是正常的,因为还没装网络插件,这时先别着急排查。kubectl get pods -n kube-system里,coredns 也会处于 Pending 状态,原因一样。下一步就是装网络插件。
5. 安装 CNI 网络插件:Pod 网段和 Service 网段别重叠
5.1 CNI 插件选型:Flannel、Calico、Cilium 怎么选
Kubernetes 集群内部的 Pod 需要互相通信,Pod 和宿主机之间也有通信要管理,这要靠 CNI 插件。
- Flannel:最简单,只提供 Overlay 网络,不包含 NetworkPolicy 支持,适合测试或简单网络环境。
- Calico:提供 BGP 路由模式和 Overlay 模式,支持 NetworkPolicy,生产环境用得多,配置适中。
- Cilium:基于 eBPF,功能强,性能好,但对内核版本有要求,组件也更多,新手容易被一堆概念劝退。
| 插件 | NetworkPolicy | 性能 | 复杂度 | 推荐场景 |
|---|---|---|---|---|
| Flannel | 不支持 | 一般 | 低 | 测试环境、内网互通简单、学习优先 |
| Calico | 支持 | 较高 | 中 | 生产环境、需要网络策略 |
| Cilium | 支持 | 高 | 高 | 内核较新、需要高性能网络 |
如果你不想折腾,测试环境我建议 Calico,它能修正很多网络问题,对后续排障也有帮助。生产环境也大有人用。下面以 Calico 展开,Flannel 的安装方式类似,但少一些策略功能。
5.2 以 Calico 为例的安装步骤
Calico 安装推荐用 operator 或 manifest 两种方式。这里用 manifest 方式,更容易理解它创建的组件。下载安装文件后,找到CALICO_IPV4POOL_CIDR变量,把它改成和 kubeadm-init.yaml 中podSubnet一致:
# 在 calico.yaml 中找到 - name: CALICO_IPV4POOL_CIDR value: "192.168.0.0/16"配置文件准备好之后,在 master 上执行:
kubectl apply -f calico.yamlCalico 会创建一组 DaemonSet:每台节点跑一个calico-nodepod,通过 BGP 或 Overlay 把节点间的 Pod 网段路由起来。同时,还会部署calico-kube-controllers等控制组件。等待状态:
kubectl get pods -n kube-system -l k8s-app=calico-node -w当每个节点上的calico-node达到 Ready,再回去kubectl get nodes,所有节点通常就会变成 Ready。如果一直没变,看下面的细节。
5.3 三个最容易忽略的细节
第一,podSubnet不要和集群所在局域网网段重叠。Calico 默认的 192.168.0.0/16 和很多家庭、办公网络冲突。如果你宿主机网段正好是 192.168.1.0/24,Pod 网段就不能再用 192.168.0.0/16,否则路由会乱。建议用 10.244.0.0/16 之类较独立的网段。
第二,多网卡节点要指定 Calico 使用的网卡。如果你的节点存在多个网卡,Calico 默认自动检测,可能选错 IP。需要在 manifest 里设置:
- name: IP_AUTODETECTION_METHOD value: "interface=eth.*"第三,防火墙要放行 BGP 端口。Calico 用 BGP 默认端口 179,iptables、firewalld、安全组都需要放行。如果是全 Overlay 模式,不一定需要放行 179,但做了 BGP 就必须。在云环境里,安全组规则也要同步放行。这三个坑不看,节点状态经常卡在 NotReady。
6. Worker 节点加入集群:Token、证书 Hash 与重复操作
6.1 节点预检与运行时对齐
Worker 节点在加入前,并不是只执行一条kubeadm join就可以的。第 2、3 节的所有基础配置,包括关闭 Swap、加载内核模块、配置 containerd、安装 kubeadm/kubelet/kubectl,都需要在每台 Worker 上重新做一遍。尤其是 containerd 配置必须与 master 完全一致,否则加入后 Pod 很可能起不来。
版本对齐也很关键。Worker 上的 kubelet 版本最好和 master 完全一致。如果 master 用 1.28.x,Worker 就别装 1.29 或 1.27,版本偏差会带来兼容问题。顺手把主机名设为唯一,并更新 /etc/hosts,让 Worker 能通过域名或 IP 访问 master 的 API Server。
6.2 获取加入命令
如果你还记得kubeadm init输出里的 join 命令,直接复制到 Worker 执行即可。如果 Token 过期或命令丢了,在 master 上重新生成:
kubeadm token create --print-join-command这会输出一行类似:
kubeadm join 192.168.1.10:6443 --token <token> --discovery-token-ca-cert-hash sha256:<hash>为什么要带 hash?它用于验证 API Server 的证书是否可信,避免节点加入到一个伪装的服务端。你可以手动用 openssl 计算,但一般直接用--print-join-command最可靠。
在 Worker 上执行加入命令前,先确认 master 可以直接访问,检查端口 6443 通不通:
nc -vz 192.168.1.10 6443不通的话,先解决网络,否则 join 会超时。
6.3 加入后验证
执行 join 后,Worker 的 kubelet 会带着证书向 API Server 注册,控制面会创建该节点的 Node 对象。在 master 上运行:
kubectl get nodes通常节点从 NotReady 到 Ready 需要几秒到几十秒。如果一直 NotReady,优先看 kubelet 日志:
journalctl -u kubelet -f --no-pager常见的报错有 CNI 配置未生效、容器运行时连接不上、节点 IP 被识别成旧地址。加入后验证 Pod 跨节点通信:
kubectl run test --image=busybox --restart=Never -- sleep 300进入 Pod 去 ping 其他节点的 Pod IP,确认 Overlay 网络能通。验证完就可以删除测试 Pod。
7. 安装完后的健康检查与故障排查
7.1 集群健康:从 componentstatuses 到 healthz
很多人装完集群后习惯用kubectl get componentstatuses看健康状态,但这个命令在较新版本已经不能准确反映控制面状态,因为它检查的是本地 static Pod 端口,很多组件已经不暴露那些端口。更好的做法是直接访问 API Server 的 healthz:
kubectl get --raw='/healthz'返回ok说明控制面可用。再看各组件 Pod:
kubectl get pods -n kube-system -o wide包括 etcd、kube-apiserver、kube-controller-manager、kube-scheduler、coredns、calico-node 等。如果某个 Pod 一直 CrashLoop,用kubectl logs -n kube-system <pod>查看日志,而不是先去改配置。
7.2 翻车日志排查流程:先看容器运行时,再看 kubelet
K8s 排障有个基本顺序:先在节点上看容器运行时,再用 kubelet 日志,最后才去控制面找原因。用 crictl 列出所有容器:
crictl ps -a如果发现某些 Pod 处于 Exited,执行:
crictl logs <container-id>看到 OOM、sandbox 镜像拉取失败、CNI 配置找不到等关键信息后,再对症处理。kubelet 日志通常反映调度和注册问题:
journalctl -u kubelet -f如果 Node 注册不上,日志里会出现连接 API Server 失败或无权限。这种时候先检查 kubelet 证书是否过期、Token 是否有效。排查时不要一股脑重启节点,很多问题只要调整 containerd 配置或 CNI 配置就能解决,重启反而让现象被掩盖。
7.3 两个我建议提前做好的备份动作
第一,备份/etc/kubernetes/pki目录和/etc/kubernetes/admin.conf。这个目录里保存了集群的 CA 证书、服务端证书、etcd 证书等。一旦节点损坏或证书丢失,没有备份会非常被动。第二,把kubeadm-init.yaml和 join 命令保存到集群外部。初始化配置是重建集群、续期证书、升级版本的依据,别只放在 /root 下。
证书默认有效期是 1 年,到期前可以用kubeadm certs renew all续期,但前提是 pki 目录仍然完整。这个动作没人愿意做,但一旦遇到,你会发现提前保存的配置胜过一切临场发挥。
最后,我个人在安装集群时有个习惯:每装完一个环节,先用命令确认这一步干净了再继续,比如 containerd 装完先 crictl pull 一下,master 初始化完先 kubectl get nodes,CNI 装完再等一下 pods。这个过程看似多花了几分钟,实际上能省掉后续一大轮的“瞎猜”。K8s 安装不像编译一个大项目,它的坑是线性的,前面错了后面必然错,所以一步步验证远比一口气跑完更高效。