golang检查tcp是否可用_宕机处理:Kubernetes集群高可用实战总结
導語 |?在企業生產環境,Kubernetes高可用是一個必不可少的特性,其中最通用的場景就是如何在Kubernetes集群宕機一個節點的情況下保障服務依舊可用。本文對在該場景下實現集群和應用高可用過程中遇到的各種問題進行了梳理和總結,希望與大家一同交流。文章作者,騰訊云架構服務研發工程師。
一、整體架構
1. control plane node管理節點采用 kubeadm 搭建的 3 節點標準高可用方案:Stacked etcd topology [1].該方案中,所有管理節點都部署 kube-apiserver,kube-controller-manager,kube-scheduler,以及 etcd 等組件。kube-apiserver 均與本地的 etcd 進行通信,etcd 在三個節點間同步數據。而 kube-controller-manager 和 kube-scheduler 也只與本地的 kube-apiserver 進行通信(或者通過 LB 訪問)。kube-apiserver 前面頂一個 LB;work 節點 kubelet 以及 kube-proxy 組件對接 LB 訪問 apiserver 。在這種架構中,如果其中任意一個 master 節點宕機了,由于 kube-controller-manager 以及 kube-scheduler 基于 Leader Election Mechanism?[2]實現了高可用,可以認為管理集群不受影響,相關組件依舊正常運行(在分布式鎖釋放后)。2. work node工作節點上部署應用,應用按照反親和[3]部署多個副本,使副本調度在不同的 work node 上,這樣如果其中一個副本所在的母機宕機了,理論上通過 Kubernetes service 可以把請求切換到另外副本上,使服務依舊可用。二、網絡
針對上述架構,我們來分析一下實際環境中節點宕機對網絡層面帶來的影響。1. service backend 剔除
正如上文所述,工作節點上應用會按照反親和部署。這里我們討論最簡單的情況:例如一個 nginx 服務,部署了兩個副本,分別落在 work node1 和 work node2 上。集群中還部署了依賴 nginx 服務的應用 A,如果某個時刻 work node1 宕機了,此時應用 A 訪問 nginx service 會有問題嗎?這里的答案是:會有問題。因為 service 不會馬上剔除掉宕機上對應的 nginx pod,同時由于 service 常用的 iptables 代理模式[4]?沒有實現 retry with another backend [5]特性,所以在一段時間內訪問會出現間歇性問題(如果請求輪詢到掛掉的 nginx pod 上),也就是說會存在一個訪問失敗間隔期(ipvs 模式具備健康檢查能力,能夠自動從 ipvs 規則中及時剔除故障的 pod,具備更高的可用性)。這個間隔期取決于 service 對應的 endpoint 什么時候踢掉宕機的 pod。之后,kube-proxy 會 watch 到 endpoint 變化,更新對應的 iptables 規則,使 service 訪問后端列表恢復正常,也就是踢掉該 pod 。要理解這個間隔,我們首先需要弄清楚 Kubernetes 節點心跳的概念:每個 node 在 kube-node-lease namespace 下會對應一個 Lease object,kubelet 每隔 node-status-update-frequency 時間(默認10s)會更新對應 node 的 Lease object 。node-controller 會每隔 node-monitor-period 時間(默認5s )檢查 Lease object 是否更新,如果超過 node-monitor-grace-period 時間(默認40s)沒有發生過更新,則認為這個 node 不健康,會更新 NodeStatus (ConditionUnknown)。如果這樣的狀態在這之后持續了一段時間(默認5 mins),則會驅逐(evict)該 node 上的 pod 。對于母機宕機的場景,node controller 在 node-monitor-grace-period 時間段沒有觀察到心跳的情況下,會更新 NodeStatus (ConditionUnknown),之后 endpoint controller 會從 endpoint backend 中踢掉該母機上的所有 pod,最終服務訪問正常,請求不會落在掛掉的 pod 上。也就是說在母機宕機后,在 node-monitor-grace-period 間隔內訪問 service 會有間歇性問題,我們可以通過調整相關參數來縮小這個窗口期,如下:(kubelet)node-status-update-frequency:default 10s(kube-controller-manager)node-monitor-period:default 5s(kube-controller-manager)node-monitor-grace-period:default 40s- Amount of time which we allow running Node to be unresponsive before marking it unhealthy. Must be N times more than kubelet's nodeStatusUpdateFrequency,?where?N?means?number?of?retries?allowed?for?kubelet?to?post?node?status.- Currently nodeStatusUpdateRetry is constantly set to 5 in kubelet.go2. 連接復用
對于使用長連接訪問的應用來說(默認使用都是 tcp 長連接,無論 HTTP/2 還是 HTTP/1 ),在沒有設置合適請求 timeout 參數的情況下可能會出現 15mins 的超時問題,詳情見?Kubernetes Controller 高可用詭異的 15mins 超時[6]。通過對上述鏈接內容的分析,我們可以知道是 TCP 的 ARQ 機制導致了 Controller 在母機宕機后 15mins 內一直超時重試,超時重試失敗后,tcp socket 關閉,應用重新創建連接。這個問題本質上不是 Kubernetes 的問題,而是應用在復用 tcp socket (長連接)時沒有考慮設置超時,導致了母機宕機后,tcp socket 沒有及時關閉,服務依舊使用失效連接導致異常。要解決這個問題,可以從兩方面考慮:第一,應用層使用超時設置或者健康檢查機制,從上層保障連接的健康狀態,作用于該應用。第二,底層調整 TCP ARQ 設置(/proc/sys/net/ipv4/tcp_retries2),縮小超時重試周期,作用于整個集群。
由于應用層的超時或者健康檢查機制無法使用統一的方案,這里只介紹如何采用系統配置的方式規避無效連接,如下:# 0.2+0.4+0.8+1.6+3.2+6.4+12.8+25.6+51.2+102.4 = 222.6s$ echo 9 > /proc/sys/net/ipv4/tcp_retries2另外對于推送類的服務,比如 Watch,在母機宕機后,可以通過 tcp keepalive 機制來關閉無效連接。這也是上面測試 cluster-coredns-controller 時其中一個連接 5 分鐘(30+30*9=300s)斷開的原因:# 30 + 30*5 = 180s$ echo 30 > /proc/sys/net/ipv4/tcp_keepalive_time$ echo 30 > /proc/sys/net/ipv4/tcp_keepalive_intvl$ echo 5 > /proc/sys/net/ipv4/tcp_keepalive_probes注意:在Kubernetes環境中,容器不會直接繼承母機tcp keepalive的配置(可以直接繼承母機tcp超時重試的配置),因此必須通過一定方式進行適配。這里介紹其中一種方式,添加initContainers使配置生效:- name: init-sysctl image: busybox command: - /bin/sh - -c - | sysctl -w net.ipv4.tcp_keepalive_time=30 sysctl -w net.ipv4.tcp_keepalive_intvl=30 sysctl -w net.ipv4.tcp_keepalive_probes=5 securityContext: privileged: true通過上述的 TCP keepalive 以及 TCP ARQ 配置,我們可以將無效連接斷開時間縮短到 4 分鐘以內,一定程度上解決了母機宕機導致的連接異常問題。不過最好的解決方案是在應用層設置超時或者健康檢查機制及時關閉底層無效連接。三、節點驅逐
對節點驅逐需要分應用和存儲兩方面進行分析:1. 應用相關
pod 驅逐可以使服務自動恢復副本數量。如上所述,node controller 會在節點心跳超時之后一段時間(默認 5 mins)驅逐該節點上的 pod,這個時間由如下參數決定:(kube-apiserver)default-not-ready-toleration-seconds:default?300(kube-apiserver)default-unreachable-toleration-seconds:default?300Kubernetes?automatically?adds?a?toleration?for?node.kubernetes.io/not-ready?and?node.kuber-netes.io/unreachable?with?toleration-Seconds=300,These?automatically-added?tolerations?mean?that?Pods?remain?bound?to?Nodes?for?5?minutes?after?one?of?these?problems?is?detected.摘自Kubernetes官方文獻引用中涉及到兩個?tolerations [7]?:
node.kubernetes.io/not-ready: Node is not ready. This corresponds to the NodeCondition Ready being “False”;
node.kubernetes.io/unreachable: Node is unreachable from the node controller. This corresponds to the NodeCondition Ready being “Unknown”.
2. 存儲相關
當 pod 使用的 volume 只支持 RWO 讀寫模式時,如果 pod 所在母機宕機了,并且隨后在其它母機上產生了替換副本,則該替換副本的創建會阻塞,如下所示:$ kubectl get pods -o widenginx-7b4d5d9fd-bmc8g 0/1 ContainerCreating 0 0s 10.0.0.1 nginx-7b4d5d9fd-nqgfz 1/1 Terminating 0 19m 192.28.1.165 10.0.0.2 $ kubectl describe pods/nginx-7b4d5d9fd-bmc8g[...truncate...]Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 3m5s default-scheduler Successfully assigned default/nginx-7b4d5d9fd-bmc8g to 10.0.0.1 Warning FailedAttachVolume 3m5s attachdetach-controller Multi-Attach error for volume "pvc-7f68c087-9e56-11ea-a2ef-5254002f7cc9" Volume is already used by pod(s) nginx-7b4d5d9fd-nqgfz??Warning??FailedMount?????????62s???kubelet,?10.0.0.1????????Unable?to?mount?volumes?for?pod?"nginx-7b4d5d9fd-bmc8g_default(bb5501ca-9fea-11ea-9730-5254002f7cc9)":?timeout?expired?waiting?for?volumes?to?attach?or?mount?for?pod?"default"/"nginx-7b4d5d9fd-nqgfz".?list?of?unmounted?volumes=[nginx-data].?list?of?unattached?volumes=[root-certificate?default-token-q2vft?nginx-data]這是因為只支持 RWO ( ReadWriteOnce – the volume can be mounted as read-write by a single node ) 的 volume 正常情況下在 Kubernetes 集群中只能被一個母機 attach,由于宕機母機無法執行 volume detach 操作,其它母機上的 pod 如果使用相同的 volume 會被掛住,最終導致容器創建一直阻塞并報錯:Multi-Attach?error?for?volume?"pvc-7f68c087-9e56-11ea-a2ef-5254002f7cc9"?Volume?is?already?used?by?pod(s)?nginx-7b4d5d9fd-nqgfz解決辦法是采用支持 RWX ( ReadWriteMany – the volume can be mounted as read-write by many nodes ) 讀寫模式的 volume。另外如果必須采用只支持 RWO 模式的 volume,則可以執行如下命令強制刪除 pod,如下:$?kubectl?delete?pods/nginx-7b4d5d9fd-nqgfz?--force?--grace-period=0之后,對于新創建的 pod,attachDetachController 會在 6 mins(代碼寫死)后強制 detach volume,并正常 attach,如下:W0811 04:01:25.024422 1 reconciler.go:328] Multi-Attach error for volume "pvc-e97c6ce6-d8a6-11ea-b832-7a866c097df1" (UniqueName: "kubernetes.io/rbd/k8s:kubernetes-dynamic-pvc-f35fc6fa-d8a6-11ea-bd98-aeb6842de1e3") from node "10.0.0.3" Volume is already exclusively attached to node 10.0.0.2 and can't be attached to anotherI0811 04:01:25.024480 1 event.go:209] Event(v1.ObjectReference{Kind:"Pod", Namespace:"default", Name:"default-nginx-6584f7ddb7-jx9s2", UID:"5322240f-db87-11ea-b832-7a866c097df1", APIVersion:"v1", ResourceVersion:"28275287", FieldPath:""}): type: 'Warning' reason: 'FailedAttachVolume' Multi-Attach error for volume "pvc-e97c6ce6-d8a6-11ea-b832-7a866c097df1" Volume is already exclusively attached to one node and can't be attached to anotherW0811 04:07:25.047767 1 reconciler.go:232] attacherDetacher.DetachVolume started for volume "pvc-e97c6ce6-d8a6-11ea-b832-7a866c097df1" (UniqueName: "kubernetes.io/rbd/k8s:kubernetes-dynamic-pvc-f35fc6fa-d8a6-11ea-bd98-aeb6842de1e3") on node "10.0.0.2" This volume is not safe to detach, but maxWaitForUnmountDuration 6m0s expired, force detachingI0811 04:07:25.047860 1 operation_generator.go:500] DetachVolume.Detach succeeded for volume "pvc-e97c6ce6-d8a6-11ea-b832-7a866c097df1" (UniqueName: "kubernetes.io/rbd/k8s:kubernetes-dynamic-pvc-f35fc6fa-d8a6-11ea-bd98-aeb6842de1e3") on node "10.0.0.2"I0811 04:07:25.148094 1 reconciler.go:288] attacherDetacher.AttachVolume started for volume "pvc-e97c6ce6-d8a6-11ea-b832-7a866c097df1" (UniqueName: "kubernetes.io/rbd/k8s:kubernetes-dynamic-pvc-f35fc6fa-d8a6-11ea-bd98-aeb6842de1e3") from node "10.0.0.3"I0811 04:07:25.148180 1 operation_generator.go:377] AttachVolume.Attach succeeded for volume "pvc-e97c6ce6-d8a6-11ea-b832-7a866c097df1" (UniqueName: "kubernetes.io/rbd/k8s:kubernetes-dynamic-pvc-f35fc6fa-d8a6-11ea-bd98-aeb6842de1e3") from node "10.0.0.3"I0811?04:07:25.148266???????1?event.go:209]?Event(v1.ObjectReference{Kind:"Pod",?Namespace:"default",?Name:"default-nginx-6584f7ddb7-jx9s2",?UID:"5322240f-db87-11ea-b832-7a866c097df1",?APIVersion:"v1",?ResourceVersion:"28275287",?FieldPath:""}):?type:?'Normal'?reason:?'SuccessfulAttachVolume'?AttachVolume.Attach?succeeded?for?volume?"pvc-e97c6ce6-d8a6-11ea-b832-7a866c097df1"而默認的 6 mins 對于生產環境來說太長了,而且 Kubernetes 并沒有提供參數進行配置,因此我向官方提了一個 PR [11]用于解決這個問題,如下:--attach-detach-reconcile-max-wait-unmount-duration?duration???maximum?amount?of?time?the?attach?detach?controller?will?wait?for?a?volume?to?be?safely?unmounted?from?its?node.?Once?this?time?has?expired,?the?controller?will?assume?the?node?or?kubelet?are?unresponsive?and?will?detach?the?volume?anyway.?(default?6m0s)通過配置 attach-detach-reconcile-max-wait-unmount-duration,可以縮短替換 pod 成功運行的時間。另外,注意 force detaching 邏輯只會在 pod 被 force delete 的時候觸發,正常 delete 不會觸發該邏輯。四、存儲
對于存儲,可以分為 Kubernetes 系統存儲和應用存儲,系統存儲專指 etcd;而應用存儲一般來說只考慮 persistent volume 。1. 系統存儲 - etcd
etcd 使用 Raft 一致性算法[12]( leader selection + log replication + safety ) 中的 leader selection 來實現節點宕機下的高可用問題,如下所示:2. 應用存儲 - persistent volume
這里考慮存儲是部署在集群外的情況(通常情況)。如果一個母機宕機了,由于沒有對外部存儲集群產生破壞,因此不會影響其它母機上應用訪問 pv 存儲。而對于 Kubernetes 存儲本身組件功能 ( in-tree, flexVolume, external-storage 以及 csi ) 的影響實際上可以歸納為對存儲插件應用的影響,這里以 csi 為例子進行說明:通過實現對上述 StatefulSet/Deployment 工作負載類型應用 ( CSI Driver+Identity Controller以及external-attacher+external-provisioner ) 的高可用即可。五、應用
1. Stateless Application
對于無狀態的服務(通常部署為 deployment 工作負載),我們可以直接通過設置反親和+多副本來實現高可用,例如 nginx 服務:apiVersion: apps/v1kind: Deploymentmetadata:name: web-serverspec:selector: matchLabels: app: web-storereplicas: 3template: metadata: labels: app: web-store spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - web-store topologyKey: "kubernetes.io/hostname" containers: - name: web-app image: nginx:1.16-alpine如果其中一個 pod 所在母機宕機了,則在 endpoint controller 踢掉該 pod backend 后,服務訪問正常。這類服務通常依賴于其它有狀態服務,例如:WebServer,APIServer 等。2. Stateful Application
對于有狀態的服務,可以按照高可用的實現類型分類如下:(1)RWX Type對于本身基于多副本實現高可用的應用來說,我們可以直接利用反親和+多副本進行部署(一般部署為 deployment 類型),后接同一個存儲(支持 ReadWriteMany,例如:Cephfs or Ceph RGW ),實現類似無狀態服務的高可用模式。其中,docker distribution,helm chartmuseum 以及 harbor jobservice 等都屬于這種類型。(2)Special Type對于特殊類型的應用,例如 database,它們一般有自己定制的高可用方案,例如常用的主從模式。這類應用通常以 statefulset 的形式進行部署,每個副本對接一個 pv,在母機宕機的情況下,由應用本身實現高可用(例如:master 選舉-主備切換)。其中,redis,postgres,以及各類db都基本是這種模式,如下是?redis?一主兩從三哨兵高可用方案還有更加復雜的高可用方案,例如 etcd 的 Raft 一致性算法:(3)Distributed Lock TypeKubernetes Controller 就是利用分布式鎖實現高可用。這里歸納了一些常用應用實現高可用的方案。當然了,各個應用可以定制適合自身的高可用方案,不可能完全一樣。結語
本文先介紹了 Kubernetes 集群高可用的整體架構,之后基于該架構從網絡,存儲,以及應用層面分析了當節點宕機時可能會出現的問題以及對應的解決方案,希望對 Kubernetes 高可用實踐有所助益。實際生產環境需要綜合上述因素全面考慮,將整體服務的恢復時間控制在一個可以接受的范圍內。參考資料:
[1]?Stacked etcd topology:https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/ha-topology/#stacked-etcd-topology
[2]?Leader Election Mechanism:https://github.com/kubernetes/client-go/tree/master/examples/leader-election
[3]?反親和:https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#more-practical-use-cases
[4]?iptables代理模式:https://kubernetes.io/docs/concepts/services-networking/service/#proxy-mode-iptables
[5]?retry with another backend:https://kubernetes.io/docs/concepts/services-networking/service/#proxy-mode-iptables
[6]?Kubernetes Controller高可用詭異的15mins超時:https://duyanghao.github.io/kubernetes-ha-http-keep-alive-bugs/
[7]?tolerations:https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/#taint-based-evictions
[8]?一直處于"Terminating"狀態:https://github.com/kubernetes/kubernetes/issues/55713#issuecomment-518340883
[9]?at most one semantics:https://kubernetes.io/docs/tasks/run-application/force-delete-stateful-set-pod/#statefulset-considerations
[10]?by kubelet:https://github.com/kubernetes/community/blob/master/contributors/design-proposals/storage/pod-safety.md#current-guarantees-for-pod-lifecycle
[11]?作者PR:https://github.com/kubernetes/kubernetes/pull/93776
[12] Raft 一致性算法論文譯文:https://www.infoq.cn/article/raft-paper/
[13] In Search of an Understandable Consensus Algorithm:https://ramcloud.atlassian.net/wiki/download/attachments/6586375/raft.pdf
[14] add node shutdown KEP:https://github.com/kubernetes/enhancements/pull/1116/files
[15] Recommended Mechanism for Deploying CSI Drivers on Kubernetes:https://github.com/kubernetes/community/blob/master/contributors/design-proposals/storage/container-storage-interface.md#recommended-mechanism-for-deploying-csi-drivers-on-kubernetes
[16] Container Storage Interface (CSI):https://github.com/container-storage-interface/spec/blob/master/spec.md
[17] Client should expose a mechanism to close underlying TCP connections:https://github.com/kubernetes/client-go/issues/374文章推薦
自繪引擎時代,為什么Flutter能突出重圍?
總結
以上是生活随笔為你收集整理的golang检查tcp是否可用_宕机处理:Kubernetes集群高可用实战总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 非此即彼的逻辑错误_MBA逻辑攻略逻辑知
- 下一篇: 判断均匀平面波的极化形式_测瑞通|怎样判