基于二进制包方式搭建 Kubernetes 单 Master 集群

大纲

Kubernetes 集群架构介绍

Kubernetes 集群架构概览

kubernetes-framework-2

Kubernetes 集群架构组件

kubernetes-framework-1

  • Master(主控节点):Kubernetes 集群控制节点,负责对集群进行调度管理,接受集群外的用户去集群操作请求。Master 由 API Server、Scheduler、Controller Manager、Etcd 存储系统组成

    • Scheduler:节点调度,选择 Node 节点来应用部署
    • API Server:集群统一入口,以 RESTful 接口将数据交给 Etcd 进行存储
    • Controller Manager:处理集群中的常规后台任务,一个资源对应一个控制器
    • Etcd 存储系统:用于存储集群相关的数据
  • Node(工作节点):Kubernetes 集群工作节点,负责运行用户业务应用容器,Node 由 Kubelet、Kube-Proxy 和 Container Runtime 组成

    • Kubelet:负责 Pod 对应的容器的创建、启停管理,与 Master 节点协作,实现集群管理的基本功能
    • Kube-Proxy:提供 Kubernetes 的通信与负载均衡功能的重要组件

Kubernetes 依赖容器运行时(Container Runtime)来管理和运行容器。

  • 历史上,Kubernetes 主要使用 Docker 作为容器运行时。Kubernetes 通过内置的 dockershim 组件与 Docker 实现兼容。
  • 从 Kubernetes 1.20 版本开始,官方宣布弃用内置的 dockershim 组件,逐步移除对 Docker 作为直接容器运行时的支持。
  • 现代 Kubernetes 推荐使用符合 Container Runtime Interface (CRI) 标准的容器运行时,主要有:
    • containerd:由 Docker 社区捐赠的轻量级容器运行时,稳定且高效,广泛使用。
    • CRI-O:专门为 Kubernetes 设计的容器运行时,注重轻量和安全。

Etcd 是什么

  • 在分布式系统中,Etcd 是一个高可用、强一致性的分布式键值存储系统,在 Kubernetes 等系统中扮演着核心元数据存储和协调中心的角色。
  • Etcd 属于更底层的基础组件,使用 Raft 算法实现强一致性(满足 CAP 中的 C 和 P),提供了一致性的 KV 存储、watch、lease、事务等机制。

Kubernetes 集群搭建说明

集群搭建要求

搭建 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 集群搭建类型分为单 Master 集群和多 Master 集群两种(如下图所示),为了提高集群的可用性,生产环境一般采用多 Master 集群方案。

kubernetes-cluster-plan

搭建单 Master 集群

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

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

术语说明

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

版本说明

软件版本安装方式
CentOS7.9多个独立虚拟机
Docker19.03.9二进制安装包
Kubernetes1.19.10二进制安装包
Etcd3.4.9二进制安装包

服务器规划

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

Host 名称角色 IPCPU 核数内存磁盘安装的组件备注
k8s-mastermaster192.168.2.191>= 6C>=6G>=60Gkube-apiserver,kube-controller-manager,kube-scheduler,kubelet,kube-proxy,cni plugin, docker,etcd 部署 Etcd 集群节点
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 集群节点(保证 Etcd 集群有奇数个节点)

如果希望 Master 节点仅用于运行控制平面组件(如 API Server、Controller Manager 和 Scheduler),不承载用户 Pod 的调度和运行任务,则 Master 节点可以不部署 Kubelet、Kube-Proxy 和 Docker(容器运行时),如下表所示:

Host 名称角色 IPCPU 核数内存磁盘安装的组件备注
k8s-mastermaster192.168.2.191>= 2C>=2G>=20Gkube-apiserver,kube-controller-manager,kube-scheduler,cni plugin,etcd 部署 Etcd 集群节点,不部署 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 集群节点(保证 Etcd 集群有奇数个节点)

kubernetes-kubeadm-1

操作系统初始化

特别注意

  • 在 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-masterk8s-node1k8s-node2k8s-node3

1
# hostnamectl set-hostname <hostname>

添加 hosts 配置信息

1
2
3
4
5
6
# 添加hosts
# vim /etc/hosts
192.168.2.191 k8s-master
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 节点上执行。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 台机器组建 Etcd 集群,可容忍 2 台机器故障。为了节省机器,这里复用 Kubernetes 节点的机器,也可以独立于 Kubernetes 集群之外部署,只要 Kubernetes 的 API Server 能够连接上就行。

生成 Etcd 证书

特别注意

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

创建存放 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
# cat << EOF | tee server-csr.json
{
"CN": "etcd",
"hosts": [
"192.168.2.191",
"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

# 生成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 节点里面安装 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 节点里面拷贝上面生成的 CA 证书和 Etcd 证书

1
2
# 拷贝证书
# cp ~/tls/etcd/*.pem /opt/etcd/ssl

在 Master 节点里面创建 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 节点里面使用 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 节点里面拷贝所有 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 节点上执行,千万不要在每个 Node 节点上单独生成 API Server 或者 Kube-Proxy 证书,否则可能会因证书不一致而导致 Kubernetes 集群启动失败
  • 简而言之,所有证书(包括 API Server、Kube-Proxy 等证书)都是在 Master 节点上面生成,Node 节点不参与生成证书。以后向 Kubernetes 集群中添加新节点时,只要将对应的证书拷贝到新节点上即可
  • 由于本文搭建的是单 Master 集群,控制平面组件(包括 API Server、Controller Manager 和 Scheduler)都运行在同一节点上,因此它们之间的通信未启用 HTTPS,即没有使用 CA 证书进行加密和验证。
  • 但是,Kubelet 和 Kube-Proxy 往往部署在与 API Server 不同的节点上,因此它们与 API Server 之间的通信启用了 HTTPS,并使用 CA 证书进行加密和身份验证。另外,API Server 与 Etcd 集群之间同样会进行加密和身份验证。
  • 值得一提的是,通过 CFSSL 工具生成证书的详细使用教程请看 这里

这里为了避免与上面生成的 Etcd 证书混淆,创建一个新的证书目录,然后在该目录下生成 Kubernetes 集群所需的证书

1
2
# 创建证书目录
mkdir -p ~/tls/k8s/
生成 API Server 证书

进入存放证书的目录

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 网段配置)。

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 << EOF | tee server-csr.json
{
"CN": "kubernetes",
"hosts": [
"10.0.0.1",
"127.0.0.1",
"192.168.2.191",
"192.168.2.112",
"192.168.2.131",
"192.168.2.236",
"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 证书拷贝到所有 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 证书拷贝到所有 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 节点上执行,包括部署 API Server、Controller Manager、Scheduler 这三大核心组件

下载二进制文件

下载地址

  • 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 签发的有效客户端证书才能与 API Server 通信。当 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 用户允许请求(申请)证书

1
# kubectl create clusterrolebinding kubelet-bootstrap --clusterrole=system:node-bootstrapper --user=kubelet-bootstrap
部署 Controller Manager

创建 Controller Manager 的配置文件,使用转义符 \\ 是为了使 EOF 保留换行符,一般情况下,以下配置信息可以直接拷贝使用。特别注意,其中的 --cluster-cidr--service-cluster-ip-range 配置参数的值不要随意更改,否则可能会影响 Kubernetes 集群的正常运行

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=127.0.0.1:8080 \\
--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 的地址(127.0.0.1:8080 表示通过本地非安全端口连接 API Server,不使用 CA 证书)。
--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 保留换行符,一般情况下,以下配置信息可以直接拷贝使用

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=127.0.0.1:8080 \\
--bind-address=127.0.0.1"
EOF

配置参数说明

参数说明
--logtostderr是否将日志输出到标准错误(false 表示写到文件,需要配合 --log-dir 使用)。
--v日志详细级别,数字越大输出越详细(2 常用于调试)。
--log-dir指定日志文件存放目录。
--leader-elect启用领导者选举机制,用于在多个 Scheduler 实例之间选主(保证同一时刻只有一个活跃调度器)。
--master指定连接 API Server 的地址(127.0.0.1:8080 表示通过本地非安全端口连接 API Server,不使用 CA 证书)。
--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 集群组件(不包括 API Server)的运行状态

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 地址,切勿直接拷贝环境变量的值

1
2
3
# 临时添加环境变量
KUBE_APISERVER="https://192.168.2.191:6443" # API Server 的地址(IP:PORT),一般是 Master 节点的 IP 和 API Server 的端口(6443)
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

提前手动拉取 Pause 镜像(Pod 的基础镜像,用于 Pod 网络命名空间),避免后面部署 CNI 网络插件时,出现 Kubelet 自动拉取 Pause 镜像失败的问题(该镜像的名称和版本在 kubelet.conf 配置文件中指定)

1
2
# 提前拉取Pod基础镜像(可选操作)
# 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
# 创建 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 服务的运行状态时,可能会看到以下错误信息。这是因为目前还没有安装 CNI 网络插件,因此该错误可以暂时忽略掉

1
2
3
11月 11 21:15:55 k8s-node1 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-node1 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-node1 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
# 查看 Kubernetes 集群中的所有证书签名请求
# kubectl get csr
NAME AGE SIGNERNAME REQUESTOR CONDITION
node-csr-BRKQdidf63HB1ZjRZLvYKeNxW0p0vSN3UG_Av8XJO3I 95s kubernetes.io/kube-apiserver-client-kubelet kubelet-bootstrap Pending
node-csr-Lcn5xLuMioS9H1aa8k9qATIAAdNIhdlT_OpCco4ePoU 97s kubernetes.io/kube-apiserver-client-kubelet kubelet-bootstrap Pending
node-csr-jDiiH16kcOjiYPjWysqaKbudnf1z948P937PHRU5VzQ 94s kubernetes.io/kube-apiserver-client-kubelet kubelet-bootstrap Pending

# 批准证书签名请求(请根据上面输出的证书签名请求名称,自行更改命令行参数)
# kubectl certificate approve node-csr-BRKQdidf63HB1ZjRZLvYKeNxW0p0vSN3UG_Av8XJO3I
# kubectl certificate approve node-csr-Lcn5xLuMioS9H1aa8k9qATIAAdNIhdlT_OpCco4ePoU
# kubectl certificate approve node-csr-jDiiH16kcOjiYPjWysqaKbudnf1z948P937PHRU5VzQ

# 查看集群中所有节点的状态(由于 CNI 网络插件还没有部署,因此节点会处于没有准备就绪状态 - NotReady)
# kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-master 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.191:6443" # API Server 的地址(IP:PORT),一般是 Master 节点的 IP 和 API Server 的端口(6443)
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)的 YAML 配置文件

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

在 Kubernetes 集群中部署 CNI 网络插件(Flannel)

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

查看 CNI 网络插件的所有 Pod

1
2
# 查看所有 Pod
# kubectl get pods -n kube-flannel
1
2
3
4
5
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

查看 CNI 网络插件的所有 DaemonSet

1
2
# 查看所有 DaemonSet
# kubectl get ds -n kube-flannel
1
2
NAME              DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
kube-flannel-ds 4 4 4 4 4 <none> 6m49s

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

1
2
# 查看集群中所有节点的运行状态(当 CNI 网络插件部署完成后,所有节点都会自动切换到准备就绪状态 - Ready)
# kubectl get node
1
2
3
4
5
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 节点上运行一个 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 << 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

为什么必须安装 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
# 查看所有节点的运行状态
# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master 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

# 查看核心组件的运行状态(不包括 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 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
# 创建Nginx
# kubectl create deployment nginx --image=nginx

# 暴露Nginx的端口
# kubectl expose deployment nginx --port=80 --type=NodePort --target-port=80

# 查看Pod列表
# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-6799fc88d8-dkltf 1/1 Running 0 12m 10.244.2.2 k8s-node3 <none> <none>

# 查看Service列表
# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 2d1h
nginx NodePort 10.0.0.200 <none> 80:31062/TCP 13m

在 Kubernetes 集群外部,通过浏览器访问 http://192.168.2.112:31062,其中 IP 可以是任意集群节点的 IP 地址,端口由 kubectl get svc 命令可得知。若 Ngninx 容器在 Kubernetes 集群中创建并启动成功,则浏览器可以正常访问 Nginx 的首页(如下图所示),如下图所示:

若希望删除刚在 Kubernetes 集群创建的 Nginx 容器,可以在 Master 节点执行以下命令:

1
2
3
4
5
# 删除Service
# kubectl delete service nginx

# 删除Deployment
# kubectl delete deployment nginx

部署 CoreDNS(可选)

特别注意

  • 以下所有操作都是仅在 Kubernetes 集群的 Master 节点上执行,请保证在部署 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 管理,需手动维护、高可用难度大
  • 容器化部署 CoreDNS 的几个关键优势如下:
优势说明
符合 Kubernetes 官方架构 CoreDNS 本身就是官方默认的集群 DNS 方案,部署成 Pod 完全符合设计思路。
自动高可用可通过 Replica SetDeployment 管理多个副本,某个 Pod 异常退出会被自动重建。
易扩容 / 升级直接通过 kubectl scalekubectl 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
# 编辑 Kubelet 的 YAML 配置文件,确保包含以下配置信息
# vim /opt/kubernetes/cfg/kubelet-config.yml
1
2
3
clusterDNS:
- 10.0.0.2 # 指向 CoreDNS Service 的 ClusterIP
clusterDomain: cluster.local # 默认域名后缀
  • 修改 CoreDNS 的 YAML 配置文件(coredns.yaml),只需要修改 spec.replicasspec.clusterIP 这两个配置
1
2
# 编辑 CoreDNS 的 YAML 配置文件,更改以下两个配置项
# 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: 2 # 指定 CoreDNS 的副本数量
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 # 指定 CoreDNS Service 的 ClusterIP

......
  • 应用 CoreDNS 的 YAML 配置文件
1
# kubectl apply -f /opt/kubernetes/cfg/coredns.yaml
  • 验证 CoreDNS 的 Pod 是否启动成功
1
# kubectl get pods -n kube-system -l k8s-app=kube-dns
1
2
3
NAME                       READY   STATUS    RESTARTS   AGE
coredns-6b9bb479b9-g6t6f 1/1 Running 0 39s
coredns-6b9bb479b9-lfd7j 1/1 Running 0 39s
  • 若 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 节点上执行

  • 查看 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
  • 再次查看 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

管理 Node 节点的组件启动,以下所有操作分别在 Kubernetes 集群的所有 Node 节点上执行

  • 查看 Node 节点组件的运行状态
1
2
# systemctl status kubelet
# systemctl status kube-proxy
  • 若在 Node 节点组件的运行状态中,发现有不可忽略的致命错误提示信息,则严格按以下顺序重启各个组件
1
2
# systemctl restart kubelet
# systemctl restart kube-proxy
  • 再次查看 Node 节点组件的运行状态
1
2
# systemctl status kubelet
# systemctl status kube-proxy

彻底解决启动顺序问题

特别注意

若每次在所有 Kubernetes 集群服务器重启后(比如断电重启),都需要手动按顺序重启每个 Kubernetes 组件,这将非常繁琐。为了彻底解决这个问题,可以使用 Systemd 的 RequiresAfterExecStartPre 特性来控制多个服务之间的启动顺序。

生成 Health-Check 的证书,用于检测 API Server 的健康状态,以下所有操作都是仅在 Kubernetes 集群的 Master 节点上执行

  • 在 Master 节点中,进入证书目录
1
2
# 进入证书目录
cd ~/tls/k8s/
  • 在 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
# 查看当前目录下的文件
# ls
ca-config.json ca.csr ca-csr.json ca-key.pem ca.pem kube-proxy.csr kube-proxy-csr.json kube-proxy-key.pem kube-proxy.pem server.csr server-csr.json server-key.pem server.pem

# 使用自签 CA 证书签发 Health-Check 证书,"-profile" 参数的值必须与 `ca-config.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

# 查看生成的 Health-Check 证书
# ls health-check*.pem
health-check-key.pem health-check.pem

# 验证生成的 Health-Check 证书
# cfssl-certinfo -cert health-check.pem
# openssl x509 -noout -text -in health-check.pem
  • 在 Master 节点中,将上面生成的 Health-Check 证书拷贝到本地目录里面
1
2
# 将 Health-Check 证书拷贝到本地目录里面
# cp ~/tls/k8s/health*.pem /opt/kubernetes/ssl/
  • 在 Master 节点中,将上面生成的 Health-Check 证书拷贝到所有 Node 节点里面
1
2
3
4
# 拷贝 Health-Check 证书到所有 Node 节点里面
# 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 << EOF
apiVersion: 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 << EOF
apiVersion: 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
# 应用 ClusterRole(仅授予只读的健康检测权限)
# kubectl apply -f /opt/kubernetes/cfg/health-check-role.yaml

# 应用 ClusterRoleBinding(将用户绑定到指定的集群角色)
# kubectl apply -f /opt/kubernetes/cfg/health-check-binding.yaml
  • 在所有节点(包括 Master 和 Node)中,分别验证 API Server 健康检测接口的调用(请自行更改 API Server 的 IP 和端口,切勿直接拷贝执行以下命令
1
2
# 若接口可以正常调用,则会返回 ok 字符串
# 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.191:6443/readyz

管理 Master 节点的组件启动,以下所有操作都是仅在 Kubernetes 集群的 Master 节点上执行

  • 在 Master 节点中,重写(覆盖)API Server 服务的 Systemd 配置文件(请自行更改 Etcd 集群节点的 IP 和端口,切勿直接拷贝执行以下命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 重写 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
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

# 重启 API Server 服务
# systemctl restart kube-apiserver

# 查看 API Server 服务的运行状态
# systemctl status kube-apiserver
  • 在 Master 节点中,重写(覆盖)Controller Manager 服务的 Systemd 配置文件(请自行更改 API Server 的 IP 和端口,切勿直接拷贝执行以下命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 重写 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
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.191:6443/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

# 重启 Controller Manager 服务
# systemctl restart kube-controller-manager

# 查看 Controller Manager 服务的运行状态
# systemctl status kube-controller-manager
  • 在 Master 节点中,重写(覆盖)Scheduler 服务的 Systemd 配置文件(请自行更改 API Server 的 IP 和端口,切勿直接拷贝执行以下命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 重写 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
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.191:6443/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

# 重启 Scheduler 服务
# systemctl restart kube-scheduler

# 查看 Scheduler 服务的运行状态
# systemctl status kube-scheduler
  • 在 Master 节点中,重写(覆盖)Kubelet 服务的 Systemd 配置文件(请自行更改 API Server 的 IP 和端口,切勿直接拷贝执行以下命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 重写 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
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.191:6443/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

# 重启 Kubelet 服务
# systemctl restart kubelet

# 查看 Kubelet 服务的运行状态
# systemctl status kubelet
  • 在 Master 节点中,重写(覆盖)Kube-Proxy 服务的 Systemd 配置文件(请自行更改 API Server 的 IP 和端口,切勿直接拷贝执行以下命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 重写 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
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.191:6443/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

# 重启 Kube-Proxy 服务
# systemctl restart kube-proxy

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

管理 Node 节点的组件启动,以下所有操作分别在 Kubernetes 集群的所有 Node 节点上执行

  • 在所有 Node 节点中,重写(覆盖)Kubelet 服务的 Systemd 配置文件(请自行更改 API Server 的 IP 和端口,切勿直接拷贝执行以下命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 重写 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
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.191:6443/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

# 重启 Kubelet 服务
# systemctl restart kubelet

# 查看 Kubelet 服务的运行状态
# systemctl status kubelet
  • 在所有 Node 节点中,重写(覆盖)Kube-Proxy 服务的 Systemd 配置文件(请自行更改 API Server 的 IP 和端口,切勿直接拷贝执行以下命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 重写 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
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.191:6443/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

# 重启 Kube-Proxy 服务
# systemctl restart kube-proxy

# 查看 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
# 查看集群的运行状态
# /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
+----------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| 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
# 查看集群的成员列表
# /opt/etcd/bin/etcdctl --endpoints=https://192.168.2.191:2379 --cacert=/opt/etcd/ssl/ca.pem --cert=/opt/etcd/ssl/server.pem --key=/opt/etcd/ssl/server-key.pem member list
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 节点移除出集群
1
2
# 将节点移除出集群
# /opt/etcd/bin/etcdctl --endpoints=https://192.168.2.191:2379 --cacert=/opt/etcd/ssl/ca.pem --cert=/opt/etcd/ssl/server.pem --key=/opt/etcd/ssl/server-key.pem member remove 5e5716f1d7eba042
  • 在无法正常启动的 etcd-3 节点上,删除本地的 Etcd 数据目录
1
2
3
4
5
# 关闭Etcd服务
# systemctl stop etcd

# 删除Etcd服务的数据目录
# rm -rf /var/lib/etcd/default.etcd
  • 通过其他健康的 Etcd 节点,将无法正常启动的 etcd-3 节点重新加入 Etcd 集群
1
2
# 将节点重新加入集群
# /opt/etcd/bin/etcdctl --endpoints=https://192.168.2.112:2379 --cacert=/opt/etcd/ssl/ca.pem --cert=/opt/etcd/ssl/server.pem --key=/opt/etcd/ssl/server-key.pem member add etcd-3 --peer-urls=https://192.168.2.131:2380
  • 执行命令将 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
# 更改Etcd的配置文件
# sed -i 's/^ETCD_INITIAL_CLUSTER_STATE="new"/ETCD_INITIAL_CLUSTER_STATE="existing"/' /opt/etcd/cfg/etcd.conf

# 重启Etcd服务
# systemctl restart etcd

# 查看Etcd服务的运行状态
# systemctl status etcd
  • 在之前无法正常启动的 etcd-3 节点上,还原本地的 Etcd 配置文件,将 ETCD_INITIAL_CLUSTER_STATE 改为 new,并重启 Etcd 服务
1
2
3
4
5
6
7
8
# 更改Etcd的配置文件
# sed -i 's/^ETCD_INITIAL_CLUSTER_STATE="existing"/ETCD_INITIAL_CLUSTER_STATE="new"/' /opt/etcd/cfg/etcd.conf

# 重启Etcd服务
# systemctl restart etcd

# 查看Etcd服务的运行状态
# systemctl status etcd
  • 在其他健康的 Etcd 节点上,再次查看 Etcd 集群的运行状态,可以发现 etcd-3 节点(会获得新的节点 ID)正常运行
1
2
# 查看集群的运行状态
# /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 | 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,那就可能会引发严重的问题。

Kubelet 无法正常启动

Kubelet 服务断电重启后,通过 systemctl status kubelet 命令查看运行状态时,服务没有处于 active (running) 状态,并且有各种错误信息,尝试多种方法依旧无法正常重启。解决方案如下:

  • 在 Etcd 集群的所有节点上,分别删除 Etcd 的数据目录(切记谨慎操作,Etcd 数据删除后,已有的容器都可能无法正常启动,并且整个 Kubernetes 集群都可能需要重建),并重启 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
  • 在 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
  • 在所有 Node 节点中,按顺序重启以下服务
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
# 查看 Kubernetes 集群中的所有证书签名请求
# kubectl get csr
NAME AGE SIGNERNAME REQUESTOR CONDITION
node-csr-BRKQdidf63HB1ZjRZLvYKeNxW0p0vSN3UG_Av8XJO3I 95s kubernetes.io/kube-apiserver-client-kubelet kubelet-bootstrap Pending
node-csr-Lcn5xLuMioS9H1aa8k9qATIAAdNIhdlT_OpCco4ePoU 97s kubernetes.io/kube-apiserver-client-kubelet kubelet-bootstrap Pending
node-csr-jDiiH16kcOjiYPjWysqaKbudnf1z948P937PHRU5VzQ 94s kubernetes.io/kube-apiserver-client-kubelet kubelet-bootstrap Pending

# 批准证书签名请求(请根据上面输出的证书签名请求名称,自行更改命令行参数)
# kubectl certificate approve node-csr-BRKQdidf63HB1ZjRZLvYKeNxW0p0vSN3UG_Av8XJO3I
# kubectl certificate approve node-csr-Lcn5xLuMioS9H1aa8k9qATIAAdNIhdlT_OpCco4ePoU
# kubectl certificate approve node-csr-jDiiH16kcOjiYPjWysqaKbudnf1z948P937PHRU5VzQ

# 查看集群中所有节点的状态(当 CNI 网络插件部署完成后,所有节点都会自动切换到准备就绪状态 - Ready)
# kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-master 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

Kubelet 发送 CSR 请求失败

Kubelet 服务启动后,无法正常向 API Server 发起 CSR(证书签名请求),解决方案如下:

  • 在当前节点上,清理 Kubelet 运行时产生的文件,并重启 Kubelet 服务
1
2
3
4
5
6
7
8
9
10
11
12
13
# 关闭 Kubelet 服务
# systemctl stop kubelet

# 删除 Kubelet 运行时残留的文件
# rm -rf /opt/kubernetes/ssl/kubelet*
# rm -rf /opt/kubernetes/cfg/kubelet.kubeconfig

# 检查 Kubelet 是否有可用的 CA 证书
# ls /opt/kubernetes/ssl/ca*.pem
/opt/kubernetes/ssl/ca-key.pem /opt/kubernetes/ssl/ca.pem

# 启动 Kubelet 服务
# systemctl start kubelet
  • 在 Master 节点上执行以下命令,批准 Kubelet 证书签名请求并允许 Node 节点加入 Kubernetes 集群
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 查看 Kubernetes 集群中的所有证书签名请求
# kubectl get csr
NAME AGE SIGNERNAME REQUESTOR CONDITION
node-csr-BRKQdidf63HB1ZjRZLvYKeNxW0p0vSN3UG_Av8XJO3I 95s kubernetes.io/kube-apiserver-client-kubelet kubelet-bootstrap Pending
node-csr-Lcn5xLuMioS9H1aa8k9qATIAAdNIhdlT_OpCco4ePoU 97s kubernetes.io/kube-apiserver-client-kubelet kubelet-bootstrap Pending
node-csr-jDiiH16kcOjiYPjWysqaKbudnf1z948P937PHRU5VzQ 94s kubernetes.io/kube-apiserver-client-kubelet kubelet-bootstrap Pending

# 批准证书签名请求(请根据上面输出的证书签名请求名称,自行更改命令行参数)
# kubectl certificate approve node-csr-BRKQdidf63HB1ZjRZLvYKeNxW0p0vSN3UG_Av8XJO3I
# kubectl certificate approve node-csr-Lcn5xLuMioS9H1aa8k9qATIAAdNIhdlT_OpCco4ePoU
# kubectl certificate approve node-csr-jDiiH16kcOjiYPjWysqaKbudnf1z948P937PHRU5VzQ

# 查看集群中所有节点的状态(当 CNI 网络插件部署完成后,所有节点都会自动切换到准备就绪状态 - Ready)
# kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-master 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 网络插件无法正常部署

  • 在 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
# 关闭 Kubelet 服务
# systemctl stop kubelet

# 手动拉取 Pause 镜像(用于 Pod 网络命名空间)
# docker pull lizhenliang/pause-amd64:3.0

# 启动 Kubelet 服务
# systemctl start kubelet
  • 当 Kubelet 服务成功启动,且 CNI 网络插件(Flannel)成功部署后,可以发现当前节点会有以下 Docker 镜像(缺一不可)
1
2
3
4
5
6
# 查看镜像列表
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ghcr.io/flannel-io/flannel v0.27.2 f2b60fe541ef 2 weeks ago 90MB
ghcr.io/flannel-io/flannel-cni-plugin v1.7.1-flannel1 cca2af40a4a9 2 months ago 10.7MB
lizhenliang/pause-amd64 3.0 99e59f495ffa 9 years ago 747kB

参考博客