經驗分享:修復服務網格Istio大量503錯誤

banq發表於2019-06-04

如果您使用Istio,可能會遇到大量503錯誤的問題,目前處理503問題的一般方式有點籠罩。  

經驗分享:修復服務網格Istio大量503錯誤
上圖中應用2中的Envoy如與應用2中本身微服務應用發生通訊錯誤,會包裝一個503錯誤發回到上游,讓上游應用1重試。

無論如何,重試不是世界末日,值得注意的是,你重試總是遭遇失敗 ,我們看到約有0.012%的請求以這種方式失敗。我們還有一個微服務架構,由於複雜的故障請求觸及5個應用程式,你會看到約為0.08%的聚合請求失敗率。不是頻繁,絕對不僅僅是“奇怪的曇花一現”。

當您開始考慮故障點時,值得關注的是邊車模型的服務網格其實增加了應用程式之間通訊的複雜性。

在服務網格世界中,我們實際上有3個獨立的連線池,而不是一個。每個都有自己的配置:

經驗分享:修復服務網格Istio大量503錯誤

  • consumer-gateway到 source-envoy(在Java程式碼中配置)
  • source envoy到 destination envoy(在DestinationRule中配置)
  • destination envoy到 sauron-seo-app(在Envoy中配置預設​​情況下啟用,但不能透過Istio配置)

事情出錯的機會很多,讓我們來看看這個真實世界的場景,看看我們的問題在哪裡。

度量
Istio收集了大量可以幫助我們發現問題的指標。這很有意思,因為Istio提供的可視性水平意味著它可能發現您之前遇到的問題!所以開啟prometheus並輸入:

sort_desc(sum(changes(istio_requests_total{response_flags="UC", response_code="503", reporter="destination"}[24h])) by (source_app, destination_app, reporter) >0)


這是讓prometheus告訴我:“在過去的24小時內,給我一個導致503的請求,響應標誌UC(上游連線問題),按source_app,destination_app和分組reporter排序)。  

結果:

{destination_app="sauron-seo-app",reporter="destination",source_app="consumer-gateway"} 58-

這告訴我,在過去的24小時,58個請求之間在consumer-gateway到sauron-seo-app產生了503的UC。
好的,我們知道我們在destination遇到了一些問題,這與我們在跟蹤中看到的內容相匹配,因為源頭正在重新嘗試到另一個destination目的地並取得成功。讓我們來看看Envoy發生了什麼。
 
我們要做的下一件事是啟用一些指標來幫助我們除錯Envoy。預設情況下,istio-proxy僅啟用一些核心特使指標以減少佔用空間。我們想要更多資訊。新增以下注釋Deployment將為您提供:

sidecar.istio.io/statsInclusionPrefixes: cluster.outbound,listener,cluster,cluster_manager,listener_manager,http_mixer_filter,tcp_mixer_filter,server,cluster.xds-grpc


注意:預設情況下禁用這些指標,因為它們具有相對較高的基數。我個人只在我除錯問題時啟用它們,然後關閉它們。

看看envoy_cluster_upstream_cx_destroy_local_with_active_rq 和envoy_cluster_upstream_cx_destroy_remote_with_active_rq指標

我們的目的地Envoy報告了一大堆remote destroy遠端關閉,我們的源Envoy有一大堆local destroy本地關閉。

這基本上告訴我們的是,sauron-seo-app Envoy和應用程式之間的聯絡已經關閉, 它看起來就像sauron-seo-app關閉(遠端關閉)它。然後consumer-gateway Envoy也在關閉了那個連線。因為consumer-gateway收到了http1.1連線的5xx響應,無法在該連線上傳送更多資料,因此除了關閉它之外別無選擇(local  destroy本地關閉)。

這證實我們需要縮小調查範圍到sauron-seo-app。

Istio-proxy除錯日誌
Istio-proxy使您可以在執行時切換多個日誌級別,這有助於除錯這些型別的問題。因此,讓我們將所有這些設定為除錯,開啟sauron-seo-app並檢視我們可以找到的內容:

kubectl -n sauron-seo-app sauron-seo-app-7667b9b7dc-jd3vg -c istio-proxy -- curl -XPOST -s -o /dev/null http://localhost:15000/logging?level=debug


檢查日誌:

kubectl -n sauron-seo-app logs -f sauron-seo-app-7667b9b7dc-jd3vg -c istio-proxy

結果:

<p class="indent">[2019-05-30 08:24:09.206][34][debug][filter] [src/envoy/http/mixer/filter.cc:133] Called Mixer::Filter : encodeHeaders 2

<p class="indent">[2019-05-30 08:24:09.206][34][debug][http] [external/envoy/source/common/http/conn_manager_impl.cc:1305] [C77][S184434754633764276] encoding headers via codec (end_stream=false):
 ':status', '503'
 'content-length', '95'
 'content-type', 'text/plain'
 'date', 'Thu, 30 May 2019 08:24:08 GMT'
 'server', 'istio-envoy'


<p class="indent">[2019-05-30 08:24:09.208][34][debug][connection] [external/envoy/source/common/network/connection_impl.cc:502] [C77] remote close

<p class="indent">[2019-05-30 08:24:09.208][34][debug][connection] [external/envoy/source/common/network/connection_impl.cc:183] [C77] closing socket: 0



我們的在連線[C77]看到503錯誤,

資料包捕獲
此時我們已經用盡了Istio和Envoy為我們提供的所有指標,我們已將其縮小到目標應用程式,似乎終止了連線。透過資料包捕獲的時間,並嘗試找出正在發生的事情。

為了在kubernetes上執行此操作,我們使用了一個名為ksniff的實用程式:

  • 檢測目標應用程式正在執行的節點
  • 部署與該節點具有親緣關係的pod,繫結到主機網路
  • TCP從特權應用程式轉儲流量並將其直接傳輸回膝上型電腦上的wireshark,就好像它是本地的一樣。


TCP轉儲非常嘈雜,所以我們想要過濾一點:
  • 我們正在尋找有關TCP連線事件的資訊,所以讓我們來看看SYN,FIN和RST。
  • 當我們調查本地特使和應用程式之間的流量時,我們只想檢視localhost,因此-i lo將針對環回介面卡。

kubectl sniff $pod -p -n $namespace -c istio-proxy -f 'tcp[tcpflags] & (tcp-syn|tcp-fin|tcp-rst) != 0' -i lo


然後我們很快就能看到連線RST,確認連線確實被我們的應用程式關閉了。

我們的伺服器是nodejs:
超時(以毫秒為單位)。預設值: 5000(5秒)。伺服器在完成寫入最後一個響應之後,在銷燬套接字之前需要等待其他傳入資料的非活動毫秒數。

TCP套接字超時
我們做了一些深入挖掘,因為我們不只是在nodejs中看到這個。我們在python應用程式中看到的程度較小,在java / tomcat中程度較小。事實證明,它們具有以下預設套接字超時:

  • nodejs:5秒
  • python:10秒
  • tomcat:20秒

因此套接字超時越低,我們得到的RST越多,503問題就會越嚴重。

即使Istio團隊正在積極尋求改進這種機制,我們發現透過在目標應用程式中設定更高的套接字保持活動設定,我們可以很容易地解決它。  

修復超時express(nodejs):

const server = app.listen(port, '0.0.0.0', () => {
  logger.info(`App is now running on http://localhost:${port}`)
})
server.keepAliveTimeout = 1000 * (60 * 6) // 6 minutes


在cherrpy(pyhton):

global_config = {
  'server.socket_timeout': 6 * 60,
}
cherrypy.config.update(global_config)


在tomcat(java-spring):

server:
  connect-timeout: 360000


 

相關文章