k8s HA Cluster部署
Rocky操作系统镜像下载链接:
bash
https://dl.rockylinux.org/vault/rocky/9.7/isos/x86_64/
环境:
| 角色 | 规格 | OS | IP |
|---|---|---|---|
| k8s-vip | none | none | 192.168.1.12 |
| lb1 | 2C2G | Rocky9.7 Minimal | 192.168.1.13 |
| lb2 | 2C2G | Rocky9.7 Minimal | 192.168.1.14 |
| Master1 | 2C4G | Rocky9.7 Minimal | 192.168.1.15 |
| Master1 | 2C4G | Rocky9.7 Minimal | 192.168.1.16 |
| Master1 | 2C4G | Rocky9.7 Minimal | 192.168.1.17 |
| Node1 | 2C4G | Rocky9.7 Minimal | 192.168.1.18 |
1.初始化系统(所有节点)
关闭防火墙,关闭SELinux::
arduino
systemctl stop firewalld;systemctl disable firewalld
setenforce 0
sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config
reboot
关闭 Swap 分区(K8S 强制要求)
bash
swapoff -a
sed -ri 's/.*swap.*/#&/' /etc/fstab
配置yum源:
先将默认yum配置删除:
bash
cd /etc/yum.repos.d/
rm -rf *.repo
然后配置镜像yum源:
ini
cat > /etc/yum.repos.d/rocky.repo << 'EOF'
[baseos]
name=Rocky Linux $releasever - BaseOS
baseurl=https://mirrors.aliyun.com/rockylinux/$releasever/BaseOS/$basearch/os/
enabled=1
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/rockylinux/RPM-GPG-KEY-Rocky-9
[appstream]
name=Rocky Linux $releasever - AppStream
baseurl=https://mirrors.aliyun.com/rockylinux/$releasever/AppStream/$basearch/os/
enabled=1
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/rockylinux/RPM-GPG-KEY-Rocky-9
[extras]
name=Rocky Linux $releasever - Extras
baseurl=https://mirrors.aliyun.com/rockylinux/$releasever/extras/$basearch/os/
enabled=1
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/rockylinux/RPM-GPG-KEY-Rocky-9
[crb]
name=Rocky Linux $releasever - CRB
baseurl=https://mirrors.aliyun.com/rockylinux/$releasever/CRB/$basearch/os/
enabled=1
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/rockylinux/RPM-GPG-KEY-Rocky-9
EOF
清理dnf缓存,列出可用仓库
css
dnf clean all
dnf makecache
dnf repolist
最小化安装后推荐基础包
perl
dnf install -y bash-completion vim wget curl net-tools bind-utils tar unzip git
节点初始化:
配置主机名(其他机器以此类推:lb1, lb2, master2, master3, node1)
arduino
hostnamectl set-hostname master1
配置本地 DNS 解析
bash
cat >> /etc/hosts <<EOF
192.168.1.12 k8s-vip
192.168.1.13 lb1
192.168.1.14 lb2
192.168.1.15 master1
192.168.1.16 master2
192.168.1.17 master3
192.168.1.18 node1
EOF
LB两台节点配置:
开启"允许绑定非本地 IP" 这是 Keepalived + HAProxy 架构的核心底座,确保当 VIP 在另一台机器上时,本机的 HAProxy 也能正常启动并监听 16443 端口
bash
cat <<EOF | tee /etc/sysctl.d/haproxy.conf
net.ipv4.ip_nonlocal_bind = 1
net.ipv4.ip_forward = 1
EOF
sysctl --system
安装 HAProxy 和 Keepalived 两台机器上安装负载均衡软件:
dnf install -y haproxy keepalived
配置 HAProxy(两台 LB 配置完全相同)
perl
# 备份原配置
mv /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.bak
# 写入新配置
cat > /etc/haproxy/haproxy.cfg <<EOF
global
log /dev/log local0
log /dev/log local1 notice
daemon
maxconn 4000
defaults
mode tcp
log global
retries 3
timeout queue 1m
timeout connect 10s
timeout client 1h
timeout server 1h
timeout check 10s
frontend k8s-apiserver
bind *:16443
mode tcp
default_backend k8s-masters
backend k8s-masters
mode tcp
balance roundrobin
option tcp-check
# 这里指向你的三台 Master 节点
• server master1 192.168.1.15:6443 check inter 2000 fall 3 rise 2
• server master2 192.168.1.16:6443 check inter 2000 fall 3 rise 2
• server master3 192.168.1.17:6443 check inter 2000 fall 3 rise 2
EOF
lb1作为master,keepalived.conf配置如下:
bash
# 备份并写入 lb1 的配置 (作为 MASTER)
mv /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf.bak
cat > /etc/keepalived/keepalived.conf <<EOF
global_defs {
router_id LVS_DEVEL
}
vrrp_script check_haproxy {
script "killall -0 haproxy"
interval 3
weight -20
}
vrrp_instance VI_1 {
state BACKUP
nopreempt # <--- 开启非抢占模式
interface ens160 # <--- 请核对你的网卡名称
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass k8s_ha
}
unicast_src_ip 192.168.1.13 # 本机 IP,告诉 Keepalived:用本机的这个 IP 发送心跳
unicast_peer {
192.168.1.14 # 对端 IP,告诉 Keepalived:不要用大喇叭喊了,直接打电话给这个具体的 IP
}
virtual_ipaddress {
192.168.1.12 # VIP
}
track_script {
check_haproxy
}
}
EOF
关于state为BACKUP的解释:为什么作为主节点master的LB1也要设置成BACKUP,而不是master,因为设置成master, 如果LB1 发生硬件故障,处于"不断重启-崩溃-重启"的死循环中,VIP 就会在两台机器之间疯狂来回切换(术语叫网络震荡/Flapping),这会导致所有的 API 请求持续被重置。所以还要加上一个参数nopreempt开启非抢占模式。
lb2作为backup,配置:
perl
# 备份并写入 lb2 的配置 (作为 BACKUP)
mv /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf.bak
cat > /etc/keepalived/keepalived.conf <<EOF
global_defs {
router_id LVS_DEVEL
}
vrrp_script check_haproxy {
script "killall -0 haproxy"
interval 3
weight -20
}
vrrp_instance VI_1 {
state BACKUP # 状态为备节点
interface ens160 # <--- 请核对你的网卡名称
virtual_router_id 51
priority 90 # 优先级降低
advert_int 1
authentication {
auth_type PASS
auth_pass k8s_ha
}
unicast_src_ip 192.168.1.14 # 本机 IP
unicast_peer {
192.168.1.13 # 对端 IP
}
virtual_ipaddress {
192.168.1.12 # VIP
}
track_script {
check_haproxy
}
}
EOF
2 master&node配置:
开启 IPv4 转发与网桥过滤
bash
cat <<EOF | tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
modprobe overlay
modprobe br_netfilter
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 --system
(4 台节点都要执行)
安装并配置 containerd 容器运行时
bash
# 卸载可能冲突的包(Rocky 9 常见自带)
dnf remove -y podman buildah
# 添加阿里云的 Docker CE 稳定版仓库
dnf config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# 安装 containerd
dnf install -y containerd.io
# 1. 创建配置目录
mkdir -p /etc/containerd
# 2. 导出 containerd 的默认完整配置
containerd config default > /etc/containerd/config.toml
# 3. 将 SystemdCgroup 的值从 false 替换为 true
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' /etc/containerd/config.toml
# 4. 启动 containerd 并设置开机自启
systemctl enable --now containerd
# 5. 检查状态确保处于 active (running)
systemctl status containerd -l --no-pager | grep Active
第一步:配置 K8S 国内源并安装组件
ini
# 1. 写入 Kubernetes v1.36 的阿里云 YUM 源
cat <<EOF | tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.36/rpm/
enabled=1
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.36/rpm/repodata/repomd.xml.key
EOF
# 2. 清理缓存并安装三大核心组件
dnf clean all
dnf makecache
dnf install -y kubelet kubeadm kubectl
# 3. 设置 kubelet 开机自启(千万不要现在 start,它会被 kubeadm init 自动唤醒)
systemctl enable kubelet
第二步:编写集群初始化剧本(仅在 master1 执行)
很多新手喜欢用一长串的命令行参数(比如 kubeadm init --control-plane-endpoint...)去初始化集群。但在生产环境和高可用架构中,作为架构师,我们永远优先推荐使用 YAML 配置文件。 这样不仅便于复查和归档,还能对底层参数(如 Cgroup 驱动)进行最精细的控制。
yaml
# 创建一个专门存放配置的目录
mkdir -p /opt/k8s
cd /opt/k8s
# 编写 kubeadm-config.yaml
cat > kubeadm-config.yaml <<EOF
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: "v1.36.0"
# 核心:指向我们刚刚做好的高可用 VIP 和 HAProxy 端口
controlPlaneEndpoint: "192.168.1.12:16443"
# 核心:使用阿里云镜像仓库替换谷歌官方仓库,拉取速度飞快
imageRepository: "registry.aliyuncs.com/google_containers"
networking:
# 预留给网络插件(如 Calico)的 Pod 网段,这个 10.244.0.0/16 是最通用的
podSubnet: "10.244.0.0/16"
serviceSubnet: "10.96.0.0/12"
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
# 核心:确保 Kubelet 的 Cgroup 驱动与 containerd 保持一致,使用 systemd
cgroupDriver: "systemd"
EOF
第三步:正式点火(仅在 master1 执行)
配置文件准备就绪。我们可以让 kubeadm 先提前拉取一下必需的镜像,确认网络畅通,然后再执行初始化。
在 master1 上执行:
ini
# 1. 预拉取镜像(这步如果能顺利秒跑完,说明阿里云源配置非常完美)
kubeadm config images pull --config=kubeadm-config.yaml
# 2. 正式初始化集群,并加上 --upload-certs 参数(非常重要!)
# --upload-certs 会将控制平面的证书加密上传到 k8s 的 secret 中,
# 这样后续加入 master2 和 master3 时,就不需要我们手动去拷贝证书文件了。
kubeadm init --config=kubeadm-config.yaml --upload-certs
假如没有启动成功,报错:
vbscript
[kubelet-start] Starting the kubelet
error: error execution phase wait-control-plane: cannot obtain client without bootstrap: could not bootstrap the admin user in file admin.conf: unable to create ClusterRoleBinding: client rate limiter Wait returned an error: rate: Wait(n=1) would exceed context deadline
To see the stack trace of this error execute with --v=5 or higher
是因为Containerd 的 Pause 镜像被墙了。
虽然我们在 kubeadm-config.yaml 中把镜像源改成了阿里云 (registry.aliyuncs.com/google_containers),K8S 的核心组件镜像也确实拉下来了。但是,Containerd 容器运行时自身底层默认写死了 去谷歌的 registry.k8s.io 拉取沙箱容器镜像(Pause 镜像)。 因为国内网络拉不到这个 Pause 镜像,哪怕 API Server 的镜像已经就绪,Pod 也永远无法创建,最终导致 kubeadm init 苦苦等待直到超时。
我们需要清理失败的残局,填平这个暗坑,然后重新点火。请在 master1 上严格按以下三步执行:
第一步:清理失败的残局(极其重要)
初始化失败后,系统会残留很多损坏的证书和配置文件。如果不清理干净,下一次 init 会直接秒报错。 在 master1 上执行:
bash
kubeadm reset -f
rm -rf /etc/kubernetes/ /var/lib/etcd/ /var/lib/kubelet/ $HOME/.kube/config
rm -rf /etc/cni/net.d
第二步:修复 Containerd 的 Pause 镜像源(暗坑修复)
我们需要把 Containerd 配置文件里写死的官方 Pause 镜像替换为阿里云的镜像。 在 master1 上执行以下命令(建议后续在 master2, master3, node1 上也执行一次):
bash
# 替换默认的 registry.k8s.io 为阿里云源
sed -i 's#registry.k8s.io/pause#registry.aliyuncs.com/google_containers/pause#g' /etc/containerd/config.toml
# 重启 containerd 让配置生效
systemctl restart containerd
# 确认替换成功(你应该能看到包含 aliyuncs 的 sandbox_image 这一行)
grep sandbox_image /etc/containerd/config.toml
第三步:确认网络并重新启动
为了确保咱们刚才那段时间 VIP 没有乱飘,顺手检查一下网络,然后再次使用配置文件初始化。 依然在 master1 上执行:
ini
# 1. 确认 VIP 还能 ping 通(或者在 lb1 上看看 192.168.1.12 还在不在)
ping -c 2 192.168.1.12
# 2. 重新执行初始化
kubeadm init --config=kubeadm-config.yaml --upload-certs
成功后可以看到提示:
sql
Your Kubernetes control-plane has initialized successfully!
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
Alternatively, if you are the root user, you can run:
export KUBECONFIG=/etc/kubernetes/admin.conf
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
https://kubernetes.io/docs/concepts/cluster-administration/addons/
You can now join any number of control-plane nodes running the following command on each as root:
kubeadm join 192.168.1.12:16443 --token 8sw2dh.53a7g0oviif1tydp \
--discovery-token-ca-cert-hash sha256:fdbb4312c296b41d107039387c372ad0915be1f5bbe793320d3040162d67a89b \
--control-plane --certificate-key a6d59374aae5555d7860551b9b6ca1ef9ee879e358efc5018bfe0d6c333f0195
Please note that the certificate-key gives access to cluster sensitive data, keep it secret!
As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use
"kubeadm init phase upload-certs --upload-certs" to reload certs afterward.
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join 192.168.1.12:16443 --token 8sw2dh.53a7g0oviif1tydp \
--discovery-token-ca-cert-hash sha256:fdbb4312c296b41d107039387c372ad0915be1f5bbe793320d3040162d67a89b
第一步:在 master1 上配置管理员权限
为了能让 master1 使用 kubectl 命令管理集群,你需要把刚才生成的管理员凭证复制到当前用户的目录下。 请在 master1 上执行:
bash
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config
# 验证一下,看看我们的单台控制面是否就绪
kubectl get nodes
# 此时你应该能看到 master1 的状态为 NotReady(这是正常的,因为还没装网络插件)。
第二步:将 master2 和 master3 加入控制平面
要让另外两台机器作为平等的 Master 节点加入,我们需要一个"证书解密密钥"。
- 重新获取证书密钥(在
master1上执行):
csharp
kubeadm init phase upload-certs --upload-certs
组装并执行高可用加入命令(在 master2 和 master3 上分别执行): 把刚才生成的密钥,拼接到你发给我的那段基础命令后面。完整的命令格式如下:
sql
kubeadm join 192.168.1.12:16443 --token 8sw2dh.53a7g0oviif1tydp \
--discovery-token-ca-cert-hash sha256:fdbb4312c296b41d107039387c372ad0915be1f5bbe793320d3040162d67a89b \
--control-plane --certificate-key e01454b570b5036d3a3f147013e64251e3d71159429919f4f235d8fab60e432f
node节点:
sql
kubeadm join 192.168.1.12:16443 --token 8sw2dh.53a7g0oviif1tydp \
--discovery-token-ca-cert-hash sha256:fdbb4312c296b41d107039387c372ad0915be1f5bbe793320d3040162d67a89b
最后的 阶段 3:打通集群网络脉络。
安装 Calico 网络插件(集群神经网络)
标准的生产级高可用架构,Calico 是最推荐的 CNI 插件,性能强悍且支持网络策略。
在 master1 上执行以下命令来部署 Calico:
bash
# 1. 下载 Calico 配置文件 (如果 raw.githubusercontent 被墙,这一步可能会卡)
curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.27.3/manifests/calico.yaml
# 2. 将 docker.io 批量替换为华为云的代理源
sed -i 's#docker.io/calico/#swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/calico/#g' calico.yaml
# 3. 应用配置
kubectl apply -f calico.yaml
国内环境,如果第一条 curl 命令一直卡住或报错拒绝连接,可以直接使用国内的加速服务下载:)
ruby
# 国内备用下载命令
curl -O https://mirror.ghproxy.com/https://raw.githubusercontent.com/projectcalico/calico/v3.27.3/manifests/calico.yaml
# 将 docker.io 批量替换为华为云的代理源
sed -i 's#docker.io/calico/#swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/calico/#g' calico.yaml
kubectl apply -f calico.yaml
部署完 Calico 后,K8S 会自动在每个节点上拉取网络组件的镜像。这个过程大概需要 1-3 分钟。
可以用下面这条命令实时"监视" Pod 的启动过程(按 Ctrl+C 退出):
sql
watch kubectl get pods -n kube-system -o wide
最后,pod全部running,代表高可用k8s集群搭建成功。
演练:模拟 LB 节点宕机(VIP 漂移测试)
动作一:确认当前 VIP 归属 在 lb1 (13) 和 lb2 (14) 上分别执行:
css
ip a | grep 192.168.1.12
找出 VIP 现在到底挂在谁身上(按照咱们之前的配置,它现在大概率在 lb1 上)。
动作二:开启实时监控 API 流量 在任意一台 Master 上(比如 master1),让 kubectl 强制通过 VIP 去请求集群状态,并每秒刷新一次:
arduino
watch -n 1 "kubectl --server=https://192.168.1.12:16443 get nodes"
动作三:实施"物理级毁灭" 切回 VMware 宿主机,找到当前挂着 VIP 的那台 LB 虚拟机(比如 lb1)执行关机。
盯着刚才在 master1 上开的 watch 监控窗口会发现,在关机的瞬间,画面可能会停顿 1 到 2 秒钟,然后立刻恢复持续刷新!完全没有报错崩溃。如果这个演练成功了,就说明流量入口已经做到了真正的无单点故障。