幾個月前,我在更新 Kubernetes 叢集中的 Deployment 時發現了一個很奇怪的連接配接逾時現象,在更新 Deployment 之後的 30 秒到兩分鐘左右,所有與以該 Deployment作為服務後端的 Service 的連接配接都會逾時或失敗。同時我還注意到其他應用在這段時間内也會出現莫名其妙的延遲現象。
一開始我懷疑是應用沒有優雅删除導緻的,但當我在更新 Deployment 的過程中(删除舊的 Pod,啟動新的 Pod)通過 curl 來測試該應用的健康檢查(liveness)和就緒檢查(readiness)Endpoints 時,很快就排除了這個可能性。
我開始懷疑人生,開始懷疑我的職業選擇,幾個小時之後我忽然想起來 Service 并不是直接與 Deployment關聯的,而是按照标簽對一組提供相同功能的 Pods的抽象,并為它們提供一個統一的入口。更重要的是,Service 是由一組 Endpoint 組成的,隻要Service中的一組Pod發生變更,Endpoint就會被更新。
想到這裡,就可以繼續排查問題了。下面在更新 Deployment的過程中通過 watch 指令來觀察有問題的 Service 的 Endpoint。
$ watch kubectl describe endpoints [endpoint name]
然後我就發現了罪魁禍首,在舊 Pod被移除的 30 秒到幾分鐘左右的時間段内,這些被删除的Pod的 IP:Port 仍然出現在Endpoint 的就緒清單中,同時新啟動的 Pod的IP:Port也沒有被添加到 Endpoint中。終于發現了連接配接失敗的根源,但是為什麼會出現這種狀況呢?仍然無解。
又經曆了幾天折騰之後,我又有了新點子,那就是調試負責更新Endpoint 的元件:kube-controller-manager,最後終于在kube-controller-manager 的日志輸出中發現了如下可疑的資訊:
I0412 22:59:59.914517 1 request.go:638] Throttling request took 2.489742918s, request: GET:
https://10.3.0.1:443/api/v1/namespaces/[somenamespace]/endpoints/[some endpoints]"
但還是感覺哪裡不對勁,明明延遲了幾分鐘,為什麼這裡顯示的隻有兩秒?
在閱讀了kube-controller-manager的源碼後,我發現了問題所在。Kube-controller-manager的主要職責是通過内部的衆多 Controller将叢集的目前狀态調整到期望狀态,其中 Endpoint Controller用于監控Pod 的生命周期事件并根據這些事件更新 Endpoint。
Endpoint Controller 内部運作了一組 workers來處理這些事件并更新Endpoint,如果有足夠多的對 Endpoint發起的請求被阻塞,那麼所有的 workers 都會忙于等待被阻塞的請求,這時候新事件隻能被添加到隊列中排隊等待,如果該隊列很長,就會花很長時間來更新 Endpoint。
為了解決這個問題,首先我通過調整kube-controller-manager 的 參數--concurrent-endpoints-syncs來增加Endpoint Controller的workers,但收效甚微。
再次仔細閱讀源碼後,我找到了兩個可以可以扭轉戰局的參數:--kube-api-qps 和--kube-api-burst。kube-controller-manager可以通過這兩個參數來限制任何 Controller(包括 Endpoint Controller)對 kube-apiserver發起的請求的速率。
這兩個參數的預設值是20,但當叢集中的主機數量非常多時,預設值顯然不滿足叢集運作的工作負載。經過不斷調試之後,我将參數 --kube-api-qps的值設定為 300,将 --kube-api-burst的值設定為 325,上面的日志資訊便消失了,同時添加或移除Pod 時Endpoint也能夠立即更新。
--kube-api-qps 和--kube-api-burst參數的值越大,kube-apiserver 和etcd 的負載就越高。在我的叢集中,通過适當地增加一些負載來解決這個問題是很值得的。