基于二进制包方式搭建 Kubernetes 多 Master 高可用集群

大纲

高可用集群的概述

高可用集群的介绍

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 节点之间通过 HaProxy 实现反向代理和负载均衡,而 HaProxy 通过 Keepalived 来保证自身的高可用性(即双机主备,也叫双机热备)。

搭建多 Master 集群

本节将基于二进制包方式搭建多 Master 的 Kubernetes 集群,核心目标如下:

  • 为 Etcd 、API Server、Kube-Proxy 自签证书
  • 部署 Etcd 集群(3 个节点)
  • 部署 Kubernetes 集群的 2 个 Master 节点
  • 部署 Kubernetes 集群的 3 个 Node 节点
  • 部署 Kubernetes 集群的 CNI 网络插件
  • 部署 Kubernetes 集群的 DNS 组件(CoreDNS)
  • 部署 HaProxy、Keepalived

术语说明

为了方便描述,本文使用 Node 节点 来替代 Kubernetes 的 Worker Node,使用 Master 节点 来替代 Master Node

版本说明

软件版本安装方式
CentOS7.9多个独立虚拟机
Docker19.03.9二进制安装包
Kubernetes1.19.10二进制安装包
Etcd3.4.9二进制安装包
HaProxy2.8.5源码编译安装
Keepalived2.2.8源码编译安装

硬件要求

搭建 Kubernetes 高可用集群需要满足以下几个条件:

  • 一台或多台机器,建议操作系统 CentOS 7(64 位)
  • Master 节点的硬件配置:2GB 或更多 RAM,2 个 CPU 或更多 CPU,硬盘 20GB 或更多
  • Node 节点的硬件配置:4GB 或更多 RAM,4 个 CPU 或更多 CPU,硬盘 40GB 或更多
  • 集群中所有机器之间的网络可以互通
  • 系统内可以访问外网,需要拉取镜像
  • 禁用 Swap 分区(必须)

服务器规划

本文所使用的 Kubernetes 集群服务器规划如下表所示。为了节省资源,Etcd 集群部署在与 Kubernetes 集群相同的服务器上。需要注意的是,在生产环境中,强烈建议将 Etcd 集群部署在独立的服务器上,以提升系统的性能和高可用性。

Host 名称角色 IPCPU 核数内存磁盘安装的组件备注
k8s-master1master192.168.2.191>= 2C>=2G>=20Gkube-apiserver,kube-controller-manager,kube-scheduler,kubelet,kube-proxy,cni plugin, docker,etcd,haproxy,keepalived 部署 Etcd、HaProxy、Keepalived
k8s-node1node(worker)192.168.2.112>= 4C>=4G>=40Gkubelet,kube-proxy,cni plugin, docker,etcd 部署 Etcd
k8s-node2node(worker)192.168.2.131>= 4C>=4G>=40Gkubelet,kube-proxy,cni plugin, docker,etcd 部署 Etcd
k8s-node3node(worker)192.168.2.236>= 4C>=4G>=40Gkubelet,kube-proxy,cni plugin, docker 不部署 Etcd
k8s-master2master192.168.2.148>= 2C>=2G>=20Gkube-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 名称角色 IPCPU 核数内存磁盘安装的组件备注
k8s-master1master192.168.2.191>= 2C>=2G>=20Gkube-apiserver,kube-controller-manager,kube-scheduler,cni plugin,etcd,haproxy,keepalived 部署 Etcd、HaProxy、Keepalived,不部署 Kubelet、Kube-Proxy、Docker
k8s-node1node(worker)192.168.2.112>= 4C>=4G>=40Gkubelet,kube-proxy,cni plugin, docker,etcd 部署 Etcd
k8s-node2node(worker)192.168.2.131>= 4C>=4G>=40Gkubelet,kube-proxy,cni plugin, docker,etcd 部署 Etcd
k8s-node3node(worker)192.168.2.236>= 4C>=4G>=40Gkubelet,kube-proxy,cni plugin, docker 不部署 Etcd
k8s-master2master192.168.2.148>= 2C>=2G>=20Gkube-apiserver,kube-controller-manager,kube-scheduler,kubelet,kube-proxy,cni plugin, haproxy,keepalived 不部署 Etcd、Kubelet、Kube-Proxy、Docker,部署 HaProxy、Keepalived

操作系统初始化

特别注意

  • 在 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
# 卸载ntpdate服务
# yum remove -y ntpdate

# 安装chrony服务
# yum install -y chrony

# 开机自启动chrony服务
# systemctl enable chronyd

# 启动chrony服务
# systemctl start chronyd

# 配置NTP源,添加国内公共的NTP服务器
# vim /etc/chrony.conf
server ntp.aliyun.com iburst
server ntp1.tencent.com iburst
server cn.pool.ntp.org iburst

# 重启chrony服务
# systemctl restart chronyd

# 检查chrony服务的运行状态
# systemctl status chronyd

# 查看chrony服务的时间同步状态
# chronyc tracking

# 强制同步一次时间(只在刚部署、时间差很多的时候用)
# chronyc makestep

设置主机名(比如 k8s-master1k8s-master2k8s-node1k8s-node2k8s-node3

1
# hostnamectl set-hostname <hostname>

添加 hosts 配置信息

1
2
3
4
5
6
7
# 添加hosts
# vim /etc/hosts
192.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.conf
net.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
# 二进制方式安装CFSSL工具
# 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
# 下载Docker的二进制包
# 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
# 创建Docker的配置目录
# mkdir /etc/docker

# 配置阿里的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

# 开机自启动Docker
# systemctl enable docker

# 启动Docker
# systemctl start docker

# 查看Docker的运行状态
# systemctl status docker

# 查看Docker的版本
# docker --version

# 查看Docker的安装信息
# docker info

搭建 Etcd 集群

Etcd 是一个分布式键值存储系统,Kubernetes 使用 Etcd 进行数据存储,所以先准备一个 Etcd 系统。为解决 Etcd 单点故障,建议采用集群方式部署,这里使用 3 台机器组建 Etcd 集群,可容忍 1 台机器故障。当然,也可以使用 5 台机器组建集群,可容忍 2 台机器故障。为了节省机器,这里复用 Kubernetes 节点的机器,也可以独立于 Kubernetes 集群之外部署,只要 Kubernetes 的 API Server 能够连接上就行。

生成 Etcd 证书

特别注意

  • 以下所有操作都是仅在 Kubernetes 集群的任意一个节点上(比如,第一个 Master 节点)执行,千万不要在每个 Etcd 集群节点上单独生成证书,否则 Etcd 集群里的节点可能会因证书不一致而导致集群启动失败
  • 以后向 Etcd 集群中添加新节点时,只要将对应的证书拷贝到新节点上即可,通过 CFSSL 工具生成证书的详细使用教程请看 这里

创建存放 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 签发 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

# 生成CA证书
# cfssl gencert -initca ca-csr.json | cfssljson -bare ca -

# 查看生成的CA证书
# ls ca*.pem
ca-key.pem ca.pem

# 使用自签 CA 证书签发 Etcd 的证书,"-profile" 参数的值必须与 `ca-config.json` 配置文件中的值一致
# cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=www server-csr.json | cfssljson -bare server

# 查看生成的 Etcd 证书
# ls server*.pem
server-key.pem server.pem

# 验证生成的 Etcd 证书
# cfssl-certinfo -cert server.pem
# openssl x509 -noout -text -in server.pem

部署 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)里面拷贝上面生成的 CA 证书和 Etcd 证书到本地目录

1
2
# 拷贝 CA 证书和 Etcd 证书到本地目录
# cp ~/tls/etcd/*.pem /opt/etcd/ssl

在第一个 Master 节点(k8s-master1)里面拷贝上面生成的 CA 证书和 Etcd 证书到其他 Master 节点里面(提供给 API Server 使用)

1
2
3
4
5
# 在其他 Master 节点上分别创建存放证书的目录
# ssh root@k8s-master2 "mkdir -p /opt/etcd/ssl/"

# 拷贝 CA 证书和 Etcd 证书到其他 Master 节点里面
# scp ~/tls/etcd/*.pem root@k8s-master2:/opt/etcd/ssl/

在第一个 Master 节点(k8s-master1)里面创建 Etcd 的配置文件,这里必须根据实际情况更改 Etcd 各节点的 IP、名称,切忌直接拷贝以下配置内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 创建Etcd的配置文件
# cat > /opt/etcd/cfg/etcd.conf << EOF
#[Member]
ETCD_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"

#[Cluster]
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
# 创建Etcd服务管理的配置文件
# 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=zap
Restart=always
RestartSec=10s
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
EOF
1
2
3
4
5
# 更新系统配置
# systemctl daemon-reload

# 开机自启动Etcd服务
# 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
# 拷贝Etcd的所有文件到各个Node节点
# scp -r /opt/etcd/ root@k8s-node1:/opt/
# scp -r /opt/etcd/ root@k8s-node2:/opt/

# 拷贝Etcd服务管理的配置文件到各个Node节点
# 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/

# 在其他2个Node节点里分别编辑Etcd的配置文件,包括更改当前节点的名称("ETCD_NAME")和IP,注意除了 "ETCD_INITIAL_CLUSTER" 配置项里的IP不需要更改,其他IP都需要更改为当前节点的IP
# vim /opt/etcd/cfg/etcd.conf

# 在其他2个Node节点里分别设置Etcd服务开机自启动
# 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
# 启动Etcd服务(多个节点必须同时启动)
# systemctl start etcd

# 查看Etcd服务的运行状态
# systemctl status etcd

# 查看Etcd服务的启动日志(可用于排查启动问题)
# journalctl -u etcd.service

若 Etcd 集群启动成功,可以在任意一个 Etcd 节点里执行以下命令来查看集群的运行状态,比如 Leader 选举结果(请自行更改 Etcd 集群节点的 IP 和端口,切忌直接拷贝执行以下命令

1
2
# 查看Etcd集群的运行状态
# /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
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
# 关闭Etcd服务
# systemctl stop etcd

# 删除Etcd服务的数据目录
# rm -rf /var/lib/etcd/default.etcd

# 重启Etcd服务(多个Etcd节点必须同时重启)
# systemctl start etcd

# 查看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 工具生成证书的详细使用教程请看 这里
生成 API Server 证书

创建存放证书的目录

1
2
# 这里为了避免与上面生成的 Etcd 证书混淆,创建一个新的证书目录,然后在该目录下存放 Kubernetes 集群所需的证书
mkdir -p ~/tls/k8s/

进入存放证书的目录

1
2
# 进入证书目录
cd ~/tls/k8s/

创建 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

创建用于生成 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(VIP)地址(比如 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 server-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
15
16
17
18
19
20
21
# 查看当前目录下的文件
# ls
ca-config.json ca-csr.json server-csr.json

# 生成CA证书
# cfssl gencert -initca ca-csr.json | cfssljson -bare ca -

# 查看生成的CA证书
# ls ca*.pem
ca-key.pem ca.pem

# 使用自签 CA 证书签发 API Server 的 HTTPS 证书,"-profile" 参数的值必须与 `ca-config.json` 配置文件中的值一致
# cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes server-csr.json | cfssljson -bare server

# 查看生成的 API Server 证书
# ls server*.pem
server-key.pem server.pem

# 验证生成的 API Server 证书
# cfssl-certinfo -cert server.pem
# openssl x509 -noout -text -in server.pem

将上面生成的 CA 证书和 API Server 证书拷贝到本地目录里面

1
2
3
4
5
# 创建存放证书的本地目录
# mkdir -p /opt/kubernetes/ssl/

# 拷贝 CA 证书和 API Server 证书到本地目录
# cp ~/tls/k8s/*.pem /opt/kubernetes/ssl/

将上面生成的 CA 证书和 API Server 证书拷贝到其他 Master 节点里面

1
2
3
4
5
# 在其他 Master 节点上分别创建存放证书的目录
# ssh root@k8s-master2 "mkdir -p /opt/kubernetes/ssl/"

# 拷贝 CA 证书和 API Server 证书到其他 Master 节点里面
# scp ~/tls/k8s/*.pem root@k8s-master2:/opt/kubernetes/ssl/

将上面生成的 CA 证书拷贝到所有 Node 节点里面

1
2
3
4
5
6
7
8
9
# 在所有 Node 节点上分别创建存放证书的目录
# 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/"

# 拷贝 CA 证书到所有 Node 节点里面
# 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/
生成 Kube-Proxy 证书

进入存放证书的目录

1
2
# 进入证书目录
cd ~/tls/k8s/

创建用于生成 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": "k8s",
"OU": "System"
}
]
}
EOF

使用自签 CA 签发 Kube-Proxy 证书

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-proxy-csr.json server.csr server-csr.json server-key.pem server.pem

# 使用自签 CA 证书签发 Kube-Proxy 证书,"-profile" 参数的值必须与 `ca-config.json` 配置文件中的值一致
# cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes kube-proxy-csr.json | cfssljson -bare kube-proxy

# 查看生成的 Kube-Proxy 证书
# ls kube-proxy*.pem
kube-proxy-key.pem kube-proxy.pem

# 验证生成的 Kube-Proxy 证书
# cfssl-certinfo -cert kube-proxy.pem
# openssl x509 -noout -text -in kube-proxy.pem

将上面生成的 Kube-Proxy 证书拷贝到本地目录里面

1
2
# 拷贝 Kube-Proxy 证书到本地目录
# cp ~/tls/k8s/kube-proxy*.pem /opt/kubernetes/ssl/

将上面生成的 Kube-Proxy 证书拷贝到其他 Master 节点里面

1
2
# 拷贝 Kube-Proxy 证书到其他 Master 节点里面
# scp ~/tls/k8s/kube-proxy*.pem root@k8s-master2:/opt/kubernetes/ssl/

将上面生成的 Kube-Proxy 证书拷贝到所有 Node 节点里面

1
2
3
4
# 拷贝 Kube-Proxy 证书到所有 Node 节点里面
# 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 地址,请记得自行更改以下配置内容里的 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 << EOF
KUBE_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=192.168.2.191 \\
--secure-port=6443 \\
--advertise-address=192.168.2.191 \\
--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/server.pem \\
--kubelet-client-key=/opt/kubernetes/ssl/server-key.pem \\
--tls-cert-file=/opt/kubernetes/ssl/server.pem \\
--tls-private-key-file=/opt/kubernetes/ssl/server-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-kube-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
# 创建Token文件
# cat > /opt/kubernetes/cfg/token.csv << EOF
c47ffb939f5ca36231d9e3121a252940,kubelet-bootstrap,10001,"system:node-bootstrapper"
EOF

Token 文件的数据格式是 Token,用户名,UID,用户组,其中 UID 仅作标识使用,可以是任意唯一值;用户组 system:node-bootstrapper 是 Kubelet TLS Bootstrap 必需的组;Token 可以使用以下命令自行生成并替换掉

1
2
# 生成Token
# 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
# 创建 API Server 服务管理的配置文件
# 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

# 开机自启动 API Server 服务
# systemctl enable kube-apiserver

# 启动 API Server 服务
# systemctl start kube-apiserver

# 查看 API Server 服务的运行状态
# systemctl status kube-apiserver

# 查看 API Server 服务的启动日志(可用于排查启动问题)
# 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/haproxy
HaProxy 的配置文件/etc/haproxy/haproxy.cfg
HaProxy 的 PID 文件/var/lib/haproxy/haproxy.pid
HaProxy 的 Socket 文件/var/lib/haproxy/stats
HaProxy 的 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/backendhttp连接模式,可选 httptcp。可以使用 tcp 来实现 Kubernetes API Server 代理。
balancebackendroundrobin负载均衡算法,可选 roundrobinleastconnsource 等。
stats authlisten 设置统计页面认证用户名和密码,例如 admin:admin
stats refreshlisten10s统计页面刷新时间,单位秒。
stats realmlistenHTTP 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

# 开机自启动HaProxy
# systemctl enable haproxy

立刻启动 HaProxy 服务

1
2
3
4
5
# 启动HaProxy
# systemctl start haproxy

# 查看HaProxy的运行状态
# systemctl status haproxy

当 HaProxy 的配置文件发生变更后,可以执行热加载(也叫平滑重载,不会中断现有连接),让配置文件生效

1
2
# 热加载HaProxy配置文件
# systemctl reload haproxy

6. 验证服务

通过 HaProxy 的 16443 反向代理端口(可自定义),验证 HaProxy 的反向代理功能是否可以工作,正常情况下 API Server 的健康检测接口会返回字符串 ok

1
2
# 验证HaProxy的反向代理功能
# 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/keepalived
Keepalived 的配置文件/usr/local/keepalived/etc/keepalived/keepalived.conf
Keepalived 的 Systemd 服务配置文件(自动生成)/usr/lib/systemd/system/keepalived.service
Keepalived 检测 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

# 生成构建文件(Makefile)
# ./configure --prefix=/usr/local/keepalived

# 编译源码
# 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
#!/bin/bash

# 获取HaProxy进程数量
HAPROXY_COUNT=$(pgrep -x haproxy | wc -l)

# 如果没有HaProxy进程,则返回非 0,Keepalived 会认为节点失效
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 脚本的绝对路径。返回 0 表示正常,返回非 0 表示失败。
interval2检测脚本执行间隔(秒),指定 Keepalived 多久执行一次健康检查脚本。
weight-100节点权重变化值,检测脚本失败时降低优先级或成功时增加优先级。负值表示降低权重,正值表示增加权重。
fall3连续失败次数阈值,当检测脚本连续失败次数达到 fall 时,节点会认为服务不可用,并扣减权重或触发 VIP 切换。
rise2连续成功次数阈值,当检测脚本连续成功次数达到 rise 时,节点会认为服务恢复,并恢复权重初始值。
stateMASTER节点初始角色(MASTERBACKUP),最终由优先级决定。
interfaceenp0s3VRRP 使用的网络接口名称,可通过 ifconfig 命令得知。
virtual_router_id51VRRP 组 ID,两个节点必须一致。
priority200节点优先级(权重),数值越大优先级越高。
advert_int1VRRP 心跳间隔(秒),默认 1 秒。
auth_typePASSVRRP 认证方式(PASSAH)。
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

# 开机自启动Keepalived
# systemctl enable keepalived

立刻启动 Keepalived 服务

1
2
3
4
5
# 启动Keepalived
# systemctl start keepalived

# 查看Keepalived的运行状态
# systemctl status keepalived

当 Keepalived 的配置文件发生变更后,可以重启服务让其生效

1
2
# 重启Keepalived
# systemctl restart keepalived

6. 验证服务

通过 Keepalived 的 VIP(比如 192.168.2.100) + HaProxy 的 16443 反向代理端口(可自定义),验证 Keepalived 的 VIP 和 HaProxy 的反向代理功能是否可以正常工作,正常情况下 API Server 的健康检测接口会返回字符串 ok请自行更改 VIP 的地址

1
2
# 验证Keepalived的VIP与HaProxy的反向代理功能
# 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 地址与状态信息,请自行更改网卡接口名称
1
2
# 查看网卡的详细信息
# ip a s enp0s3

输出示例如下,可以看到 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
# 关闭Haproxy
# systemctl stop haproxy
1
2
# 查看Keepalived的运行状态
# 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 地址与状态信息,请自行更改网卡接口名称
1
2
# 查看网卡的详细信息
# ip a s enp0s3

输出示例如下,可以看到 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
# 启动Haproxy
# systemctl start haproxy
1
2
# 查看网卡的详细信息
# ip a s enp0s3

输出示例如下,可以看到 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 保留换行符。其中 --master 配置参数需要指定为 Keepalived 的虚拟 IP 地址(比如 192.168.2.100) + HaProxy 反向代理的端口(比如 16443),而不是直接使用 API Server 的 IP 地址。同时,--cluster-cidr--service-cluster-ip-range 配置参数的值不要随意更改,否则可能会影响 Kubernetes 集群的正常运行。请记得自行更改以下的 --master 配置参数,切忌直接拷贝使用,其他配置参数通常不需要更改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 创建配置文件
# cat > /opt/kubernetes/cfg/kube-controller-manager.conf << EOF
KUBE_CONTROLLER_MANAGER_OPTS="--logtostderr=false \\
--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 \\
--cluster-signing-duration=87600h0m0s"
EOF

配置参数说明

参数说明
--logtostderr是否将日志输出到标准错误(false 表示写到文件,需要配合 --log-dir 使用)。
--v日志详细级别,数字越大输出越详细(常用 2 作为调试级别)。
--log-dir指定日志文件存放目录。
--leader-elect是否启用领导者选举机制,用于多个 Controller Manager 实例之间选主。
--master指定 API Server 的地址,K8s 高可用集群中需要使用 Keepalived 的虚拟 IP(VIP)来替代。
--bind-addressController Manager 进程绑定监听的地址(127.0.0.1 表示仅监听本地)。
--allocate-node-cidrs是否为每个节点自动分配 Pod CIDR 地址段(通常与 --cluster-cidr 搭配使用)。
--cluster-cidrPod 网络的 CIDR 范围,分配给各节点的 Pod 网段从这里划分(示例:10.244.0.0/16)。
--service-cluster-ip-rangeService 虚拟 IP 地址段,用于分配 Cluster IP 类型的 Service。
--cluster-signing-cert-file集群签发证书时使用的 CA 证书路径(用于 kubelet、组件证书等)。
--cluster-signing-key-file集群签发证书时使用的 CA 私钥路径。
--root-ca-file提供给 Service Account 及其他客户端的根 CA 证书文件路径。
--service-account-private-key-file用于为 Service Account Token 签名的私钥文件路径。
--cluster-signing-duration集群签发证书的有效期(示例:87600h0m0s 表示 10 年)。

配置 Systemd 管理 Controller Manager 服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 创建 Controller Manager 服务管理的配置文件
# 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

# 开机自启动 Controller Manager 服务
# systemctl enable kube-controller-manager

# 启动 Controller Manager 服务
# systemctl start kube-controller-manager

# 查看 Controller Manager 服务的运行状态
# systemctl status kube-controller-manager

# 查看 Controller Manager 服务的启动日志(可用于排查启动问题)
# journalctl -u kube-controller-manager.service
部署 Scheduler

创建 Scheduler 配置文件,使用转义符 \\ 是为了使 EOF 保留换行符。其中 --master 配置参数需要指定为 Keepalived 的虚拟 IP 地址(比如 192.168.2.100)+ HaProxy 反向代理的端口(比如 16443),而不是直接使用 API Server 的 IP 地址。请记得自行更改以下的 --master 配置参数,切忌直接拷贝使用,其他配置参数通常不需要更改

1
2
3
4
5
6
7
8
9
# 创建配置文件
# cat > /opt/kubernetes/cfg/kube-scheduler.conf << EOF
KUBE_SCHEDULER_OPTS="--logtostderr=false \\
--v=2 \\
--log-dir=/opt/kubernetes/logs \\
--leader-elect \\
--master=192.168.2.100:16443 \\
--bind-address=127.0.0.1"
EOF

配置参数说明

参数说明
--logtostderr是否将日志输出到标准错误(false 表示写到文件,需要配合 --log-dir 使用)。
--v日志详细级别,数字越大输出越详细(2 常用于调试)。
--log-dir指定日志文件存放目录。
--leader-elect启用领导者选举机制,用于在多个 Scheduler 实例之间选主(保证同一时刻只有一个活跃调度器)。
--master指定 API Server 的地址,K8s 高可用集群中需要使用 Keepalived 的虚拟 IP(VIP)来替代。
--bind-addressScheduler 进程绑定监听的地址(127.0.0.1 表示仅监听本地)。

配置 Systemd 管理 Scheduler 服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 创建 Scheduler 服务管理的配置文件
# 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

# 开机自启动 Scheduler 服务
# systemctl enable kube-scheduler

# 启动 Scheduler 服务
# systemctl start kube-scheduler

# 查看 Scheduler 服务的运行状态
# systemctl status kube-scheduler

# 查看 Scheduler 服务的启动日志(可用于排查启动问题)
# journalctl -u kube-scheduler.service

在 Master 节点中,当 API Server、Controller Manager、Scheduler 组件都成功启动后,可以通过 kubectl 工具查看当前 Kubernetes 集群组件的运行状态

1
2
3
4
5
6
7
8
# 查看核心组件的运行状态(不包括 API Server)
# kubectl get cs
NAME 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 << EOF
KUBELET_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 << EOF
kind: 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

生成 bootstrap.kubeconfig 配置文件,请自行修改这里环境变量中的 API Server 地址,且指定为 Keepalived 的虚拟 IP 地址(比如 192.168.2.100) + HaProxy 反向代理的端口(比如 16443),切忌直接拷贝环境变量的值

1
2
3
# 临时添加环境变量
KUBE_APISERVER="https://192.168.2.100:16443" # API Server 的地址(IP:PORT),需要使用 Keepalived 的虚拟 IP 地址 + HaProxy 反向代理的端口
TOKEN="c47ffb939f5ca36231d9e3121a252940" # 必须与 token.csv 文件里的 token 保持一致
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

# 切换到 default 上下文
# kubectl config use-context default --kubeconfig=bootstrap.kubeconfig
1
2
# 移动配置文件
# mv bootstrap.kubeconfig /opt/kubernetes/cfg

配置 Systemd 管理 Kubelet 服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 创建 Kubelet 服务管理的配置文件
# 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

# 开机自启动 Kubelet 服务
# systemctl enable kubelet

# 启动 Kubelet 服务
# systemctl start kubelet

# 查看 Kubelet 服务的运行状态(若有网络错误信息,可暂时忽略)
# systemctl status kubelet

# 查看 Kubelet 服务的启动日志(可用于排查启动问题)
# journalctl -u kubelet.service

当 Kubelet 服务正常启动后,会自动向 API Server 发起 CSR(证书签名请求)。一旦该请求被批准,Kubelet 就会获得客户端证书,并开始与 API Server 通信,从而将它所在的 Node 节点加入到 Kubernetes 集群中,所以无需使用 kubeadm join 命令手动加入节点。

特别注意

由于还没有安装 CNI 网络插件,因此在查看 Kubelet 服务的运行状态时,可能会看到 Container runtime network not ready ...... network plugin is not ready: cni config uninitialized 这样的网络错误信息,可以暂时忽略掉。

在任意一个 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
# 查看 Kubernetes 集群中的所有证书签名请求
# kubectl get csr
NAME 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

# 查看集群中所有节点的状态(由于 CNI 网络插件还没有部署,因此节点会处于没有准备就绪状态 - NotReady)
# kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-master1 NotReady <none> 20h v1.19.10
k8s-node1 NotReady <none> 4m4s v1.19.10
k8s-node2 NotReady <none> 3m46s v1.19.10
k8s-node3 NotReady <none> 3m53s v1.19.10
部署 Kube-Proxy

创建 Kube-Proxy 的配置文件,使用转义符 \\ 是为了使 EOF 保留换行符,以下配置内容可以直接拷贝使用

1
2
3
4
5
6
# cat > /opt/kubernetes/cfg/kube-proxy.conf << EOF
KUBE_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
# 创建配置参数文件
# cat > /opt/kubernetes/cfg/kube-proxy-config.yml << EOF
kind: 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
EOF

生成 kube-proxy.kubeconfig 文件,请自行修改这里环境变量中的 API Server 地址,切忌直接拷贝环境变量的值

1
2
# 临时添加环境变量
KUBE_APISERVER="https://192.168.2.100:16443" # API Server 的地址(IP:PORT),需要使用 Keepalived 的虚拟 IP 地址 + HaProxy 反向代理的端口
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

# 切换到 default 上下文
# 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
# 创建 Kube-Proxy 服务管理的配置文件
# 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

# 开机自启动 Kube-Proxy 服务
# systemctl enable kube-proxy

# 启动 Kube-Proxy 服务
# systemctl start kube-proxy

# 查看 Kube-Proxy 服务的运行状态
# systemctl status kube-proxy

# 查看 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),不需要在 Node 节点重复执行该操作。换言之,以下操作只在 Master 节点上执行

下载 CNI 网络插件(Flannel)的配置文件

1
2
3
4
5
# 下载配置文件
# wget -P /opt/kubernetes/cfg/ https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

# 部署 CNI 网络插件(Flannel),成功后所有节点会自动下载相应的 Docker 镜像
# kubectl apply -f /opt/kubernetes/cfg/kube-flannel.yml

查看 Kubernetes 集群中所有节点的状态

1
2
3
4
5
6
7
# 查看集群中所有节点的状态(当网络插件部署完成后,所有节点都会处于准备就绪状态 - Ready)
# kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-master1 Ready <none> 23h v1.19.10
k8s-node1 Ready <none> 173m v1.19.10
k8s-node2 Ready <none> 172m v1.19.10
k8s-node3 Ready <none> 172m v1.19.10

CNI 网络插件的安装细节

这个 kubectl apply 命令会创建一个 DaemonSet 类型的资源,而 DaemonSet 的特性是:会在 Kubernetes 集群中所有「可调度」的 Node 节点上运行一个副本。换言之,在 Master 节点上,通过 kubectl apply 命令安装 CNI 网络插件后,默认会将 CNI 网络插件部署到整个 Kubernetes 集群的所有节点(包括 Master 和 Node),无需手动在每个节点上重复执行 kubectl apply 命令来安装 CNI 网络插件。

为什么必须安装 CNI 网络插件

  • Kubernetes 本身不包含网络实现,但它要求集群中的所有 Pod 能够彼此通信(无论位于哪个节点),这是 Kubernetes 网络模型的基本要求。
  • 使用 kubeadm init 初始化 Master 节点后,默认只有控制平面功能,没有网络功能。
  • 当没有安装 CNI 网络插件时,Pod 会卡在 ContainerCreating 状态,因为找不到网络(CNI)配置。
  • 当安装 CNI 网络插件(如 Flannel)后,多个节点之间的 Pod 才能通信和正常运行。

创建授权 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 << EOF
apiVersion: 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

参考资料