知识点9---Kubernetes高可用部署(kubeadm方式)
前面知识点8总结了用于测试或个人学习时的单节点k8s部署,本篇知识点介绍生产使用的高可用kubernetes部署。和单节点的区别在于高可用至少需要 3 台数量为基数的主节点,往上3、5、7都可以,3台是多数情况,和一套高可用入口服务 HAProxy + Keepalived ,至少两个节点,HAProxy 负责将外部访问请求负载均衡的转发到当前集群所有存活的主节点上,Keepalived 提供对外的集群虚拟IP入口,以及对 HAProxy 故障切换,业内常说的k8s VIP 指的就是 Virtual IP Address(虚拟 IP)的缩写,不过在工作中如果你用的不是开源云,统一入口服务的实现会有不同的方式,像阿里云等都有自己的服务,这就需要当下联系供应商了解了。本篇将入口服务部署复用两台主节点,各位读者在生产正式部署时要分开,也就是说正式生产环境需要至少 7 台机器
第一步:部署环境准备
部署服务分布如下
系统:CentOS 7
内核:5.6.15
docker 26.1.4
cri-dockerd 3.7.0
k8s 1.28.0
Ha VIP 两个组件是单独的软件使用yum直接安装版本不影响
192.168.239.70 master01.k8s.com -》 Ha1 VIP1 docker cri-dockerd master01(kube-apiserver、kube-controller-manager、kube-scheduler、coredns、etcd、kube-proxy、flannel)
192.168.239.73 master02.k8s.com -》 Ha2 VIP2 docker cri-dockerd master02(kube-apiserver、kube-controller-manager、kube-scheduler、coredns、etcd、kube-proxy、flannel)
192.168.239.74 master03.k8s.com -》 docker cri-dockerd master03(kube-apiserver、kube-controller-manager、kube-scheduler、coredns、etcd、kube-proxy、flannel)
192.168.239.71 worker01.k8s.com -》 docker cri-dockerd worker01(kube-proxy、flannel)
192.168.239.72 worker02.k8s.com -》 docker cri-dockerd worker02(kube-proxy、flannel)
括号中的 kube-apiserver 等k8s本体服务,在kubeadm方式下,不需要你操心部署,工具自动部署时有固定的模板,你只需要知道主、从节点上都有那些类型的服务就可以,coredns这个Pod是给集群内部提供DNS功能的,在kubeadm方式下它会在集群中自动被分配到某个节点上,通常是主节点中的一个
kube-proxy、flannel 这个两个属于工作节点上的核心软件,由于kubeadm是基于容器自动部署所以k8s本体的软件也在容器里,因此主节点也需要安装,在后面会介绍二进制部署,k8s本体直接运行在物理机上,也不需要容器,所以也就不需要它们两个提供集群网络转发策略维护和容器ip分配了
首先所有主机的IP配置与实际使用的网段一致,比如使用VMware虚拟机保持NAT或者桥接的策略即可,不要在服务器上自己自定义子网,配置文件:/etc/sysconfig/network-scripts/ifcfg-ens33,
BOOTPROTO="none"或BOOTPROTO="static"(禁用DHCP)
IPADDR="192.168.239.70"(设置静态IP)
PREFIX="24"(等同于255.255.255.0)
GATEWAY="192.168.239.2"(默认网关)
DNS1="114.114.114.114"(DNS服务器)
ONBOOT="yes"(开机自动启用网卡)
所有集群主机均需添加主机名与IP地址的映射关系,配置文件:/etc/hosts
192.168.239.70 master01.k8s.com
192.168.239.73 master02.k8s.com
192.168.239.74 master03.k8s.com
192.168.239.71 worker01.k8s.com
192.168.239.72 worker02.k8s.com
所有主机关闭防火墙配置服务,执行如下命令
停止服务:systemctl stop firewalld
禁用自启:systemctl disable firewalld
所有主机关闭SELinux,/etc/selinux/config
# 设置为禁用
SELINUX = disabled
#或者执行非交互式命令
sed -ri 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
所有主机时间需要同步 -》https://blog.csdn.net/dudadudadd/article/details/110947177,或者最小化安装 ntpdate,同步阿里云
yum install ntpdate
ntpdate time1.aliyun.com
# 后续同步使用 crontab -e
crontab -e
0 * * * * /usr/sbin/ntpdate time1.aliyun.com
# 或者你有自己的批量执行命令脚本
(crontab -l 2>/dev/null; echo "0 * * * * /usr/sbin/ntpdate time1.aliyun.com") | crontab -
# 也可以批量删除
crontab -l | grep -v "time1.aliyun.com" | crontab -
所有主机升级系统内核,K8s有很多插件,需要的内核版本比较高,并且新内核的一些软件旧内核可能没有
# 首先看一下当前内核,centos 7 默认内核是3.x
uname -r
24年底所有的在线 os 7 系统可用源基本都关了,因此不能在线安装,只能手动编译内核,目前能下载到的只有清华源,所以去下载源码包编译,下载个差不多的就行本文用5.6.15
https://mirrors.tuna.tsinghua.edu.cn/kernel/v5.x/linux-5.6.15.tar.gz
# 安装编译依赖
yum groupinstall -y "Development Tools"
yum install -y ncurses-devel bison flex elfutils-libelf-devel openssl-devel \
bc rpm-build redhat-rpm-config asciidoc hmaccalc perl-ExtUtils-Embed \
pesign xmlto audit-libs-devel binutils-devel elfutils-devel \
newt-devel numactl-devel pciutils-devel python-devel zlib-devel \
gcc gcc-c++ make perl diffutils gettext
# 编译并安装
tar -zxf linux-5.6.15.tar.gz
cd linux-5.6.15
# 按照当前内核参数生成配置文件,预期结果,会在最后存在几个无法识别的自动用默认值,这些不影响
make olddefconfig
# 下面这个命令用来启动配置编译参数微调界面,注意不要直接改 .config 文件,因为内核编译参数存在依赖关系,编译界面中看似改了一个,但其实联动的不止一个
make menuconfig
# 在进入的界面中,小键盘的 上下 控制菜单光标,回车是进入子菜单,空格是取消或选中,选中有三种状态 * (编译进内核)、M(编译成独立的模块)、[] (这种空白意思是不编译),那种状态不需要你控制,你需要选中即可,按两次esc回到上一级目录,按照这个操作方式确定以下内核参数在预期内
# 命名空间隔离
General setup -> Namespaces support -> 确保所有选项都选中
# 资源限制的基础建设能力,包括最核心的内存子系统相关
General setup -> Control Group support -> 除了 RDMA controller、Support for eBPF programs attached to cgroups 、Debug controller 之外都选中
General setup -> CPU/Task time and stats accounting -> Pressure stall information tracking 确保选中
# 容器镜像分层存储
File systems -> Overlay filesystem support 确保它的状态是选中,且紧挨着它的 Overlay 开头的扩展都不选!!!这些扩展不是自身有问题就是自身存在安全风险。有些版本它可能在 File systems -> Miscellaneous filesystems 下面
# 容器网络通信
Networking support -> Networking options -> 802.1d Ethernet Bridging 确保选中
# 下面四个是 K8s 需要的网络策略支持
Networking support -> Networking options -> Network packet filtering framework (Netfilter) -> Bridged IP/ARP packets filtering 确保选中
Networking support -> Networking options -> Network packet filtering framework (Netfilter) -> Core Netfilter Configuration -> "comment" match support 确保选中
Networking support -> Networking options -> Network packet filtering framework (Netfilter) -> Core Netfilter Configuration -> Netfilter connection tracking support 确保选中
Networking support -> Networking options -> Network packet filtering framework (Netfilter) -> Core Netfilter Configuration -> etfilter nf_tables support 确保选中
# 下面两个是 ipvs 网络模式
Networking support -> Networking options -> Network packet filtering framework (Netfilter) -> IP virtual server support 确保选中
Networking support -> Networking options -> Virtual (secure) IP: tunneling 确保选中
# 检查软件签名能力,默认是打开的,有需要可以关掉。
Enable loadable module support -> Module signature verification
确保上面这些内核参数没问题后,用小键盘左右键,移动界面最下方光标到 Save ,随后回车,最后用 esc 退出即可
# 开始编译
make -j 4
# 编译完成先安装内核模块
make modules_install
# 再安装内核
make install
所有主机设置GRUB引导,从而设置新安装内核为默认启动项
# 查看当前有启动项
awk -F\' '$1=="menuentry " {print i++ " : " $2}' /boot/grub2/grub.cfg
# 预期安装上面的内核后,它应当在第一个,所以设置默认启动第 0 位的内核
grub2-set-default 0
# 重新生成内核引导配置文件,必须重新生成
grub2-mkconfig -o /boot/grub2/grub.cfg
重启所有节点
reboot
# 重启后确定内核版本
uname -r
# 对于旧的内核,比如自带的3.x可以直接删掉了
[root@master01 ~]# rpm -qa | grep kernel
kernel-3.10.0-1160.71.1.el7.x86_64
kernel-debug-devel-3.10.0-1160.119.1.el7.x86_64
kernel-tools-3.10.0-1160.71.1.el7.x86_64
kernel-3.10.0-1160.119.1.el7.x86_64
kernel-tools-libs-3.10.0-1160.71.1.el7.x86_64
kernel-headers-3.10.0-1160.119.1.el7.x86_64
#删除后刷新启动项
grub2-mkconfig -o /boot/grub2/grub.cfg
# 如果你未来要更新这个手动编译升级的内核,先去更新你的新内核,旧内核需要手动删除如下的文件列表
rm -f /boot/vmlinuz-5.6.15
rm -f /boot/initramfs-5.6.15.img
rm -f /boot/System.map-5.6.15
rm -rf /lib/modules/5.6.15
这里留一个温馨提示,升级内核用的源码一定不要删除,因为 /lib/modules/5.6.15 这个路径下存在源码路径的软连接,在你后期使用系统的过程中会调用源码编译路径下的资源,并且后续在安装一些软件时,涉及到内核版本强关联的软件,公共渠道通常是没有非原装内核对应版本的,这个时候就需要指定编译后的源码路径,比如安装GPU驱动等
所有节点,修改系统配置文件,让系统自身拥有转发能力和网桥过滤,就和前面知识点KVM同一个操作,只不过多了个网桥流量进入防火墙过滤。按照官方建议,新建/etc/sysctl.d/k8s.conf文件写入如下配置。和直接修改/etc/sysctl.conf的区别在于/etc/sysctl.conf是传统的配置方式,不过同配置项它的优先级最低,且不方便管理
#让原本在OSI模型第二层数据链路层的网桥数据,也进入第三层网络层的防火墙
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
#启用IPv4转发
net.ipv4.ip_forward = 1
#禁用swap分区
vm.swappiness = 0
网桥过滤,需要加载一个模块,所有主机执行后加入持久化
# 加载模块,这个 -- 可以不写,他是因为有些模块名称里面有 - ,所以 -- 是modprobe 命令的一个参数告诉它参数的识别方式
modprobe -- br_netfilter
# 查看结果
lsmod | grep br_netfilter
# 生成配置文件持久化
echo "br_netfilter" | tee /etc/modules-load.d/k8s.conf
随后刷新内核配置
sysctl -p /etc/sysctl.d/k8s.conf
所有主机安装ipset及ipvsadm,IPSET是一个IP地址集合工具,用来优化防火墙配置时的IP列表可能很多或者很长的问题,IPVS是内核级别的服务代理功能,你可以理解为它是一个内核自带的Nginx,但它比Nginx强大很多,K8s使用它来完成内部转发这类防火墙功能,安装ipvsadm工具主要用于通过命令行查看和管理IPVS功能
yum -y install ipset ipvsadm
随后开始配置IPVS,首先创建 /etc/sysconfig/modules/ipvs.modules 文件,用来系统启动时自动调用,加载IPVS需要的模块,它本质是一个脚本,所以不放在/etc/modules-load.d下面
cat > /etc/sysconfig/modules/ipvs.modules << 'EOF'
#!/bin/bash
modprobe -- ip_vs
modprobe -- ip_vs_rr
modprobe -- ip_vs_wrr
modprobe -- ip_vs_sh
modprobe -- nf_conntrack
EOF
ip_vs:基础模块
ip_vs_rr:轮询算法模块
ip_vs_wrr:加权轮询算法模块
ip_vs_sh:哈希算法模块
nf_conntrack:连接跟踪模块
第一次先手动执行一次
chmod 755 /etc/sysconfig/modules/ipvs.modules
bash /etc/sysconfig/modules/ipvs.modules
lsmod | grep -e ip_vs -e nf_conntrack
现在检查所有节点是否有交换分区,如果有官方建议关掉
# 临时工关闭
swapoff -a
# 注释 /etc/fstab 文件中的Swap分区即可后续也不再启用
再次重启所有节点
reboot
# 验证内核版本
uname -r
# 验证模块
lsmod | grep -e ip_vs -e nf_conntrack -e br_netfilter
# 验证内核参数
sysctl -a | grep -e net.bridge.bridge-nf-call -e net.ipv4.ip_forward -e vm.swappiness
所有节点 SSH 互信 -》https://blog.csdn.net/dudadudadd/article/details/117816179
同时取消SSH显示输入yes -》 https://blog.csdn.net/dudadudadd/article/details/117899463
到此 K8s 的安装前置准备完成
第二步:所有节点部署 docker -》https://blog.csdn.net/dudadudadd/article/details/158845838 ,务必确保docker使用20.10+版本,本文使用26.1.4。由于要和 k8s 一起使用,需要在配置文件下添加一行
vi /etc/docker/daemon.json
追加:
"exec-opts": ["native.cgroupdriver=systemd"]
systemctl daemon-reload
systemctl restart docker
第三步:所有节点安装 cri-dockerd 接口服务。先在master01上安装,其他节点同步文件就行
# 先确定系统是那个架构,本文部署环境是 x86_64
uname -m
# 随后去下载 0.3.7 的,注意不要下错架构,x86 下载amd的就行
https://github.com/Mirantis/cri-dockerd/releases/download/v0.3.7/cri-dockerd-0.3.7.amd64.tgz
# 它就是一条二进制命令,解压后,放入系统 bin 路径下
tar -zxf cri-dockerd-0.3.7.amd64.tgz
mv cri-dockerd/cri-dockerd /usr/local/bin/
chmod +x /usr/local/bin/cri-dockerd
cri-dockerd --version
创建 systemd 服务文件,让系统能识别到 cri-dockerd 服务,文件内容不用改直接复制即可
vi /etc/systemd/system/cri-dockerd.service
[Unit]
Description=CRI Interface for Docker Application Container Engine
Documentation=https://docs.mirantis.com
After=network-online.target firewalld.service docker.service
Wants=network-online.target
[Service]
Type=notify
ExecStart=/usr/local/bin/cri-dockerd --pod-infra-container-image=registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.9 --network-plugin=cni --cni-conf-dir=/etc/cni/net.d --cni-bin-dir=/opt/cni/bin --container-runtime-endpoint=unix:///var/run/cri-dockerd.sock --cri-dockerd-root-directory=/var/lib/dockershim --docker-endpoint=unix:///var/run/docker.sock --cri-dockerd-root-directory=/var/lib/docker
ExecReload=/bin/kill -s HUP $MAINPID
TimeoutSec=0
RestartSec=2
Restart=always
StartLimitBurst=3
StartLimitInterval=60s
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity
Delegate=yes
KillMode=process
[Install]
WantedBy=multi-user.target
vi /etc/systemd/system/cri-dockerd.socket
[Unit]
Description=CRI Docker Socket for the API
PartOf=cri-docker.service
[Socket]
ListenStream=/var/run/cri-dockerd.sock
SocketMode=0660
SocketUser=root
SocketGroup=docker
[Install]
WantedBy=sockets.target
启动服务。服务启动没有问题,则另外两台节点同步二进制命令和配置,启动服务即可
# 通过服务文件到其他所有节点,我这里用自己写的工具脚本,大家自己各县各显神通就行
./ml-tools/bin/syn_scp -synf /etc/systemd/system/cri-dockerd.socket -ni -t 4
./ml-tools/bin/syn_scp -synf /etc/systemd/system/cri-dockerd.service -ni -t 4
./ml-tools/bin/syn_scp -synf /usr/local/bin/cri-dockerd -ni -t 5
# 随后启动
systemctl daemon-reload
systemctl start cri-dockerd
systemctl status cri-dockerd
systemctl enable cri-dockerd.service
#服务运行起来后,会存在一个如下的路径,这个就是后面k8s 要使用的 CRI 接口
ls /var/run/cri-dockerd.sock
第四步:master01和master02节点上安装统一入口服务,也就是集群的 VIP 和 负载均衡服务,在业内也叫 LB 服务,如果你用的不是原生云,则每个厂商实现方式不一定一样
yum -y install haproxy keepalived
修改 /etc/haproxy/haproxy.cfg配置文件,删除默认文件中 global 开始的所有内容,随后将下面的配置改动为你自己的,然后复制
global
# 全局最大连接数
maxconn 2000
# 每个进程的最大文件描述符数量
ulimit-n 16384
# 日志配置:发送到本地 syslog,级别为 err
log 127.0.0.1 local0 err
# 统计信息的超时时间
stats timeout 30s
defaults
# 继承 global 的日志配置
log global
# 默认模式为 http
mode http
# 开启 http 日志记录
option httplog
# 连接超时时间 (毫秒)
timeout connect 5000
# 客户端空闲超时时间 (毫秒)
timeout client 50000
# 服务端空闲超时时间 (毫秒)
timeout server 50000
# http 请求超时时间 (秒)
timeout http-request 15s
# http 长连接超时时间 (秒)
timeout http-keep-alive 15s
# 定义监控台入口配置 (用于查看 HAProxy 自身状态),monitor-in这个名字你可以自定义
frontend monitor-in
# 绑定端口 33305
bind *:33305
# 模式,或者说是用那种协议服务
mode http
# 记录日志
option httplog
# 监控页面的 uri,访问 http://ip:33305/monitor 可查看状态,monitor-uri是HAProxy 内部的一个指令,用来配置监控的路由,正式因为有了它,HAProxy 就知道你当前这一小段配置的是HAProxy 服务自己的监控端口
monitor-uri /monitor
# k8s集群的入口配置:k8s-master (也是一个自定义的后端服务名字,下面用来定义如何处理进入的 API Server 流量)
frontend k8s-master
# 监听所有 IP 的 16443 端口 (这是客户端访问 VIP / LB 的端口),一般写6443,和 k8s 服务一致,但这里由于是复用了两台主节点所有用别的了
bind 0.0.0.0:16443
# 绑定本地回环地址的 16443 端口,这个配置看似和上面冲突了,但它本质上不是给用户用的,是为了防止在 master01 节点上访问 VIP,流量转了一圈后又回到master01会发生路由超时的诡异问题,配上这个 HAProxy 就可以直接将流量打到回环地址上,不走网卡解析那一套
bind 127.0.0.1:16443
# 模式设置为 tcp (因为 k8s API Server 是 https 流量,四层代理更合适)
mode tcp
# 开启 tcp 日志
option tcplog
# tcp 请求检查延迟
tcp-request inspect-delay 5s
# 默认后端指向 k8s-master
default_backend k8s-master
# 后端配置:k8s-master (定义真实的 Master 节点池)
backend k8s-master
mode tcp
option tcplog
# 开启 tcp 健康检查
option tcp-check
# 负载均衡算法:轮询 (roundrobin)
balance roundrobin
# 默认服务器配置参数:
# inter 10s: 每10秒检查一次
# downinter 5s: 宕机后每5秒检查一次
# rise 2: 检查成功2次判定为上线
# fall 2: 检查失败2次判定为下线
# slowstart 60s: 慢启动时间
# maxconn 250: 单台最大连接数
# maxqueue 256: 最大队列数
# weight 100: 负载均衡的权重,所有节点相同
default-server inter 10s downinter 5s rise 2 fall 2 slowstart 60s maxconn 250 maxqueue 256 weight 100
# 定义具体的 Master 节点 (请根据实际环境修改 IP) 如果实际使用中不同节点权重需要不同就写在 check 前面
server master01.k8s.com 192.168.239.70:6443 check
server master02.k8s.com 192.168.239.73:6443 check
server master03.k8s.com 192.168.239.74:6443 check
启动服务
# 将配置文件同步给 master02 节点
scp /etc/haproxy/haproxy.cfg master02.k8s.com:/etc/haproxy/haproxy.cfg
# 随后两台节点上启动服务
systemctl start haproxy
systemctl status haproxy
systemctl enable haproxy
访问http://<masterIP>:33305/monitor,预期返回:200 OK和"Service ready"

现在,把请求负载均衡到 k8s 主节点的服务有了,但外部访问总不能直接写死某一个 haproxy ,因此需要一个总的虚拟IP,以及能够确定服务服务状态的东西。Keepalived的配置也简单,同样修改 /etc/keepalived/keepalived.conf配置文件,删除默认的内容后追加
! Configuration File for keepalived
global_defs {
# [可选] 路由标识,不同节点之间唯一,主要用于区分不同的 Keepalived 实例
router_id LVS_DEVEL1
# [重要] 指定运行下面脚本的用户。默认可能是 keepalived 用户,这里改为 root 防止权限不足
script_user root
# [重要] 启用脚本安全模式。配合上面的 script_user,确保脚本能被 root 顺利执行
enable_script_security
}
# --- 定义健康检查脚本 ---
vrrp_script chk_apiserver {
# [必须修改] 检查脚本的路径。这个脚本用来检测 HAProxy 是否存活
script "/etc/keepalived/check_apiserver.sh"
# 检查间隔:每 5 秒执行一次脚本
interval 5
# 权重变化:如果脚本检测失败(返回非0),优先级(priority)减 5
weight -5
# 失败阈值:连续失败 2 次才算真的失败
fall 2
# 成功阈值:连续成功 1 次就算恢复
rise 1
}
# --- 定义 VRRP 实例 ---
vrrp_instance VI_1 {
# [主备区别] 状态。
# 主节点写 MASTER,备节点写 BACKUP
state MASTER
# [必须修改] 绑定的网卡接口。
# 注意:这里必须是你服务器实际连接网络的网卡名(如 ens33, eth0 等),不要用 lo
interface ens33
# [必须修改] 发送和响应 VRRP 包的源 IP。
# 这里填写当前这台服务器的真实物理 IP (Master 的 IP),确保心跳包能发出去
mcast_src_ip 192.168.239.70
# 虚拟路由 ID。
# 主备节点的这个 ID 必须一致,范围 0-255,用来区分不同的集群
virtual_router_id 51
# [主备区别] 优先级。
# 数字越大优先级越高。Master 通常设高一点(如 101),Backup 设低一点(如 100)
priority 101
# 广播间隔:每隔 2 秒发送一次心跳通告
advert_int 2
# 认证配置(防止非法节点加入)
authentication {
auth_type PASS # 认证类型,通常用密码 PASS
auth_pass abc123 # [建议修改] 认证密码,主备节点必须完全一致
}
# 虚拟 IP 地址 (VIP)。
# 这就是你的高可用 IP,客户端(如 kubectl)连接的就是这个 IP
virtual_ipaddress {
192.168.239.100/24
}
# 关联上面定义的检查脚本。
# 意思是:这个实例的状态要跟随 chk_apiserver 脚本的执行结果变化
track_script {
chk_apiserver
}
}
这里说一个非常重要的点:Keepalived 和 haproxy 不是必须一体的。多数中小集群会部署在一起,好处是经济省钱,因为说白了 Keepalived 它只是提供一个在网络中标明一个可被发现的虚拟IP,并把这个IP的访问使用 VRRP 协议的方式转发给真实物理机,相当于包了一层外衣。haproxy 也有一层转发的能力,是为了对集群主节点提供灾备切换和负载均衡的目的,所以从使用感官上 Keepalived 和 haproxy 是一条直线,因此大多一起部署。
无论这两个服务在不在一起,都会发生一个问题,业内叫做“检测延迟”和“资源浪费”。检测延迟是指,某台 HAProxy 挂了,绑定它的 Keepalived 需要一段时间自身随着调用检测脚本降低自身的优先级,在这期间访问会失败。资源浪费是指,某台 Keepalived 挂了那修复时间内,这台 Keepalived 对应的HAProxy 可能存在空闲。这也侧面反映出 k8s 其实也没有那么的完善,很多东西需要外部第三方服务辅助
有的读者一定会想,为什么不用 Nginx,这里就要扩展一个网络小知识,Nginx本质上是个web服务应用,大家感觉上它放在这里配置简单,普遍是多数使用它的转发功能,但要告诉大家的是Nginx的服务奔溃感知,开源版本下只有被动检测,当服务实际访问错误达到预制才会被标记为不可用,想要实现 Keepalived 这种主动检查非常复杂,你要去花钱买商用版本,或者自己融合第三方模块。当然这并不是直接原因,无论主动还是被动,有能力使用再加上配置简单,这样看确实没有问题。真正让Nginx不适合这里的原因是转发实现的方式,Nginx它负责转发Web服务,也就是http或者https协议,在知识点1中,给大家说过网络模型 ISO 和 TCP/IP ,http、https协议属于应用层实现,就导致Nginx在转发时会打开会话数据在http下还没有太大的影响,但是对于 k8s 这种走https协议的服务来讲Nginx会打开看会话的内容从而做出响应的动作,这也就是它能够根据请求API接口的不同实现转发,可这其中有两个关键问题,第一Nginx如何获取 k8s 的证书?第二就算拿到证书,解析了会话数据,请求到了 k8s ,k8s 如何响应?该不该响应这个已经被打开过的会话?而 HAProxy 它属于传输层实现,基于TCP,它只关心请求去哪里,不关心你去干什么,所以它的转发效率极高,适用于数据库、SSH接口等无需后续处理的转发和负载均衡,且它的故障转移配置和很简单。还有一个本质的原因是 HAProxy + Keepalived 这个组合是官方文档中配置实例,后面初始化要用到这个服务所提供的vip去添加证书认证,扩展名称,以及去绑定控制平台统一入口,如果你换其他软件,怎么配置,都是个麻烦事
言归正传,定义服务检查脚本/etc/keepalived/check_apiserver.sh,脚本的内容也非常简单
#!/bin/bash
# 检查 HAProxy 是否存活;若不存活,则停止 Keepalived,主要是解决检测延迟问题
# 端口检测(如16443是否监听)这里先注释!!!先注释!!先注释!!重要的事情说三遍
#if ! ss -tuln | grep -q ':16443'; then
# systemctl stop keepalived
# exit 1
#fi
exit 0
不要忘了给脚本执行权限
chmod 755 /etc/keepalived/check_apiserver.sh
启动服务
# 将配置文件和脚本同步给master02节点
scp /etc/keepalived/keepalived.conf master02.k8s.com:/etc/keepalived/keepalived.conf
scp /etc/keepalived/check_apiserver.sh master02.k8s.com:/etc/keepalived/check_apiserver.sh
# 改 master02 上 keepalived.conf 配置文件的节点独属配置
# 在两个节点上启动 VIP 服务
systemctl start keepalived
systemctl status keepalived
systemctl enable keepalived
验证也很简单没看所有keepalived服务节点,其中会有一个节点的ens33网卡除了自己的物理ip外还会有一条虚拟ip,其他节点正常
[root@master01 opt]# ip a s ens33
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:0c:29:62:a1:6b brd ff:ff:ff:ff:ff:ff
inet 192.168.239.70/24 brd 192.168.239.255 scope global noprefixroute ens33
valid_lft forever preferred_lft forever
inet 192.168.239.100/24 scope global secondary ens33 《-----------看这里
valid_lft forever preferred_lft forever
inet6 fe80::e32e:e86:936b:3dce/64 scope link tentative noprefixroute dadfailed
valid_lft forever preferred_lft forever
inet6 fe80::2957:b9:7da7:f237/64 scope link tentative noprefixroute dadfailed
valid_lft forever preferred_lft forever
inet6 fe80::843e:2720:c9a9:56fd/64 scope link tentative noprefixroute dadfailed
valid_lft forever preferred_lft forever
这里说一下为什么,检查脚本中停止的逻辑先注释了,haproxy服务有个很抽象的操作,当它刚启动会检查所有代理的后端接口是不是全正常,如果没有一个正常的,则它自身也不监听bind端口,而本文到此还没有去部署配置 k8s 所有如果不注释,会发现keepalived永远起不来
第五步:现在开始正式安装 k8s 。先去github上看好你自己要安装的 k8s 版本 https://github.com/kubernetes/kubernetes/releases,一定要是 1.24+ 的,本文安装1.28.0。在未来无论工作还是自己使用,尽量不要选择 1.20 - 1.24 之间的版本,要不你就去安装低版本默认支持docker,要不就安装新版配合插件
所有节点准备k8s的国内镜像yum,一般是阿里云,新建/etc/yum.repos.d/kubernetes.repo文件
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
gpgcheck=0
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
enabled=1
如果你要安装1.28以上的版本,需要使用阿里云新版仓库。将仓库路径中的版本换成你需要的,支持那些版本可以看https://mirrors.aliyun.com/kubernetes-new/core/stable
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.28/rpm/
enabled=1
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.28/rpm/repodata/repomd.xml.key
刷新yum,正常会出现 kubernetes 仓库
yum clean all
yum makecache
yum repolist
开始安装 k8s 服务,先查询yum中是否有你需要的版本,和文本同版本就不用看了,已经确定有了
yum --showduplicates list kubeadm | grep 1.28.0
随后所有 master 节点上安装核心服务
yum install -y kubelet-1.28.0-0 kubeadm-1.28.0-0 kubectl-1.28.0-0
另外两台节点上安装除客户端外的核心服务
yum install -y kubelet-1.28.0-0 kubeadm-1.28.0-0
安装完成后,所有节点修改配置文件/etc/sysconfig/kubelet,禁用 k8s 对交换分区的检查,因为上面已经关了,没必要再让它执行。以及指定 cri-dockerd 服务数据路径和启动方式(和docker统一)
KUBELET_EXTRA_ARGS="--fail-swap-on=false --container-runtime-endpoint=unix:///var/run/cri-dockerd.sock --cgroup-driver=systemd"
确保 k8s 和 docker 是开启自启的
systemctl enable kubelet docker
第六步:初始化,有别于单主节点命令行初始化,高可用的集群初始化,即使现在用的是kubeadm方式也有些复杂,需要借助 yaml 格式资源文件的方式,首先需要准备一个如下的配置文件 /opt/kubeadm-config.yaml,这个文件你可以在官网上找到样例
# 第一节配置段,用来声明 InitConfiguration 集群初始化的配置
# kubeadm API 版本,其他版本去官网 https://kubernetes.io/docs/reference/config-api/ 自己找,本文用的 1.28.0 需要使用v1beta3
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
# 自定义token,注意有格式要求,可以通过 kubeadm token generate 命令生成一个。usages表明在未来1小时内可以使用这个token进行认证,groups是用来指定使用该token加入的节点属于哪个组默认值即可,这两个都不用改。由于当前初始化的是主节点,所以这个token等价于定义命令行初始化最后日志输出token的生成逻辑
bootstrapTokens:
- token: "9a08jv.c0izixklcxtmnze7"
description: "kubeadm 使用的token 这里自定义,并设置有效期1小时,用于签名和身份认证"
ttl: "1h"
usages:
- authentication
- signing
groups:
- system:bootstrappers:kubeadm:default-node-token
# 这里定义当前初始化节点的ip和服务端口。注意一个误区,k8s并不需要你每个主节点用这个配置文件去初始化,你可以通俗的理解为这里写的InitConfiguration部分相当于一个集群的起点,就是第一台节点,这里写的IP也只是初始化进程执行时面向整个集群总配置下识别当前节点算那块小饼干而已
localAPIEndpoint:
advertiseAddress: "192.168.239.70"
bindPort: 6443
# 定义当前节点在k8s中的名称(name)、运行时接口、污点(key和value可以自定义,effect只支持三种值:NoSchedule-不参与新Pod运行调度、PreferNoSchedule-尽量不调度、NoExecute驱逐运行执行的Pod,对于主节点来讲默认需要有一个NoSchedule)
nodeRegistration:
name: "master01.k8s.com"
criSocket: "/var/run/cri-dockerd.sock"
taints:
- key: "kubeadmNode"
value: "master"
effect: "NoSchedule"
ignorePreflightErrors:
- Swap
- SystemVerification
#注意这个不是瞎写的是三个横杠,是用来分割配置段的分隔符
---
# 第二段配置,用来定义集群基本配置
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
#集群版本
kubernetesVersion: v1.28.0
# 集群名称(影响 kubeconfig 中 context 名称)
clusterName: kubernetes
# 控制节点的IP。注意:此处不能写单个 Master 的 IP,必须是负载均衡后的 VIP!
controlPlaneEndpoint: "192.168.239.100:16443"
# 这里定义 k8s 生成证书的扩展备用域名,这里写上面 VIP 就可以使得所有节点访问主节点时认同一个ip。默认包含有网关和当前节点的真实IP
apiServer:
certSANs:
- 192.168.239.100
timeoutForControlPlane: 4m0s
# 证书存储目录(默认路径)
certificatesDir: /etc/kubernetes/pki
# 下面这些都是标准默认的配置没有特殊含义
controllerManager: {}
# 使用 CoreDNS(默认),也可设为 kube-dns(已弃用)
dns:
type: CoreDNS
# etcd 数据目录(建议挂载独立磁盘)
etcd:
local:
dataDir: /var/lib/etcd
# 镜像仓库配置(国内加速)和命令行单主节点初始化一样是一个保底的仓库,安装时实际会走先前准备好的repo
imageRepository: registry.cn-hangzhou.aliyuncs.com/google_containers
# 网络配置 ,集群内 DNS 域名(Service 的默认后缀)、Pod 网段、Service 网段
networking:
dnsDomain: k8s.com
podSubnet: 10.10.0.0/16
serviceSubnet: 10.11.0.0/16
scheduler: {}
一定要注意污点,如果你不显示的在这里配置k8s会默认带一个污点,后期如果在其他地方要使用,一定要记得,分清你到底用的是默认污点还是自己自定义的污点
这里说一个题外话,有些地方初始化前,为了防止镜像拉不下来,会先提前去手动拉取,或者想知道当前k8s版本用的静态Pod都是那些版本,可以运行如下命令查看,总之手动用你的方法让当前部署的操作节点的docker有这些镜像就行,其他节点加入集群时会从当前节点同步
# 有个技巧,可以把list 换成 pull 先拉一下看看报不报错
kubeadm config images list \
--kubernetes-version=v1.28.0 \
--image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers
registry.cn-hangzhou.aliyuncs.com/google_containers/kube-apiserver:v1.28.0
registry.cn-hangzhou.aliyuncs.com/google_containers/kube-controller-manager:v1.28.0
registry.cn-hangzhou.aliyuncs.com/google_containers/kube-scheduler:v1.28.0
registry.cn-hangzhou.aliyuncs.com/google_containers/kube-proxy:v1.28.0
registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.9
registry.cn-hangzhou.aliyuncs.com/google_containers/etcd:3.5.9-0
registry.cn-hangzhou.aliyuncs.com/google_containers/coredns:v1.10.1
现在主节点初始化,执行如下命令
kubeadm init --config /opt/kubeadm-config.yaml --upload-certs
运行后的结果和单主节点一样,最终会输出一个成功日志,以及后续命令引导,同样的一定要保存下来
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 the control-plane node running the following command on each as root:
kubeadm join 192.168.239.100:16443 --token 9a08jv.c0izixklcxtmnze7 \
--discovery-token-ca-cert-hash sha256:8e60fbf48ce79f8fdb72ab808c6af18716c94ae6be54f0f6c625c2229d4d68a5 \
--control-plane --certificate-key 168bdf585eb4a24e368929daa47f3d156cf6eaede1bf8463fe93de9fab73a377
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.239.100:16443 --token 9a08jv.c0izixklcxtmnze7 \
--discovery-token-ca-cert-hash sha256:8e60fbf48ce79f8fdb72ab808c6af18716c94ae6be54f0f6c625c2229d4d68a5
看日志中,你会发现比单主节点的集群多一种操作类型,就是其他主节点加入集群的命令,先去操作普通用户的证书配置
# 准备一个普通用户
useradd k8sadmin
passwd k8sadmin
# 在root用户身份下,该配置文件给上面的普通用户 sudo 权限
visudo
#追加一行,你自己的环境可以写所有命令都允许,且无需密码
k8sadmin ALL=(ALL) NOPASSWD: ALL
# 工作中要限制
k8sadmin ALL=(ALL) ALL
# 或者 只允许执行某些命令
k8sadmin ALL=(ALL) /usr/bin/systemctl, /bin/rm
# 切换到该用户下执行命令
su k8sadmin
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
第七步:给当前master01主节点添加网络组件。这一步和单主节点部署没区别。背后的原理是当你执行 kubectl apply -f <网络插件配置文件>.yaml 时,你实际上是在向 Kubernetes 的 API Server 提交一个集群级别的资源配置。这个操作与你当前登录的是哪个 Master 节点无关,因为所有 Master 节点都连接着同一个后端存储(etcd),集群状态是共享的。绝大多数网络插件(如 Flannel, Calico)都是通过一种名为 DaemonSet 的控制器来部署的。DaemonSet 的作用是确保集群中的每一个节点(包括所有 Master 和 Worker 节点)上都运行且仅运行一个该插件的 Pod 副本
# 命令,不要看官方仓库的,因为国内访问不到官方的 helm 仓库
# 需要手动创建命名空间
kubectl create ns kube-flannel
kubectl label --overwrite ns kube-flannel pod-security.kubernetes.io/enforce=privileged pod-security.kubernetes.io/warn=privileged pod-security.kubernetes.io/audit=privileged
# 下载安装文件,版本方面用稳定版就行,本文用v0.25.6
curl -L -o kube-flannel.yml https://cdn.jsdelivr.net/gh/flannel-io/flannel@v0.25.6/Documentation/kube-flannel.yml
#随后修改下载的到文件,搜索 "Network": "10.244.0.0/16" 换成你自己的
sed -i 's|10.244.0.0/16|10.10.0.0/16|g' kube-flannel.yml
# 一下命令可以查看是否修改全了
grep -A 5 -B 5 "Network" kube-flannel.yml
# 手动拉取镜像,因为本文在同步验证文档部署时发现,依靠k8s自己拉不下来
# 具体的镜像名称,看 kube-flannel.yml 中的 image ,如果各位读者用的和本文版本一致,直接执行命令就行
docker pull docker.io/flannel/flannel:v0.25.6
docker pull docker.io/flannel/flannel-cni-plugin:v1.5.1-flannel2
# 执行安装
kubectl apply -f kube-flannel.yml
# 如果中途遇到问题,需要先删除,然后从创建nc哪一步重头开始
kubectl delete -f kube-flannel.yml
# 部署需要一点时间,你可以陆续查询关注状态
[k8sadmin@master01 opt]$ kubectl get pods -n kube-flannel
NAME READY STATUS RESTARTS AGE
kube-flannel-ds-zvggr 0/1 Init:0/2 0 21s
# 直到它变为 运行状态
[k8sadmin@master01 opt]$ kubectl get pods -n kube-flannel
NAME READY STATUS RESTARTS AGE
kube-flannel-ds-d2nwc 1/1 Running 0 55s
# 现在 再去看集群节点,主节点就时 ready 状态了
[k8sadmin@master01 opt]$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
master01.k8s.com Ready control-plane 135m v1.28.0
第八步:节点加入
看上面的初始化日志,其他主节点加入使用如下命令
kubeadm join 192.168.239.100:16443 --token 9a08jv.c0izixklcxtmnze7 \
--discovery-token-ca-cert-hash sha256:8e60fbf48ce79f8fdb72ab808c6af18716c94ae6be54f0f6c625c2229d4d68a5 \
--control-plane --certificate-key 168bdf585eb4a24e368929daa47f3d156cf6eaede1bf8463fe93de9fab73a377 \
--cri-socket unix:///var/run/cri-dockerd.sock --ignore-preflight-errors=Swap,SystemVerification
普通的 worker 节点加入,使用如下命令
kubeadm join 192.168.239.100:16443 --token 9a08jv.c0izixklcxtmnze7 \
--discovery-token-ca-cert-hash sha256:8e60fbf48ce79f8fdb72ab808c6af18716c94ae6be54f0f6c625c2229d4d68a5 \
--cri-socket unix:///var/run/cri-dockerd.sock --ignore-preflight-errors=Swap,SystemVerification
如果在后续使用或工作中,部署的间隔由于一些不可抗力因素延后了,超过了初始化配置文件中规定的超时时间,或者说你要加入新的节点,可使用如下命令,在任意一台已经在集群中且状态正常的master节点上重新加载证书
kubeadm init phase upload-certs --upload-certs
不过,要分清楚,这条命令的核心目的是让 k8s 加载集群证书到内存中的密码本里,并输出一个解密私钥,其他节点加入集群时,用开头初始化中定义的签名9a08jv.c0izixklcxtmnze7表示自己是一个合法的节点,随后用私钥去认证从而获得证书放入到自己的本地,并自动完成后续的加入集群工作
言归正传,在 master01 上陆续观察集群节点信息,直到所有节点的状态都是 Ready ,集群部署好了,这个过程随着集群节点数的大小不同需要一些时间
[k8sadmin@master01 opt]$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
master01.k8s.com Ready control-plane 33m v1.28.0
master02.k8s.com Ready control-plane 25m v1.28.0
master03.k8s.com Ready control-plane 25m v1.28.0
worker01.k8s.com Ready <none> 23m v1.28.0
worker02.k8s.com Ready <none> 23m v1.28.0
[k8sadmin@master01 root]$ kubectl get ns
NAME STATUS AGE
default Active 22h
kube-flannel Active 22h
kube-node-lease Active 22h
kube-public Active 22h
kube-system Active 22h
[k8sadmin@master01 root]$ kubectl get pods -n kube-system -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
coredns-6554b8b87f-tfppm 1/1 Running 1 (10m ago) 22h 10.10.1.5 master02.k8s.com <none> <none>
coredns-6554b8b87f-x7gqk 1/1 Running 1 (10m ago) 22h 10.10.1.4 master02.k8s.com <none> <none>
etcd-master01.k8s.com 1/1 Running 1 (10m ago) 22h 192.168.239.70 master01.k8s.com <none> <none>
etcd-master02.k8s.com 1/1 Running 1 (10m ago) 22h 192.168.239.73 master02.k8s.com <none> <none>
etcd-master03.k8s.com 1/1 Running 2 (9m59s ago) 22h 192.168.239.74 master03.k8s.com <none> <none>
kube-apiserver-master01.k8s.com 1/1 Running 1 (10m ago) 22h 192.168.239.70 master01.k8s.com <none> <none>
kube-apiserver-master02.k8s.com 1/1 Running 1 (10m ago) 22h 192.168.239.73 master02.k8s.com <none> <none>
kube-apiserver-master03.k8s.com 1/1 Running 2 (9m59s ago) 22h 192.168.239.74 master03.k8s.com <none> <none>
kube-controller-manager-master01.k8s.com 1/1 Running 1 (10m ago) 22h 192.168.239.70 master01.k8s.com <none> <none>
kube-controller-manager-master02.k8s.com 1/1 Running 1 (10m ago) 22h 192.168.239.73 master02.k8s.com <none> <none>
kube-controller-manager-master03.k8s.com 1/1 Running 2 (9m59s ago) 22h 192.168.239.74 master03.k8s.com <none> <none>
kube-proxy-d72nz 1/1 Running 1 (10m ago) 22h 192.168.239.71 worker01.k8s.com <none> <none>
kube-proxy-f2z7w 1/1 Running 1 (10m ago) 22h 192.168.239.73 master02.k8s.com <none> <none>
kube-proxy-g6p4d 1/1 Running 1 (10m ago) 22h 192.168.239.72 worker02.k8s.com <none> <none>
kube-proxy-qlqlm 1/1 Running 2 (9m59s ago) 22h 192.168.239.74 master03.k8s.com <none> <none>
kube-proxy-wff2h 1/1 Running 1 (10m ago) 22h 192.168.239.70 master01.k8s.com <none> <none>
kube-scheduler-master01.k8s.com 1/1 Running 1 (10m ago) 22h 192.168.239.70 master01.k8s.com <none> <none>
kube-scheduler-master02.k8s.com 1/1 Running 1 (10m ago) 22h 192.168.239.73 master02.k8s.com <none> <none>
kube-scheduler-master03.k8s.com 1/1 Running 2 (9m59s ago) 22h 192.168.239.74 master03.k8s.com <none> <none>
让有了至少一台正常的主节点后,释放 VIP 服务检查脚本 /etc/keepalived/check_apiserver.sh,并重启它们
#!/bin/bash
# 检查 HAProxy 是否存活;若不存活,则停止 Keepalived,主要是解决检测延迟问题
# 端口检测(如16443是否监听)
# ss -tuln 是专门查看所有监听端口列表的命令,类型为 LISTEN
# 不要用 ps -ef 没有结果的
# grep -q 是静默模式匹配,不生成任何输出,只关注返回码,如果匹配到了则返回 0 (真) 取反就正常走下面的 exit 0 了
if ! ss -tuln | grep -q ':16443'; then
systemctl stop keepalived
exit 1
fi
exit 0
随后重启 VIP 的两个服务
systemctl restart haproxy
systemctl restart keepalived
如果你等了很长时间,发现就是有那么几个节点一直不 Ready 则去对应节点看一下 docker ps 的结果是否正常,一般是某个服务的镜像拉不下来之类的网络问题,而且多数是 flannel 的,动手拉取后重启节点即可
至于重启某一台节点,标准的做法是先停止k8s对它的资源调度,从而排空它
kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data
去目标节点上操作重启
# 容器服务器
reboot
# 或者在目标节点上重启k8s服务
systemctl restart kubelet
重启后,在恢复过来
kubectl uncordon <node-name>
如果是想彻底退役某台节点,排空后,在k8s 主节点上删除它
kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data --force
kubectl delete node <node-name>
# 后面这些命令在要退役的节点上执行,用来删除 k8s 的所有数据
kubeadm reset -f
# 清理 CNI 配置
rm -rf /etc/cni/net.d
# 清理 kubeconfig 文件
rm -rf $HOME/.kube
# 清理 iptables 规则(可选,彻底清理网络规则)
sudo iptables -F && sudo iptables -t nat -F && sudo iptables -t mangle -F
# 清理网络接口(可选)
sudo ip link delete cni0 2>/dev/null
sudo ip link delete flannel.1 2>/dev/null
如果某一个pod报错,你可以通过以下命令查看它的详情事件
kubectl describe pod <pod名字> -n kube-flannel
或者看他的日志
kubectl logs <pod名称> -n <命名空间> -c <容器名称> --previous
更多推荐
所有评论(0)