「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
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 文档。
网络检查:在内网环境中,我们已经禁止 Control Plane 的防火墙,以允许端口放行。
权限检查:在默认配置中,kubelet 具有 --allow-privileged=true 选项,因此能以 CAP_SYS_ADMIN 允许 Calico 容器,满足要求。
集群参数:
在默认配置中,kubelet 以 --network-plugin=cni 启动,满足要求。
集群必须使用 Calico 作为唯一插件,且不支持从其他网络插件迁移到 Calico 插件。我们满足要求。
我们在内核i启用 ipvs 模块,因此 kube-proxy 运行在 IPVS 模式下。
主机网段、Service 网段是自动选择的,不存在冲突。
内核模块依赖:我们已在开始处添加 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