「Kubernetes」- 搭建高可用集群 @20210331

问题描述

正如标题,在虚拟化环境中,我们将使用 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 所在的主机中,以减少集群主机数量。

# TODO 补充集群架构图

部署信息

操作系统: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.4
3)Calico 3.18.1

网络信息
1)Control Plane:172.31.253.60(负载均衡的虚拟地址),172.31.253.61 k8s-cp-01,172.31.253.62 k8s-cp-02,172.31.253.63 k8s-cp-03
2)Node:

参考文档

我们按照官方文档的要求,完成对 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 节点

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

1)兼容 Linux 主机;

我们使用官方提供的工具部署 Kubernets 集群,所以需要在兼容的 Linux 发行版中。这点比较容易满足。

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

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

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

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

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

配置允许 iptables 处理 bridge 流量:

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

# run kube-proxy in ipvs mode
ip_vs
ip_vs_rr
ip_vs_wrr
ip_vs_sh
nf_conntrack_ipv4

EOF
systemctl restart systemd-modules-load.service

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

配置 ipvs 模块,以使 kube-proxy 运行在 IPVS 模式下:

# 添加内核模块
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
nf_conntrack_ipv4

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

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

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

5)必要端口未被占用;

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

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 <<EOF | tee /etc/docker/daemon.json
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2"
}
EOF

systemctl enable docker
systemctl daemon-reload
systemctl restart docker

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

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

执行主机:所有 Control Plane 节点

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

版本选择

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

安装服务

apt-get update \
    && apt-get install -y apt-transport-https ca-certificates curl

# 尽管下载 apt-key.gpg 存在困难,但是应该尽量从官方站点下载(请勿随意使用第三方密钥)
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.20.4-00 kubeadm=1.20.4-00 kubectl=1.20.4-00
apt-mark hold kubelet kubeadm kubectl # 禁止更新

第三阶段、配置高可用环境(kube-vip)

执行主机:首个 Control Plane 节点(我们的 k8s-cp-01 节点)

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

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

配置 control-plane-endpoint 地址

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

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

创建 kube-vip 配置

mkdir -pv /etc/kube-vip/

cat > /etc/kube-vip/config.yaml <<EOF
localPeer:
  id: k8s-cp-01
  address: 172.31.253.61
  port: 10000
remotePeers:
- id: k8s-cp-02
  address: 172.31.253.62
  port: 10000
- id: k8s-cp-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: 8443
  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 节点(我们的 k8s-cp-01 节点)

集群初始化

# kubeadm init \
    --control-plane-endpoint "control-plane-endpoint.d3rm.org:8443"     \
    --apiserver-bind-port 6443 \
    --image-repository "registry.aliyuncs.com/google_containers"
[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 control-plane nodes by copying certificate authorities
and service account keys on each node and then running the following as root:

  kubeadm join control-plane-endpoint.d3rm.org:6443 --token lgm5el.esqwyahynz6eqyjx \
    --discovery-token-ca-cert-hash sha256:52626a3d6bdab90344575f3eb3a3f03b3bc43d0ca4f0411781ae2cdea024b520 \
    --control-plane 

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

kubeadm join control-plane-endpoint.d3rm.org:6443 --token lgm5el.esqwyahynz6eqyjx \
    --discovery-token-ca-cert-hash sha256:52626a3d6bdab90344575f3eb3a3f03b3bc43d0ca4f0411781ae2cdea024b520 

完成后续任务

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

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

安装网络插件

如果未安装网络插件,查看 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 在 Kubernetes 1.20 中的测试,我们的版本满足要求。
在默认配置中,kubelet 以 --network-plugin=cni 启动,满足要求。
集群必须使用 Calico 作为唯一插件,且不支持从其他网络插件迁移到 Calico 插件。我们满足要求。
我们在内核i启用 ipvs 模块,因此 kube-proxy 运行在 IPVS 模式下。
主机网段、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 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

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

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

添加 Worker Node 节点

此时,能够直接使用 kubeadm init 输出的命令:

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

集群其他操作

删除 Control Plane 节点

kubectl drain "<node name>" --delete-local-data --force --ignore-daemonsets # 
kubeadm reset # **NOTE** run on <node name>
kubectl delete node "<node name>" # 

添加新节点到集群中

# 获取 TOKEN 参数
kubeadm token list   # 查看已有 TOKEN  
kubeadm token create # 创建新的 TOKEN

# 获取 discovery-token-ca-cert-hash 参数 
openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | \
   openssl dgst -sha256 -hex | sed 's/^.* //'

# 加入集群
kubeadm join "control-plane-endpoint.d3rm.org:8443" --token "4pedgf.a2uc2vvtrknce1wb" \
    --discovery-token-ca-cert-hash "sha256:0fce70ebfdc980066ea08cdfdc54a835215077834b54f10a36196c22a9dacb09" 

添加 Control Plane 节点:

# 获取 certificate key 参数
kubeadm init phase upload-certs --upload-certs

# 获取加入集群的命令
kubeadm token create --print-join-command --certificate-key "<certificate key>"

# 执行输出的 kubeadm join 命令

常见问题处理

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 生成的 kubeadm join 命令无法加入。

解决方案:重新生成 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

error execution phase control-plane-prepare/download-certs

问题描述

# kubeadm join control-plane-endpoint.d3rm.org:6443 --token f16qw8.3sawd6jbv3eaytju \
    --discovery-token-ca-cert-hash sha256:95147bd44e3109a96414ac7f9d480ef5d42a43819bd91d22c84289f71b460bde \
    --control-plane \
    --certificate-key e6f755259360e408894fde6a3eba4eebf889823fd06d59a2d555c061a03b62a8
[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'
[preflight] Running pre-flight checks before initializing the new control plane instance
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
[download-certs] Downloading the certificates in Secret "kubeadm-certs" in the "kube-system" Namespace
error execution phase control-plane-prepare/download-certs: error downloading certs: error downloading the secret: secrets "kubeadm-certs" is forbidden: User "system:bootstrap:f16qw8" cannot get resource "secrets" in API group "" in the namespace "kube-system"
To see the stack trace of this error execute with --v=5 or higher

原因分析:我们不清楚为什么 kubeadm init 生成的 kubeadm join 命令无法加入,certificate-key 的有效期为两小时,我们的场景中已经过期,需要重新生成

解决方案

# 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

相关文章

「Kubernetes」- 高可用集群(使用Kubeadm与Keepalived搭建)

参考文献

Container runtimes | 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? - Super User
Install Calico networking and network policy for on-premises deployments
IPVS-Based In-Cluster Load Balancing Deep Dive | Kubernetes
Creating a cluster with kubeadm | Kubernetes
kubernetes - Nodes get no certificates when trying to join a cluster with `kubeadm` - Stack Overflow

上一篇:搭建K8S高可用集群


下一篇:CentOS 7安装指定版本kubernetes