【Kubernetes】------ 使用 kubeadm 从零搭建 K8s 集群(实操避坑版)
上一篇讲了 K8s 的核心原理,这篇直接上手------用 kubeadm 搭一个真正能跑的集群。
不是 Minikube 那种单节点玩具,是一个 1 Master + 2 Worker 的三节点集群,装完就能部署应用。踩过的坑我都标出来了,照着做基本不会翻车。
一、环境说明与前置条件
本文以 Ubuntu 22.04 LTS 为例。如果你用 CentOS/RHEL,大部分步骤一样,但包管理命令和防火墙配置需要自己调整。
| 组件 | 版本 | 用途 | 备注 |
|---|---|---|---|
| 操作系统 | Ubuntu 22.04 LTS | 三台虚拟机 | 至少 2 核 CPU / 2G 内存 |
| Kubernetes | 1.28+ | 集群版本 | 本文以 1.28 为例 |
| containerd | 1.7+ | 容器运行时 | K8s 1.24 起不再直接支持 Docker |
| Calico | 3.26+ | CNI 网络插件 | 生产环境推荐 |
节点规划:
| 主机名 | IP | 角色 | 配置 |
|---|---|---|---|
| k8s-master | 192.168.1.100 | Control Plane | 2C/2G |
| k8s-node1 | 192.168.1.101 | Worker Node | 2C/2G |
| k8s-node2 | 192.168.1.102 | Worker Node | 2C/2G |
⚠️ 注意: 以下所有步骤,除非特别说明,三台机器都要执行。
二、系统基础配置
Step 1:关闭 swap 和配置主机名
K8s 要求关闭 swap,原因很简单:kubelet 需要精确控制内存分配,swap 会干扰它对节点资源的判断。
bash
# 关闭 swap(临时)
sudo swapoff -a
# 永久关闭:注释掉 /etc/fstab 中的 swap 行
sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
设置主机名(每台机器分别执行):
bash
# 在 master 上执行
sudo hostnamectl set-hostname k8s-master
# 在 node1 上执行
sudo hostnamectl set-hostname k8s-node1
# 在 node2 上执行
sudo hostnamectl set-hostname k8s-node2
配置 hosts 文件,三台机器都加:
bash
cat >> /etc/hosts << EOF
192.168.1.100 k8s-master
192.168.1.101 k8s-node1
192.168.1.102 k8s-node2
EOF
✅ 成功标志: 执行 free -h 看不到 swap 分区,hostname 返回正确的主机名。
Step 2:加载内核模块和配置网络参数
这一步很容易忘,但漏了会导致后面 Pod 网络出问题。
bash
# 加载必需的内核模块
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 --system
✅ 成功标志: 执行 lsmod | grep br_netfilter 有输出,sysctl net.bridge.bridge-nf-call-iptables 返回 1。
三、安装 containerd
K8s 1.24 起移除了对 dockershim 的支持。别纠结,直接用 containerd,它本来就是 Docker 底层的运行时。
bash
# 安装依赖
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
# 添加 Docker 官方 GPG 密钥(containerd 包在 Docker 仓库里)
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# 添加 Docker 仓库
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# 安装 containerd
sudo apt-get update
sudo apt-get install -y containerd.io
配置 containerd 使用 systemd 作为 cgroup driver(这一步是关键):
bash
# 生成默认配置
sudo containerd config default | sudo tee /etc/containerd/config.toml > /dev/null
# 修改 SystemdCgroup = true
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
# 重启 containerd
sudo systemctl restart containerd
sudo systemctl enable containerd
🔴 重点: SystemdCgroup = true 这一行必须改。不改的话,kubelet 和 containerd 的 cgroup 管理方式不一致,Pod 会莫名其妙 OOMKilled。别问我怎么知道的。
✅ 成功标志: sudo systemctl status containerd 显示 active (running)。
四、安装 kubeadm、kubelet、kubectl
bash
# 添加 Kubernetes apt 仓库
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gpg
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
# 安装
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
# 锁定版本,防止自动升级
sudo apt-mark hold kubelet kubeadm kubectl
⚠️ 注意: 最后一步 apt-mark hold 很重要。生产环境里 K8s 组件自动升级可能导致集群不可用。我们团队之前踩过这个坑------周末 kubelet 自动升级了小版本,结果跟 API Server 版本不兼容,周一来了集群就挂了。
✅ 成功标志: kubeadm version 返回版本号。
五、初始化 Control Plane
只在 master 节点 执行:
bash
sudo kubeadm init \
--apiserver-advertise-address=192.168.1.100 \
--pod-network-cidr=10.244.0.0/16 \
--service-cidr=10.96.0.0/16 \
--kubernetes-version=v1.28.0
参数说明:
| 参数 | 值 | 说明 |
|---|---|---|
--apiserver-advertise-address |
master 的 IP | API Server 监听地址 |
--pod-network-cidr |
10.244.0.0/16 | Pod 网络地址段,Calico 默认用这个 |
--service-cidr |
10.96.0.0/16 | Service 网络地址段 |
--kubernetes-version |
v1.28.0 | 指定版本,避免拉取最新版 |
初始化成功后,输出末尾会有类似这样的信息:
Your Kubernetes control plane has been successfully initialized!
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Then you can join any number of control-plane nodes by running:
kubeadm join 192.168.1.100:6443 --token <token> \
--discovery-token-ca-cert-hash sha256:<hash>
Then you can join any number of worker nodes by running the following on each:
kubeadm join 192.168.1.100:6443 --token <token> \
--discovery-token-ca-cert-hash sha256:<hash>
🔴 重点: 把最后那个 kubeadm join 命令抄下来保存好。等下 Worker 节点加入集群要用。如果忘了,可以用这个命令重新生成:
bash
kubeadm token create --print-join-command
配置 kubectl(在 master 上执行):
bash
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(这是正常的,还没装网络插件)。
六、安装网络插件(Calico)
集群现在还不能跑 Pod,因为没有网络插件。Pod 之间互相访问、Service 转发流量都靠它。
bash
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/calico.yaml
等一两分钟,让 Calico 的 Pod 全部启动:
bash
# 监控 Pod 启动状态
kubectl get pods -n kube-system -w
✅ 成功标志: kubectl get nodes 显示 master 状态变为 Ready。kubectl get pods -n kube-system 中所有 Pod 都是 Running。
⚠️ 注意: 如果 Calico Pod 卡在 ContainerCreating 或 ImagePullBackOff,大概率是网络问题。检查 master 是否能访问外网(Calico 镜像在 Docker Hub 上)。如果拉不下来,可以提前在所有节点上 crictl pull calico/node:v3.26.1。
七、Worker 节点加入集群
在 每台 Worker 节点 上执行之前保存的 join 命令:
bash
sudo kubeadm join 192.168.1.100:6443 --token <token> \
--discovery-token-ca-cert-hash sha256:<hash>
回到 master 验证:
bash
kubectl get nodes
预期输出:
NAME STATUS ROLES AGE VERSION
k8s-master Ready control-plane 10m v1.28.0
k8s-node1 Ready <none> 2m v1.28.0
k8s-node2 Ready <none> 1m v1.28.0
✅ 成功标志: 三个节点都是 Ready 状态。如果 Worker 节点是 NotReady,等一两分钟再看------Calico 需要在新节点上也部署完成。
八、验证集群:部署一个 Nginx
集群搭好了,跑个应用验证一下:
bash
# 创建 Deployment
kubectl create deployment nginx-test --image=nginx:1.25 --replicas=3
# 暴露 Service
kubectl expose deployment nginx-test --port=80 --type=NodePort
# 查看 Pod 分布
kubectl get pods -o wide
# 查看 Service
kubectl get svc nginx-test
验证 Pod 分散在不同节点上(说明调度正常):
bash
kubectl get pods -o wide
预期输出类似:
NAME READY STATUS RESTARTS AGE IP NODE
nginx-test-7d6dd5c955-abc12 1/1 Running 0 30s 10.244.1.5 k8s-node1
nginx-test-7d6dd5c955-def34 1/1 Running 0 30s 10.244.2.5 k8s-node2
nginx-test-7d6dd5c955-ghi56 1/1 Running 0 30s 10.244.2.6 k8s-node2
访问测试:
bash
# 获取 NodePort
NODE_PORT=$(kubectl get svc nginx-test -o jsonpath='{.spec.ports[0].nodePort}')
# 通过任意节点的 IP 访问
curl http://192.168.1.101:$NODE_PORT
如果返回 Nginx 的欢迎页面,集群就搭好了。
测试完清理资源:
bash
kubectl delete deployment nginx-test
kubectl delete svc nginx-test
九、常见问题排查
问题一:kubeadm init 卡在 [wait-control-plane]
原因: 大概率是 containerd 没正常运行,或者 CRI socket 路径不对。
排查:
bash
sudo systemctl status containerd
sudo crictl info
解决: 如果 containerd 没启动,sudo systemctl start containerd。如果 crictl 报错连接不上,检查 /etc/containerd/config.toml 里的 SystemdCgroup 是否改成了 true。
问题二:Worker 节点 join 后一直是 NotReady
原因: Calico 还没部署到新节点,或者节点间网络不通。
排查:
bash
# 在 NotReady 的节点上执行
sudo journalctl -u kubelet -f
解决: 等待 2-3 分钟。如果超过 5 分钟还是 NotReady,检查节点间能否互相 ping 通,以及防火墙是否放行了 6443、10250、179(BGP)等端口。
问题三:Pod 跨节点访问不通
原因: CNI 插件没装好,或者节点间防火墙阻断了 Pod 网络。
排查:
bash
# 检查 Calico 状态
kubectl get pods -n kube-system -l k8s-app=calico-node
calicoctl node status
解决: 确认 Calico Pod 全部 Running。如果 Calico 用的是 BGP 模式,确保节点间 TCP 179 端口没被防火墙拦截。
问题四:token 过期了,Worker 节点加不进来
原因: kubeadm 生成的 token 默认 24 小时过期。
解决:
bash
# 重新生成 join 命令
kubeadm token create --print-join-command
十、总结:你搭完集群后要记住的事
- kubeadm 只管集群初始化。它不管机器 provisioning、不管监控、不管日志,这些要自己配。
- containerd 的 SystemdCgroup 必须改成 true。这是最容易忘的一步,也是最容易导致诡异问题的一步。
- token 会过期 。Worker 节点要在 24 小时内加入,或者用
kubeadm token create重新生成。 - 网络插件是必需的。没装 CNI 插件的集群就是一个空壳,Pod 之间互相访问不了。
- 生产环境要加 --control-plane-endpoint。单 master 的话没问题,但如果以后要加高可用,初始化时就要配好负载均衡器地址。