自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Kubernetes 驚天地泣鬼神之大Bug

云計算
自從三月份生產(chǎn)、非生產(chǎn)全面上線 Kubernetes 后,本以為部署的問題可以舒一口氣了,但是斷斷續(xù)續(xù)在生產(chǎn)、非生產(chǎn)環(huán)境發(fā)現(xiàn)一個詭異的問題,這禮拜又調(diào)試了三天,在快要放棄的時候居然找到原因了,此缺陷影響目前(2018-5-23)為止所有 Kubernetes 版本(見后面更新,夸大了)。

自從三月份生產(chǎn)、非生產(chǎn)全面上線 Kubernetes 后,本以為部署的問題可以舒一口氣了,但是斷斷續(xù)續(xù)在生產(chǎn)、非生產(chǎn)環(huán)境發(fā)現(xiàn)一個詭異的問題,這禮拜又調(diào)試了三天,在快要放棄的時候居然找到原因了,此缺陷影響目前(2018-5-23)為止所有 Kubernetes 版本(見后面更新,夸大了),包括 GitHub 當前 master 分支,后果是某種情況觸發(fā)下,Kubernetes service 不能提供服務(wù),影響非常惡劣。

問題的現(xiàn)象是,在某種情況下,一個或者多個 Kubernetes service 對應的 Kubernetes endpoints 消失幾分鐘至幾十分鐘,然后重新出現(xiàn),然后又消失,用 "kubectl get ep --all-namespaces" 不斷查詢,可以觀察到一些 AGE 在分鐘級別的 endpoints 要么忽然消失,要么 AGE 突然變小從一分鐘左右起步。Endpoints 的不穩(wěn)定,必然導致對應的 Kubernetes service 無法穩(wěn)定提供服務(wù)。有人在 Github 上報告了 issue,但是一直沒得到開發(fā)人員注意和解決。

首先解釋下 Kubernetes 大體上的實現(xiàn)機制,有助于理解下面的破案過程。

Kubernetes 的實現(xiàn)原理跟配置管理工具 CFengine、Ansible、Salt 等非常類似:configuration convergence——不斷的比較期望的配置和實際的配置,修訂實際配置以收斂到期望配置。

Kubernetes 的關(guān)鍵系統(tǒng)服務(wù)有 api-server、scheduler、controller-manager 三個。api-server 一方面作為 Kubernetes 系統(tǒng)的訪問入口點,一方面作為背后 etcd 存儲的代理服務(wù)器,Kubernetes 里的所有資源對象,包括 Service、Deployment、ReplicaSet、DaemonSet、Pod、Endpoint、ConfigMap、Secret 等等,都是通過 api-server 檢查格式后,以 protobuf 格式序列化并存入 etcd。這就是一個寫入“期望配置”的過程。

Controller-manager 服務(wù)里包含了很多 controller 實例,對應各種資源類型:

 

001.jpg
v1.12.0-alpha.0/cmd/kube-controller-manager/app/controllermanager.go#L317

這些 controller 做的事情就是收斂:它通過 api-server 的 watch API 去間接的 watch etcd,以收取 etcd 里數(shù)據(jù)的 changelog,對改變(包括ADD、DELETE、UPDATE)的資源(也就是期望的“配置“),逐個與通過 kubelet + Docker 收集到的信息(實際“配置”)做對比并修正差異。

比如 etcd 里增加了一個 Pod 資源,那么 controller 就要請求 scheduler 調(diào)度,然后請求 kubelet 創(chuàng)建 Pod,如果etcd里刪除了一個 Service 資源,那么就要刪除其對應的 endpoint 資源,而這個 endpoint 刪除操作會被 kube-proxy 監(jiān)聽到而觸發(fā)實際的 iptables 命令。

注意上面 controller 通過 watch 獲取 changelog 只是一個實現(xiàn)上的優(yōu)化,它沒有周期性的拿出所有的資源對象然后逐個判斷,在集群規(guī)模很大時,這樣的全量收斂是很慢的,后果就是集群的調(diào)度、自我修復進行的非常慢。

有了大框架的理解后,endpoints 被誤刪的地方是很容易找到的:

 

002.jpg
v1.12.0-alpha.0/pkg/controller/endpoint/endpoints_controller.go#L396

然后問題來了,什么情況下那個 Services(namespace).Get(name) 會返回錯誤呢?通過注釋,可以看到在 service 刪除時會走到 397 行里去,把無用的 endpoints 刪掉,因為 endpoint 是給 service 服務(wù)的,service 不存在時,endpoint 沒有存在的意義。

然后問題來了,通過 "kubectl get svc" 可以看到出問題期間,服務(wù)資源一直都在,并沒有重建,也沒有剛剛部署,甚至很多服務(wù)資源都是創(chuàng)建了幾個月,為啥它對應的 endpoints 會被誤刪然后重建呢?這個問題困擾了我兩個月,花了很長時間和很多腦細胞,今天在快放棄時突然有了重大發(fā)現(xiàn)。

由于我司搭建的 Kubernetes 集群開啟了 X509 認證以及 RBAC 權(quán)限控制,為了便于審查,我開啟了 kube-apiserver 的審計日志,在出問題時,審計日志中有個特別的模式:

 

003.jpg
用 jq 命令摘取的審計日志片段

審計日志中,在每個 endpoint delete & create 發(fā)生前,都有一個 "/api/v1/services...watch=true" 的 watch 調(diào)用,上面講到,controller-manager 要不斷的去爬 etcd 里面資源的 changelog,這里很奇怪的問題是,這個調(diào)用的 "resourceVersion=xxx" 的版本值始終不變,難道不應該不斷從 changelog 末尾繼續(xù)爬取因為 resourceVersion 不斷遞增么?

通過一番艱苦卓絕的連猜帶蒙,終于找到“爬取changelog”對應的代碼:

 

004.jpg
v1.12.0-alpha.0/staging/src/k8s.io/client-go/tools/cache/reflector.go#L394

上面的代碼對 resourceVersion 的處理邏輯是: 遍歷 event 列表,取當前 event 的 "resourceVersion" 字段作為新的要爬取的“起始resourceVersion",所以很顯然遍歷結(jié)束后,"起始resourceVersion" 也就是***一條 event 的 "resourceVersion"。

好了,我們來看看 event 列表漲啥樣,把上面 api-server 的請求照搬就可以看到了:

 

  1. kubectl proxy 
  2. curl -s localhost:8001/api/v1/services?resourceVersion=xxxx&timeoutSeconds=yyy&watch=t 

 

005.jpg
/api/v1/services 的輸出,僅示意,注意跟上面的審計日志不是同一個時間段的

可以很明顯看到,坑爹,resourceVersion 不是遞增的!

這個非遞增的問題其實很容易想明白,resourceVersion 并不是 event 的序列號,它是 Kubernetes 資源自身的版本號,如果把 etcd 看作 Subversion,兩者都有一個全局遞增的版本號,把 Kubernetes 資源看作 Subversion 里保存的文件。 在 svn revision=100 時存入一個文件 A,那么A的版本是 100,然后不斷提交其它文件的修改到 svn,然后在某個版本刪掉 A,此時用 svn log 看到的相關(guān)文件的“自身***一次修改版本”并不是遞增的,***一條的“自身***一次修改版本”是 100。

所以真相大白了,reflector.go 那段遍歷 etcd "changelog" 的代碼,誤以為 event 序列的 resourceVersion 是遞增的了,雖然絕大部分時候是這樣的。由于這段代碼是通用的,所有資源都受這個影響,所以是非常嚴重的 bug,之前看到很多人報告(我自己也遇到)controller-manager 報錯 "reflector.go:341] k8s.io/kubernetes/pkg/controller/bootstrap/bootstrapsigner.go:152: watch of *v1.ConfigMap ended with: too old resource version: " 很可能也是這個導致的,因為它失誤從很老的 resourceVersion 開始爬取 etcd "changelog",但 etcd 已經(jīng)把太老的 changelog 給 “compact” 掉了。

***總結(jié)下怎么觸發(fā)以及繞過這個 bug:

觸發(fā)

陸續(xù)創(chuàng)建、刪除、創(chuàng)建 Kubernetes service 對象,然后"kubectl delete svc xxx"刪掉創(chuàng)建時間靠前的 service,也就是往 service event list 末尾插入了一條 resourceVersion 比較小的記錄,這將使得 controller-manager 去從已經(jīng)爬過的 service event list 位置重新爬取重放,然后就重放了 service 的 ADDED、DELETED event,于是 controller-manager 內(nèi)存里緩存的 service 對象被刪除,導致 EndpointController 刪除了“不存在的”service 對應的 endpoints。

繞過

用 docker restart 重啟 kube-controller-manager 容器,強迫其從 event list ***位置開始爬取

創(chuàng)建一個 service,其 resourceVersion 會是 etcd 的***全局版本號,這個 ADDED event 會出現(xiàn)在 event list 末尾,從而 controller-manager 從這個***的 resourceVersion 開始爬取

"kubectl edit" 修改某個 service,加點無意義的 label 之類的,保存,這樣其 resourceVersion 也會更新,之后跟上一個 workaround 原理一樣了。

錯誤的繞過方式

  • "kubectl delete pod/xxx":不影響 service event list
  • "kubectl delete deploy/xxxx":不影響 service event list

Kubernetes 里凡是創(chuàng)建后基本不變的資源,比如 service、configmap、secrets 都會受這個影響,它們的版本號都很久,刪一個后都會觸發(fā)這個 bug,比如 configmap,可能就會被重復的更新,映射到容器里的 config file 也就不斷更新,對于不支持 config hot reload 的服務(wù)沒有影響,對于支持 config hot reload 的服務(wù),會頻繁的重新加載配置文件重新初始化,可能導致意想不到的問題。

Bug 的修復很簡單,在遍歷 event list 選擇 resourceVersion 時,總是取 max(event.resourceVersion, currentResourceVersion) 即可,我提了個 pull request 給 Kubernetes 官方看看,希望我這個粗暴的修正不會帶來新的問題。

責任編輯:未麗燕 來源: 知乎專欄
相關(guān)推薦

2012-12-28 11:03:02

2024-07-03 12:01:36

2017-07-18 10:05:58

2009-11-24 09:44:59

WIN7chrome谷歌

2016-01-11 10:10:53

2019-01-22 10:10:07

2023-05-10 09:42:39

代碼開源

2023-11-17 22:56:47

ChatGPTAI

2019-07-29 10:46:14

HTTPHTTP 3HTTP 2

2021-09-13 15:54:01

程序員技能開發(fā)者

2023-03-31 11:36:52

阿里中臺架構(gòu)

2013-11-26 15:44:25

Android設(shè)計模式

2010-05-21 17:19:15

2009-07-19 21:51:19

FTTx光纖

2019-10-17 10:12:02

Go語言Java函數(shù)

2015-08-27 16:55:09

2012-04-26 22:41:13

2016-01-05 09:14:52

戴爾云計算

2011-05-11 14:52:28

2013-12-13 09:31:43

點贊
收藏

51CTO技術(shù)棧公眾號