docker容器優雅停機導致的問題說明

現象

測試同學反饋頁面某功能有時候不能使用。

分析

根據頁面返回結果和URL,從開發同學那裏瞭解到該現象是新環境裏a服務調b服務返回異常導致的。從多次請求的結果看來,大概50%的機率返回異常。按常理判斷如果b服務有多個節點的,可能有其中個別節點異常會導致此現象發生,但是b服務只有一個節點,請求的同時,觀察b服務的日誌,在a調用結果異常的情況下,b沒有任何日誌輸出,正常情況日誌輸出返回結果,說明請求沒有到達b服務,如果對調用鏈路不熟的情況下完全可以抓包明確請求是否到達。那麼調用失敗的請求是去哪裏了呢?調用鏈路如下:
docker容器優雅停機導致的問題說明
圖1
圖1可以看出a調用b的地址是從consul-server上拿到的,在consul-server控制檯看到的情況入下圖:
docker容器優雅停機導致的問題說明
圖2
注:圖中gw-mini是服務b
圖2 中看到單節點的b服務,在consul註冊中心上註冊了兩個實例,實際是這樣嗎?繼續看下圖:
docker容器優雅停機導致的問題說明
圖3
圖3中b服務是單節點,ip是10.100.73.72,而consul-server中的另外一個地址10.100.73.75是其他服務的ip地址。在consul上b服務下面的兩個實例健康檢查都是正常狀態,難怪a調用b老是出現異常的返回結果,a拿到的地址list裏面有一個地址是不對的。爲什麼consul上b服務會有兩個實例?看看異常實例10.100.73.75的詳情,如下圖:
docker容器優雅停機導致的問題說明
圖4
健康檢查URL是b服務的沒錯,返回結果也正常,http status_code 200,跳了統一登錄認證,此處其實是有攔截器導致返回結果是200。在此服務中還有其他實例,但是處於異常狀態,下圖所示:
docker容器優雅停機導致的問題說明
圖5
在k8s集羣中,服務所在pod的ip(即:註冊到consul上的ip地址)是根據網絡插件配置自動分配的,每次更新服務都會重建pod,pod ip也會隨之改變。此時基本可以確定,在更新的時候,註冊在consul上的老版本實例沒有及時移除掉,其使用的ip在k8s回收之後重新分配給了新服務的pod,且新服務裏面有攔截器,所有請求先跳轉登錄,因此導致了這樣的問題出現,如果服務多了問題會更加嚴重。
拉了開發同學一起看看,是不是服務在關閉的時候不會反註冊,從consul上下線自己。開發同學在本地IDE啓動服務,點停止按鈕(發送SIGTERM信號)的時候日誌打印出deregister service相關內容,且在幾秒鐘後測試consul上服務也自動下線了。但是在出問題的新環境裏面,更新服務時沒有看到任何反註冊日誌,服務也不會從consul上自動下線,可以判定是優雅停機出現了問題。有查閱docker官方文檔,在docker中,容器停止的時候會給進程號爲1的進程發送SIGTERM,且在默認10s之後進程還沒退出的話發送SIGKILL信號強制關閉。查看此項目dockerfile,其中entrypoint是以shellform格式運行了一個bash腳本,在bash腳本中啓動了java進程,以此種方式運行的進程,實際上上是sh –c的子命令,且不會傳遞信號給entrypoint進程,所以在關閉的時候java進程並沒有收到優雅停機信號,自然也沒機會通知consul下線服務。

處理

兩方面,一個是讓java進程能夠接收到SIGTERM信號,另一個是健康檢查的問題。
第一個問題,整改dockerfile,entrypoint採用execform的格式寫,且完全沒必要放在bash腳本里面,直接啓動java進程。
第二個問題,對健康檢查URL去掉攔截。
還有一方面就是設置pod lifecycle,添加prestop鉤子。

結果

整改之後,服務關閉的時候正常輸出deregister service信息,consul-server上也會自動下線服務。此異常頁面再沒出現過類似問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章