大纲 高可用集群的介绍 Kubernetes 作为容器集群系统,通过健康检查与重启策略实现了 Pod 的故障自我修复能力,通过调度算法将 Pod 分布式部署,并持续监控其预期副本数。当节点发生故障时,系统能够自动在正常节点上重新启动 Pod,从而实现应用层的高可用性。在 Kubernetes 集群环境中,高可用性还需考虑以下两个层面:Etcd 存储库的高可用性,以及 Kubernetes Master 节点的高可用性。 Etcd 可以部署 3 集群个节点来保证高可用性。Kubernetes Master 节点承担总控中心角色,通过持续与工作节点(Worker)上的 Kubelet 和 Kube-Proxy 进行通信,维护整个集群的健康工作状态。一旦 Master 节点发生故障,将无法使用 kubectl 工具或 API 进行集群管理。Kubernetes Master 节点主要包含三个核心服务:API Server、Controller Manager 和 Scheduler。其中,Controller Manager 和 Scheduler 组件自身已通过内置的 Leader 选举机制实现了高可用。因此,Kubernetes Master 节点的高可用主要是针对 API Server 组件,该组件以 HTTP API 形式提供服务,其高可用实现方式与 Web 服务器类似,可以通过负载均衡器(比如 HaProxy / Nginx)对其多个实例进行负载均衡,使其支持水平扩展,并可以使用 Keepalived 来保证负载均衡器自身的高可用性。
Kubernetes 高可用集群的两种搭建方式
Kubernetes 高可用集群通常有两种部署方式:二进制手动部署 和 Kubeadm 自动化部署。在生产环境中,更推荐使用二进制部署方式,因为可控性高、组件版本可精细管理,便于根据业务需求进行深度定制。而在开发或测试环境,则可以使用 Kubeadm 快速搭建高可用集群,部署流程简单、效率更高。 值得一提的是,如果之前是 基于二进制包方式搭建 Kubernetes 单 Master 集群 的,那么可以在此基础上对 Master 节点进行扩容,无需从零开始搭建 Kubernetes 多 Master 集群;此时,最关键的步骤是重新生成 Kubernetes 集群所需的证书,并部署 HaProxy 与 Keepalived,最后配置 Controller Manager、Scheduler、Kubelet、Kube-Proxy 这些组件使用 Keepalived 的 VIP 与 HaProxy 的反向代理端口去连接 API Server。
Kubernetes 多 Master 集群的整体架构如下,多个 Master 节点之间通过 HaProxy 实现反向代理和负载均衡,而 HaProxy 通过 Keepalived 来保证自身的高可用性(即双机主备,也叫双机热备)。
搭建多 Master 集群 本节将基于二进制包方式搭建多 Master 的 Kubernetes 集群,核心目标如下:
为 Etcd 、Kubernetes 核心组件自签证书 部署 Etcd 集群(3 个节点) 部署 Kubernetes 集群的 2 个 Master 节点 部署 Kubernetes 集群的 3 个 Node 节点 部署 Kubernetes 集群的 CNI 网络插件(Flannel) 部署 Kubernetes 集群的 DNS 组件(CoreDNS) 部署 HaProxy、Keepalived,保证 API Server 的高可用性 术语说明
为了方便描述,本文使用 Node 节点 来替代 Kubernetes 的 Worker Node,使用 Master 节点 来替代 Master Node。
版本与硬件 搭建 Kubernetes 高可用集群所使用的软件版本如下:
软件 版本 安装方式 CentOS 7.9多个独立虚拟机 Docker 19.03.9二进制安装包 Kubernetes 1.19.10二进制安装包 Etcd 3.4.9二进制安装包 HaProxy 2.8.5源码编译安装 Keepalived 2.2.8源码编译安装
搭建 Kubernetes 高可用集群所使用的硬件需要满足以下几个条件
多台机器或虚拟机,建议操作系统 CentOS 7(64 位) 集群中所有机器之间的网络可以互通 系统内可以访问外网,需要拉取镜像 系统禁用 Swap 分区(必须) Master 节点的硬件配置要求当 Master 节点只运行运行控制平面组件(如 API Server、Controller Manager 和 Scheduler)2GB 或更多 RAM,2 个 CPU 或更多 CPU,硬盘 20GB 或更多 当 Master 节点运行控制平面组件,且承载用户 Pod 的调度和运行任务 6GB 或更多 RAM,6 个 CPU 或更多 CPU,硬盘 60GB 或更多 Node 节点的硬件配置要求 4GB 或更多 RAM,4 个 CPU 或更多 CPU,硬盘 40GB 或更多 服务器规划 本文所使用的 Kubernetes 集群服务器规划如下表所示。为了节省资源,Etcd 集群部署在与 Kubernetes 集群相同的服务器上。需要注意的是,在生产环境中,强烈建议将 Etcd 集群部署在独立的服务器上,以提升系统的性能和高可用性。
Host 名称 角色 IP CPU 核数 内存 磁盘 安装的组件 备注 k8s-master1 master 192.168.2.191 >= 6C >=6G >=60G kube-apiserver,kube-controller-manager,kube-scheduler,kubelet,kube-proxy,cni plugin, docker,etcd,haproxy,keepalived 部署 Etcd、HaProxy、Keepalived k8s-node1 node(worker) 192.168.2.112 >= 4C >=4G >=40G kubelet,kube-proxy,cni plugin, docker,etcd 部署 Etcd k8s-node2 node(worker) 192.168.2.131 >= 4C >=4G >=40G kubelet,kube-proxy,cni plugin, docker,etcd 部署 Etcd k8s-node3 node(worker) 192.168.2.236 >= 4C >=4G >=40G kubelet,kube-proxy,cni plugin, docker 不部署 Etcd k8s-master2 master 192.168.2.148 >= 6C >=6G >=60G kube-apiserver,kube-controller-manager,kube-scheduler,kubelet,kube-proxy,cni plugin, docker,haproxy,keepalived 不部署 Etcd,部署 HaProxy、Keepalived
如果希望 Master 节点仅用于运行控制平面组件(如 API Server、Controller Manager 和 Scheduler),不承载用户 Pod 的调度和运行任务,则 Master 节点可以不部署 Kubelet、Kube-Proxy 和 Docker(容器运行时),如下表所示:
Host 名称 角色 IP CPU 核数 内存 磁盘 安装的组件 备注 k8s-master1 master 192.168.2.191 >= 2C >=2G >=20G kube-apiserver,kube-controller-manager,kube-scheduler,cni plugin,etcd,haproxy,keepalived 部署 Etcd、HaProxy、Keepalived,不部署 Kubelet、Kube-Proxy、Docker k8s-node1 node(worker) 192.168.2.112 >= 4C >=4G >=40G kubelet,kube-proxy,cni plugin, docker,etcd 部署 Etcd k8s-node2 node(worker) 192.168.2.131 >= 4C >=4G >=40G kubelet,kube-proxy,cni plugin, docker,etcd 部署 Etcd k8s-node3 node(worker) 192.168.2.236 >= 4C >=4G >=40G kubelet,kube-proxy,cni plugin, docker 不部署 Etcd k8s-master2 master 192.168.2.148 >= 2C >=2G >=20G kube-apiserver,kube-controller-manager,kube-scheduler,kubelet,kube-proxy,cni plugin, haproxy,keepalived 不部署 Etcd、Kubelet、Kube-Proxy、Docker,部署 HaProxy、Keepalived
虚拟 IP(VIP)的说明
虚拟 IP(VIP)由 Keepalived 提供。 在配置 Keepalived 的 virtual_ipaddress 参数时,所指定的 VIP 地址必须与 HaProxy 所在节点处于同一网段。 例如,如果两个 HaProxy 节点的网段为 192.168.2.0/24,则 VIP 也应设置为该网段内的地址(如 192.168.2.x)。 操作系统初始化 特别注意
在 Kubernetes 集群的所有节点(包括 Master 和 Node 节点)上,分别执行以下系统初始化操作 。关闭防火墙
1 2 3 4 5 # systemctl stop firewalld# systemctl disable firewalld
关闭 selinux
1 2 3 4 5 # setenforce 0# sed -i 's/enforcing/disabled/' /etc/selinux/config
关闭 swap 分区
1 2 3 4 5 $ swapoff -a # sed -i 's/.*swap.*/#&/' /etc/fstab
系统时间同步(强烈建议使用 chrony 而不是 ntpdate)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 # yum remove -y ntpdate# yum install -y chrony# systemctl enable chronyd# systemctl start chronyd# vim /etc/chrony.confserver ntp.aliyun.com iburst server ntp1.tencent.com iburst server cn.pool.ntp.org iburst # systemctl restart chronyd# systemctl status chronyd# chronyc tracking# chronyc makestep
设置主机名(比如 k8s-master1、k8s-master2、k8s-node1、k8s-node2、k8s-node3)
1 # hostnamectl set -hostname <hostname>
添加 hosts 配置信息
1 2 3 4 5 6 7 # vim /etc/hosts192.168.2.191 k8s-master1 192.168.2.148 k8s-master2 192.168.2.112 k8s-node1 192.168.2.131 k8s-node2 192.168.2.236 k8s-node3
将桥接的 IPv4 流量传递到 iptables 的链
1 2 3 4 5 6 7 # vim /etc/sysctl.d/k8s.confnet.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 # sysctl --system
安装 CFSSL 工具 特别注意
以下所有操作都是仅在 Kubernetes 集群的任意一个 Master 节点上(比如 k8s-master1)执行 。CFSSL 是 CloudFlare 开源的一款 PKI/TLS 工具,包含一个命令行工具和一个用于签名、验证并且捆绑 TLS 证书的 HTTP API 服务,使用 Go 语言编写。
1 2 3 4 5 6 7 8 9 10 # curl -L https://pkg.cfssl.org/R1.2/cfssl_linux-amd64 -o /usr/local /bin/cfssl# curl -L https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64 -o /usr/local /bin/cfssljson# curl -L https://pkg.cfssl.org/R1.2/cfssl-certinfo_linux-amd64 -o /usr/local /bin/cfssl-certinfo# chmod +x /usr/local /bin/cfssl*# export PATH=/usr/local /bin:$PATH
安装 Docker 服务 特别注意
在 Kubernetes 集群的所有节点(包括 Master 和 Node 节点)上分别安装 Docker,这里采用二进制安装方式,CentOS 上还可以通过 YUM 安装 。如果 Master 节点仅用于运行控制平面组件(如 API Server、Controller Manager 和 Scheduler),不承载用户 Pod 的调度和运行任务,则 Master 节点可以不安装 Docker 服务。 下载并解压 Docker 的二进制包
1 2 3 4 5 6 7 8 # wget https://download.docker.com/linux/static/stable/x86_64/docker-19.03.9.tgz# tar -xvf docker-19.03.9.tgz# mv docker/* /usr/bin
创建 Docker 的配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # mkdir /etc/docker# cat > /etc/docker/daemon.json << EOF{ "registry-mirrors" : [ "https://b9pmyelo.mirror.aliyuncs.com" , "https://ustc-edu-cn.mirror.aliyuncs.com" , "https://mirror.iscas.ac.cn" , "https://docker.nju.edu.cn" , "https://docker.m.daocloud.io" , "https://ccr.ccs.tencentyun.com" , "https://dockerhub.timeweb.cloud" ] } EOF
配置 Systemd 管理 Docker
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 # cat > /usr/lib/systemd/system/docker.service << EOF[Unit] Description=Docker Application Container Engine Documentation=https://docs.docker.com After=network-online.target After=firewalld.service Wants=network-online.target [Service] Type=notify ExecStart=/usr/bin/dockerd ExecReload=/bin/kill -s HUP $MAINPID LimitNOFILE=infinity LimitNPROC=infinity LimitCORE=infinity TimeoutStartSec=0 Delegate=yes KillMode=process Restart=on-failure StartLimitBurst=3 StartLimitInterval=60s [Install] WantedBy=multi-user.target EOF
启动并设置开机自启动 Docker 服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # systemctl daemon-reload# systemctl enable docker# systemctl start docker# systemctl status docker# docker --version # docker info
搭建 Etcd 集群 Etcd 是一个分布式键值存储系统,Kubernetes 使用 Etcd 进行数据存储,所以先准备一个 Etcd 系统。为解决 Etcd 单点故障,建议采用集群方式部署,这里使用 3 台机器组建 Etcd 集群,可容忍 1 台机器故障。当然,也可以使用 5 台机器组建 Etcd 集群,可容忍 2 台机器故障。为了节省机器,这里复用 Kubernetes 节点的机器,也可以独立于 Kubernetes 集群之外部署,只要 Kubernetes 的 API Server 能够连接上就行。
生成 Etcd 证书 特别注意
以下所有操作都是仅在 Kubernetes 集群的任意一个节点上(比如,第一个 Master 节点)执行,千万不要在每个 Etcd 集群节点上单独生成证书,否则 Etcd 集群里的节点可能会因证书不一致而导致集群启动失败 。以后向 Etcd 集群中添加新节点时,只要将对应的证书拷贝到新节点上即可,通过 CFSSL 工具生成证书的详细使用教程请看 这里 。 这里生成的 Etcd 证书,主要是提供给 API Server 使用的,目的是为了 API Server 与 Etcd 集群之间使用 CA 证书进行加密和身份验证。 组件 使用的证书 Etcd ca.pem、server-key.pem、server.pem
创建存放 Etcd 证书的目录
1 2 3 4 5 mkdir -p ~/tls/etcd/ cd ~/tls/etcd/
创建 CA 证书的配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # cat << EOF | tee ca-config.json{ "signing" : { "default" : { "expiry" : "87600h" }, "profiles" : { "www" : { "expiry" : "87600h" , "usages" : [ "signing" , "key encipherment" , "server auth" , "client auth" ] } } } } EOF
创建 CA 证书签名的配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # cat << EOF | tee ca-csr.json{ "CN" : "etcd ca" , "key" : { "algo" : "rsa" , "size" : 2048 }, "names" : [ { "C" : "CN" , "ST" : "BeiJing" , "L" : "BeiJing" , "O" : "k8s" , "OU" : "System" } ], "ca" : { "expiry" : "87600h" } } EOF
创建用于生成 Etcd 证书的配置文件,如果 hosts 字段不为空,则需要指定授权使用该证书的 IP 或域名列表。由于该证书后续会被 Etcd 集群使用到,因此通常指定为 Etcd 集群所有节点的 IP 集合。因为本文的 Etcd 集群节点和 Kubernetes 的集群节点共同安装在不同虚拟机内(即服务器复用),所以 IP 列表就是 Kubernetes 集群所有节点的 IP 集合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 # cat << EOF | tee server-csr.json{ "CN" : "etcd" , "hosts" : [ "192.168.2.191" , "192.168.2.148" , "192.168.2.112" , "192.168.2.131" , "192.168.2.236" ], "key" : { "algo" : "rsa" , "size" : 2048 }, "names" : [ { "C" : "CN" , "ST" : "BeiJing" , "L" : "BeiJing" , "O" : "k8s" , "OU" : "System" } ] } EOF
生成 CA 证书,并使用自签 CA 签发 Etcd 证书
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # ls ca-config.json ca-csr.json server-csr.json # cfssl gencert -initca ca-csr.json | cfssljson -bare ca -# ls ca*.pemca-key.pem ca.pem # cfssl gencert -ca =ca.pem -ca -key=ca-key.pem -config =ca-config.json -profile =www server-csr.json | cfssljson -bare server# ls server*.pemserver-key.pem server.pem # cfssl -certinfo -cert server.pem# openssl x509 -noout -text -in server.pem
拷贝上面生成的 CA 证书和 Etcd 证书到本地目录
1 2 3 4 5 # mkdir -p /opt/etcd/ssl/# cp ~/tls/etcd/*.pem /opt/etcd/ssl/
拷贝上面生成的 CA 证书和 Etcd 证书到其他 Master 节点里面(提供给 API Server 使用)
1 2 3 4 5 # ssh root@k8s-master2 "mkdir -p /opt/etcd/ssl/" # scp ~/tls/etcd/*.pem root@k8s-master2:/opt/etcd/ssl/
部署 Etcd 集群 在第一个 Master 节点(k8s-master1)里面安装 Etcd
1 2 3 4 5 6 7 8 9 10 11 # wget https://github.com/etcd-io/etcd/releases/download/v3.4.9/etcd-v3.4.9-linux-amd64.tar.gz# mkdir -p /opt/etcd/{bin,cfg,ssl}# tar zxvf etcd-v3.4.9-linux-amd64.tar.gz# mv etcd-v3.4.9-linux-amd64/{etcd,etcdctl} /opt/etcd/bin/
在第一个 Master 节点(k8s-master1)里面创建 Etcd 的配置文件,这里必须根据实际情况更改 Etcd 节点的 IP、名称,切勿直接拷贝以下配置内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # cat > /opt/etcd/cfg/etcd.conf << EOFETCD_NAME="etcd-1" ETCD_DATA_DIR="/var/lib/etcd/default.etcd" ETCD_LISTEN_PEER_URLS="https://192.168.2.191:2380" ETCD_LISTEN_CLIENT_URLS="https://192.168.2.191:2379" ETCD_INITIAL_ADVERTISE_PEER_URLS="https://192.168.2.191:2380" ETCD_ADVERTISE_CLIENT_URLS="https://192.168.2.191:2379" ETCD_INITIAL_CLUSTER="etcd-1=https://192.168.2.191:2380,etcd-2=https://192.168.2.112:2380,etcd-3=https://192.168.2.131:2380" ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster" ETCD_INITIAL_CLUSTER_STATE="new" EOF
1 2 3 4 5 6 7 8 9 10 # 配置说明: ETCD_NAME:节点名称,集群中唯一 ETCDDATADIR:数据目录路径 ETCD_LISTEN_PEER_URLS:集群通信监听地址 ETCD_LISTEN_CLIENT_URLS:客户端访问监听地址 ETCD_INITIAL_ADVERTISE_PEER_URLS:集群通告地址 ETCD_ADVERTISE_CLIENT_URLS:客户端通告地址 ETCD_INITIAL_CLUSTER:集群节点地址 ETCD_INITIAL_CLUSTER_TOKEN:集群 Token ETCD_INITIAL_CLUSTER_STATE:加入集群的当前状态,new 是新集群,existing 表示加入已有集群
在第一个 Master 节点(k8s-master1)里面使用 Systemd 管理 Etcd 服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 # cat > /usr/lib/systemd/system/etcd.service << EOF[Unit] Description=Etcd Server After=network.target After=network-online.target Wants=network-online.target [Service] Type=notify EnvironmentFile=/opt/etcd/cfg/etcd.conf ExecStart=/opt/etcd/bin/etcd \ --cert -file=/opt/etcd/ssl/server.pem \--key -file=/opt/etcd/ssl/server-key.pem \--peer -cert-file=/opt/etcd/ssl/server.pem \--peer -key-file=/opt/etcd/ssl/server-key.pem \--trusted -ca-file=/opt/etcd/ssl/ca.pem \--peer -trusted-ca-file=/opt/etcd/ssl/ca.pem \--logger =zapRestart=always RestartSec=10s LimitNOFILE=65536 [Install] WantedBy=multi-user.target EOF
1 2 3 4 5 # systemctl daemon-reload# systemctl enable etcd
在第一个 Master 节点(k8s-master1)里面拷贝所有 Etcd 文件到其他 2 个 Node 节点,并在这 2 个 Node 节点里分别更改 Etcd 的配置文件和设置 Etcd 服务开机自启动,最终 Etcd 集群会有 3 个节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 # scp -r /opt/etcd/ root@k8s-node1:/opt/# scp -r /opt/etcd/ root@k8s-node2:/opt/# scp /usr/lib/systemd/system/etcd.service root@k8s-node1:/usr/lib/systemd/system/# scp /usr/lib/systemd/system/etcd.service root@k8s-node2:/usr/lib/systemd/system/# vim /opt/etcd/cfg/etcd.conf# systemctl daemon-reload# systemctl enable etcd
比如,Etcd 集群第 2 个节点的配置文件(etcd.conf)内容如下:
1 2 3 4 5 6 7 8 9 10 11 ETCD_NAME="etcd-2" ETCD_DATA_DIR="/var/lib/etcd/default.etcd" ETCD_LISTEN_PEER_URLS="https://192.168.2.112:2380" ETCD_LISTEN_CLIENT_URLS="https://192.168.2.112:2379" #[Cluster] ETCD_INITIAL_ADVERTISE_PEER_URLS="https://192.168.2.112:2380" ETCD_ADVERTISE_CLIENT_URLS="https://192.168.2.112:2379" ETCD_INITIAL_CLUSTER="etcd-1=https://192.168.2.191:2380,etcd-2=https://192.168.2.112:2380,etcd-3=https://192.168.2.131:2380" ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster" ETCD_INITIAL_CLUSTER_STATE="new"
比如,Etcd 集群第 3 个节点的配置文件(etcd.conf)内容如下:
1 2 3 4 5 6 7 8 9 10 11 ETCD_NAME="etcd-3" ETCD_DATA_DIR="/var/lib/etcd/default.etcd" ETCD_LISTEN_PEER_URLS="https://192.168.2.131:2380" ETCD_LISTEN_CLIENT_URLS="https://192.168.2.131:2379" #[Cluster] ETCD_INITIAL_ADVERTISE_PEER_URLS="https://192.168.2.131:2380" ETCD_ADVERTISE_CLIENT_URLS="https://192.168.2.131:2379" ETCD_INITIAL_CLUSTER="etcd-1=https://192.168.2.191:2380,etcd-2=https://192.168.2.112:2380,etcd-3=https://192.168.2.131:2380" ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster" ETCD_INITIAL_CLUSTER_STATE="new"
启动 Etcd 集群 在 Etcd 集群的所有节点上分别执行以下命令来启动 Etcd,值得一提的是,必须在多个 Etcd 节点里同时执行 systemctl start etcd 命令来启动 Etcd 集群,否则单个 Etcd 节点是无法正常启动的
1 2 3 4 5 6 7 8 # systemctl start etcd# systemctl status etcd# journalctl -u etcd.service
若 Etcd 集群启动成功,可以在任意一个 Etcd 节点里执行以下命令来查看集群的运行状态,比如 Leader 选举结果(请自行更改 Etcd 集群节点的 IP 和端口,切勿直接拷贝执行以下命令 )
1 2 3 4 5 6 7 +----------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+ | ENDPOINT | ID | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS | +----------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+ | https://192.168.2.191:2379 | 9a8c44f9310511 | 3.4.9 | 442 kB | true | false | 2 | 519 | 519 | | | https://192.168.2.112:2379 | a00042f5519886e3 | 3.4.9 | 451 kB | false | false | 2 | 519 | 519 | | | https://192.168.2.131:2379 | 9cb4eb07d38510b5 | 3.4.9 | 446 kB | false | false | 2 | 519 | 519 | | +----------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
若 Etcd 集群启动失败,可以在所有的 Etcd 节点里分别执行以下操作来解决
1 2 3 4 5 6 7 8 9 10 11 # systemctl stop etcd# rm -rf /var/lib/etcd/default.etcd# systemctl start etcd# systemctl status etcd
特别注意
Etcd 集群使用了 Raft 协议,很多操作要依赖时间戳和进行超时判断,因此必须安装类似 chrony 这样的工具来同步操作系统的时间,否则可能会影响 Etcd 集群的正常工作。
搭建 Kubernetes 集群 生成集群证书 特别注意
以下所有操作都是仅在 Kubernetes 集群的任意一个 Master 节点(比如 k8s-master1)上执行,千万不要在每个 Kubernetes 集群节点上单独生成 API Server 或者 Kube-Proxy 等证书,否则可能会因证书不一致而导致 Kubernetes 集群启动失败 。简而言之,所有证书(包括 API Server、Kube-Proxy 等证书)都是在任意一个 Master 节点上面生成,其他 Kubernetes 集群节点不参与生成证书。以后向 Kubernetes 集群中添加任何新节点时,只要将对应的证书拷贝到新节点上即可 。值得一提的是,通过 CFSSL 工具生成证书的详细使用教程请看 这里 。 组件 使用的证书 CA 根证书 ca.pem、ca-key.pemAPI Server ca.pem、kube-apiserver-key.pem、kube-apiserver.pemController Manager ca.pem、kube-controller-manager-key.pem、kube-controller-manager.pemScheduler ca.pem、kube-scheduler-key.pem、kube-scheduler.pemKubelet ca.pemKube-Proxy ca.pem、kube-proxy-key.pem、kube-proxy.pem
生成 CA 根证书 创建存放证书的目录
进入存放证书的目录
创建 CA 证书的配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # cat > ca-config.json << EOF{ "signing" : { "default" : { "expiry" : "87600h" }, "profiles" : { "kubernetes" : { "expiry" : "87600h" , "usages" : [ "signing" , "key encipherment" , "server auth" , "client auth" ] } } } } EOF
创建 CA 证书签名的配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # cat > ca-csr.json << EOF{ "CN" : "kubernetes" , "key" : { "algo" : "rsa" , "size" : 2048 }, "names" : [ { "C" : "CN" , "L" : "Beijing" , "ST" : "Beijing" , "O" : "k8s" , "OU" : "System" } ] } EOF
生成 CA 证书
1 2 3 4 5 6 7 8 9 10 # cfssl gencert -initca ca-csr.json | cfssljson -bare ca -# ls ca*.pemca-key.pem ca.pem # cfssl -certinfo -cert ca.pem# openssl x509 -noout -text -in ca.pem
将上面生成的 CA 证书拷贝到本地目录里面
1 2 3 4 5 # mkdir -p /opt/kubernetes/ssl/# cp ~/tls/k8s/ca*.pem /opt/kubernetes/ssl/
将上面生成的 CA 证书拷贝到其他 Master 节点里面
1 2 3 4 5 # ssh root@k8s-master2 "mkdir -p /opt/kubernetes/ssl/" # scp ~/tls/k8s/ca*.pem root@k8s-master2:/opt/kubernetes/ssl/
将上面生成的 CA 证书拷贝到所有 Node 节点里面
1 2 3 4 5 6 7 8 9 # ssh root@k8s-node1 "mkdir -p /opt/kubernetes/ssl/" # ssh root@k8s-node2 "mkdir -p /opt/kubernetes/ssl/" # ssh root@k8s-node3 "mkdir -p /opt/kubernetes/ssl/" # scp ~/tls/k8s/ca*.pem root@k8s-node1:/opt/kubernetes/ssl/# scp ~/tls/k8s/ca*.pem root@k8s-node2:/opt/kubernetes/ssl/# scp ~/tls/k8s/ca*.pem root@k8s-node3:/opt/kubernetes/ssl/
生成 API Server 证书 进入存放证书的目录
创建用于生成 API Server 证书的配置文件,如果 hosts 字段不为空,需要指定授权使用该证书的 IP 或域名列表。由于该证书会被 Kubernetes 集群使用到,通常需要包含 Kubernetes 集群所有节点的 IP,以及 Kubernetes 服务的 Cluster IP(即 kube-apiserver.conf 配置文件中指定的 --service-cluster-ip-range 网段的第一个可用 IP,例如 10.0.0.1,具体取决于 Kubernetes 集群的 Service 网段配置),最后还要加上 Keepalived 的虚拟 IP 地址(比如 192.168.2.100)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 # cat << EOF | tee kube-apiserver-csr.json{ "CN" : "kubernetes" , "hosts" : [ "10.0.0.1" , "127.0.0.1" , "192.168.2.191" , "192.168.2.148" , "192.168.2.112" , "192.168.2.131" , "192.168.2.236" , "192.168.2.100" , "kubernetes" , "kubernetes.default" , "kubernetes.default.svc" , "kubernetes.default.svc.cluster" , "kubernetes.default.svc.cluster.local" ], "key" : { "algo" : "rsa" , "size" : 2048 }, "names" : [ { "C" : "CN" , "ST" : "BeiJing" , "L" : "BeiJing" , "O" : "k8s" , "OU" : "System" } ] } EOF
使用自签 CA 签发 API Server 证书
1 2 3 4 5 6 7 8 9 10 11 12 13 14 # ls ca-config.json ca.csr ca-csr.json ca-key.pem ca.pem kube-apiserver-csr.json # cfssl gencert -ca =ca.pem -ca -key=ca-key.pem -config =ca-config.json -profile =kubernetes kube-apiserver-csr.json | cfssljson -bare kube-apiserver# ls kube-apiserver*.pemkube-apiserver-key.pem kube-apiserver.pem # cfssl -certinfo -cert kube-apiserver.pem# openssl x509 -noout -text -in kube-apiserver.pem
将上面生成的 API Server 证书拷贝到本地目录里面
1 2 # cp ~/tls/k8s/kube-apiserver*.pem /opt/kubernetes/ssl/
将上面生成的 API Server 证书拷贝到其他 Master 节点里面
1 2 # scp ~/tls/k8s/kube-apiserver*.pem root@k8s-master2:/opt/kubernetes/ssl/
生成 Controller Manager 证书 进入存放证书的目录
创建用于生成 Controller Manager 证书的配置文件,hosts 必须包含 127.0.0.1、所有 Master 节点的 IP、Keepalived 的虚拟 IP 地址(比如 192.168.2.100),请自行更改,切勿直接拷贝使用以下配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 # cat > kube-controller-manager-csr.json << EOF{ "CN" : "system:kube-controller-manager" , "hosts" : [ "127.0.0.1" , "192.168.2.191" , "192.168.2.148" , "192.168.2.100" ], "key" : { "algo" : "rsa" , "size" : 2048 }, "names" : [ { "C" : "CN" , "ST" : "Beijing" , "L" : "Beijing" , "O" : "system:kube-controller-manager" , "OU" : "System" } ] } EOF
使用自签 CA 签发 Controller Manager 证书
1 2 3 4 5 6 7 8 9 10 11 12 13 14 # ls ca-config.json ca.csr ca-csr.json ca-key.pem ca.pem kube-apiserver.csr kube-apiserver-csr.json kube-apiserver-key.pem kube-apiserver.pem kube-controller-manager-csr.json # cfssl gencert -ca =ca.pem -ca -key=ca-key.pem -config =ca-config.json -profile =kubernetes kube-controller-manager-csr.json | cfssljson -bare kube-controller-manager# ls kube-controller-manager*.pemkube-controller-manager-key.pem kube-controller-manager.pem # cfssl -certinfo -cert kube-controller-manager.pem# openssl x509 -noout -text -in kube-controller-manager.pem
将上面生成的 Controller Manager 证书拷贝到本地目录里面
1 2 # cp ~/tls/k8s/kube-controller-manager*.pem /opt/kubernetes/ssl/
将上面生成的 Controller Manager 证书拷贝到其他 Master 节点里面
1 2 # scp ~/tls/k8s/kube-controller-manager*.pem root@k8s-master2:/opt/kubernetes/ssl/
生成 Scheduler 证书 进入存放证书的目录
创建用于生成 Scheduler 证书的配置文件,hosts 必须包含 127.0.0.1、所有 Master 节点的 IP、Keepalived 的虚拟 IP 地址(比如 192.168.2.100),请自行更改,切勿直接拷贝使用以下配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 # cat > kube-scheduler-csr.json << EOF{ "CN" : "system:kube-scheduler" , "hosts" : [ "127.0.0.1" , "192.168.2.191" , "192.168.2.148" , "192.168.2.100" ], "key" : { "algo" : "rsa" , "size" : 2048 }, "names" : [ { "C" : "CN" , "ST" : "BeiJing" , "L" : "BeiJing" , "O" : "system:kube-scheduler" , "OU" : "System" } ] } EOF
使用自签 CA 签发 Scheduler 证书
1 2 3 4 5 6 7 8 9 10 11 12 13 14 # ls ca-config.json ca.csr ca-csr.json ca-key.pem ca.pem kube-apiserver.csr kube-apiserver-csr.json kube-apiserver-key.pem kube-apiserver.pem kube-controller-manager.csr kube-controller-manager-csr.json kube-controller-manager-key.pem kube-controller-manager.pem kube-scheduler-csr.json # cfssl gencert -ca =ca.pem -ca -key=ca-key.pem -config =ca-config.json -profile =kubernetes kube-scheduler-csr.json | cfssljson -bare kube-scheduler# ls kube-scheduler*.pemkube-scheduler-key.pem kube-scheduler.pem # cfssl -certinfo -cert kube-scheduler.pem# openssl x509 -noout -text -in kube-scheduler.pem
将上面生成的 Scheduler 证书拷贝到本地目录里面
1 2 # cp ~/tls/k8s/kube-scheduler*.pem /opt/kubernetes/ssl/
将上面生成的 Scheduler 证书拷贝到其他 Master 节点里面
1 2 # scp ~/tls/k8s/kube-scheduler*.pem root@k8s-master2:/opt/kubernetes/ssl/
生成 Kube-Proxy 证书 进入存放证书的目录
创建用于生成 Kube-Proxy 证书的配置文件,hosts 配置项的值可以为空,因为 Kube-Proxy 不直接被访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # cat > kube-proxy-csr.json << EOF{ "CN" : "system:kube-proxy" , "hosts" : [], "key" : { "algo" : "rsa" , "size" : 2048 }, "names" : [ { "C" : "CN" , "L" : "Beijing" , "ST" : "Beijing" , "O" : "system:node-proxier" , "OU" : "System" } ] } EOF
使用自签 CA 签发 Kube-Proxy 证书
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # ls ca-config.json ca-csr.json ca.pem kube-apiserver-csr.json kube-apiserver.pem kube-controller-manager-csr.json kube-controller-manager.pem kube-scheduler.csr kube-scheduler-key.pem ca.csr ca-key.pem kube-apiserver.csr kube-apiserver-key.pem kube-controller-manager.csr kube-controller-manager-key.pem kube-proxy-csr.json kube-scheduler-csr.json kube-scheduler.pem # cfssl gencert -ca =ca.pem -ca -key=ca-key.pem -config =ca-config.json -profile =kubernetes kube-proxy-csr.json | cfssljson -bare kube-proxy# ls kube-proxy*.pemkube-proxy-key.pem kube-proxy.pem # cfssl -certinfo -cert kube-proxy.pem# openssl x509 -noout -text -in kube-proxy.pem
将上面生成的 Kube-Proxy 证书拷贝到本地目录里面
1 2 # cp ~/tls/k8s/kube-proxy*.pem /opt/kubernetes/ssl/
将上面生成的 Kube-Proxy 证书拷贝到其他 Master 节点里面
1 2 # scp ~/tls/k8s/kube-proxy*.pem root@k8s-master2:/opt/kubernetes/ssl/
将上面生成的 Kube-Proxy 证书拷贝到所有 Node 节点里面
1 2 3 4 # scp ~/tls/k8s/kube-proxy*.pem root@k8s-node1:/opt/kubernetes/ssl/# scp ~/tls/k8s/kube-proxy*.pem root@k8s-node2:/opt/kubernetes/ssl/# scp ~/tls/k8s/kube-proxy*.pem root@k8s-node3:/opt/kubernetes/ssl/
单独部署 Master 节点 特别注意
以下所有操作都是仅在 Kubernetes 集群的两台 Master 节点上执行,也就是分别在两台 Master 节点上部署 API Server、Controller Manager、Scheduler、HaProxy、Keepalived 。
下载二进制文件 下载地址
Kubernetes 相关的二进制包可以从 GitHub 下载得到,这里下载的版本是 v1.19.10。 当打开 GitHub 链接后,会发现里面有很多二进制包,只需要下载一个 Server 二进制包就够了,里面包含了 Master 节点相关的二进制包。 下载并解压二进制包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # wget https://dl.k8s.io/v1.19.10/kubernetes-server-linux-amd64.tar.gz# mkdir -p /opt/kubernetes/{bin,cfg,ssl,logs}# tar zxvf kubernetes-server-linux-amd64.tar.gz# cd kubernetes/server/bin# cp kube-apiserver kube-scheduler kube-controller-manager /opt/kubernetes/bin/# cp kubectl /usr/bin/
部署 API Server 创建 API Server 的配置文件,使用转义符 \\ 是为了使 EOF 保留换行符。请自行更改 --bind-address 与 --advertise-address 的值为当前 Master 节点的 IP 地址(比如 192.168.2.191),而 --etcd-servers 需要改为 Etcd 所有集群节点的 IP 地址,其他配置可以不更改,切勿直接拷贝以下配置内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 # cat > /opt/kubernetes/cfg/kube-apiserver.conf << EOFKUBE_APISERVER_OPTS="--logtostderr=false \\ --v=2 \\ --log-dir=/opt/kubernetes/logs \\ --etcd-servers=https://192.168.2.191:2379,https://192.168.2.112:2379,https://192.168.2.131:2379 \\ --bind-address=<当前节点的IP地址> \\ --secure-port=6443 \\ --advertise-address=<当前节点的IP地址> \\ --allow-privileged=true \\ --service-cluster-ip-range=10.0.0.0/24 \\ --enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,ResourceQuota,NodeRestriction \\ --authorization-mode=RBAC,Node \\ --enable-bootstrap-token-auth=true \\ --token-auth-file=/opt/kubernetes/cfg/token.csv \\ --service-node-port-range=30000-32767 \\ --kubelet-client-certificate=/opt/kubernetes/ssl/kube-apiserver.pem \\ --kubelet-client-key=/opt/kubernetes/ssl/kube-apiserver-key.pem \\ --tls-cert-file=/opt/kubernetes/ssl/kube-apiserver.pem \\ --tls-private-key-file=/opt/kubernetes/ssl/kube-apiserver-key.pem \\ --client-ca-file=/opt/kubernetes/ssl/ca.pem \\ --service-account-key-file=/opt/kubernetes/ssl/ca-key.pem \\ --etcd-cafile=/opt/etcd/ssl/ca.pem \\ --etcd-certfile=/opt/etcd/ssl/server.pem \\ --etcd-keyfile=/opt/etcd/ssl/server-key.pem \\ --audit-log-maxage=30 \\ --audit-log-maxbackup=3 \\ --audit-log-maxsize=100 \\ --audit-log-path=/opt/kubernetes/logs/k8s-audit.log" EOF
配置参数说明
参数 含义 --logtostderr启用将日志输出到 stderr(通常设为 false 以输出到指定目录)。 --v日志详细级别(verbosity level),数值越大输出越详细。 --log-dir日志文件输出目录。 --etcd-serversEtcd 集群地址列表(用逗号分隔,支持 HTTPS)。 --bind-addressKube-ApiServer 监听的 IP 地址,通常是 Master 节点的 IP 地址。 --secure-portHTTPS 安全端口(默认 6443)。 --advertise-address集群中对外通告的地址(其他组件访问时使用),通常是 Master 节点的 IP 地址。 --allow-privileged是否允许特权容器(启用授权相关特性)。 --service-cluster-ip-rangeService 虚拟 IP 地址段,用于分配 Cluster IP。 --enable-admission-plugins启用的准入控制插件列表(例如 NamespaceLifecycle, LimitRanger 等)。 --authorization-mode认证 / 授权模式(如 RBAC,Node 表示启用 RBAC 和节点自管理)。 --enable-bootstrap-token-auth启用基于 Bootstrap Token 的 TLS 引导认证。 --token-auth-fileBootstrap Token 的静态文件路径(比如 token.csv)。 --service-node-port-rangeNodePort 类型 Service 默认分配的端口范围(如 30000-32767)。 --kubelet-client-certificateKube-ApiServer 访问 kubelet 时使用的客户端证书路径。 --kubelet-client-keyKube-ApiServer 访问 kubelet 时使用的客户端私钥路径。 --tls-cert-fileKube-ApiServer HTTPS 使用的证书文件。 --tls-private-key-fileKube-ApiServer HTTPS 使用的私钥文件。 --client-ca-file用于验证客户端证书(如 kubelet/client)的 CA 证书。 --service-account-key-file用于签名 Service Account Token 的私钥文件。 --etcd-cafile连接 Etcd 时验证其证书的 CA 证书路径。 --etcd-certfileKube-ApiServer 连接 Etcd 时使用的客户端证书。 --etcd-keyfileKube-ApiServer 连接 Etcd 时使用的客户端私钥。 --audit-log-maxage审计日志保留的最大天数。 --audit-log-maxbackup审计日志保留的备份数量。 --audit-log-maxsize单个审计日志文件的最大大小(MB)。 --audit-log-path审计日志输出路径。
TLS Bootstraping 机制介绍
在 Master 节点中,API Server 启用 TLS 认证后,Node 节点上的 kubelet 和 kube-proxy 必须使用由 CA 签发的有效客户端证书才能与 kube-apiserver 通信。当 Node 节点数量较多时,手动颁发这些客户端证书的工作量大,且会增加集群扩展的复杂度。为简化这一流程,Kubernetes 引入了 TLS Bootstrapping 机制:kubelet 会以低权限用户身份向 API Server 自动申请客户端证书,并由 API Server 动态签发,从而实现证书的自动化管理。TLS Bootstrapping 机制的工作流程如图所示 。
创建在 API Server 配置文件(kube-apiserver.conf)中通过 --token-auth-file 指定的 Token 文件,用于配置 TLS Bootstrapping 机制
1 2 3 4 # cat > /opt/kubernetes/cfg/token.csv << EOFc47ffb939f5ca36231d9e3121a252940,kubelet-bootstrap,10001,"system:node-bootstrapper" EOF
Token 文件的数据格式是 Token,用户名,UID,用户组,其中 UID 仅作标识使用,可以是任意唯一值;用户组 system:node-bootstrapper 是 Kubelet TLS Bootstrap 必需的组;Token 可以使用以下命令自行生成并替换掉
1 2 # head -c 16 /dev/urandom | od -An -t x | tr -d ' '
配置 Systemd 管理 API Server 服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # cat > /usr/lib/systemd/system/kube-apiserver.service << EOF[Unit] Description=Kubernetes API Server Documentation=https://github.com/kubernetes/kubernetes After=network-online.target [Service] EnvironmentFile=/opt/kubernetes/cfg/kube-apiserver.conf ExecStart=/opt/kubernetes/bin/kube-apiserver \$KUBE_APISERVER_OPTS Restart=on-failure [Install] WantedBy=multi-user.target EOF
启动并设置开机自启动 API Server 服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 # systemctl daemon-reload# systemctl enable kube-apiserver# systemctl start kube-apiserver# systemctl status kube-apiserver# journalctl -u kube-apiserver.service
授权 kubelet-bootstrap 用户允许请求(申请)证书,请注意,无论有多少个 Master 节点,都只需执行一次授权,重复执行授权会提示失败
1 # kubectl create clusterrolebinding kubelet-bootstrap --clusterrole =system:node-bootstrapper --user =kubelet-bootstrap
部署 HaProxy 特别注意
在两台 Master 节点上,分别安装 HaProxy,两台 Master 节点上的 HaProxy 配置文件都是一样的 。生产级 Kubernetes 高可用集群通常采用 HAProxy + Keepalived 实现 API Server 的负载均衡(高性能四层负载均衡),而不是使用 Nginx + Keepalived 替代(可选)。同时,会结合使用 Nginx Ingress 处理应用流量负载,两者分层清晰、运维简单 。1. 安装概述
HAProxy 是一款广泛使用的反向代理与负载均衡软件,可以采用其四层(TCP)模式对 Kubernetes API Server 进行负载均衡,从而实现多 Master 节点的高可用访问。通过 HAProxy 构建 API Server 的高可用架构,其整体设计如下图所示:
安装说明 路径 HaProxy 的安装路径 /usr/local/sbin/haproxyHaProxy 的配置文件 /etc/haproxy/haproxy.cfgHaProxy 的 PID 文件 /var/lib/haproxy/haproxy.pidHaProxy 的 Socket 文件 /var/lib/haproxy/statsHaProxy 的 Systemd 服务配置文件 /usr/lib/systemd/system/haproxy.service
2. 安装步骤
安装依赖软件包
1 2 # yum install -y gcc make pcre-devel openssl openssl-devel systemd-devel wget
下载 HaProxy 源码包(官网下载地址 )
1 2 # wget https://www.haproxy.org/download/2.8/src/haproxy-2.8.5.tar.gz
下载 HaProxy 源码包
1 2 # tar -xvf haproxy-2.8.5.tar.gz
编译源码与安装
1 2 3 4 5 6 7 8 9 10 11 12 13 # cd haproxy-2.8.5# make TARGET=linux-glibc \ USE_OPENSSL=1 \ USE_PCRE=1 \ USE_SYSTEMD=1 \ USE_ZLIB=1 \ -j $(nproc)# make install
3. 创建用户
创建 HaProxy 用户与用户组
1 2 3 4 5 # groupadd --system haproxy# useradd --system --no -create-home --shell /sbin/nologin --gid haproxy haproxy
4. 配置服务
配置 Systemd 管理 HaProxy 服务
1 2 # vim /usr/lib/systemd/system/haproxy.service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [Unit] Description=HAProxy Load Balancer After=network.target [Service] User=haproxy Group=haproxy LimitNOFILE=1048576 Environment="CONFIG=/etc/haproxy/haproxy.cfg" "PIDFILE=/var/lib/haproxy/haproxy.pid" ExecStartPre=/usr/local/sbin/haproxy -f $CONFIG -c -q ExecStart=/usr/local/sbin/haproxy -Ws -f $CONFIG -p $PIDFILE ExecReload=/bin/kill -USR2 $MAINPID KillMode=mixed Type=notify [Install] WantedBy=multi-user.target
创建 HaProxy 的运行目录
1 2 3 4 5 6 7 8 # mkdir -p /var/lib/haproxy# chown -R haproxy:haproxy /var/lib/haproxy# chmod 750 /var/lib/haproxy
创建 HaProxy 的配置目录
1 2 # mkdir -p /etc/haproxy
创建 HaProxy 的配置文件
1 2 # vim /etc/haproxy/haproxy.cfg
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 #--------------------------------------------------------------------- # Global settings #--------------------------------------------------------------------- global # to have these messages end up in /var/log/haproxy.log you will # need to: # 1) configure syslog to accept network log events. This is done # by adding the '-r' option to the SYSLOGD_OPTIONS in # /etc/sysconfig/syslog # 2) configure local2 events to go to the /var/log/haproxy.log # file. A line like the following can be added to # /etc/sysconfig/syslog # # local2.* /var/log/haproxy.log # log 127.0.0.1 local2 # chroot /var/lib/haproxy # pidfile /var/run/haproxy.pid # user haproxy # group haproxy maxconn 4000 nbthread 4 # nbproc 1 # daemon # turn on stats unix socket stats socket /var/lib/haproxy/stats #--------------------------------------------------------------------- # common defaults that all the 'listen' and 'backend' sections will # use if not designated in their block #--------------------------------------------------------------------- defaults mode http log global option httplog option dontlognull option http-server-close option redispatch retries 3 timeout http-request 10s timeout queue 1m timeout connect 10s timeout client 1m timeout server 1m timeout http-keep-alive 10s timeout check 10s maxconn 3000 #--------------------------------------------------------------------- # kubernetes apiserver frontend which proxys to the backends #--------------------------------------------------------------------- frontend kubernetes-apiserver mode tcp bind *:16443 option tcplog default_backend kubernetes-apiserver #--------------------------------------------------------------------- # round robin balancing between the various backends #--------------------------------------------------------------------- backend kubernetes-apiserver mode tcp balance roundrobin server master01.k8s.io 192.168.2.191:6443 check server master02.k8s.io 192.168.2.148:6443 check #--------------------------------------------------------------------- # collection haproxy statistics message #--------------------------------------------------------------------- listen stats bind *:1080 stats auth admin:admin stats refresh 5s stats realm HAProxy\ Statistics stats uri /admin?stats
HaProxy 配置文件的使用说明
配置项 类型 默认值 说明 maxconn全局 / 前端 / 后端 2000最大连接数,控制 HAProxy 可以同时处理的并发连接数。 nbthread全局 1指定每个进程使用的线程数。多线程模式可提高单进程并发性能。 nbproc全局 1指定 HAProxy 启动的进程数。多进程模式用于多核优化,但 Systemd 下不推荐与 daemon 配合使用。 daemon全局 禁用 是否后台运行。Systemd 管理 HaProxy 时通常禁用,HAProxy 由 Systemd 控制前后台。 modedefaults/frontend/backend http连接模式,可选 http 或 tcp。可以使用 tcp 来实现 Kubernetes API Server 代理。 balancebackend roundrobin负载均衡算法,可选 roundrobin、leastconn、source 等。 stats authlisten 设置统计页面认证用户名和密码,例如 admin:admin。 stats refreshlisten 10s统计页面刷新时间,单位秒。 stats realmlisten HTTP Basic Auth 的 realm 名称,显示在认证弹窗中。 stats urilisten /HaProxy 统计页面访问 URI,例如 /admin?stats。
Haproxy 的配置建议
maxconn + nbthread 可以提升单进程并发性能。mode tcp 是代理 Kubernetes API Server 的标准配置,因为 API Server 是 HTTPS / TCP 流量。当使用 Systemd 管理 HaProxy 时,建议配置 nbproc = 1 + nbthread > 1 + 不启用 daemon ,这是最稳定和高效的配置方式。 5. 启动服务
设置开机自启动 HaProxy 服务
1 2 3 4 5 # systemctl daemon-reload# systemctl enable haproxy
立刻启动 HaProxy 服务
1 2 3 4 5 # systemctl start haproxy# systemctl status haproxy
当 HaProxy 的配置文件发生变更后,可以执行热加载(也叫平滑重载,不会中断现有连接),让配置文件生效
1 2 # systemctl reload haproxy
6. 验证服务
通过 HaProxy 的 16443 反向代理端口(可自定义),验证 HaProxy 的反向代理功能是否可以工作,正常情况下 API Server 的健康检测接口会返回字符串 ok
1 2 # curl --cacert /opt/kubernetes/ssl/ca.pem https://127.0.0.1:16443/healthz
浏览器通过 http://<master-ip>:1080/admin?stats 访问 HaProxy 的统计页面,默认的登录用户名和密码是 admin / admin;如果可以正常访问(如下图所示),则说明 HaProxy 可以正常运行
部署 Keepalived 特别注意
在两台 Master 节点上,分别安装 Keepalived,两台 Master 节点上的 Keepalived 配置文件是不一样的 。1. 安装概述
Keepalived 是一个高可用软件,基于 VIP 绑定实现服务器双机主备(也叫双机热备),在上述拓扑中,Keepalived 主要根据 HaProxy 的运行状态判断是否需要故障转移(VIP 切换)。 例如,当 HaProxy 主节点挂掉,VIP 会自动绑定在 HaProxy 备节点,从而保证 VIP 一直可用,实现 HaProxy 高可用。 安装说明 路径 Keepalived 的安装路径 /usr/local/keepalivedKeepalived 的配置文件 /usr/local/keepalived/etc/keepalived/keepalived.confKeepalived 的 Systemd 服务配置文件(自动生成) /usr/lib/systemd/system/keepalived.serviceKeepalived 检测 HaProxy 是否可用的 Shell 脚本 /usr/local/keepalived/etc/keepalived/scripts/haproxy_check.sh
2. 安装步骤
安装依赖软件包
1 2 # yum install -y gcc gcc-c++ make openssl-devel libnl libnl-devel net-snmp-devel popt-devel
下载 Keepalived 源码包(官网下载地址 )
1 2 # wget https://www.keepalived.org/software/keepalived-2.2.8.tar.gz
解压 Keepalived 源码包
1 2 # tar -xvf keepalived-2.2.8.tar.gz
编译源码与安装
1 2 3 4 5 6 7 8 9 10 11 # cd keepalived-2.2.8# make -j $(nproc)# make install
3. 创建脚本
创建用于 Keepalived 检测 HaProxy 服务是否可用的 Shell 脚本(比如 haproxy_check.sh)
1 2 3 4 5 # mkdir -p /usr/local /keepalived/etc/keepalived/scripts/# vim /usr/local /keepalived/etc/keepalived/scripts/haproxy_check.sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 HAPROXY_COUNT=$(pgrep -x haproxy | wc -l ) if [ "$HAPROXY_COUNT " -eq 0 ]; then echo "HAProxy not running" exit 1 else echo "HAProxy running" exit 0 fi
授予 HaProxy 检测脚本可执行权限
1 2 # chmod +x /usr/local /keepalived/etc/keepalived/scripts/haproxy_check.sh
4. 配置服务
Keepalived 配置文件的使用说明
配置项 示例值 说明 router_idk8s本节点唯一标识,用于日志区分(不同节点可不同)。 script"killall -0 haproxy"检测 HAProxy 服务是否可用,可以是 Shell 命令,还可以是 Shell 脚本的绝对路径。Keepalived 会根据脚本的返回状态码(0 表示正常,非 0 表示不正常)判断是否调节点整权重或触发 VIP 切换。 interval2检测脚本执行间隔(秒),指定 Keepalived 多久执行一次健康检查脚本。 weight-100节点权重变化值,检测脚本失败时降低优先级或成功时增加优先级。负值表示降低权重,正值表示增加权重。 fall3连续失败次数阈值,当检测脚本连续失败次数达到 fall 时,节点会认为服务不可用,并扣减权重或触发 VIP 切换。 rise2连续成功次数阈值,当检测脚本连续成功次数达到 rise 时,节点会认为服务恢复,并恢复权重初始值。 stateMASTER节点初始角色(MASTER 或 BACKUP),最终由优先级决定。 interfaceenp0s3VRRP 使用的网络接口名称,可通过 ifconfig 命令得知。 virtual_router_id51VRRP 组 ID,多个节点必须一致。 priority200节点优先级(权重),数值越大优先级越高。 advert_int1VRRP 心跳间隔(秒),默认 1 秒。 auth_typePASSVRRP 认证方式(PASS 或 AH)。 auth_passceb1b3ec013d66163d6abVRRP 认证密码,多个节点必须一致。 virtual_ipaddress192.168.2.100Keepalived 提供的 VIP(虚拟 IP),由 MASTER 持有。 track_scriptcheck_haproxy指定用于健康检查的脚本,检测失败时会调节点整权重或触发 VIP 切换。
Keepalived 什么时候触发 VIP 切换
Keepalived 的 VIP 切换依赖节点权重变化,只有当 MASTER 节点的实时权重下降到低于 BACKUP 节点时,VIP 才会切换到权重更高的节点。 在 Keepalived 配置文件中,vrrp_script 可以通过 weight 动态调整节点权重:正值会增加节点权重,负值会降低节点权重。 当 Keepalived 脚本检测失败时,节点权重会根据 weight 设置下降,如果下降后 MASTER 节点的权重低于 BACKUP 节点,VIP 就会切换。 当 Keepalived 脚本检测成功时,且连续检测成功的次数达到 rise 次后,节点权重会恢复为初始值,原 MASTER 节点可能会重新成为 MASTER。 Keepalived 的 weight 必须设置足够大,否则即使 Keepalived 脚本检测失败,MASTER 节点的权重仍可能比 BACKUP 节点高,从而导致不会触发 VIP 切换。 Keepalived 调整节点权重的细节
Keepalived 不会因为检测脚本持续失败而反复扣减节点权重,只有当检测脚本的检测结果从 "成功" 变为 "失败" 时,才会触发一次节点权重扣减。 也就是说,第一次检测被判定为失败,会执行一次 weight 扣减;当后续检测脚本继续失败时,由于节点已经处于失败状态,不会再重复扣减节点权重。 当检测脚本恢复成功后,Keepalived 会依据 rise 参数进行节点权重恢复。当检测脚本连续成功达到 rise 次后,节点权重会恢复到初始值。 简而言之,Keepalived 的节点权重扣减是一次性的,直到检测脚本恢复成功,才再次生效 。同样的,在节点处于健康状态期间,检测脚本即便持续成功,也不会重复增加节点权重。在第一个 Master 节点 (比如 k8s-master)中,创建 Keepalived 的配置文件,请自行更改 interface(网卡接口名称)与 virtual_ipaddress(虚拟 IP)配置项
1 2 # vim /usr/local /keepalived/etc/keepalived/keepalived.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ! Configuration File for keepalived global_defs { router_id k8s } vrrp_script check_haproxy { script "/usr/local/keepalived/etc/keepalived/scripts/haproxy_check.sh" interval 2 weight -100 fall 3 rise 2 } vrrp_instance VI_1 { state MASTER interface enp0s3 virtual_router_id 51 priority 250 advert_int 1 authentication { auth_type PASS auth_pass ceb1b3ec013d66163d6ab } virtual_ipaddress { 192.168.2.100 } track_script { check_haproxy } }
在第二个 Master 节点 (比如 k8s-master2)中,创建 Keepalived 的配置文件,请自行更改 interface(网卡接口名称)与 virtual_ipaddress(虚拟 IP)配置项
1 2 # vim /usr/local /keepalived/etc/keepalived/keepalived.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ! Configuration File for keepalived global_defs { router_id k8s } vrrp_script check_haproxy { script "/usr/local/keepalived/etc/keepalived/scripts/haproxy_check.sh" interval 2 weight -100 fall 3 rise 2 } vrrp_instance VI_1 { state BACKUP interface enp0s3 virtual_router_id 51 priority 200 advert_int 1 authentication { auth_type PASS auth_pass ceb1b3ec013d66163d6ab } virtual_ipaddress { 192.168.2.100 } track_script { check_haproxy } }
5. 启动服务
设置开机自启动 Keepalived 服务
1 2 3 4 5 # systemctl daemon-reload# systemctl enable keepalived
立刻启动 Keepalived 服务
1 2 3 4 5 # systemctl start keepalived# systemctl status keepalived
当 Keepalived 的配置文件发生变更后,可以重启服务让其生效
1 2 # systemctl restart keepalived
6. 验证服务
通过 Keepalived 的 VIP(比如 192.168.2.100) + HaProxy 的 16443 反向代理端口(可自定义),验证 Keepalived 的 VIP 和 HaProxy 的反向代理功能是否可以正常工作,正常情况下 API Server 的健康检测接口会返回字符串 ok,请自行更改 VIP 的地址
1 2 # curl --cacert /opt/kubernetes/ssl/ca.pem https://192.168.2.100:16443/healthz
7. 验证高可用性
验证目标
本节旨在验证 HaProxy + Keepalived 是否能够实现双机主备(即双机热备)高可用机制。具体而言,当第一个 Master 节点上的 HaProxy 发生宕机时,Keepalived 能够将虚拟 IP(VIP)自动切换到第二个 Master 节点上。当第一个 Master 节点上的 HaProxy 恢复运行时,虚拟 IP(VIP)会自动切换回第一个 Master 节点上。这样,在 Kubernetes 集群内部仍可通过 VIP 访问 API Server,从而保障 Kubernetes 集群整体服务的高可用性。
(1) 在第一个 Master 节点(比如 k8s-master)中,查看指定网卡(比如 enp0s3)的详细 IP 地址与状态信息,请自行更改网卡接口名称 输出示例如下,可以看到 VIP(虚拟 IP)地址 192.168.2.100 绑定了网卡接口 enp0s3
1 2 3 4 5 6 7 8 9 10 11 12 2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 08:00:27:de:6f:6b brd ff:ff:ff:ff:ff:ff inet 192.168.2.191/24 brd 192.168.2.255 scope global noprefixroute dynamic enp0s3 valid_lft 42617sec preferred_lft 42617sec inet 192.168.2.100/32 scope global enp0s3 valid_lft forever preferred_lft forever inet6 fe80::8b9e:77d3:4d79:eab9/64 scope link tentative noprefixroute dadfailed valid_lft forever preferred_lft forever inet6 fe80::6c1a:25c4:bdbd:dad3/64 scope link tentative noprefixroute dadfailed valid_lft forever preferred_lft forever inet6 fe80::8dee:f54a:fcd2:369b/64 scope link tentative noprefixroute dadfailed valid_lft forever preferred_lft forever
(2) 在第一个 Master 节点(比如 k8s-master)中,关闭 HaProxy 服务,并查看 Keepalived 的运行状态 1 2 # systemctl stop haproxy
1 2 # systemctl status keepalived
输出示例如下,可以看到 Keepalived 扣减了当前节点的权重(计算公式:250 - 100 = 150),同时 VIP 也被移除了
1 2 3 4 5 6 7 8 11月 08 14:14:40 k8s-master Keepalived_vrrp[30483]: Sending gratuitous ARP on enp0s3 for 192.168.2.100 11月 08 14:14:40 k8s-master Keepalived_vrrp[30483]: Sending gratuitous ARP on enp0s3 for 192.168.2.100 11月 08 14:18:02 k8s-master Keepalived_vrrp[30483]: Script `check_haproxy` now returning 1 11月 08 14:18:06 k8s-master Keepalived_vrrp[30483]: VRRP_Script(check_haproxy) failed (exited with status 1) 11月 08 14:18:06 k8s-master Keepalived_vrrp[30483]: (VI_1) Changing effective priority from 250 to 150 11月 08 14:18:09 k8s-master Keepalived_vrrp[30483]: (VI_1) Master received advert from 192.168.2.148 with higher priority 200, ours 150 11月 08 14:18:09 k8s-master Keepalived_vrrp[30483]: (VI_1) Entering BACKUP STATE 11月 08 14:18:09 k8s-master Keepalived_vrrp[30483]: (VI_1) removing VIPs.
(3) 在第二个 Master 节点(比如 k8s-master2)中,查看指定网卡(比如 enp0s3)的详细 IP 地址与状态信息,请自行更改网卡接口名称 输出示例如下,可以看到 VIP(虚拟 IP)地址 192.168.2.100 绑定了网卡接口 enp0s3,也就是 VIP 成功切换到 BACKUP 节点
1 2 3 4 5 6 7 8 9 10 11 12 2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 08:00:27:78:05:48 brd ff:ff:ff:ff:ff:ff inet 192.168.2.148/24 brd 192.168.2.255 scope global noprefixroute dynamic enp0s3 valid_lft 41406sec preferred_lft 41406sec inet 192.168.2.100/32 scope global enp0s3 valid_lft forever preferred_lft forever inet6 fe80::8b9e:77d3:4d79:eab9/64 scope link tentative noprefixroute dadfailed valid_lft forever preferred_lft forever inet6 fe80::6c1a:25c4:bdbd:dad3/64 scope link tentative noprefixroute dadfailed valid_lft forever preferred_lft forever inet6 fe80::8dee:f54a:fcd2:369b/64 scope link tentative noprefixroute dadfailed valid_lft forever preferred_lft forever
(4) 在第一个 Master 节点(比如 k8s-master)中,重新启动 HaProxy 服务,并查看指定网卡(比如 enp0s3)的详细 IP 地址与状态信息,请自行更改网卡接口名称 1 2 # systemctl start haproxy
输出示例如下,可以看到 VIP(虚拟 IP)地址 192.168.2.100 又绑定了网卡接口 enp0s3,也就是 VIP 再次切换回 MASTER 节点
1 2 3 4 5 6 7 8 9 10 11 12 2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 08:00:27:de:6f:6b brd ff:ff:ff:ff:ff:ff inet 192.168.2.191/24 brd 192.168.2.255 scope global noprefixroute dynamic enp0s3 valid_lft 42617sec preferred_lft 42617sec inet 192.168.2.100/32 scope global enp0s3 valid_lft forever preferred_lft forever inet6 fe80::8b9e:77d3:4d79:eab9/64 scope link tentative noprefixroute dadfailed valid_lft forever preferred_lft forever inet6 fe80::6c1a:25c4:bdbd:dad3/64 scope link tentative noprefixroute dadfailed valid_lft forever preferred_lft forever inet6 fe80::8dee:f54a:fcd2:369b/64 scope link tentative noprefixroute dadfailed valid_lft forever preferred_lft forever
此时,若再次在第一个 Master 节点(比如 k8s-master)中查看 Keepalived 的运行状态,可以看到 Keepalived 将当前节点的权重恢复为初始值,输出示例如下
1 2 3 4 5 11月 08 14:23:49 k8s-master Keepalived_vrrp[30483]: VRRP_Script(check_haproxy) succeeded 11月 08 14:23:49 k8s-master Keepalived_vrrp[30483]: (VI_1) Changing effective priority from 150 to 250 11月 08 14:23:52 k8s-master Keepalived_vrrp[30483]: Sending gratuitous ARP on enp0s3 for 192.168.2.100 11月 08 14:23:52 k8s-master Keepalived_vrrp[30483]: Sending gratuitous ARP on enp0s3 for 192.168.2.100 11月 08 14:23:52 k8s-master Keepalived_vrrp[30483]: Sending gratuitous ARP on enp0s3 for 192.168.2.100
部署 Controller Manager 创建 Controller Manager 的配置文件,使用转义符 \\ 是为了使 EOF 保留换行符。一般情况下,以下配置内容可以直接拷贝使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 # cat > /opt/kubernetes/cfg/kube-controller-manager.conf << EOFKUBE_CONTROLLER_MANAGER_OPTS="--logtostderr=false \\ --v=2 \\ --log-dir=/opt/kubernetes/logs \\ --leader-elect=true \\ --bind-address=127.0.0.1 \\ --allocate-node-cidrs=true \\ --cluster-cidr=10.244.0.0/16 \\ --service-cluster-ip-range=10.0.0.0/24 \\ --secure-port=10257 \\ --kubeconfig=/opt/kubernetes/cfg/kube-controller-manager.kubeconfig \\ --authentication-kubeconfig=/opt/kubernetes/cfg/kube-controller-manager.kubeconfig \\ --authorization-kubeconfig=/opt/kubernetes/cfg/kube-controller-manager.kubeconfig \\ --client-ca-file=/opt/kubernetes/ssl/ca.pem \\ --use-service-account-credentials=true \\ --service-account-private-key-file=/opt/kubernetes/ssl/ca-key.pem \\ --cluster-signing-cert-file=/opt/kubernetes/ssl/ca.pem \\ --cluster-signing-key-file=/opt/kubernetes/ssl/ca-key.pem \\ --requestheader-client-ca-file=/opt/kubernetes/ssl/ca.pem \\ --tls-cert-file=/opt/kubernetes/ssl/kube-controller-manager.pem \\ --tls-private-key-file=/opt/kubernetes/ssl/kube-controller-manager-key.pem \\ --root-ca-file=/opt/kubernetes/ssl/ca.pem \\ --controllers=*,bootstrapsigner,tokencleaner \\ --deployment-controller-sync-period=10s \\ --enable-garbage-collector=true \\ --terminated-pod-gc-threshold=50 \\ --node-monitor-period=5s \\ --node-monitor-grace-period=20s \\ --pod-eviction-timeout=2m0s \\ --cluster-signing-duration=87600h0m0s" EOF
配置参数说明
参数 说明 --logtostderr是否将日志输出到标准错误,false 时输出到文件 --v日志级别(Verbose),数字越大日志越详细 --log-dir日志文件存放目录 --leader-elect启用 Leader 选举以保证高可用 --bind-addressController Manager 监听的本地 IP 地址 --allocate-node-cidrs自动为 Node 分配 Pod CIDR --cluster-cidrPod 网络 CIDR,用于分配 Pod IP --service-cluster-ip-rangeService 虚拟 IP 地址段 --secure-portController Manager HTTPS 端口 --kubeconfig用于访问 API Server 的 kubeconfig 文件 --authentication-kubeconfigController Manager 身份认证 kubeconfig --authorization-kubeconfigController Manager 权限认证 kubeconfig --client-ca-file验证客户端证书的 CA 文件 --use-service-account-credentials使用 ServiceAccount Token 调用 API Server --service-account-private-key-file用于生成 ServiceAccount Token 的私钥 --cluster-signing-cert-file集群证书签名 CA 文件 --cluster-signing-key-file集群证书签名私钥 --requestheader-client-ca-file用于验证 API Server 聚合层(Aggregation Layer)客户端请求头证书的 CA 文件 --tls-cert-fileController Manager TLS 证书 --tls-private-key-fileController Manager TLS 私钥 --root-ca-file根 CA 文件,用于客户端和服务端通信验证 --controllers启用的 Controller 类型列表 --deployment-controller-sync-periodDeployment Controller 同步周期 --enable-garbage-collector启用垃圾回收机制,删除无用资源 --terminated-pod-gc-threshold触发终止 Pod 垃圾回收的阈值 --node-monitor-period节点状态检测周期 --node-monitor-grace-period节点不可达后等待时间 --pod-eviction-timeoutPod 被逐出前等待的超时时间 --cluster-signing-duration集群签发证书的有效期(示例:87600h0m0s 表示 10 年)
生成 kube-controller-manager.kubeconfig 配置文件,请自行修改这里环境变量中的 API Server 地址,且指定为 Keepalived 的虚拟 IP 地址(比如 192.168.2.100) + HaProxy 反向代理的端口(比如 16443),切勿直接拷贝环境变量的值
1 2 KUBE_APISERVER="https://192.168.2.100:16443"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 # kubectl config set -cluster kubernetes \ --certificate -authority=/opt/kubernetes/ssl/ca.pem \ --embed -certs=true \ --server =${KUBE_APISERVER} \ --kubeconfig =kube-controller-manager.kubeconfig# kubectl config set -credentials system:kube-controller-manager \ --client -certificate=/opt/kubernetes/ssl/kube-controller-manager.pem \ --client -key=/opt/kubernetes/ssl/kube-controller-manager-key.pem \ --embed -certs=true \ --kubeconfig =kube-controller-manager.kubeconfig# kubectl config set -context system:kube-controller-manager@kubernetes \ --cluster =kubernetes \ --user =system:kube-controller-manager \ --kubeconfig =kube-controller-manager.kubeconfig# kubectl config use-context system:kube-controller-manager@kubernetes \ --kubeconfig =kube-controller-manager.kubeconfig
1 2 # mv kube-controller-manager.kubeconfig /opt/kubernetes/cfg
配置 Systemd 管理 Controller Manager 服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # cat > /usr/lib/systemd/system/kube-controller-manager.service << EOF[Unit] Description=Kubernetes Controller Manager Documentation=https://github.com/kubernetes/kubernetes After=network-online.target [Service] EnvironmentFile=/opt/kubernetes/cfg/kube-controller-manager.conf ExecStart=/opt/kubernetes/bin/kube-controller-manager \$KUBE_CONTROLLER_MANAGER_OPTS Restart=on-failure [Install] WantedBy=multi-user.target EOF
启动并设置开机自启动 Controller Manager 服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 # systemctl daemon-reload# systemctl enable kube-controller-manager# systemctl start kube-controller-manager# systemctl status kube-controller-manager# journalctl -u kube-controller-manager.service
当 Controller Manager 启动后,在查看运行状态时,可能会看到以下警告 / 错误信息。如果 K8s 集群是裸机或本地环境(没有 AWS / Azure 等云提供商),这两个报错可以忽略,它们不会影响 K8s 集群的核心功能
1 2 11月 11 18:58:54 k8s-master1 kube-controller-manager[15484]: E1111 18:58:54.496737 15484 core.go:90] Failed to start service controller: WARNING: no cloud provider provided, services of type LoadBalancer will fail 11月 11 18:59:07 k8s-master1 kube-controller-manager[15484]: E1111 18:59:07.629134 15484 core.go:230] failed to start cloud node lifecycle controller: no cloud provider provided
部署 Scheduler 创建 Scheduler 配置文件,使用转义符 \\ 是为了使 EOF 保留换行符。一般情况下,以下配置内容可以直接拷贝使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # cat > /opt/kubernetes/cfg/kube-scheduler.conf << EOFKUBE_SCHEDULER_OPTS="--logtostderr=false \\ --v=2 \\ --log-dir=/opt/kubernetes/logs \\ --leader-elect=true \\ --bind-address=127.0.0.1 \\ --secure-port=10259 \\ --kubeconfig=/opt/kubernetes/cfg/kube-scheduler.kubeconfig \\ --authentication-kubeconfig=/opt/kubernetes/cfg/kube-scheduler.kubeconfig \\ --authorization-kubeconfig=/opt/kubernetes/cfg/kube-scheduler.kubeconfig \\ --client-ca-file=/opt/kubernetes/ssl/ca.pem \\ --requestheader-client-ca-file=/opt/kubernetes/ssl/ca.pem \\ --tls-cert-file=/opt/kubernetes/ssl/kube-scheduler.pem \\ --tls-private-key-file=/opt/kubernetes/ssl/kube-scheduler-key.pem" EOF
配置参数说明
参数 说明 --logtostderr是否将日志输出到标准错误(false 表示输出到文件,需要配合 --log-dir 使用)。 --v日志级别,数值越大日志越详细。一般设置为 2。 --log-dir日志文件输出目录(当 logtostderr=false 时有效)。 --leader-elect启用 Leader 选举,用于多个 Scheduler 实例保证高可用。 --bind-addressScheduler 监听地址,127.0.0.1 表示仅允许本机访问。 --secure-portScheduler HTTPS 服务监听端口,默认是 10259。 --kubeconfigScheduler 访问 API Server 的凭证文件。 --authentication-kubeconfigAPI Server 调用 Scheduler 的认证配置。 --authorization-kubeconfigAPI Server 调用 Scheduler 的授权配置。 --client-ca-file用于验证 API Server 证书的 CA 文件。 --requestheader-client-ca-file用于验证 API Server 聚合层(Aggregation Layer)客户端请求头证书的 CA 文件。 --tls-cert-fileScheduler 作为 HTTPS 服务端的证书。 --tls-private-key-fileScheduler 服务证书对应的私钥。
生成 kube-scheduler.kubeconfig 配置文件,请自行修改这里环境变量中的 API Server 地址,且指定为 Keepalived 的虚拟 IP 地址(比如 192.168.2.100) + HaProxy 反向代理的端口(比如 16443),切勿直接拷贝环境变量的值
1 2 KUBE_APISERVER="https://192.168.2.100:16443"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 # kubectl config set -cluster kubernetes \ --certificate -authority=/opt/kubernetes/ssl/ca.pem \ --embed -certs=true \ --server =${KUBE_APISERVER} \ --kubeconfig =kube-scheduler.kubeconfig# kubectl config set -credentials system:kube-scheduler \ --client -certificate=/opt/kubernetes/ssl/kube-scheduler.pem \ --client -key=/opt/kubernetes/ssl/kube-scheduler-key.pem \ --embed -certs=true \ --kubeconfig =kube-scheduler.kubeconfig# kubectl config set -context system:kube-scheduler@kubernetes \ --cluster =kubernetes \ --user =system:kube-scheduler \ --kubeconfig =kube-scheduler.kubeconfig# kubectl config use-context system:kube-scheduler@kubernetes \ --kubeconfig =kube-scheduler.kubeconfig
1 2 # mv kube-scheduler.kubeconfig /opt/kubernetes/cfg
配置 Systemd 管理 Scheduler 服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # cat > /usr/lib/systemd/system/kube-scheduler.service << EOF[Unit] Description=Kubernetes Scheduler Documentation=https://github.com/kubernetes/kubernetes After=network-online.target [Service] EnvironmentFile=/opt/kubernetes/cfg/kube-scheduler.conf ExecStart=/opt/kubernetes/bin/kube-scheduler \$KUBE_SCHEDULER_OPTS Restart=on-failure [Install] WantedBy=multi-user.target EOF
启动并设置开机自启动 Scheduler 服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 # systemctl daemon-reload# systemctl enable kube-scheduler# systemctl start kube-scheduler# systemctl status kube-scheduler# journalctl -u kube-scheduler.service
当 API Server、Controller Manager、Scheduler 组件都成功启动后,可以通过 kubectl 工具查看当前 Kubernetes 集群组件(不包括 API Server)的运行状态
1 2 3 4 5 6 7 8 # kubectl get csNAME STATUS MESSAGE ERROR scheduler Healthy ok controller-manager Healthy ok etcd-0 Healthy {"health" :"true" } etcd-1 Healthy {"health" :"true" } etcd-2 Healthy {"health" :"true" }
提示
这里的 kubectl get cs 命令只会显示部分组件的运行状态(主要是 Etcd、Controller Manager、Scheduler),不包括 API Server;因为 API Server 是 Kubernetes 的核心服务,一般需要通过其它方式进行监控。
部署 Master 与 Node 节点 特别注意
在 Kubernetes 集群的所有节点(包括 Master 和 Node 节点)上分别执行以下操作,除了特别说明之外(比如,只在 Master 节点批准 Kubelet 的证书签名请求)。简而言之,Master 和 Node 节点都需要安装 Kubelet、Kube-Proxy、CNI 网络插件。 如果 Master 节点仅用于运行控制平面组件(如 API Server、Controller Manager 和 Scheduler),不承载用户 Pod 的调度和运行任务,则 Master 节点可以不部署 Kubelet 和 Kube-Proxy,但还是必须安装 CNI 网络插件。。 下载二进制文件 下载地址
Kubernetes 相关的二进制包可以从 GitHub 下载得到,这里下载的版本是 v1.19.10。 当打开 GitHub 链接后,会发现里面有很多二进制包,只需要下载一个 Server 二进制包就够了,里面包含了 Master 节点相关的二进制包。 由于上面单独部署 Maser 节点时,已经下载并解压过 Kubernetes 的二进制包文件,因此这里只需要在所有 Node 节点上下载二进制包文件即可
1 2 3 4 5 # wget https://dl.k8s.io/v1.19.10/kubernetes-server-linux-amd64.tar.gz# tar zxvf kubernetes-server-linux-amd64.tar.gz
在所有节点(包括 Master 和 Node 节点)上,分别执行以下命令拷贝对应的可执行文件
1 2 3 4 5 6 7 8 9 10 11 # cd kubernetes/server/bin# mkdir -p /opt/kubernetes/{bin,cfg,ssl,logs}# cp kubelet kube-proxy /opt/kubernetes/bin# cp -n kubectl /usr/bin/
部署 Kubelet 创建 Kubelet 的配置文件,使用转义符 \\ 是为了使 EOF 保留换行符,请自行更改 --hostname-override 的值为当前节点的主机名(比如 k8s-node1),切勿直接拷贝以下配置内容
1 2 3 4 5 6 7 8 9 10 11 12 13 # cat > /opt/kubernetes/cfg/kubelet.conf << EOFKUBELET_OPTS="--logtostderr=false \\ --v=2 \\ --log-dir=/opt/kubernetes/logs \\ --hostname-override=<当前节点的主机名> \\ --network-plugin=cni \\ --kubeconfig=/opt/kubernetes/cfg/kubelet.kubeconfig \\ --bootstrap-kubeconfig=/opt/kubernetes/cfg/bootstrap.kubeconfig \\ --config=/opt/kubernetes/cfg/kubelet-config.yml \\ --cert-dir=/opt/kubernetes/ssl \\ --pod-infra-container-image=lizhenliang/pause-amd64:3.0" EOF
配置参数说明
参数 说明 --logtostderr是否将日志输出到标准错误(false 表示写入日志目录) --v日志详细级别,值越大日志越详细(2 为常用调试级别) --log-dir日志存放目录 --hostname-override覆盖节点的主机名,在集群中显示为指定名称,通常填写当前节点的主机名 --network-plugin使用的网络插件类型(比如 CNI) --kubeconfigkubelet 连接 API Server 的 kubeconfig 配置文件路径 --bootstrap-kubeconfigkubelet 首次启动时用于申请证书的引导配置文件路径 --configkubelet 主配置文件路径 --cert-dir存放 kubelet TLS 证书的目录 --pod-infra-container-imagePod 基础容器(Pause 容器)镜像,用于提供网络命名空间
创建 Kubelet 的配置参数文件,一般情况下,以下配置内容可以直接拷贝使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 # cat > /opt/kubernetes/cfg/kubelet-config.yml << EOFkind: KubeletConfiguration apiVersion: kubelet.config.k8s.io/v1beta1 address: 0.0.0.0 port: 10250 readOnlyPort: 10255 cgroupDriver: cgroupfs clusterDNS: - 10.0.0.2 clusterDomain: cluster.local failSwapOn: false authentication: anonymous: enabled: false webhook: cacheTTL: 2m0s enabled: true x509: clientCAFile: /opt/kubernetes/ssl/ca.pem authorization: mode: Webhook webhook: cacheAuthorizedTTL: 5m0s cacheUnauthorizedTTL: 30s evictionHard: imagefs.available: 15% memory.available: 100Mi nodefs.available: 10% nodefs.inodesFree: 5% maxOpenFiles: 1000000 maxPods: 110 EOF
配置参数说明
参数 说明 备注 kind资源类型 固定为 KubeletConfiguration apiVersion配置 API 版本 当前常用版本为 kubelet.config.k8s.io/v1beta1 addressKubelet 监听的 IP 地址 0.0.0.0 表示监听所有网卡地址portKubelet 的主要服务端口 默认值为 10250,用于 API Server 与 Kubelet 通信(带认证) readOnlyPort只读端口 默认值为 10255(不建议对外开放),提供只读状态信息 cgroupDrivercgroup 驱动类型 与容器运行时(如 Docker、Containerd)保持一致,常见值:systemd 或 cgroupfs clusterDNS集群内 DNS 服务 IP 地址 通常设置为 Kube-DNS 或 CoreDNS 的 ClusterIP,如 10.0.0.2 clusterDomain集群内部 DNS 域名后缀 默认值为 cluster.local failSwapOn是否在系统启用 Swap 分区时拒绝启动 建议 false(否则 Swap 分区未关闭时 Kubelet 会报错) authentication.anonymous.enabled是否允许匿名访问 建议设为 false,禁止匿名访问,提高安全性 authentication.webhook.enabled是否启用 Webhook 认证 启用后可通过 API Server 的 Token 校验机制认证 authentication.webhook.cacheTTLWebhook 认证缓存时间 如 2m0s 表示缓存 2 分钟 authentication.x509.clientCAFile客户端证书 CA 文件路径 指向集群 CA 证书文件 authorization.mode授权模式 常用值为 Webhook,即由 API Server 进行鉴权 authorization.webhook.cacheAuthorizedTTL授权缓存时间(通过) 配置示例:5m0s authorization.webhook.cacheUnauthorizedTTL授权缓存时间(未通过) 配置示例:30s evictionHard.imagefs.available镜像文件系统最小可用空间阈值 低于该值会触发 Pod 驱逐,例如 15% evictionHard.memory.available节点可用内存阈值 低于 100Mi 时触发 Pod 驱逐 evictionHard.nodefs.available节点文件系统最小可用空间阈值 低于 10% 时触发 Pod 驱逐 evictionHard.nodefs.inodesFree节点可用 inode 阈值 低于 5% 时触发 Pod 驱逐 maxOpenFilesKubelet 允许打开的最大文件数 默认值较小,建议设置为 1000000 maxPods每个节点允许运行的最大 Pod 数 默认值为 110,可根据节点的硬件性能调整
生成 bootstrap.kubeconfig 配置文件,请自行修改这里环境变量中的 API Server 地址,且指定为 Keepalived 的虚拟 IP 地址(比如 192.168.2.100) + HaProxy 反向代理的端口(比如 16443),切勿直接拷贝环境变量的值
1 2 3 KUBE_APISERVER="https://192.168.2.100:16443" TOKEN="c47ffb939f5ca36231d9e3121a252940"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # kubectl config set -cluster kubernetes \--certificate -authority=/opt/kubernetes/ssl/ca.pem \--embed -certs=true \--server =${KUBE_APISERVER} \--kubeconfig =bootstrap.kubeconfig# kubectl config set -credentials "kubelet-bootstrap" \--token =${TOKEN} \--kubeconfig =bootstrap.kubeconfig# kubectl config set -context default \--cluster =kubernetes \--user ="kubelet-bootstrap" \--kubeconfig =bootstrap.kubeconfig# kubectl config use-context default --kubeconfig =bootstrap.kubeconfig
1 2 # mv bootstrap.kubeconfig /opt/kubernetes/cfg
提前手动拉取 Pause 镜像(Pod 的基础镜像,用于 Pod 网络命名空间),避免后面部署 CNI 网络插件时,Kubelet 出现自动拉取 Pause 镜像失败的问题(该镜像的名称和版本在 kubelet.conf 配置文件中通过 --pod-infra-container-image 指定)
1 2 # docker pull lizhenliang/pause-amd64:3.0
配置 Systemd 管理 Kubelet 服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # cat > /usr/lib/systemd/system/kubelet.service << EOF[Unit] Description=Kubernetes Kubelet Documentation=https://github.com/kubernetes/kubernetes After=network-online.target After=docker.service [Service] EnvironmentFile=/opt/kubernetes/cfg/kubelet.conf ExecStart=/opt/kubernetes/bin/kubelet \$KUBELET_OPTS Restart=on-failure LimitNOFILE=65536 [Install] WantedBy=multi-user.target EOF
启动并设置开机自启动 Kubelet 服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 # systemctl daemon-reload# systemctl enable kubelet# systemctl start kubelet# systemctl status kubelet# journalctl -u kubelet.service
在查看 Kubelet 服务的运行状态时,可能会看到以下错误信息。这是因为目前还没有安装 CNI 网络插件,因此该错误可以暂时忽略掉
1 2 3 11月 11 21:15:55 k8s-master1 kubelet[7509]: E1112 11:15:55.312188 7509 kubelet.go:2134] Container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized 11月 11 21:16:00 k8s-master1 kubelet[7509]: E1112 11:16:00.329434 7509 kubelet.go:2134] Container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized 11月 11 21:16:05 k8s-master1 kubelet[7509]: E1112 11:16:05.347068 7509 kubelet.go:2134] Container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized
特别注意
当 Kubelet 服务正常启动后,会自动向 API Server 发起 CSR(证书签名请求)。一旦该 CSR 请求被批准,Kubelet 就会获得客户端证书,并开始与 API Server 通信,从而将它所在的 Node 节点加入到 Kubernetes 集群中,所以无需使用 kubeadm join 命令手动将当前节点加入 Kubernetes 集群,也无需像 Kube-Proxy 一样使用自签 CA 签发证书。
在任意一个 Master 节点上执行以下命令 ,批准 Kubelet 证书签名请求并允许 Node 节点加入 Kubernetes 集群
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 # kubectl get csrNAME AGE SIGNERNAME REQUESTOR CONDITION node-csr-B91KCv7T_4A4RCfvfEBfFOP3V5zCYKK5NQkVLDmyly4 86s kubernetes.io/kube-apiserver-client-kubelet kubelet-bootstrap Pending node-csr-F4zdBAJxxKnDLsWJbYHVi_XKF8Uwh1MMarXNJiHAUy0 85s kubernetes.io/kube-apiserver-client-kubelet kubelet-bootstrap Pending node-csr-GYbsPLYySA0LiUkuhbpXlApj-JC-TDnA7SVRPHWU2mI 88s kubernetes.io/kube-apiserver-client-kubelet kubelet-bootstrap Pending node-csr-bLUS_w2or-Rq9c5JgCzaf5e6QYHND6PTEqQFTvDsY5g 96s kubernetes.io/kube-apiserver-client-kubelet kubelet-bootstrap Pending node-csr-ndBBynU94khVf6ADv4pdmtrlsxDOXAnyxu18e_VnhBU 87s kubernetes.io/kube-apiserver-client-kubelet kubelet-bootstrap Pending # kubectl certificate approve node-csr-B91KCv7T_4A4RCfvfEBfFOP3V5zCYKK5NQkVLDmyly4# kubectl certificate approve node-csr-F4zdBAJxxKnDLsWJbYHVi_XKF8Uwh1MMarXNJiHAUy0# kubectl certificate approve node-csr-GYbsPLYySA0LiUkuhbpXlApj-JC-TDnA7SVRPHWU2mI# kubectl certificate approve node-csr-bLUS_w2or-Rq9c5JgCzaf5e6QYHND6PTEqQFTvDsY5g# kubectl certificate approve node-csr-ndBBynU94khVf6ADv4pdmtrlsxDOXAnyxu18e_VnhBU# kubectl get nodeNAME STATUS ROLES AGE VERSION k8s-master1 NotReady <none> 10m v1.19.10 k8s-master2 NotReady <none> 18s v1.19.10 k8s-node1 NotReady <none> 29s v1.19.10 k8s-node2 NotReady <none> 22s v1.19.10 k8s-node3 NotReady <none> 25s v1.19.10
部署 Kube-Proxy 创建 Kube-Proxy 的配置文件,使用转义符 \\ 是为了使 EOF 保留换行符,以下配置内容可以直接拷贝使用
1 2 3 4 5 6 # cat > /opt/kubernetes/cfg/kube-proxy.conf << EOFKUBE_PROXY_OPTS="--logtostderr=false \\ --v=2 \\ --log-dir=/opt/kubernetes/logs \\ --config=/opt/kubernetes/cfg/kube-proxy-config.yml" EOF
配置参数说明
参数 说明 --logtostderr=false日志输出到文件而不是标准错误输出(stderr) --v=2设置日志详细级别为 2(数值越大日志越详细) --log-dir=/opt/kubernetes/logs指定日志文件存放目录 --config=/opt/kubernetes/cfg/kube-proxy-config.yml指定 kube-proxy 的配置参数文件路径
创建 Kube-Proxy 的配置参数文件,请自行更改 hostnameOverride 的值为当前节点的主机名(比如 k8s-node1),其中的 clusterCIDR 要与 kube-apiserver.conf 配置文件中的 --service-cluster-ip-range 一致,切勿直接拷贝以下配置内容
1 2 3 4 5 6 7 8 9 10 11 12 # cat > /opt/kubernetes/cfg/kube-proxy-config.yml << EOFkind: KubeProxyConfiguration apiVersion: kubeproxy.config.k8s.io/v1alpha1 bindAddress: 0.0.0.0 metricsBindAddress: 0.0.0.0:10249 clientConnection: kubeconfig: /opt/kubernetes/cfg/kube-proxy.kubeconfig hostnameOverride: <当前节点的主机名> clusterCIDR: 10.0.0.0/24 mode: "iptables" EOF
配置参数说明
参数 说明 备注 kind资源类型,固定为 KubeProxyConfiguration 必填 apiVersion配置文件的 API 版本,当前主流为 kubeproxy.config.k8s.io/v1alpha1 必填 bindAddressKube-Proxy 监听的地址,用于服务代理流量 一般使用 0.0.0.0 监听所有网卡 metricsBindAddress暴露 Prometheus 指标的监听地址 默认端口 10249,可用于监控 clientConnection.kubeconfigKube-Proxy 访问 API Server 的 kubeconfig 文件路径 必填,包含 system:kube-proxy 证书信息 hostnameOverride当前节点的主机名,注册到集群时的 Node 名称 建议与系统主机名一致 clusterCIDRPod 网段,需与 Controller Manager 的 --cluster-cidr 一致 必填,否则可能导致 Service 转发异常 modeKube-Proxy 的工作模式,可选值 iptables 或 ipvs 默认 iptables,兼容性最好;ipvs 性能更好,但要求节点的操作系统内核支持 ip_vs 模块
生成 kube-proxy.kubeconfig 文件,请自行修改这里环境变量中的 API Server 地址,且指定为 Keepalived 的虚拟 IP 地址(比如 192.168.2.100) + HaProxy 反向代理的端口(比如 16443),切勿直接拷贝环境变量的值
1 2 KUBE_APISERVER="https://192.168.2.100:16443"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # kubectl config set -cluster kubernetes \--certificate -authority=/opt/kubernetes/ssl/ca.pem \--embed -certs=true \--server =${KUBE_APISERVER} \--kubeconfig =kube-proxy.kubeconfig# kubectl config set -credentials kube-proxy \--client -certificate=/opt/kubernetes/ssl/kube-proxy.pem \--client -key=/opt/kubernetes/ssl/kube-proxy-key.pem \--embed -certs=true \--kubeconfig =kube-proxy.kubeconfig# kubectl config set -context default \--cluster =kubernetes \--user =kube-proxy \--kubeconfig =kube-proxy.kubeconfig# kubectl config use-context default --kubeconfig =kube-proxy.kubeconfig
1 2 # mv kube-proxy.kubeconfig /opt/kubernetes/cfg
配置 Systemd 管理 Kube-Proxy 服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # cat > /usr/lib/systemd/system/kube-proxy.service << EOF[Unit] Description=Kubernetes Proxy Documentation=https://github.com/kubernetes/kubernetes After=network-online.target After=docker.service [Service] EnvironmentFile=/opt/kubernetes/cfg/kube-proxy.conf ExecStart=/opt/kubernetes/bin/kube-proxy \$KUBE_PROXY_OPTS Restart=on-failure LimitNOFILE=65536 [Install] WantedBy=multi-user.target EOF
启动并设置开机自启动 Kube-Proxy 服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 # systemctl daemon-reload# systemctl enable kube-proxy# systemctl start kube-proxy# systemctl status kube-proxy# journalctl -u kube-proxy.service
部署 CNI 网络插件 所有节点的操作 特别注意
在 Kubernetes 集群的所有节点(包括 Master 和 Node 节点)上面,分别下载并解压 CNI 网络插件的二进制包 。
1 2 3 4 5 6 7 8 9 # wget https://github.com/containernetworking/plugins/releases/download/v0.8.6/cni-plugins-linux-amd64-v0.8.6.tgz# mkdir -p /opt/cni/bin# mkdir -p /etc/cni/net.d/# tar zxvf cni-plugins-linux-amd64-v0.8.6.tgz -C /opt/cni/bin
Master 节点的操作 特别注意
在任意一个 Master 节点上面部署 CNI 网络插件(Flannel),不需要在所有的 Master 节点和 Node 节点中重复执行。简而言之,以下操作仅需要在任意一个 Master 节点上执行 。
下载 CNI 网络插件(Flannel)的 YAML 配置文件
1 2 # wget -P /opt/kubernetes/cfg/ https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
在 Kubernetes 集群中部署 CNI 网络插件(Flannel)
1 2 # kubectl apply -f /opt/kubernetes/cfg/kube-flannel.yml
查看 CNI 网络插件的所有 Pod
1 2 # kubectl get pods -n kube-flannel
1 2 3 4 5 6 NAME READY STATUS RESTARTS AGE pod/kube-flannel-ds-29gvn 1/1 Running 0 5m14s pod/kube-flannel-ds-99gvw 1/1 Running 0 5m14s pod/kube-flannel-ds-ntsf5 1/1 Running 0 5m14s pod/kube-flannel-ds-xqrnd 1/1 Running 0 5m14s pod/kube-flannel-ds-xwr2h 1/1 Running 0 5m14s
查看 CNI 网络插件的所有 DaemonSet
1 2 # kubectl get ds -n kube-flannel
1 2 NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE kube-flannel-ds 5 5 5 5 5 <none> 6m59s
查看 Kubernetes 集群中所有节点的运行状态
1 2 3 4 5 6 NAME STATUS ROLES AGE VERSION k8s-master1 Ready <none> 5h28m v1.19.10 k8s-master2 Ready <none> 5h17m v1.19.10 k8s-node1 Ready <none> 5h17m v1.19.10 k8s-node2 Ready <none> 5h17m v1.19.10 k8s-node3 Ready <none> 5h17m v1.19.10
CNI 网络插件的安装细节
这个 kubectl apply 命令会创建一个 DaemonSet 类型的资源,而 DaemonSet 的特性是:会在 Kubernetes 集群中所有「可调度」的 Node 节点上运行一个 Pod 副本。换言之,在任意一个 Master 节点上,通过 kubectl apply 命令安装 CNI 网络插件后,默认会将 CNI 网络插件部署到整个 Kubernetes 集群的所有节点(包括 Master 和 Node),无需手动在每个节点上重复执行 kubectl apply 命令来安装 CNI 网络插件。
创建用于授权 API Server 访问 Kubelet 的配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 # cat > /opt/kubernetes/cfg/apiserver-to-kubelet-rbac.yaml << EOFapiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: annotations: rbac.authorization.kubernetes.io/autoupdate: "true" labels: kubernetes.io/bootstrapping: rbac-defaults name: system:kube-apiserver-to-kubelet rules: - apiGroups: - "" resources: - nodes/proxy - nodes/stats - nodes/log - nodes/spec - nodes/metrics - pods/log verbs: - "*" --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: system:kube-apiserver namespace: "" roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: system:kube-apiserver-to-kubelet subjects: - apiGroup: rbac.authorization.k8s.io kind: User name: kubernetes EOF
授权 API Server 访问 Kubelet
1 2 # kubectl apply -f /opt/kubernetes/cfg/apiserver-to-kubelet-rbac.yaml
为什么必须安装 CNI 网络插件
Kubernetes 本身不包含网络实现,但它要求集群中的所有 Pod 能够彼此通信(无论位于哪个节点),这是 Kubernetes 网络模型的基本要求。 通过 Kubeadm 搭建 Kubernetes 集群时,使用 kubeadm init 初始化 Master 节点后,默认只有控制平面功能,没有网络功能。 当没有安装 CNI 网络插件时,Pod 会卡在 ContainerCreating 状态,因为找不到网络(CNI)配置。 当安装 CNI 网络插件(如 Flannel)后,多个节点之间的 Pod 才能通信和正常运行。 测试集群整体功能 在任意一个 Master 节点中执行以下命令,查看集群中所有节点的运行状态,当节点的运行状态都变更为 Ready 时,则表示 Kubernetes 集群已经成功搭建起来了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # kubectl get nodesNAME STATUS ROLES AGE VERSION k8s-master1 Ready <none> 5h32m v1.19.10 k8s-master2 Ready <none> 5h21m v1.19.10 k8s-node1 Ready <none> 5h22m v1.19.10 k8s-node2 Ready <none> 5h22m v1.19.10 k8s-node3 Ready <none> 5h22m v1.19.10 # kubectl get csNAME STATUS MESSAGE ERROR controller-manager Healthy ok scheduler Healthy ok etcd-2 Healthy {"health" :"true" } etcd-0 Healthy {"health" :"true" } etcd-1 Healthy {"health" :"true" } # kubectl version --short Client Version: v1.19.10 Server Version: v1.19.10
特别注意
在使用二进制方式完成 Kubernetes 集群的搭建时,执行 kubectl get pods -n kube-system 命令可能无法看到任何 Pod 的运行状态。这是因为 Kubernetes 中的 Pod 是由 Kubelet 负责向 API Server 注册并创建的,仅仅启动 API Server、Controller Manager、Scheduler、Kubelet、Kube-Proxy 等二进制组件,并不会自动生成或运行这些系统组件对应的 Pod。系统核心组件如 CoreDNS、Metrics Server 等都需要用户手动部署相关的 YAML 资源文件,Kubelet 才会拉取镜像并创建对应的 Pod。因此,若未部署这些资源,kube-system 命名空间中将不会显示任何 Pod。
在任意一个 Master 节点中执行以下命令,目的是在 Kubernetes 集群里创建一个 Nginx 的 Deployment,验证 Kubernetes 集群是否正常运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # kubectl create deployment nginx --image =nginx# kubectl expose deployment nginx --port =80 --type =NodePort --target -port=80# kubectl get pods -o wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx-6799fc88d8-w8g9b 1/1 Running 0 64s 10.244.2.2 k8s-node3 <none> <none> # kubectl get svcNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 6h42m nginx NodePort 10.0.0.38 <none> 80:31603/TCP 64s
在 Kubernetes 集群外部,通过浏览器访问 http://192.168.2.191:31603,其中 IP 可以是任意集群节点的 IP 地址,端口由 kubectl get svc 命令可得知。若 Ngninx 容器在 Kubernetes 集群中创建并启动成功,则浏览器可以正常访问 Nginx 的首页(如下图所示),如下图所示:
若希望删除刚在 Kubernetes 集群创建的 Nginx 容器,可以在任意一个 Master 节点上执行以下命令:
1 2 3 4 5 # kubectl delete service nginx# kubectl delete deployment nginx
部署 CoreDNS(可选) 特别注意
以下所有操作仅在任意一个 Master 节点上执行,不需要在所有的 Master 节点和 Node 节点中重复执行,请保证在部署 CoreDNS 之前,Kubernetes 集群是可以正常运行的。 CodreDNS 的作用是:在 Kubernetes 集群内,可以通过 Service 的 DNS 名称(域名)对 Pod 进行访问,比如:http://<service-name>.<namespace>.svc.cluster.local:<port>,具体使用案例请看 这里 。 CoreDNS 的部署说明
由于上面搭建的 Kubernetes 集群已经具备运行 Pod 的完整容器化环境,这种情况下 CoreDNS 最合适的部署方式就是容器化部署,即通过 Kubernetes 自己来管理 CoreDNS 的 Pod。CoreDNS 也可以通过二进制包进行部署的,两种部署方式的区别如下: 部署方式 适合场景 优点 缺点 容器化部署 CoreDNS 集群支持运行 Pod,且至少 kube-system 命名空间可用 符合 Kubernetes 生态,自动扩缩容、自动重启,便于管理 需要容器运行环境 二进制部署 CoreDNS 极简集群、节点无容器环境,或仅测试 DNS 不依赖容器,部署简单 无法通过 Kubernetes 管理,需手动维护、高可用难度大
优势 说明 符合 Kubernetes 官方架构 CoreDNS 本身就是官方默认的集群 DNS 方案,部署成 Pod 完全符合设计思路。 自动高可用 可通过 Replica Set 或 Deployment 管理多个副本,某个 Pod 异常退出会被自动重建。 易扩容 / 升级 直接通过 kubectl scale 或 kubectl apply 即可扩容、升级。 与 kube-proxy、kubelet 无缝配合 kubelet 会自动配置 Pod 的 DNS 指向 CoreDNS 的 ClusterIP。 支持弹性伸缩 可配合 Pod 横向自动扩容(Horizontal Pod Autoscaler,简称 HPA)实现负载自动扩缩容。
CoreDNS 的部署步骤
从 CoreDNS 官方仓库下载最新的 YAML 配置文件模板,并生成新的配置文件 1 2 3 4 5 6 7 8 9 10 11 12 # wget https://raw.githubusercontent.com/coredns/deployment/master/kubernetes/coredns.yaml.sed -O coredns.yaml.sed# sed -e "s/CLUSTER_DOMAIN/cluster.local/g" \ -e "s/REVERSE_CIDRS/in-addr.arpa ip6.arpa/g" \ -e "s/UPSTREAMNAMESERVER/8.8.8.8/g" \ -e "s/}STUBDOMAINS/}/" \ coredns.yaml.sed > coredns.yaml # mv coredns.yaml /opt/kubernetes/cfg/
检查 Kubelet 的 YAML 配置文件(kubelet-config.yml),确保包含以下配置信息(注意,Kubernetes 默认的 DNS Service IP 是 10.0.0.10,也可以指定为实际规划的 Pod 网段内的 IP,本文使用自定义的 IP) 1 2 # vim /opt/kubernetes/cfg/kubelet-config.yml
1 2 3 clusterDNS: - 10.0 .0 .2 clusterDomain: cluster.local
修改 CoreDNS 的 YAML 配置文件(coredns.yaml),只需要修改 spec.replicas 和 spec.clusterIP 这两个配置 1 2 # vim /opt/kubernetes/cfg/coredns.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ...... apiVersion: apps/v1 kind: Deployment metadata: name: coredns namespace: kube-system labels: k8s-app: kube-dns kubernetes.io/name: "CoreDNS" app.kubernetes.io/name: coredns spec: replicas: 3 strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 ......
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ...... apiVersion: v1 kind: Service metadata: name: kube-dns namespace: kube-system annotations: prometheus.io/port: "9153" prometheus.io/scrape: "true" labels: k8s-app: kube-dns kubernetes.io/cluster-service: "true" kubernetes.io/name: "CoreDNS" app.kubernetes.io/name: coredns spec: selector: k8s-app: kube-dns app.kubernetes.io/name: coredns clusterIP: 10.0 .0 .2 ......
1 # kubectl apply -f /opt/kubernetes/cfg/coredns.yaml
1 # kubectl get pods -n kube-system -l k8s-app=kube-dns
1 2 3 4 NAME READY STATUS RESTARTS AGE coredns-6b9bb479b9-gpvvw 1/1 Running 0 21s coredns-6b9bb479b9-l7z5b 1/1 Running 0 21s coredns-6b9bb479b9-nzm5c 1/1 Running 0 21s
若 CoreDNS 的 Pod 启动失败,可以通过查看日志信息来定位问题 1 # kubectl logs coredns-6b9bb479b9-lfd7j -n kube-system
若希望删除 CoreDNS 的所有资源(包括 Pod、Controller、Service 等),可以执行以下命令 1 # kubectl delete -f /opt/kubernetes/cfg/coredns.yaml
多 Master 集群搭建问题 集群启动顺序问题 Kubernetes 集群包含多个组件,这些组件之间存在依赖关系。例如,大多数组件都依赖 API Server,因此 API Server 通常需要优先启动,而 API Server 本身又依赖 Etcd 集群先行启动。建议按以下顺序启动各个组件,否则有可能导致某些组件无法正常启动(尤其各个组件都是通过 Systemd 管理服务,并且集群的所有服务器都发生断电重启的情况下 ):
1、Etcd 集群(所有集群节点的 Etcd) 2、Master 节点组件:(1) API Server (2) Controller Manager (3) Scheduler 3、Node 节点组件:(1) Kubelet (2) Kube-Proxy 4、网络插件(如 Flannel、Calico、Cilium 等) 5、其他附加组件(如 CoreDNS、Ingress Controller、Dashboard 等) Kubernetes 集群各个组件之间的依赖关系
Etcd 集群:Kubernetes 的数据存储后端,保存 API Server 的所有配置信息和资源对象;若未启动,API Server 无法连接存储并会报错退出。 API Server(Master 节点):集群核心入口,Controller Manager、Scheduler、kubectl 等都通过它访问集群状态;必须先启动才能让其他组件连接工作。 Controller Manager(Master 节点):通过 API Server 访问和修改集群状态(如创建 Pod、更新节点信息);若 API Server 未启动,则会不断重试连接而启动失败。 Scheduler(Master 节点):通过 API Server 获取未调度的 Pod 列表并为其分配合适的 Node;依赖 API Server 正常运行。 手动解决启动顺序问题 特别注意
当所有 Kubernetes 集群服务器重启后(比如断电重启),部分 Kubernetes 组件可能无法正确启动,因为每个组件之间存在一定的依赖关系,这时候就需要人工介入处理。
管理 Master 节点的组件启动,以下所有操作都是仅在 Kubernetes 集群的所有 Master 节点上执行
1 2 3 4 5 # systemctl status kube-apiserver# systemctl status kube-controller-manager# systemctl status kube-scheduler# systemctl status kubelet# systemctl status kube-proxy
若在 Master 节点组件的运行状态中,发现有不可忽略的致命错误提示信息,则严格按以下顺序重启各个组件 1 2 3 4 5 # systemctl restart kube-apiserver# systemctl restart kube-controller-manager# systemctl restart kube-scheduler# systemctl restart kubelet# systemctl restart kube-proxy
1 2 3 4 5 # systemctl status kube-apiserver# systemctl status kube-controller-manager# systemctl status kube-scheduler# systemctl status kubelet# systemctl status kube-proxy
管理 Node 节点的组件启动,以下所有操作分别在 Kubernetes 集群的所有 Node 节点上执行
1 2 # systemctl status kubelet# systemctl status kube-proxy
若在 Node 节点组件的运行状态中,发现有不可忽略的致命错误提示信息,则严格按以下顺序重启各个组件 1 2 # systemctl restart kubelet# systemctl restart kube-proxy
1 2 # systemctl status kubelet# systemctl status kube-proxy
彻底解决启动顺序问题 特别注意
若每次在所有 Kubernetes 集群服务器重启后(比如断电重启),都需要手动按顺序重启每个 Kubernetes 组件,这将非常繁琐。为了彻底解决这个问题,可以使用 Systemd 的 Requires、After、ExecStartPre 特性来控制多个服务之间的启动顺序。
生成 Health-Check 的证书,用于检测 API Server 的健康状态,以下所有操作都是仅在 Kubernetes 集群的任意一个 Master 节点上执行
在任意一个 Master 节点中,创建用于生成 Health-Check 证书的配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # cat > health-check-csr.json << EOF{ "CN" : "health-check" , "hosts" : [], "key" : { "algo" : "rsa" , "size" : 2048 }, "names" : [ { "O" : "system:health-checkers" } ] } EOF
在任意一个 Master 节点中,使用自签 CA 签发 Health-Check 证书 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # ls ca-config.json ca.pem kube-apiserver-key.pem kube-controller-manager-key.pem kube-proxy-key.pem kube-scheduler-key.pem ca.csr health-check-csr.json kube-apiserver.pem kube-controller-manager.pem kube-proxy.pem kube-scheduler.pem ca-csr.json kube-apiserver.csr kube-controller-manager.csr kube-proxy.csr kube-scheduler.csr ca-key.pem kube-apiserver-csr.json kube-controller-manager-csr.json kube-proxy-csr.json kube-scheduler-csr.json # cfssl gencert -ca =ca.pem -ca -key=ca-key.pem -config =ca-config.json -profile =kubernetes health-check-csr.json | cfssljson -bare health-check# ls health-check*.pemhealth-check-key.pem health-check.pem # cfssl -certinfo -cert health-check.pem# openssl x509 -noout -text -in health-check.pem
在任意一个 Master 节点中,将上面生成的 Health-Check 证书拷贝到本地目录里面 1 2 # cp ~/tls/k8s/health*.pem /opt/kubernetes/ssl/
在任意一个 Master 节点中,将上面生成的 Health-Check 证书拷贝到其他 Master 节点里面 1 2 # scp ~/tls/k8s/health-check*.pem root@k8s-master2:/opt/kubernetes/ssl/
在任意一个 Master 节点中,将上面生成的 Health-Check 证书拷贝到所有 Node 节点里面 1 2 3 4 # scp ~/tls/k8s/health-check*.pem root@k8s-node1:/opt/kubernetes/ssl/# scp ~/tls/k8s/health-check*.pem root@k8s-node2:/opt/kubernetes/ssl/# scp ~/tls/k8s/health-check*.pem root@k8s-node3:/opt/kubernetes/ssl/
在任意一个 Master 节点中,创建 ClusterRole 的配置文件(仅授予只读的健康检测权限) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # cat > /opt/kubernetes/cfg/health-check-role.yaml << EOFapiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: health-check-role rules: - nonResourceURLs: - /healthz - /livez - /readyz verbs: - get EOF
在任意一个 Master 节点中,创建绑定用户到 ClusterRole 的配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # cat > /opt/kubernetes/cfg/health-check-binding.yaml << EOFapiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: health-check-binding roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: health-check-role subjects: - kind: User name: health-check apiGroup: rbac.authorization.k8s.io EOF
在任意一个 Master 节点中,应用 YAML 配置文件 1 2 3 4 5 # kubectl apply -f /opt/kubernetes/cfg/health-check-role.yaml# kubectl apply -f /opt/kubernetes/cfg/health-check-binding.yaml
在所有节点(包括 Master 和 Node)中,分别验证 API Server 健康检测接口的调用(请自行更改 API Server 的地址,需要使用 Keepalived 的 VIP 和 HaProxy 的反向代理端口,切勿直接拷贝执行以下命令 ) 1 2 # curl --silent --fail --cacert /opt/kubernetes/ssl/ca.pem --cert /opt/kubernetes/ssl/health-check.pem --key /opt/kubernetes/ssl/health-check-key.pem https://192.168.2.100:16443/readyz
管理 Master 节点的组件启动,以下所有操作都是仅在 Kubernetes 集群的所有 Master 节点上执行
在所有 Master 节点中,下载 Etcd 的 etcdctl 可执行文件(如果本地不存在,才需要下载可执行文件) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # ls /opt/etcd/bin/etcdctl# wget https://github.com/etcd-io/etcd/releases/download/v3.4.9/etcd-v3.4.9-linux-amd64.tar.gz# mkdir -p /opt/etcd/bin# tar zxvf etcd-v3.4.9-linux-amd64.tar.gz# mv etcd-v3.4.9-linux-amd64/etcdctl /opt/etcd/bin/
在所有 Master 节点中,重写(覆盖)API Server 服务的 Systemd 配置文件(请自行更改 Etcd 集群节点的 IP 和端口,切勿直接拷贝执行以下命令 ) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # cat > /usr/lib/systemd/system/kube-apiserver.service << EOF[Unit] Description=Kubernetes API Server Documentation=https://github.com/kubernetes/kubernetes After=network-online.target [Service] EnvironmentFile=/opt/kubernetes/cfg/kube-apiserver.conf ExecStartPre=/bin/bash -c 'until ETCDCTL_API=3 /opt/etcd/bin/etcdctl --endpoints=https://192.168.2.191:2379,https://192.168.2.112:2379,https://192.168.2.131:2379 --cacert=/opt/etcd/ssl/ca.pem --cert=/opt/etcd/ssl/server.pem --key=/opt/etcd/ssl/server-key.pem endpoint status --write-out=table | grep -v "^+" | grep -q " true "; do echo "Waiting for Etcd cluster to be ready..."; sleep 2; done && echo "Etcd cluster is now ready."' ExecStart=/opt/kubernetes/bin/kube-apiserver \$KUBE_APISERVER_OPTS Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target EOF
1 2 3 4 5 6 7 8 # systemctl daemon-reload# systemctl restart kube-apiserver# systemctl status kube-apiserver
在所有 Master 节点中,重写(覆盖)Controller Manager 服务的 Systemd 配置文件(请自行更改 API Server 的地址,需要使用 Keepalived 的 VIP 和 HaProxy 的反向代理端口,切勿直接拷贝执行以下命令 ) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # cat > /usr/lib/systemd/system/kube-controller-manager.service << EOF[Unit] Description=Kubernetes Controller Manager Documentation=https://github.com/kubernetes/kubernetes After=network-online.target [Service] EnvironmentFile=/opt/kubernetes/cfg/kube-controller-manager.conf ExecStartPre=/bin/bash -c 'until curl --silent --fail --cacert /opt/kubernetes/ssl/ca.pem --cert /opt/kubernetes/ssl/health-check.pem --key /opt/kubernetes/ssl/health-check-key.pem https://192.168.2.100:16443/readyz > /dev/null 2>&1; do echo "Waiting for API Server to be ready..."; sleep 2; done && echo "API Server is now ready."' ExecStart=/opt/kubernetes/bin/kube-controller-manager \$KUBE_CONTROLLER_MANAGER_OPTS Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target EOF
1 2 3 4 5 6 7 8 # systemctl daemon-reload# systemctl restart kube-controller-manager# systemctl status kube-controller-manager
在所有 Master 节点中,重写(覆盖)Scheduler 服务的 Systemd 配置文件(请自行更改 API Server 的地址,需要使用 Keepalived 的 VIP 和 HaProxy 的反向代理端口,切勿直接拷贝执行以下命令 ) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # cat > /usr/lib/systemd/system/kube-scheduler.service << EOF[Unit] Description=Kubernetes Scheduler Documentation=https://github.com/kubernetes/kubernetes After=network-online.target [Service] EnvironmentFile=/opt/kubernetes/cfg/kube-scheduler.conf ExecStartPre=/bin/bash -c 'until curl --silent --fail --cacert /opt/kubernetes/ssl/ca.pem --cert /opt/kubernetes/ssl/health-check.pem --key /opt/kubernetes/ssl/health-check-key.pem https://192.168.2.100:16443/readyz > /dev/null 2>&1; do echo "Waiting for API Server to be ready..."; sleep 2; done && echo "API Server is now ready."' ExecStart=/opt/kubernetes/bin/kube-scheduler \$KUBE_SCHEDULER_OPTS Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target EOF
1 2 3 4 5 6 7 8 # systemctl daemon-reload# systemctl restart kube-scheduler# systemctl status kube-scheduler
在所有 Master 节点中,重写(覆盖)Kubelet 服务的 Systemd 配置文件(请自行更改 API Server 的地址,需要使用 Keepalived 的 VIP 和 HaProxy 的反向代理端口,切勿直接拷贝执行以下命令 ) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # cat > /usr/lib/systemd/system/kubelet.service << EOF[Unit] Description=Kubernetes Kubelet Documentation=https://github.com/kubernetes/kubernetes After=network-online.target After=docker.service [Service] EnvironmentFile=/opt/kubernetes/cfg/kubelet.conf ExecStartPre=/bin/bash -c 'until curl --silent --fail --cacert /opt/kubernetes/ssl/ca.pem --cert /opt/kubernetes/ssl/health-check.pem --key /opt/kubernetes/ssl/health-check-key.pem https://192.168.2.100:16443/readyz > /dev/null 2>&1; do echo "Waiting for API Server to be ready..."; sleep 2; done && echo "API Server is now ready."' ExecStart=/opt/kubernetes/bin/kubelet \$KUBELET_OPTS Restart=on-failure RestartSec=5 LimitNOFILE=65536 [Install] WantedBy=multi-user.target EOF
1 2 3 4 5 6 7 8 # systemctl daemon-reload# systemctl restart kubelet# systemctl status kubelet
在所有 Master 节点中,重写(覆盖)Kube-Proxy 服务的 Systemd 配置文件(请自行更改 API Server 的地址,需要使用 Keepalived 的 VIP 和 HaProxy 的反向代理端口,切勿直接拷贝执行以下命令 ) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # cat > /usr/lib/systemd/system/kube-proxy.service << EOF[Unit] Description=Kubernetes Proxy Documentation=https://github.com/kubernetes/kubernetes After=network-online.target After=docker.service [Service] EnvironmentFile=/opt/kubernetes/cfg/kube-proxy.conf ExecStartPre=/bin/bash -c 'until curl --silent --fail --cacert /opt/kubernetes/ssl/ca.pem --cert /opt/kubernetes/ssl/health-check.pem --key /opt/kubernetes/ssl/health-check-key.pem https://192.168.2.100:16443/readyz > /dev/null 2>&1; do echo "Waiting for API Server to be ready..."; sleep 2; done && echo "API Server is now ready."' ExecStart=/opt/kubernetes/bin/kube-proxy \$KUBE_PROXY_OPTS Restart=on-failure RestartSec=5 LimitNOFILE=65536 [Install] WantedBy=multi-user.target EOF
1 2 3 4 5 6 7 8 # systemctl daemon-reload# systemctl restart kube-proxy# systemctl status kube-proxy
管理 Node 节点的组件启动,以下所有操作分别在 Kubernetes 集群的所有 Node 节点上执行
在所有 Node 节点中,重写(覆盖)Kubelet 服务的 Systemd 配置文件(请自行更改 API Server 的地址,需要使用 Keepalived 的 VIP 和 HaProxy 的反向代理端口,切勿直接拷贝执行以下命令 ) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # cat > /usr/lib/systemd/system/kubelet.service << EOF[Unit] Description=Kubernetes Kubelet Documentation=https://github.com/kubernetes/kubernetes After=network-online.target After=docker.service [Service] EnvironmentFile=/opt/kubernetes/cfg/kubelet.conf ExecStartPre=/bin/bash -c 'until curl --silent --fail --cacert /opt/kubernetes/ssl/ca.pem --cert /opt/kubernetes/ssl/health-check.pem --key /opt/kubernetes/ssl/health-check-key.pem https://192.168.2.100:16443/readyz > /dev/null 2>&1; do echo "Waiting for API Server to be ready..."; sleep 2; done && echo "API Server is now ready."' ExecStart=/opt/kubernetes/bin/kubelet \$KUBELET_OPTS Restart=on-failure RestartSec=5 LimitNOFILE=65536 [Install] WantedBy=multi-user.target EOF
1 2 3 4 5 6 7 8 # systemctl daemon-reload# systemctl restart kubelet# systemctl status kubelet
在所有 Node 节点中,重写(覆盖)Kube-Proxy 服务的 Systemd 配置文件(请自行更改 API Server 的地址,需要使用 Keepalived 的 VIP 和 HaProxy 的反向代理端口,切勿直接拷贝执行以下命令 ) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # cat > /usr/lib/systemd/system/kube-proxy.service << EOF[Unit] Description=Kubernetes Proxy Documentation=https://github.com/kubernetes/kubernetes After=network-online.target After=docker.service [Service] EnvironmentFile=/opt/kubernetes/cfg/kube-proxy.conf ExecStartPre=/bin/bash -c 'until curl --silent --fail --cacert /opt/kubernetes/ssl/ca.pem --cert /opt/kubernetes/ssl/health-check.pem --key /opt/kubernetes/ssl/health-check-key.pem https://192.168.2.100:16443/readyz > /dev/null 2>&1; do echo "Waiting for API Server to be ready..."; sleep 2; done && echo "API Server is now ready."' ExecStart=/opt/kubernetes/bin/kube-proxy \$KUBE_PROXY_OPTS Restart=on-failure RestartSec=5 LimitNOFILE=65536 [Install] WantedBy=multi-user.target EOF
1 2 3 4 5 6 7 8 # systemctl daemon-reload# systemctl restart kube-proxy# systemctl status kube-proxy
Etcd 无法正常启动 Etcd 集群断电重启后,通过 systemctl status etcd 命令查看 Etcd 节点(比如:节点名称是 etcd-3,IP 是 192.168.2.131)的运行状态时,提示 Raft 同步失败(如下)。最有可能的原因是这个 Etcd 节点的数据目录损坏或不一致导致,解决方案如下: 1 2 3 4 5 8月 07 09:43:32 k8s-node2 etcd[6043]: {"level":"info","ts":"2025-08-07T09:43:32.007+0800","caller":"rafthttp/stream.go:425","msg":"established TCP streaming connection with remote peer","stream-reader-type":"stream MsgApp v2","local-member-id":"9cb4eb07d38510b5","remote-peer-id":"a00042f5519886e3"} 8月 07 09:43:32 k8s-node2 etcd[6043]: {"level":"info","ts":"2025-08-07T09:43:32.026+0800","caller":"etcdserver/server.go:715","msg":"initialized peer connections; fast-forwarding election ticks","local-member-id":"9cb4eb07d38510b5","forward-ticks":8,"forward-duration":"800ms","election-ticks":10,"election-timeout... 8月 07 09:43:38 k8s-node2 etcd[6043]: {"level":"warn","ts":"2025-08-07T09:43:38.988+0800","caller":"etcdserver/server.go:2065","msg":"failed to publish local member to cluster through raft","local-member-id":"9cb4eb07d38510b5","local-member-attributes":"{Name:etcd-3 ClientURLs:[https://192.168.2.131:2379]}","requ... 8月 07 09:43:45 k8s-node2 etcd[6043]: {"level":"warn","ts":"2025-08-07T09:43:45.988+0800","caller":"etcdserver/server.go:2065","msg":"failed to publish local member to cluster through raft","local-member-id":"9cb4eb07d38510b5","local-member-attributes":"{Name:etcd-3 ClientURLs:[https://192.168.2.131:2379]}","requ... Hint: Some lines were ellipsized, use -l to show in full.
在其他健康的 Etcd 节点上,查看 Etcd 集群的运行状态,可以发现缺少了 etcd-3 节点 1 2 3 4 5 6 +----------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+ | ENDPOINT | ID | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS | +----------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+ | https://192.168.2.191:2379 | 9a8c44f9310511 | 3.4.9 | 5.0 MB | false | false | 369 | 314583 | 314583 | | | https://192.168.2.112:2379 | a00042f5519886e3 | 3.4.9 | 5.0 MB | true | false | 369 | 314583 | 314583 | | +----------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
通过其他健康的 Etcd 节点,查看 Etcd 集群的成员列表,虽然 etcd-3 节点无法正常启动,但依旧可以看到 etcd-3 节点 1 2 3 9a8c44f9310511, started, etcd-1, https://192.168.2.191:2380, https://192.168.2.191:2379, false 5e5716f1d7eba042, started, etcd-3, https://192.168.2.131:2380, https://192.168.2.131:2379, false a00042f5519886e3, started, etcd-2, https://192.168.2.112:2380, https://192.168.2.112:2379, false
在其他健康的 Etcd 节点上,将无法正常启动的 etcd-3 节点移除出集群 在无法正常启动的 etcd-3 节点上,删除本地的 Etcd 数据目录 1 2 3 4 5 # systemctl stop etcd# rm -rf /var/lib/etcd/default.etcd
通过其他健康的 Etcd 节点,将无法正常启动的 etcd-3 节点重新加入 Etcd 集群 执行命令将 etcd-3 节点重新加入 Etcd 集群后,会输出以下内容 1 2 3 4 ETCD_NAME="etcd-3" ETCD_INITIAL_CLUSTER="etcd-1=https://192.168.2.191:2380,etcd-3=https://192.168.2.131:2380,etcd-2=https://192.168.2.112:2380" ETCD_INITIAL_ADVERTISE_PEER_URLS="https://192.168.2.131:2380" ETCD_INITIAL_CLUSTER_STATE="existing"
在无法正常启动的 etcd-3 节点上,更改本地的 Etcd 配置文件,将 ETCD_INITIAL_CLUSTER_STATE 改为 existing,并重启 Etcd 服务(如果上面的操作都生效了,那么这时候 Etcd 服务应该就可以正常启动了) 1 2 3 4 5 6 7 8 # sed -i 's/^ETCD_INITIAL_CLUSTER_STATE="new"/ETCD_INITIAL_CLUSTER_STATE="existing"/' /opt/etcd/cfg/etcd.conf# systemctl restart etcd# systemctl status etcd
在之前无法正常启动的 etcd-3 节点上,还原本地的 Etcd 配置文件,将 ETCD_INITIAL_CLUSTER_STATE 改为 new,并重启 Etcd 服务 1 2 3 4 5 6 7 8 # sed -i 's/^ETCD_INITIAL_CLUSTER_STATE="existing"/ETCD_INITIAL_CLUSTER_STATE="new"/' /opt/etcd/cfg/etcd.conf# systemctl restart etcd# systemctl status etcd
在其他健康的 Etcd 节点上,再次查看 Etcd 集群的运行状态,可以发现 etcd-3 节点(会获得新的节点 ID)正常运行 1 2 3 4 5 6 7 +----------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+ | ENDPOINT | ID | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS | +----------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+ | https://192.168.2.191:2379 | 9a8c44f9310511 | 3.4.9 | 5.0 MB | false | false | 369 | 323848 | 323848 | | | https://192.168.2.112:2379 | a00042f5519886e3 | 3.4.9 | 5.0 MB | true | false | 369 | 323848 | 323848 | | | https://192.168.2.131:2379 | d282ba1f56161393 | 3.4.9 | 5.0 MB | false | false | 369 | 323848 | 323848 | | +----------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
问题分析与总结
ETCD_INITIAL_CLUSTER_STATE="new" 表示 Etcd 节点将以 "全新集群" 的方式启动,此时它会根据 ETCD_INITIAL_CLUSTER 中的成员列表初始化集群,并假设 Raft 日志为空。如果该节点此前已经加入过集群,当再次使用 new 启动时,就会导致与现有集群中的成员 ID 冲突,触发 Raft 同步失败。而 ETCD_INITIAL_CLUSTER_STATE="existing" 则用于已存在的集群节点,表示该节点将从其他成员处拉取 Raft 日志进行同步。因此,当 etcd-3 节点已经出现在 Etcd 集群的成员列表中时,配置 ETCD_INITIAL_CLUSTER_STATE="new" 是错误的,应该改为 existing,否则该节点在重启时会错误地尝试初始化一个新的集群,导致 Raft 冲突和无法加入集群。简而言之,在初次搭建 Etcd 集群时,ETCD_INITIAL_CLUSTER_STATE="new" 是正确的,但在 Etcd 节点重启或者重新加入集群时,如果这个值仍然是 new,那就可能会引发严重的问题。
连接 API Server 失败 Kubernetes 部分组件(比如 Controller Manager)在启动后,无法与 API Server 通过 HTTPS 协议进行加密通信,出现以下错误信息:
1 2 3 4 5 6 11月 11 11:56:48 k8s-master1 kube-controller-manager[7582]: I1111 11:56:48.590181 7582 leaderelection.go:243] attempting to acquire leader lease kube-system/kube-controller-manager... 11月 11 11:56:48 k8s-master1 kube-controller-manager[7582]: E1111 11:56:48.591986 7582 leaderelection.go:325] error retrieving resource lock kube-system/kube-controller-manager: the server rejected our request for an unknown reason (get endpoints kube-controller-manager) 11月 11 11:56:51 k8s-master1 kube-controller-manager[7582]: E1111 11:56:51.722757 7582 leaderelection.go:325] error retrieving resource lock kube-system/kube-controller-manager: the server rejected our request for an unknown reason (get endpoints kube-controller-manager) 11月 11 11:56:54 k8s-master1 kube-controller-manager[7582]: E1111 11:56:54.201575 7582 leaderelection.go:325] error retrieving resource lock kube-system/kube-controller-manager: the server rejected our request for an unknown reason (get endpoints kube-controller-manager) 11月 11 11:56:57 k8s-master1 kube-controller-manager[7582]: E1111 11:56:57.184837 7582 leaderelection.go:325] error retrieving resource lock kube-system/kube-controller-manager: the server rejected our request for an unknown reason (get endpoints kube-controller-manager) 11月 11 11:56:59 k8s-master1 kube-controller-manager[7582]: E1111 11:56:59.859566 7582 leaderelection.go:325] error retrieving resource lock kube-system/kube-controller-manager: the server rejected our request for an unknown reason (get endpoints kube-controller-manager)
这通常是 Kubernetes 组件与 API Server 通信时,所有使用的 CA 证书不匹配导致(或者缺少 CA 证书配置),这里以 Controller Manager 组件为例,给出解决方法
更改 Controller Manager 的配置文件,使其启动后打印详细的日志信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # cat > /opt/kubernetes/cfg/kube-controller-manager.conf << EOFKUBE_CONTROLLER_MANAGER_OPTS="--logtostderr=true \\ --v=2 \\ --log-dir=/opt/kubernetes/logs \\ --leader-elect=true \\ --master=192.168.2.100:16443 \\ --bind-address=127.0.0.1 \\ --allocate-node-cidrs=true \\ --cluster-cidr=10.244.0.0/16 \\ --service-cluster-ip-range=10.0.0.0/24 \\ --cluster-signing-cert-file=/opt/kubernetes/ssl/ca.pem \\ --cluster-signing-key-file=/opt/kubernetes/ssl/ca-key.pem \\ --root-ca-file=/opt/kubernetes/ssl/ca.pem \\ --service-account-private-key-file=/opt/kubernetes/ssl/ca-key.pem \\ --use-service-account-credentials=true \\ --kubeconfig=/opt/kubernetes/cfg/kube-controller-manager.kubeconfig \\ --cluster-signing-duration=87600h0m0s" EOF
重启 Controller Manager 服务,查看输出的详细日志信息,以此定位问题
1 2 3 4 5 6 7 8 9 10 11 # systemctl daemon-reload# systemctl restart kube-controller-manager# systemctl status kube-controller-manager# journalctl -u kube-controller-manager -f
手动测试与 API Server 的连接和认证
1 2 3 4 5 6 7 8 9 # kubectl --kubeconfig =/opt/kubernetes/cfg/kube-controller-manager.kubeconfig get componentstatuses# curl -k \ --cert /opt/kubernetes/ssl/controller-manager.pem \ --key /opt/kubernetes/ssl/controller-manager-key.pem \ --cacert /opt/kubernetes/ssl/ca.pem \ https://192.168.2.100:16443/version
检查 API Server 的证书,必须包含 Keepalived 的虚拟 IP 地址(比如 192.168.2.100)
1 2 # openssl x509 -in /opt/kubernetes/ssl/server.pem -text -noout | grep -A 5 "X509v3 Subject Alternative Name"
检查 Controller Manager 证书中的 Subject 和 SAN
1 2 # openssl x509 -in /opt/kubernetes/ssl/controller-manager.pem -text -noout | grep -A 10 "Subject:"
检查 HaProxy 与 Keepalived 是否可以正常工作
1 2 # curl --cacert /opt/kubernetes/ssl/ca.pem https://192.168.2.100:16443/healthz
经检查,发现 Controller Manager 缺少了以下配置项,尤其是缺少 --requestheader-client-ca-file 配置项 ,导致无法与 API Server 通过 HTTPS 协议进行加密通信(使用 CA 证书)
1 2 3 4 5 6 --authentication-kubeconfig=/opt/kubernetes/cfg/kube-controller-manager.kubeconfig \\ --authorization-kubeconfig=/opt/kubernetes/cfg/kube-controller-manager.kubeconfig \\ --client-ca-file=/opt/kubernetes/ssl/ca.pem \\ --requestheader-client-ca-file=/opt/kubernetes/ssl/ca.pem \\ --tls-cert-file=/opt/kubernetes/ssl/controller-manager.pem \\ --tls-private-key-file=/opt/kubernetes/ssl/controller-manager-key.pem \\
最终,Controller Manager 使用以下配置信息后,可以与 API Server 通过 HTTPS 协议进行加密通信(使用 CA 证书)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 # cat > /opt/kubernetes/cfg/kube-controller-manager.conf << EOFKUBE_CONTROLLER_MANAGER_OPTS="--logtostderr=false \\ --v=2 \\ --log-dir=/opt/kubernetes/logs \\ --leader-elect=true \\ --bind-address=127.0.0.1 \\ --allocate-node-cidrs=true \\ --cluster-cidr=10.244.0.0/16 \\ --service-cluster-ip-range=10.0.0.0/24 \\ --secure-port=10257 \\ --kubeconfig=/opt/kubernetes/cfg/kube-controller-manager.kubeconfig \\ --authentication-kubeconfig=/opt/kubernetes/cfg/kube-controller-manager.kubeconfig \\ --authorization-kubeconfig=/opt/kubernetes/cfg/kube-controller-manager.kubeconfig \\ --client-ca-file=/opt/kubernetes/ssl/ca.pem \\ --use-service-account-credentials=true \\ --service-account-private-key-file=/opt/kubernetes/ssl/ca-key.pem \\ --cluster-signing-cert-file=/opt/kubernetes/ssl/ca.pem \\ --cluster-signing-key-file=/opt/kubernetes/ssl/ca-key.pem \\ --requestheader-client-ca-file=/opt/kubernetes/ssl/ca.pem \\ --tls-cert-file=/opt/kubernetes/ssl/kube-controller-manager.pem \\ --tls-private-key-file=/opt/kubernetes/ssl/kube-controller-manager-key.pem \\ --root-ca-file=/opt/kubernetes/ssl/ca.pem \\ --controllers=*,bootstrapsigner,tokencleaner \\ --deployment-controller-sync-period=10s \\ --enable-garbage-collector=true \\ --terminated-pod-gc-threshold=50 \\ --node-monitor-period=5s \\ --node-monitor-grace-period=20s \\ --pod-eviction-timeout=2m0s \\ --cluster-signing-duration=87600h0m0s" EOF
提示
配置项 --requestheader-client-ca-file 用于验证 API Server 聚合层(Aggregation Layer)客户端请求头证书的 CA 文件,Controller Manager 在与启用了聚合层的 API Server 通信时,会使用该 CA 验证代理请求来源的合法性,可以与 --requestheader-allowed-names、--requestheader-extra-headers-prefix、--requestheader-group-headers、--requestheader-username-headers 等参数配合使用。
Kubelet 无法正常启动 Kubelet 服务断电重启后,通过 systemctl status kubelet 命令查看运行状态时,服务没有处于 active (running) 状态,并且有各种错误信息,尝试多种方法依旧无法正常重启。解决方案如下:
在 Etcd 集群的所有节点上,分别删除 Etcd 的数据目录(切记谨慎操作,Etcd 数据删除后,已有的容器都可能无法正常启动,并且整个 Kubernetes 集群都可能需要重建 ),并重启 Etcd 服务 1 2 3 4 5 6 7 8 9 10 11 # systemctl stop etcd# rm -rf /var/lib/etcd/default.etcd# systemctl start etcd# systemctl status etcd
在任意一个 Master 节点中,按顺序重启以下服务 1 2 3 4 5 # systemctl restart kube-apiserver# systemctl restart kube-controller-manager# systemctl restart kube-scheduler# systemctl restart kubelet# systemctl restart kube-proxy
1 2 # systemctl restart kubelet# systemctl restart kube-proxy
在任意一个 Master 节点中,批准 Kubelet 证书签名请求并允许 Node 节点加入 Kubernetes 集群 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 # kubectl get csrNAME AGE SIGNERNAME REQUESTOR CONDITION node-csr-B91KCv7T_4A4RCfvfEBfFOP3V5zCYKK5NQkVLDmyly4 86s kubernetes.io/kube-apiserver-client-kubelet kubelet-bootstrap Pending node-csr-F4zdBAJxxKnDLsWJbYHVi_XKF8Uwh1MMarXNJiHAUy0 85s kubernetes.io/kube-apiserver-client-kubelet kubelet-bootstrap Pending node-csr-GYbsPLYySA0LiUkuhbpXlApj-JC-TDnA7SVRPHWU2mI 88s kubernetes.io/kube-apiserver-client-kubelet kubelet-bootstrap Pending node-csr-bLUS_w2or-Rq9c5JgCzaf5e6QYHND6PTEqQFTvDsY5g 96s kubernetes.io/kube-apiserver-client-kubelet kubelet-bootstrap Pending node-csr-ndBBynU94khVf6ADv4pdmtrlsxDOXAnyxu18e_VnhBU 87s kubernetes.io/kube-apiserver-client-kubelet kubelet-bootstrap Pending # kubectl certificate approve node-csr-B91KCv7T_4A4RCfvfEBfFOP3V5zCYKK5NQkVLDmyly4# kubectl certificate approve node-csr-F4zdBAJxxKnDLsWJbYHVi_XKF8Uwh1MMarXNJiHAUy0# kubectl certificate approve node-csr-GYbsPLYySA0LiUkuhbpXlApj-JC-TDnA7SVRPHWU2mI# kubectl certificate approve node-csr-bLUS_w2or-Rq9c5JgCzaf5e6QYHND6PTEqQFTvDsY5g# kubectl certificate approve node-csr-ndBBynU94khVf6ADv4pdmtrlsxDOXAnyxu18e_VnhBU# kubectl get nodeNAME STATUS ROLES AGE VERSION k8s-master1 NotReady <none> 10m v1.19.10 k8s-master2 NotReady <none> 18s v1.19.10 k8s-node1 NotReady <none> 29s v1.19.10 k8s-node2 NotReady <none> 22s v1.19.10 k8s-node3 NotReady <none> 25s v1.19.10
Kubelet 发送 CSR 请求失败 Kubelet 服务启动后,无法正常向 API Server 发起 CSR(证书签名请求),解决方案如下:
在当前节点上,清理 Kubelet 运行时产生的文件,并重启 Kubelet 服务 1 2 3 4 5 6 7 8 9 10 11 12 13 # systemctl stop kubelet# rm -rf /opt/kubernetes/ssl/kubelet*# rm -rf /opt/kubernetes/cfg/kubelet.kubeconfig# ls /opt/kubernetes/ssl/ca*.pem/opt/kubernetes/ssl/ca-key.pem /opt/kubernetes/ssl/ca.pem # systemctl start kubelet
在任意一个 Master 节点上,执行以下命令,批准 Kubelet 证书签名请求并允许 Node 节点加入 Kubernetes 集群 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # kubectl get csrNAME AGE SIGNERNAME REQUESTOR CONDITION node-csr-B91KCv7T_4A4RCfvfEBfFOP3V5zCYKK5NQkVLDmyly4 95s kubernetes.io/kube-apiserver-client-kubelet kubelet-bootstrap Pending # kubectl certificate approve node-csr-B91KCv7T_4A4RCfvfEBfFOP3V5zCYKK5NQkVLDmyly4# kubectl get nodeNAME STATUS ROLES AGE VERSION k8s-master1 NotReady <none> 10m v1.19.10 k8s-master2 NotReady <none> 18s v1.19.10 k8s-node1 NotReady <none> 29s v1.19.10 k8s-node2 NotReady <none> 22s v1.19.10 k8s-node3 NotReady <none> 25s v1.19.10
CNI 网络插件无法正常部署 在 Kubernetes 集群中部署 CNI 网络插件(Flannel)后,通过 systemctl status kubelet 命令查看 Kubelet 的运行状态时,出现以下镜像错误信息,导致 CNI 网络插件(Flannel)无法正常部署 1 2 3 4 k8s-node1 kubelet[30963]: E0805 19:42:19.257544 30963 kuberuntime_sandbox.go:69] CreatePodSandbox for pod "kube-flannel-ds-g5q5l_kube-flannel(0c854c29-c158-45ca-a6c4-5d9ee7d9879b)" failed: rpc error: code = Unknown desc = failed pulling image "lizhenliang/pause-amd64:3.0": Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers) k8s-node1 kubelet[14733]: E0805 21:34:10.987901 14733 kubelet.go:2134] Container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized k8s-node1 kubelet[14733]: E0805 21:34:15.991080 14733 kubelet.go:2134] Container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized k8s-node1 kubelet[14733]: E0805 21:34:21.015325 14733 kubelet.go:2134] Container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized
这是因为 Kubelet 会根据 kubelet.conf 配置文件,从 Docker Hub 拉取 lizhenliang/pause-amd64:3.0 镜像;若网络访问超时,就会出现上述错误信息。解决方案有这几种:手动拉取镜像、配置 Docker 代理、更换 Docker 镜像源,最后重启 Kubelet 服务 1 2 3 4 5 6 7 8 # systemctl stop kubelet# docker pull lizhenliang/pause-amd64:3.0# systemctl start kubelet
当 Kubelet 服务成功启动,且 CNI 网络插件(Flannel)成功部署后,可以发现当前节点会有以下 Docker 镜像(缺一不可) 1 2 3 4 5 6 # docker imagesREPOSITORY TAG IMAGE ID CREATED SIZE ghcr.io/flannel-io/flannel v0.27.4 e83704a17731 5 weeks ago 91.4MB ghcr.io/flannel-io/flannel-cni-plugin v1.8.0-flannel1 bb28ded63816 5 weeks ago 10.8MB lizhenliang/pause-amd64 3.0 99e59f495ffa 9 years ago 747kB
参考资料