「Kubernetes」- 搭建高可用集群(集群、搭建、高可用、1.18、kube-vip)

  CREATED BY JENKINSBOT

问题描述

正如标题,在虚拟化环境中,我们将使用 kubeadm 部署高可用集群。我们选择通过 kubeadm 部署集群是因为:通过 kubeadm 部署的集群能够满足最佳实践的要求;并且我们能够通过 kubeadm 命令进行集群管理,比如 Bootstrap Token 管理、集群升级等等;并且我们还未达到使用 Kubespray 的规模。因此,我们遵循着官方的建议,完成对生产环境的部署(虽然以后我们肯定会踩别人踩过的坑,但是现实情况就是这个样子)。我们也知道还有很多第三方的解决方案,但是我们依旧未达到那样的规模,贸然的引入复杂且巨大的技术栈,对于我们来说并不是明智的选择。迭代更新与推翻重建是必然的,我们无法一部到位。所以,对于我们来说,就目前的情形以及未来的规模,选择 kubeadm 部署集群是最好的选择。

创建这篇笔记是为了记录我们的部署过程,并为大家提供部署经验,也便于我们在日后能够进行快速部署,而不是指导大家如何部署高可用的 Kubernetes 集群。虽然参考笔记我们的能够保证集群部署成功,但是由于部分细节未提及,很可能会部署失败,因此我们仍旧建议参考官方文档完成集群的部署。

该笔记将记录:在 Linux 中,如何搭建 Kubernetes 1.20 集群,以及常见问题处理。

补充说明

集群架构

集群架构:我们采用官方的 Stacked Control Plane 架构,以为该架构所需要的主机数量少,而其缺点也是我们能接受的,且从目前来看对未来的影响不大。

节点数量:共计 6 个节点,Control Plane x 3,Worker Node x 3

高可用性:我们采用 kube-vip 来实现高可用,所有节点访问 VIP 来通讯。而且 kube-vip x 3 都运行在 Control Plane 所在的主机中,以减少集群主机数量。

部署信息

操作系统
Ubuntu 18.04.5 LTS(鉴于 CentOS 现在的情况,我们只能另选其他发行版。而服务软件支持的 Linux 发行版是有限的,比如 Docker 支持 CentOS、Debian、Fedora、Ubuntu 发行版。此外,结合使用习惯,我们能在 Debian 和 Ubuntu 之间选择。最后决定选择 Ubuntu 是因为该发行版的工具链更丰富,而 Debian 的应用及内核会稍微落后些。也许 Ubuntu 的稳定性差些,但是都在容忍范围内,不然我们就要容忍工具链陈旧难以排查问题的情况)

服务版本
1)Docker 19.03.12
2)kubeadm 1.20.5
3)Calico 3.18.1

网络信息
1)Control Plane:172.31.253.60(负载均衡的虚拟地址),172.31.253.61 k8scp-01,172.31.253.62 k8scp-02,172.31.253.63 k8scp-03
2)Worker Node:172.31.253.71 k8swn-01,172.31.253.72 k8swn-02,172.31.253.73 k8swn-03

参考文档

我们按照官方文档的要求,完成对 Kubernetes 1.20 集群的部署。以下是我们阅读官方文档的顺序:
1)Container runtimes | Kubernetes
2)Installing Kubernetes with deployment tools | Kubernetes
…. Bootstrapping clusters with kubeadm
…….. Installing kubeadm
…….. Creating a cluster with kubeadm | Kubernetes
…….. Creating Highly Available clusters with kubeadm | Kubernetes

鉴于篇幅有限,我们仅记录与此次部署相关的内容。我们忽略细节描述与解释说明,更多内容,参考官方文档。

解决方案

第一阶段、运行环境检查

执行主机:所有节点

Control Plane、Worker Node

环境要求必须满足,需要检查以下信息:

1)兼容 Linux 主机;

通用:我们使用官方提供的工具部署 kubeadm 来部署 Kubernets 集群,所以需要与其兼容的 Linux 发行版。我们使用 Ubuntu 18.04 发行版。

干净:我们建议的新的主机中部署节点,而不建议在已有其他环境的主机中部署集群。各种环境及配置交织在一起,集群组件可能无法正常处理这些场景,需要更精细的配置。

2)最少 2 CPU + 2G RAM 资源,或以上;

部署 Kubernetes 集群的最小资源要求,否则将无法运行其他应用程序。

节点主机尽量相互远离,在我们的实验环境中,所有的节点都位于相同物理主机,当主机负载便高(磁盘响应慢),etcd 的写入变慢,etcd 内部开始选举,而 etcd 选举期间无法连接,导致 apiserver 无法访问,进而导致 controller-manager 和 scheduler 的频繁重启。

3)节点网络互联(公网或私网皆可);

注意事项:这里的“互联”是指通过网卡的网络地址能够直接互相访问。而不是“阿里云主机能够访问腾讯云主机”的那种互联,这两个主机内网地址是不能互联的,所以是不可行的。但是,Hostinger 的主机与 Vultr 的主机能够满足要求,因为这两家服务商的主机网卡直接绑定公网地址。

此外,如果主机存在多张网卡,则需要正确配置路由,以保证节点能够互相访问。

配置允许 iptables 处理 bridge 流量:

# 添加内核模块
cat > /etc/modules-load.d/k8s.conf <<EOF
br_netfilter
EOF
systemctl restart systemd-modules-load.service

# 修改内核参数
cat > /etc/sysctl.d/k8s.conf <<EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sysctl --system

配置 ipvs 模块,当 kube-proxy 运行在 IPVS mode 下需要使用这些模块:

# 添加内核模块
cat >> /etc/modules-load.d/k8s.conf <<EOF
# run kube-proxy in ipvs mode
ip_vs
ip_vs_rr
ip_vs_wrr
ip_vs_sh
## use nf_conntrack instead of nf_conntrack_ipv4 for Linux kernel 4.19 and later
## nf_conntrack_ipv4
nf_conntrack
EOF
systemctl restart systemd-modules-load.service

配置 Calico 需要使用内核模块:

# **追加** Calico 要求的内核模块
# 那些注释(#)的内核模块,虽然官方文档要求,但是可能因为文档未更新。在 Ubuntu 18.04 TLS 中,我们未找到这些模块
# https://github.com/kubernetes-sigs/kubespray/issues/6289
cat >> /etc/modules-load.d/k8s.conf <<EOF
# for Calico
ip_set
ip_tables
ip6_tables
ipt_REJECT
ipt_rpfilter
ipt_set
nf_conntrack_netlink
# nf_conntrack_proto_sctp
sctp
xt_addrtype
xt_comment
xt_conntrack
# xt_icmp
# xt_icmp6
xt_ipvs
xt_mark
xt_multiport
# xt_rpfilter
xt_sctp
xt_set
xt_u32
ipip
EOF

# 加载内核模块配置
systemctl restart systemd-modules-load.service

禁用 IPv6 协议栈(参考 a 5-second delay 问题):

# 针对 Ubuntu 18.04 发行版,或者其他使用 Netplan 的 Ubunut 发行版

cat > /etc/netplan/99-zzz-no-ipv6.yaml <<EOF
network:
  ethernets:
    enp1s0:
      link-local: []
EOF

netplan apply

# 针对 CentOS Linux release 7.x 发行版,但是不适用 Ubuntu 18.04 发行版

cat > /etc/sysctl.d/99-disable-ipv6.conf <<EOF
net.ipv6.conf.all.disable_ipv6=1
net.ipv6.conf.default.disable_ipv6=1
net.ipv6.conf.default.disable_ipv6=1
EOF

sysctl --system

4)主机信息唯一:主机名、MAC 地址、product_uuid 信息;

查看 cat /sys/class/dmi/id/product_uuid 文件,保证 product_uuid 唯一,否则安装过程会失败。

5)必要端口未被占用;

需要检查的必要端口,参考 Check required ports 页面。我们采用新的主机,所以必要端口不太可能被占用。

同时需要将端口添加到防火墙,以允许访问。我们选择禁用防火墙:

systemctl stop ufw.service
systemctl disable ufw.service

6)禁用交换分区(SWAP);

如果未禁用交换分区,则当内存页的换入与换出,会影响调度性能。官方要求必须管理交换分区:

sed -i -E 's%(^[^#].+\sswap\s.+)%# \1%g' /etc/fstab
swapoff -a

7)检查容器运行环境;

我们已经在最开始部署容器运行环境,并进行相关设置。kubeadm 会根据 Unix Domain Socket 路径来甄别主机中部署的容器环境(Docker /var/run/dockershim.sock;containerd /run/containerd/containerd.sock;CRI-O /var/run/crio/crio.sock)。

我们(1)采用 Docker 作为容器运行环境,并(2)采用 systemd 作为 cgroup manager 以简化问题(而未使用 cgroupfs 作为 cgroup manager)。

安装 Docker 环境:Installing Docker on Ubuntu

修改 Docker 配置:

cat > /etc/docker/daemon.json <<EOF
{
  "live-restore": true,
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2"
}
EOF

systemctl start docker
systemctl enable docker
systemctl reload docker
systemctl restart docker

注意事项:在主机中,不能同时运行两种容器环境,否则会出现错误。

第二阶段、安装 kubeadm/kubectl/kubelet 工具

执行主机:所有节点

Control Plane、Worker Node

接下来,开始安装 kubeadm/kubectl/kubelet 应用;

版本选择

如果不知道应该选择什么版本,建议参考云平台支持的版本:
1)Supported Versions of Kubernetes
2)Amazon EKS Kubernetes versions – Amazon EKS

虽然在官方文档中允许跨一个次版本号,但是为了避免不必要的问题,对于这些工具,我们统一使用 1.18.9 版本(我们从 AWS 支持的版本中选择的)。

# 04/08/2021 我们在部署 1.20.5 版本时(这是目前为止最新的版本),遇到各种失败,尤其是在加入第三个节点时 etcd 无法自动创建。当我们切换到 1.18.9 版本,相同的部署步骤,集群能够正常运行(对于大型服务,不用最新版似乎是黄金定律)

安装服务

# 首先,安装依赖工具
apt-get update \
    && apt-get install -y apt-transport-https ca-certificates curl

# 然后,导入仓库密钥
# 尽管下载 apt-key.gpg 存在困难,但是应该尽量从官方站点下载(请勿随意使用第三方密钥)
# apt-key adv --keyserver keyserver.ubuntu.com --recv-keys  FEEA9169307EA071 8B57C5C2836F4BEB
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -

cat > /etc/apt/sources.list.d/kubernetes.list <<EOF
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
EOF

apt-get update

# 最后,安装工具
# apt-get install -y kubelet=1.18.17-00 kubeadm=1.18.17-00 kubectl=1.18.17-00 --allow-downgrades
apt-get install -y kubelet=1.18.9-00 kubeadm=1.18.9-00 kubectl=1.18.9-00
apt-mark hold kubelet kubeadm kubectl # 禁止更新

我们这里保存 ./apt-key.gpg 文件

第三阶段、配置 kube-vip 环境

执行主机:首个 Control Plane 节点(k8scp-01)

官方文档已给出高可用方案,参考 kubeadm/ha-considerations.md at master · kubernetes/kubeadm 文档。

我们采用其中的 kube-vip 实现高可用,替代以往的 keepalivd + haproxy 方案。

注意事项,如果采用阿里云主机部署,需要购买 HaVIP 服务,才能实现 VIP 的访问。否则,即使 VIP 绑定到某台主机中,其他主机也无法访问该地址。这个 HaVIP 是为了实现 VIP 的访问(可能是因为网络环境的关系),并不是为了替代 kube-vip 或 keepalived 服务。现在(04/21/2021),HaVIP 需要申请(🔗),通过之后在 专有网络/高可用虚拟IP 中使用服务。但是,只能绑定两台机器,所以无法配置真正的高可用,仅能部署单 Master 集群。

配置 control-plane-endpoint 地址

cat >> /etc/hosts <<EOF
172.31.253.60 control-plane-endpoint
EOF

我们将域名解析直接写入 /etc/hosts 文件,但是我们建议将该域名通过 DNS 服务解析。

创建 kube-vip 配置

mkdir -pv /etc/kube-vip/

cat > /etc/kube-vip/config.yaml <<EOF
localPeer:
  id: k8scp-01
  address: 172.31.253.61
  port: 10000
remotePeers:
- id: k8scp-02
  address: 172.31.253.62
  port: 10000
- id: k8scp-03
  address: 172.31.253.63
  port: 10000
vip: 172.31.253.60
gratuitousARP: true
singleNode: false
startAsLeader: true
interface: "enp1s0"
loadBalancers:
- name: API Server Load Balancer
  type: tcp
  port: 56443
  bindToVip: true
  backends:
  - port: 6443
    address: 172.31.253.61
  - port: 6443
    address: 172.31.253.62
  - port: 6443
    address: 172.31.253.63
EOF

创建 kube-vip 的 Static Pod 配置

mkdir -pv /etc/kubernetes/manifests/

docker run -it --rm plndr/kube-vip:0.3.3 sample manifest \
    > /etc/kubernetes/manifests/kube-vip.yaml

第四阶段、通过 kubeadm 部署集群

执行主机:首个 Control Plane 节点(k8scp-01)

集群初始化

# kubeadm init                                                          \
    --control-plane-endpoint "control-plane-endpoint:56443"             \
    --apiserver-bind-port 6443                                          \
    --upload-certs                                                      \
    --image-repository "registry.aliyuncs.com/google_containers"        \
    --v=10
[init] Using Kubernetes version: v1.20.5
[preflight] Running pre-flight checks
...
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

Alternatively, if you are the root user, you can run:

  export KUBECONFIG=/etc/kubernetes/admin.conf

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

You can now join any number of the control-plane node running the following command on each as root:

  kubeadm join control-plane-endpoint.d3rm.org:8443 --token tswdac.5r48i5er2k8vhpfx \
    --discovery-token-ca-cert-hash sha256:1fc8a4ce5b335807a18cbe7750aed90b0d7efbed06f9123271a7266eb31c15e9 \
    --control-plane --certificate-key 42f30016c5ce2c76de2fbdf14dd7d85c125b5b02567110bb4d22a48c24341d96

Please note that the certificate-key gives access to cluster sensitive data, keep it secret!
As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use
"kubeadm init phase upload-certs --upload-certs" to reload certs afterward.

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join control-plane-endpoint.d3rm.org:8443 --token tswdac.5r48i5er2k8vhpfx \
    --discovery-token-ca-cert-hash sha256:1fc8a4ce5b335807a18cbe7750aed90b0d7efbed06f9123271a7266eb31c15e9

完成后续任务

按照 kubeadm init 要求,完成后续设置:

mkdir -p $HOME/.kube
cp /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config

配置 kube-proxy ipvs 模式

# kubectl edit -n kube-system configmaps kube-proxy
...
mode: ipvs
...

# kubectl delete pods -n kube-system -l k8s-app=kube-proxy
pod "kube-proxy-xxxxx" deleted

能够在初始化配置文件中设置,然后再完成初始化设置。但是,暂时无法通过命令行指定 kube-proxy 运行模式。

第五阶段、安装网络插件

如果未安装网络插件,查看 journalctl -f -u kubelet 将提示如下错误消息:

10541 kubelet.go:2184] Container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized

我们使用 Calico 网络插件:

1)检查依赖,参考 System requirements 文档。

存储检查:我们使用 Kubernetes API datastore 存储(这也是我们采用的部署方式决定的)。

网络检查:在内网环境中,我们已经禁止 Control Plane 的防火墙,以允许端口放行。

权限检查:在默认配置中,kubelet 具有 –allow-privileged=true 选项,因此能以 CAP_SYS_ADMIN 允许 Calico 容器,满足要求。

集群要求:
官方完成 Calico v3.18 在 1.18/1.19/1.20 中的测试,我们的版本满足要求。

在默认配置中,kubelet 以 –network-plugin=cni 启动,满足要求。

集群必须使用 Calico 作为唯一插件,且不支持从其他网络插件迁移到 Calico 插件。我们满足要求。

我们在内核启用 ipvs 模块,因此 kube-proxy 运行在 IPVS 模式下(但需要部署时修改 ConfigMap 配置)。

主机网段、Service 网段是自动选择的,不存在冲突。
应用层策略要求:MutatingAdmissionWebhook enabled;Kubernetes version 1.16+ requires Istio version 1.2;

内核模块依赖:我们已在开始处添加 Calico 依赖的内核模块。

2)下载并根据需要修改

# 按照文档的描述,我们的场景应该使用 Kubernetes API datastore 存储,且小于 50 个节点数
curl https://docs.projectcalico.org/manifests/calico.yaml -O

3)应用网络配置:

kubectl apply -f calico.yaml

第六阶段、添加其他节点到集群中

添加 Control Plane 节点

鉴于前几个阶段已经执行,现仅需在节点上执行第三阶段(部署 kube-vip 服务),以及将节点加入集群,但执行顺序上有所不同

1)修改 /etc/hosts 文件

2)创建 kube-vip 配置
但是,需要对配置文件进行修改,比如 localPeer remotePeers startAsLeader interface 字段。这里不再详细说明,针对该配置文件的格式,已经非常明确应该修改哪些内容。

3)将节点加入集群(我们还不清楚为什么使用 kubeadm init 输出的命令会失败,需要重新生成才能加入集群):

kubeadm join control-plane-endpoint.d3rm.org:8443 --token tswdac.5r48i5er2k8vhpfx \
  --discovery-token-ca-cert-hash sha256:1fc8a4ce5b335807a18cbe7750aed90b0d7efbed06f9123271a7266eb31c15e9 \
  --control-plane --certificate-key 42f30016c5ce2c76de2fbdf14dd7d85c125b5b02567110bb4d22a48c24341d96

4)创建 kube-vip static pod 配置
该步骤必须放在节点加入集群后执行,这是因为 kube-vip 的怪异行为,必须这样处理。

添加 Worker 节点

1)修改 /etc/hosts 文件
主要是为了处理「修改主机名的」问题。如果主机名已经解析到 127.0.0.1 便无需修改。

2):

// 创建加入命令(如果有必要)

# kubeadm token create --print-join-command
W1130 03:58:43.247465 2325287 configset.go:202] WARNING: kubeadm cannot validate...
kubeadm join 192.168.10.130:6443 --token ugb23y.35g8iz02esnlu39m --discovery-tok...

// 加入集群

kubeadm join control-plane-endpoint.d3rm.org:6443 --token 4pedgf.a2uc2vvtrknce1wb \
    --discovery-token-ca-cert-hash sha256:0fce70ebfdc980066ea08cdfdc54a835215077834b54f10a36196c22a9dacb09 

常见问题处理

open /etc/kubernetes/pki/ca.crt: no such file or directory

问题描述

# kubeadm join control-plane-endpoint.d3rm.org:8443 --token zb93u8.0xbcfu00xlo8ulbr \                   
>     --discovery-token-ca-cert-hash sha256:396025ff1affbf913c70af034e20ab7c0385631625439db833a099eee88fa0a3 \
>     --control-plane                                                  
[preflight] Running pre-flight checks                                
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
error execution phase preflight:                                                                           
One or more conditions for hosting a new control plane instance is not satisfied.                                                                                                    
                                                                                                                                 
failure loading certificate for CA: couldn't load the certificate file /etc/kubernetes/pki/ca.crt: open /etc/kubernetes/pki/ca.crt: no such file or directory

Please ensure that:                                                           
* The cluster has a stable controlPlaneEndpoint address.
* The certificates that must be shared among control plane instances are provided.
                                                                
                                                                      
To see the stack trace of this error execute with --v=5 or higher

原因分析:我们遇到该错误是因为在 kubeadm init 时,未指定 –upload-certs 选项,因此后续操作需要手动传递证书到各个节点。

解决方案:重新生成 join 命令

# kubeadm init phase upload-certs --upload-certs
[upload-certs] Storing the certificates in Secret "kubeadm-certs" in the "kube-system" Namespace
[upload-certs] Using certificate key:
67b240dd68a9f4dba7017164bb095b08cf21b6445e20199d529269b7bd685973

# kubeadm token create --print-join-command --certificate-key 67b240dd68a9f4dba7017164bb095b08cf21b6445e20199d529269b7bd685973
kubeadm join control-plane-endpoint.d3rm.org:6443 --token i4lcbu.5jpmipmdp6wjde3j     --discovery-token-ca-cert-hash sha256:95147bd44e3109a96414ac7f9d480ef5d42a43819bd91d22c84289f71b460bde     --control-plane --certificate-key 67b240dd68a9f4dba7017164bb095b08cf21b6445e20199d529269b7bd685973

calico/node is not ready: BIRD is not ready: BGP not established with 10.117.6.1

# vim calico.yaml
...
- name: IP_AUTODETECTION_METHOD
  value: "interface=<ethx>"
...

也可能是因为主机环境过于复杂,而不是“干净的新主机”,导致集群组建无法正常处理,需要进一步配置组件。

我们犯过的错误

我们犯过以下错误:

1)主机环境不干净:导致组件无法针对环境做出正确判断,导致集群初始化失败。比如:

在 kubeadm reset 后,主机残留 Calico 网络配置。我们应该重启主机,然后再次使用该节点初始化。

在 kubeadm reset 后,当前节点的 etcd 未从 etcd 集群中剔除,导致当前节点无法再次加入集群。我们应该完全重置集群(测试环境),或者修改主机名(使其成为“新主机”)。

2)组件版本不一致:导致节点在加入集群时失败;

在测试 1.18 版本后,我们部署 1.20 版本,但是仅将初始操作的主节点升级,而未升级其他节点。我们应该检查所有主机的软件版本,保证所有版本一致。

参考文献

calico/node is not ready: BIRD is not ready: BGP not established (Calico 3.6 / k8s 1.14.1) · Issue #2561
Container runtimes | Kubernetes
Creating a cluster with kubeadm | Kubernetes
Disabling IPv6 in Ubuntu Server 18.04 – Ask Ubuntu
Install Calico networking and network policy for on-premises deployments
IPVS-Based In-Cluster Load Balancing Deep Dive | Kubernetes
jenkins – Kubernetes – Calico-Nodes 0/1 Ready – Stack Overflow
kubeadm – Enable IPVS Mode in Kube Proxy on a ready Kubernetes Local Cluster – Stack Overflow
kubernetes – Nodes get no certificates when trying to join a cluster with `kubeadm`
kubernetes/README.md at master · kubernetes/kubernetes
kube-vip/kubernetes-control-plane.md at master · plunder-app/kube-vip
linux – Is there a way to refresh the current configuration used by modprobe with a newly updated modules.conf file?
gnupg – How do I bypass/ignore the gpg signature checks of apt? – Ask Ubuntu