这篇记录把三台新的 VMware Ubuntu 虚拟机加入现有 Kubernetes 集群,作为普通 worker 节点使用。

目标节点:

192.168.3.218  k8s-worker1
192.168.3.219  k8s-worker2
192.168.3.220  k8s-worker3

现有控制面:

192.168.3.214  k8s-master1
192.168.3.215  k8s-master2
192.168.3.216  k8s-master3
192.168.3.217  k8s-vip

Kubernetes API 入口继续使用:

192.168.3.217:8443

这里有一个关键前提:这三台是 worker,不是 control-plane,所以不要在它们上面配置 Keepalived、HAProxy、etcd,也不要执行带 --control-plane 的 join 命令。

一、先切换 VMware 网卡为 VMXNET3

之前集群里出现过 VMware e1000 网卡在高流量下卡死的问题,所以新 worker 节点一开始就切到 vmxnet3

先关闭虚拟机,然后在 VMware 界面里把网卡类型改为 VMXNET3。如果界面里没有这个选项,也可以直接编辑虚拟机的 .vmx 文件。

找到类似配置:

ethernet0.virtualDev = "e1000"

改成:

ethernet0.virtualDev = "vmxnet3"

保存后启动虚拟机。

启动后检查网卡驱动:

sudo apt update
sudo apt install -y ethtool

ip -br addr
ethtool -i ens160

期望看到:

driver: vmxnet3

切换到 VMXNET3 后,Ubuntu 里的网卡名很可能从 ens33 变成 ens160。后面所有配置都按 ens160 写,如果实际网卡名不同,就替换成自己的。

二、配置固定 IP

三台 worker 都建议使用固定 IP。下面以 192.168.3.218 为例,另外两台把 IP 和 hostname 改掉即可。

先备份 netplan 配置:

sudo cp /etc/netplan/*.yaml /tmp/
ls /etc/netplan/

编辑 netplan:

sudo vim /etc/netplan/00-installer-config.yaml

示例配置:

network:
  version: 2
  ethernets:
    ens160:
      dhcp4: false
      addresses:
        - 192.168.3.218/24
      routes:
        - to: default
          via: 192.168.3.1
      nameservers:
        addresses:
          - 192.168.3.1
          - 223.5.5.5
          - 8.8.8.8

应用配置:

sudo netplan try
sudo netplan apply

检查网络:

ip addr show ens160
ip route
ping -c 4 192.168.3.217
ping -c 4 www.baidu.com

三台节点分别使用:

k8s-worker1  192.168.3.218/24
k8s-worker2  192.168.3.219/24
k8s-worker3  192.168.3.220/24
gateway      192.168.3.1
interface    ens160

如果虚拟机是从旧模板克隆出来的,可以检查是否还有旧网卡名:

grep -R "ens33" /etc/netplan /etc/systemd /etc/NetworkManager 2>/dev/null

如果搜到 ens33,改成当前实际网卡名,比如 ens160

三、设置 hostname 和 hosts

192.168.3.218 上执行:

sudo hostnamectl set-hostname k8s-worker1

192.168.3.219 上执行:

sudo hostnamectl set-hostname k8s-worker2

192.168.3.220 上执行:

sudo hostnamectl set-hostname k8s-worker3

三台 worker 都写入相同的 /etc/hosts

sudo tee -a /etc/hosts >/dev/null <<'EOF'

# k8s hosts begin
192.168.3.214 k8s-master1
192.168.3.215 k8s-master2
192.168.3.216 k8s-master3
192.168.3.217 k8s-vip
192.168.3.218 k8s-worker1
192.168.3.219 k8s-worker2
192.168.3.220 k8s-worker3
# k8s hosts end
EOF

是的,三台 master 节点也建议补上 worker 的 hosts 映射。这样在 master 上执行排查命令时,可以直接使用 k8s-worker1k8s-worker2k8s-worker3 这些 hostname。

k8s-master1k8s-master2k8s-master3 上分别执行:

sudo tee -a /etc/hosts >/dev/null <<'EOF'

# k8s workers begin
192.168.3.218 k8s-worker1
192.168.3.219 k8s-worker2
192.168.3.220 k8s-worker3
# k8s workers end
EOF

验证:

hostname
ping -c 2 k8s-vip
ping -c 2 k8s-worker1
ping -c 2 k8s-worker2
ping -c 2 k8s-worker3

四、关闭 swap 并配置内核参数

三台 worker 都执行。

关闭 swap:

sudo swapoff -a
sudo cp /etc/fstab /etc/fstab.bak.$(date +%Y%m%d%H%M%S)
sudo sed -ri '/[[:space:]]swap[[:space:]]/ s/^([^#])/#\1/' /etc/fstab

加载 Kubernetes 需要的内核模块:

sudo tee /etc/modules-load.d/k8s.conf >/dev/null <<'EOF'
overlay
br_netfilter
EOF

sudo modprobe overlay
sudo modprobe br_netfilter

配置 sysctl:

sudo tee /etc/sysctl.d/k8s.conf >/dev/null <<'EOF'
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF

sudo sysctl --system

检查:

free -h
lsmod | egrep 'overlay|br_netfilter'
sysctl net.ipv4.ip_forward

五、安装 containerd

三台 worker 都执行。

sudo apt update
sudo apt install -y containerd ca-certificates curl gpg apt-transport-https

生成默认配置,并启用 systemd cgroup:

sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml >/dev/null
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml

sudo systemctl restart containerd
sudo systemctl enable containerd
sudo systemctl status containerd --no-pager

确认:

systemctl is-active containerd
sudo ctr version

六、安装 kubeadm、kubelet、kubectl

版本要和现有集群保持一致。先在任意 master 上查看:

kubectl get nodes
kubectl version

我这套文档里当前按 v1.36 写:

K8S_VERSION=v1.36

三台 worker 都执行:

sudo mkdir -p -m 755 /etc/apt/keyrings

curl -fsSL "https://pkgs.k8s.io/core:/stable:/${K8S_VERSION}/deb/Release.key" \
  | sudo gpg --batch --yes --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:/${K8S_VERSION}/deb/ /" \
  | sudo tee /etc/apt/sources.list.d/kubernetes.list >/dev/null

sudo apt update
sudo apt install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
sudo systemctl enable --now kubelet
# 如果集群实际不是 `v1.36`,把 `K8S_VERSION` 改成当前集群对应小版本仓库,例如 `v1.35`
sudo apt install -y --allow-change-held-packages kubelet kubeadm kubectl

如果集群实际不是 v1.36,把 K8S_VERSION 改成当前集群对应的小版本仓库,例如 v1.35

安装完成后,给 crictl 写入默认 runtime endpoint:

# 安装工具
sudo apt install -y cri-tools
sudo tee /etc/crictl.yaml >/dev/null <<'EOF'
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 10
debug: false
EOF

确认 containerd 可以被 CRI 工具访问:

sudo crictl info | grep runtimeType

七、固定 kubelet 的 node-ip

如果机器只有一张网卡,这一步通常不是必须的。但为了避免 kubelet 选错 IP,建议显式指定。

192.168.3.218 执行:

NODE_IP=192.168.3.218

sudo mkdir -p /etc/systemd/system/kubelet.service.d
sudo tee /etc/systemd/system/kubelet.service.d/20-node-ip.conf >/dev/null <<EOF
[Service]
Environment="KUBELET_EXTRA_ARGS=--node-ip=${NODE_IP}"
EOF

sudo systemctl daemon-reload
sudo systemctl restart kubelet

192.168.3.219192.168.3.220 分别把 NODE_IP 改成自己的 IP。

八、生成 worker join 命令

在任意 master 上执行:

kubeadm token create --print-join-command

输出类似:

kubeadm join 192.168.3.217:8443 \
  --token xxxxxx.xxxxxxxxxxxxxxxx \
  --discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

如果输出里不是 192.168.3.217:8443,先确认当前集群的 controlPlaneEndpoint

kubectl -n kube-system get configmap kubeadm-config -o yaml | grep controlPlaneEndpoint

普通 worker 的 join 命令不要带这些参数:

--control-plane
--certificate-key

建议补上 containerd 的 CRI socket:

sudo kubeadm join 192.168.3.217:8443 \
  --token xxxxxx.xxxxxxxxxxxxxxxx \
  --discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \
  --cri-socket=unix:///run/containerd/containerd.sock

192.168.3.218192.168.3.219192.168.3.220 三台 worker 上分别执行这条命令。

九、检查节点状态

回到任意 master,检查节点:

kubectl get nodes -o wide

期望看到:

k8s-master1   Ready   control-plane   ...   192.168.3.214
k8s-master2   Ready   control-plane   ...   192.168.3.215
k8s-master3   Ready   control-plane   ...   192.168.3.216
k8s-worker1   Ready   <none>          ...   192.168.3.218
k8s-worker2   Ready   <none>          ...   192.168.3.219
k8s-worker3   Ready   <none>          ...   192.168.3.220

给 worker 补一个角色标签,方便显示:

kubectl label node k8s-worker1 node-role.kubernetes.io/worker=
kubectl label node k8s-worker2 node-role.kubernetes.io/worker=
kubectl label node k8s-worker3 node-role.kubernetes.io/worker=

再看:

kubectl get nodes -o wide

如果集群已经切到 Calico,可以检查 Calico 相关 Pod 是否正常:

kubectl get pods -A -o wide | egrep 'calico|tigera|k8s-worker'

也可以直接跑一个测试 Pod:

kubectl run test-nginx --image=nginx:alpine --restart=Never
kubectl get pod test-nginx -o wide
kubectl delete pod test-nginx

十、Longhorn 节点准备

如果 worker 后面要承载 Longhorn 卷,三台 worker 还要安装 Longhorn 常用依赖。

sudo apt update
sudo apt install -y open-iscsi nfs-common
sudo systemctl enable --now iscsid

检查:

systemctl is-active iscsid

然后在 Longhorn UI 或命令行里确认新节点已经出现,并根据实际磁盘规划决定是否允许调度副本。

十一、常见问题

1. 节点一直 NotReady

先看 kubelet:

sudo systemctl status kubelet --no-pager
sudo journalctl -u kubelet -n 100 --no-pager

再看 CNI:

kubectl get pods -A -o wide | egrep 'calico|flannel|cni'

如果 CNI Pod 没有在新 worker 上起来,节点通常会保持 NotReady

2. kubeadm join 连接不上 API

在 worker 上确认能访问 VIP:

ping -c 4 192.168.3.217
nc -vz 192.168.3.217 8443

如果 8443 不通,回 master 检查 HAProxy 和 Keepalived:

systemctl is-active haproxy keepalived
ip addr | grep 192.168.3.217

3. VMXNET3 后 IP 不见了

大概率是网卡名变化导致 netplan 还写着旧的 ens33

ip -br link
grep -R "ens33" /etc/netplan /etc/NetworkManager 2>/dev/null

把配置里的旧网卡名改成当前实际网卡名,比如 ens160,再执行:

sudo netplan apply

4. 节点曾经加入过其他集群

如果这台 worker 不是全新机器,先清理旧 kubeadm 状态:

sudo kubeadm reset -f
sudo rm -rf /etc/cni/net.d /var/lib/cni /var/lib/kubelet/pki
sudo systemctl restart containerd kubelet

然后重新执行 join。

最后检查清单

三台 worker 加入完成后,至少检查这些:

kubectl get nodes -o wide
kubectl get pods -A -o wide | grep k8s-worker
kubectl top nodes

VMXNET3 检查:

for iface in ens160; do
  ethtool -i "$iface" | grep driver
done

预期结果:

三台 worker 都是 Ready
三台 worker InternalIP 分别是 192.168.3.218、192.168.3.219、192.168.3.220
网卡驱动是 vmxnet3
Pod 能调度到新 worker
kubectl top nodes 能看到新节点指标

到这里,三台新虚拟机就已经以普通 worker 身份加入集群。后续可以根据用途再给它们打标签,比如通用业务、GitLab、数据库、存储或监控节点。