摘要:官方只提到了一句「使用負載均衡器將 apiserver 暴露給工做節點」,而這偏偏是部署過程當中須要解決的重點問題。
本文分享自華爲雲社區《Kubernetes 高可用集羣落地二三事》,做者:zuozewei。javascript
1、高可用拓撲
能夠設置 HA 集羣:css
- 使用堆疊(stacked)控制平面節點,其中 etcd 節點與控制平面節點共存;
- 使用外部 etcd 節點,其中 etcd 在與控制平面不一樣的節點上運行;
在設置 HA 集羣以前,應該仔細考慮每種拓撲的優缺點。html
一、堆疊(Stacked) etcd 拓撲
主要特色:java
- etcd 分佈式數據存儲集羣堆疊在 kubeadm 管理的控制平面節點上,做爲控制平面的一個組件運行。
- 每一個控制平面節點運行 kube-apiserver,kube-scheduler 和 kube-controller-manager 實例。
- kube-apiserver 使用 LB 暴露給工做節點。
- 每一個控制平面節點建立一個本地 etcd 成員(member),這個 etcd 成員只與該節點的 kube-apiserver 通訊。這一樣適用於本地 kube-controller-manager 和 kube-scheduler 實例。
- 簡單概況:每一個 master 節點上運行一個 apiserver 和 etcd, etcd 只與本節點 apiserver 通訊。
- 這種拓撲將控制平面和 etcd 成員耦合在同一節點上。相對使用外部 etcd 集羣,設置起來更簡單,並且更易於副本管理。
- 然而堆疊集羣存在耦合失敗的風險。若是一個節點發生故障,則 etcd 成員和控制平面實例都將丟失,而且冗餘會受到影響。能夠經過添加更多控制平面節點來下降此風險。應該爲 HA 集羣運行至少三個堆疊的控制平面節點(防止腦裂)。
- 這是 kubeadm 中的默認拓撲。當使用 kubeadm init 和 kubeadm join --control-plane 時,在控制平面節點上會自動建立本地 etcd 成員。
二、外部 etcd 拓撲
主要特色:node
- 具備外部 etcd 的 HA 集羣是一種這樣的拓撲,其中 etcd 分佈式數據存儲集羣在獨立於控制平面節點的其餘節點上運行。
- 就像堆疊的 etcd 拓撲同樣,外部 etcd 拓撲中的每一個控制平面節點都運行 kube-apiserver,kube-scheduler 和 kube-controller-manager 實例。
- 一樣 kube-apiserver 使用負載均衡器暴露給工做節點。可是,etcd 成員在不一樣的主機上運行,每一個 etcd 主機與每一個控制平面節點的 kube-apiserver 通訊。
- 簡單概況: etcd 集羣運行在單獨的主機上,每一個 etcd 都與 apiserver 節點通訊。
- 這種拓撲結構解耦了控制平面和 etcd 成員。所以,它提供了一種 HA 設置,其中失去控制平面實例或者 etcd 成員的影響較小,而且不會像堆疊的 HA 拓撲那樣影響集羣冗餘。
- 可是,此拓撲須要兩倍於堆疊 HA 拓撲的主機數量。具備此拓撲的 HA 集羣至少須要三個用於控制平面節點的主機和三個用於 etcd 節點的主機。
- 須要單獨設置外部 etcd 集羣。
三、小結
官方這裏主要是解決了高可用場景下 apiserver 與 etcd 集羣的關係,以及控制平面節點防止單點故障。可是集羣對外訪問接口不可能將三個 apiserver 都暴露出去,一個節點掛掉時仍是不能自動切換到其餘節點。官方只提到了一句「使用負載均衡器將 apiserver 暴露給工做節點」,而這偏偏是部署過程當中須要解決的重點問題。git
Notes: 此處的負載均衡器並非 kube-proxy,此處的 Load Balancer 是針對 apiserver 的。github
最後,咱們總結一下兩種拓撲:web
- 堆疊(Stacked) etcd 拓撲:設置簡單,易於副本管理 ,不過存在耦合失敗風險。若是節點發生故障,則 etcd 成員和控制平面實例有丟失的可能,推薦測試開發環境;
- 外部 etcd 拓撲:解耦了控制平面和 etcd 成員,不會像堆疊的 HA 拓撲那樣有影響集羣冗餘的風險,不過須要兩倍於堆疊 HA 拓撲的主機數量,設置相對複雜,推薦生產環境。
2、部署架構
如下是咱們在測試環境所用的部署架構:redis
這裏採用 kubeadm 方式搭建高可用 k8s 集羣,k8s 集羣的高可用實際是 k8s 各核心組件的高可用,這裏使用主備模式:算法
- apiserver 經過 keepalived+haproxy 實現高可用,當某個節點故障時觸發 keepalived vip 轉移,haproxy 負責將流量負載到 apiserver 節點;
- controller-manager k8s 內部經過選舉方式產生領導者(由 --leader-elect 選型控制,默認爲 true),同一時刻集羣內只有一個 controller-manager 組件運行,其他處於 backup 狀態;
- scheduler k8s 內部經過選舉方式產生領導者(由 --leader-elect 選型控制,默認爲 true),同一時刻集羣內只有一個scheduler組件運行,其他處於 backup 狀態;
- etcd 經過運行 kubeadm 方式自動建立集羣來實現高可用,部署的節點數爲奇數,3節點方式最多容忍一臺機器宕機。
3、環境示例
主機列表:
這裏共有 12 臺主機,3 臺 control plane,9 臺 worker。
4、核心組件
一、haproxy
haproxy提供高可用性,負載均衡,基於 TCP 和 HTTP 的代理,支持數以萬記的併發鏈接。
haproxy 可安裝在主機上,也可以使用 docker 容器實現。文本採用第一種。
建立配置文件 /etc/haproxy/haproxy.cfg,重要配置以中文註釋標出:
#--------------------------------------------------------------------- # Example configuration for a possible web application. See the # full configuration options online. # # https://www.haproxy.org/download/2.1/doc/configuration.txt # https://cbonte.github.io/haproxy-dconv/2.1/configuration.html # #--------------------------------------------------------------------- #--------------------------------------------------------------------- # Global settings #--------------------------------------------------------------------- global # to have these messages end up in /var/log/haproxy.log you will # need to: # # 1) configure syslog to accept network log events. This is done # by adding the '-r' option to the SYSLOGD_OPTIONS in # /etc/sysconfig/syslog # # 2) configure local2 events to go to the /var/log/haproxy.log # file. A line like the following can be added to # /etc/sysconfig/syslog # # local2.* /var/log/haproxy.log # log 127.0.0.1 local2 # chroot /var/lib/haproxy pidfile /var/run/haproxy.pid maxconn 4000 # user haproxy # group haproxy # daemon # turn on stats unix socket stats socket /var/lib/haproxy/stats #--------------------------------------------------------------------- # common defaults that all the 'listen' and 'backend' sections will # use if not designated in their block #--------------------------------------------------------------------- defaults mode http log global option httplog option dontlognull option http-server-close option forwardfor except 127.0.0.0/8 option redispatch retries 3 timeout http-request 10s timeout queue 1m timeout connect 10s timeout client 1m timeout server 1m timeout http-keep-alive 10s timeout check 10s maxconn 3000 #--------------------------------------------------------------------- # main frontend which proxys to the backends #--------------------------------------------------------------------- frontend kubernetes-apiserver mode tcp bind *:9443 ## 監聽9443端口 # bind *:443 ssl # To be completed .... acl url_static path_beg -i /static /images /javascript /stylesheets acl url_static path_end -i .jpg .gif .png .css .js default_backend kubernetes-apiserver #--------------------------------------------------------------------- # round robin balancing between the various backends #--------------------------------------------------------------------- backend kubernetes-apiserver mode tcp # 模式tcp balance roundrobin # 採用輪詢的負載算法 # k8s-apiservers backend # 配置apiserver,端口6443 server k8s-master-1 xxx.16.106.208:6443 check server k8s-master-2 xxx.16.106.80:6443 check server k8s-master-3 xxx.16.106.14:6443 check
分別在三個 master 節點啓動 haproxy。
二、keepalived
keepalived 是以 VRRP(虛擬路由冗餘協議)協議爲基礎, 包括一個 master 和多個 backup。 master 劫持 vip 對外提供服務。master 發送組播,backup 節點收不到 vrrp 包時認爲 master 宕機,此時選出剩餘優先級最高的節點做爲新的 master, 劫持 vip。keepalived 是保證高可用的重要組件。
keepalived 可安裝在主機上,也可以使用 docker 容器實現。文本採用第一種。
配置 keepalived.conf, 重要部分以中文註釋標出:
! Configuration File for keepalived global_defs { router_id k8s-master-1 } vrrp_script chk_haproxy { script "/bin/bash -c 'if [[ $(netstat -nlp | grep 9443) ]]; then exit 0; else exit 1; fi'" # haproxy 檢測 interval 2 # 每2秒執行一次檢測 weight 11 # 權重變化 } vrrp_instance VI_1 { state MASTER # backup節點設爲BACKUP interface eth0 virtual_router_id 50 # id設爲相同,表示是同一個虛擬路由組 priority 100 # 初始權重 authentication { auth_type PASS auth_pass 1111 } virtual_ipaddress { 172.16.106.187 # vip } track_script { chk_haproxy } }
- vrrp_script 用於檢測 haproxy 是否正常。若是本機的 haproxy 掛掉,即便 keepalived 劫持vip,也沒法將流量負載到 apiserver。
- 我所查閱的網絡教程所有爲檢測進程, 相似 killall -0 haproxy。這種方式用在主機部署上能夠,但容器部署時,在 keepalived 容器中沒法知道另外一個容器 haproxy 的活躍狀況,所以我在此處經過檢測端口號來判斷 haproxy 的健康情況。
- weight 可正可負。爲正時檢測成功 +weight,至關與節點檢測失敗時自己 priority 不變,但其餘檢測成功節點 priority 增長。爲負時檢測失敗自己 priority 減小。
- 另外不少文章中沒有強調 nopreempt 參數,意爲不可搶佔,此時 master 節點失敗後,backup 節點也不能接管 vip,所以我將此配置刪去。
分別在三臺節點啓動 keepalived,查看 keepalived master 日誌:
Dec 25 15:52:45 k8s-master-1 Keepalived_vrrp[12562]: VRRP_Script(chk_haproxy) succeeded # haproxy檢測成功 Dec 25 15:52:46 k8s-master-1 Keepalived_vrrp[12562]: VRRP_Instance(VI_1) Changing effective priority from 100 to 111 # priority增長 Dec 25 15:54:06 k8s-master-1 Keepalived_vrrp[12562]: VRRP_Instance(VI_1) Transition to MASTER STATE Dec 25 15:54:06 k8s-master-1 Keepalived_vrrp[12562]: VRRP_Instance(VI_1) Received advert with lower priority 111, ours 111, forcing new election Dec 25 15:54:07 k8s-master-1 Keepalived_vrrp[12562]: VRRP_Instance(VI_1) Entering MASTER STATE Dec 25 15:54:07 k8s-master-1 Keepalived_vrrp[12562]: VRRP_Instance(VI_1) setting protocol VIPs. # 設置vip Dec 25 15:54:07 k8s-master-1 Keepalived_vrrp[12562]: Sending gratuitous ARP on eth0 for 172.16.106.187 Dec 25 15:54:07 k8s-master-1 Keepalived_vrrp[12562]: VRRP_Instance(VI_1) Sending/queueing gratuitous ARPs on eth0 for 172.16.106.187 Dec 25 15:54:07 k8s-master-1 Keepalived_vrrp[12562]: Sending gratuitous ARP on eth0 for 172.16.106.187 Dec 25 15:54:07 k8s-master-1 Keepalived_vrrp[12562]: Sending gratuitous ARP on eth0 for 172.16.106.187 Dec 25 15:54:07 k8s-master-1 Keepalived_vrrp[12562]: Sending gratuitous ARP on eth0 for 172.16.106.187 Dec 25 15:54:07 k8s-master-1 Keepalived_vrrp[12562]: Sending gratuitous ARP on eth0 for 172.16.106.187 Dec 25 15:54:07 k8s-master-1 avahi-daemon[756]: Registering new address record for 172.16.106.187 on eth0.IPv4. Dec 25 15:54:10 k8s-master-1 kubelet: E1225 15:54:09.999466 1047 kubelet_node_status.go:442] Error updating node status, will retry: failed to patch status "{\"status\":{\"$setElementOrder/conditions\":[{\"type\":\"NetworkUnavailable\"},{\"type\":\"MemoryPressure\"},{\"type\":\"DiskPressure\"},{\"type\":\"PIDPressure\"},{\"type\":\"Ready\"}],\"addresses\":[{\"address\":\"172.16.106.187\",\"type\":\"InternalIP\"},{\"address\":\"k8s-master-1\",\"type\":\"Hostname\"},{\"$patch\":\"replace\"}],\"conditions\":[{\"lastHeartbeatTime\":\"2020-12-25T07:54:09Z\",\"type\":\"MemoryPressure\"},{\"lastHeartbeatTime\":\"2020-12-25T07:54:09Z\",\"type\":\"DiskPressure\"},{\"lastHeartbeatTime\":\"2020-12-25T07:54:09Z\",\"type\":\"PIDPressure\"},{\"lastHeartbeatTime\":\"2020-12-25T07:54:09Z\",\"type\":\"Ready\"}]}}" for node "k8s-master-1": Patch "https://apiserver.demo:6443/api/v1/nodes/k8s-master-1/status?timeout=10s": write tcp 172.16.106.208:46566->172.16.106.187:6443: write: connection reset by peer Dec 25 15:54:11 k8s-master-1 Keepalived_vrrp[12562]: Sending gratuitous ARP on eth0 for 172.16.106.187 Dec 25 15:54:11 k8s-master-1 Keepalived_vrrp[12562]: VRRP_Instance(VI_1) Sending/queueing gratuitous ARPs on eth0 for 172.16.106.187 Dec 25 15:54:11 k8s-master-1 Keepalived_vrrp[12562]: Sending gratuitous ARP on eth0 for 172.16.106.187 Dec 25 15:54:11 k8s-master-1 Keepalived_vrrp[12562]: Sending gratuitous ARP on eth0 for 172.16.106.187 Dec 25 15:54:11 k8s-master-1 Keepalived_vrrp[12562]: Sending gratuitous ARP on eth0 for 172.16.106.187 Dec 25 15:54:11 k8s-master-1 Keepalived_vrrp[12562]: Sending gratuitous ARP on eth0 for 172.16.106.187 Dec 25 15:54:12 k8s-master-1 Keepalived_vrrp[12562]: Sending gratuitous ARP on eth0 for 172.16.106.187
查看 master vip:
[root@k8s-master-1 ~]# ip a|grep eth0 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000 inet 172.16.106.208/24 brd 172.16.106.255 scope global noprefixroute dynamic eth0 inet 172.16.106.187/32 scope global eth0
能夠看到 vip 已綁定到 keepalived master
下面進行破壞性測試:
暫停 keepalived master 節點 haproxy:
[root@k8s-master-1 ~]# service haproxy stop Redirecting to /bin/systemctl stop haproxy.service
查看 keepalived k8s-master-1 節點日誌:
Dec 25 15:58:31 k8s-master-1 Keepalived_vrrp[12562]: /bin/bash -c 'if [[ $(netstat -nlp | grep 9443) ]]; then exit 0; else exit 1; fi' exited with status 1 Dec 25 15:58:31 k8s-master-1 Keepalived_vrrp[12562]: VRRP_Script(chk_haproxy) failed Dec 25 15:58:31 k8s-master-1 Keepalived_vrrp[12562]: VRRP_Instance(VI_1) Changing effective priority from 111 to 100 Dec 25 15:58:32 k8s-master-1 Keepalived_vrrp[12562]: VRRP_Instance(VI_1) Received advert with higher priority 111, ours 100 Dec 25 15:58:32 k8s-master-1 Keepalived_vrrp[12562]: VRRP_Instance(VI_1) Entering BACKUP STATE Dec 25 15:58:32 k8s-master-1 Keepalived_vrrp[12562]: VRRP_Instance(VI_1) removing protocol VIPs.
能夠看到 haproxy 檢測失敗,priority 下降,同時另外一節點 priority 比 k8s-master-1 節點高, k8s-master-1 置爲 backup
查看 k8s-master-2 節點 keepalived日誌:
Dec 25 15:58:35 k8s-master-2 Keepalived_vrrp[3661]: VRRP_Instance(VI_1) Transition to MASTER STATE Dec 25 15:58:35 k8s-master-2 Keepalived_vrrp[3661]: VRRP_Instance(VI_1) Received advert with lower priority 111, ours 111, forcing new election Dec 25 15:58:36 k8s-master-2 Keepalived_vrrp[3661]: VRRP_Instance(VI_1) Entering MASTER STATE Dec 25 15:58:36 k8s-master-2 Keepalived_vrrp[3661]: VRRP_Instance(VI_1) setting protocol VIPs. Dec 25 15:58:36 k8s-master-2 Keepalived_vrrp[3661]: Sending gratuitous ARP on eth0 for 172.16.106.187 Dec 25 15:58:36 k8s-master-2 avahi-daemon[740]: Registering new address record for 172.16.106.187 on eth0.IPv4.
能夠看到 k8s-master-2 被選舉爲新的 master。
5、安裝部署
一、安裝 docker / kubelet
參考上文 使用 kubeadm 安裝單master kubernetes 集羣(腳本版)
二、初始化第一個 master
kubeadm.conf 爲初始化的配置文件:
[root@master01 ~]# more kubeadm-config.yaml apiVersion: kubeadm.k8s.io/v1beta2 kind: ClusterConfiguration kubernetesVersion: v1.16.4 apiServer: certSANs: #填寫全部kube-apiserver節點的hostname、IP、VIP - k8s-master-1 - k8s-master-2 - k8s-master-3 - k8s-worker-1 - apiserver.demo ..... controlPlaneEndpoint: "172.27.34.130:6443" networking: podSubnet: "10.244.0.0/16"
初始化 k8s-master-1:
# kubeadm init # 根據您服務器網速的狀況,您須要等候 3 - 10 分鐘 kubeadm init --config=kubeadm-config.yaml --upload-certs # 配置 kubectl rm -rf /root/.kube/ mkdir /root/.kube/ cp -i /etc/kubernetes/admin.conf /root/.kube/config # 安裝 calico 網絡插件 # 參考文檔 https://docs.projectcalico.org/v3.13/getting-started/kubernetes/self-managed-onprem/onpremises echo "安裝calico-3.13.1" kubectl apply -f calico-3.13.1.yaml
三、初始化第2、三個 master 節點
能夠和第一個Master節點一塊兒初始化第2、三個Master節點,也能夠從單Master節點調整過來,只須要:
- 增長 Master 的 LoadBalancer
- 將全部節點的 /etc/hosts 文件中 apiserver.demo 解析爲 LoadBalancer 的地址
- 添加第2、三個Master節點
- 初始化 master 節點的 token 有效時間爲 2 小時
這裏咱們演示第一個Master節點初始化2個小時後再初始化:
# 只在 第一個 master 上執行 [root@k8s-master-1 ~]# kubeadm init phase upload-certs --upload-certs I1225 16:25:00.247925 19101 version.go:252] remote version is much newer: v1.20.1; falling back to: stable-1.19 W1225 16:25:01.120802 19101 configset.go:348] WARNING: kubeadm cannot validate component configs for API groups [kubelet.config.k8s.io kubeproxy.config.k8s.io] [upload-certs] Storing the certificates in Secret "kubeadm-certs" in the "kube-system" Namespace [upload-certs] Using certificate key: 5c120930eae91fc19819f1cbe71a6986a78782446437778cc0777062142ef1e6
得到 join 命令:
# 只在 第一個 master 節點上執行 [root@k8s-master-1 ~]# kubeadm token create --print-join-command W1225 16:26:27.642047 20949 configset.go:348] WARNING: kubeadm cannot validate component configs for API groups [kubelet.config.k8s.io kubeproxy.config.k8s.io] kubeadm join apiserver.demo:6443 --token kab883.kyw62ylnclbf3mi6 --discovery-token-ca-cert-hash sha256:566a7142ed059ab5dee403dd4ef6d52cdc6692fae9c05432e240bbc08420b7f0
則,第2、三個 master 節點的 join 命令以下:
# 命令行中,前面爲得到的 join 命令,control-plane 指定的爲得到的 certificate key kubeadm join apiserver.demo:6443 --token kab883.kyw62ylnclbf3mi6 \ --discovery-token-ca-cert-hash sha256:566a7142ed059ab5dee403dd4ef6d52cdc6692fae9c05432e240bbc08420b7f0 \ --control-plane --certificate-key 5c120930eae91fc19819f1cbe71a6986a78782446437778cc0777062142ef1e6
檢查 master 初始化結果:
[root@k8s-master-1 ~]# kubectl get nodes NAME STATUS ROLES AGE VERSION k8s-master-1 Ready master 2d v1.19.2 k8s-master-2 Ready master 2d v1.19.2 k8s-master-3 Ready master 2d v1.19.2
四、初始化 worker節點
針對全部的 worker 節點執行:
# 只在 worker 節點執行 # 替換 x.x.x.x 爲 ApiServer LoadBalancer 的 IP 地址 export MASTER_IP=x.x.x.x # 替換 apiserver.demo 爲初始化 master 節點時所使用的 APISERVER_NAME export APISERVER_NAME=apiserver.demo echo "${MASTER_IP} ${APISERVER_NAME}" >> /etc/hosts # 替換爲前面 kubeadm token create --print-join-command 的輸出結果 kubeadm join apiserver.demo:6443 --token kab883.kyw62ylnclbf3mi6 --discovery-token-ca-cert-hash sha256:566a7142ed059ab5dee403dd4ef6d52cdc6692fae9c05432e240bbc08420b7f0
檢查 worker 初始化結果:
[root@k8s-master-1 ~]# kubectl get nodes NAME STATUS ROLES AGE VERSION k8s-master-1 Ready master 2d v1.19.2 k8s-master-2 Ready master 2d v1.19.2 k8s-master-3 Ready master 2d v1.19.2 k8s-worker-1 Ready <none> 2d v1.19.2 k8s-worker-2 Ready <none> 2d v1.19.2 k8s-worker-3 Ready <none> 2d v1.19.2 k8s-worker-4 Ready <none> 2d v1.19.2 k8s-worker-5 Ready <none> 2d v1.19.2 k8s-worker-6 Ready <none> 2d v1.19.2 k8s-worker-7 Ready <none> 2d v1.19.2 k8s-worker-8 Ready <none> 2d v1.19.2 k8s-worker-9 Ready <none> 2d v1.19.2
本文資料:
參考資料:
- [1]:https://www.kuboard.cn/install/install-kubernetes.html
- [2]:https://github.com/loong576/Centos7.6-install-k8s-v1.16.4-HA-cluster
- [3]:https://kubernetes.io/zh/docs/setup/production-environment/tools/kubeadm/ha-topology/
- [4]:https://www.kubernetes.org.cn/6964.html