歡迎光臨
每天分享高質量文章

Kubernetes服務發現與故障排除

本文為Kubernetes監控系列的第三篇文章,系列目錄如下:

  1. Kubernetes監控開源工具基本介紹以及如何使用Sysdig進行監控

  2. Kubernetes叢集的監控報警策略最佳實踐

  3. Kubernetes中的服務發現與故障排除(本篇)

  4. Docker與Kubernetes在WayBlazer的使用案例(敬請期待)

本文是關於在生產中使用Kubernetes系列的一部分。第一部分介紹了Kubernetes和監控工具的基礎知識;第二部分涵蓋了Kubernetes報警的最佳實踐;這部分將介紹對Kubernetes故障排除,特別是服務發現,最後一部分是監控Kubernetes的實際使用案例。像Kubernetes,DC / OS Mesos或Docker Swarm這樣的容器編排平臺可以幫助你方便地管理容器,但對解決容器問題並沒有什麼幫助:
  • 它們是孤立的,你和你要監視的行程之間存在一定的障礙,在主機上執行的傳統故障排除工具不瞭解容器、名稱空間和編排平臺。

  • 它們帶來最小的執行時間,只需要服務及其依賴項,而不需要所有的故障排除工具,想象下只用busybox來進行故障排除!

  • 它們排程在你叢集、進行容器移動,擴容或者縮容。隨著流程的結束,它們變得非常不穩定,出現並消失。

  • 並透過新的虛擬網路層互相通訊。

本文將透過一個真實用例講訴如何在Kubernetes中進行故障排除。我們將涵蓋Kubernetes內部3個不同層的故障排除:
  • 第一部分:Kubernetes故障排除之服務發現

  • 第二部分:Kubernetes故障排除之DNS解析

  • 第三部分:Kubernetes故障排除之使用Docker執行容器

本文場景將使用一個簡單的Kubernetes服務[1],包括3個Nginx pod和一個帶有curl命令的客戶端。在連結中可以看到場景需要使用的yaml檔案 backend.yaml。如果你剛剛接觸Kubernetes,可以看下“Understanding how Kubernetes DNS services work[2]”這篇文章來學習如何部署上述服務。實際操作如下:
$ kubectl create namespace critical-app
namespace "critical-app" created
$ kubectl create -f backend.yaml
service "backend" created
deployment "backend" created
然後建立一個客戶端來載入我們的後端服務:
$ kubectl run -it --image=tutum/curl client --namespace critical-app --restart=Never

Kubernetes故障排除之服務發現

在我們的client容器中可以執行一個簡單測試 root@client:/# curl backend 來檢視我們的Kubernetes service是如何工作的。但我們不想遺漏下什麼,我們認為可以使用FQDN進行服務發現測試,在Kubernetes檔案[3]中會發現,每個service都會獲得一個預設的DNS:my-svc.my-namespace.svc.cluster.local。因此,我們用它作為FQDN進行測試。
在client容器的shell端執行:root@client:/# curl backend.critical-app.svc.cluster.local,不過這次curl命令卡住了10秒,然後傳回了預期網址!作為一個分散式系統工程師,這是最糟糕的事情之一:你希望直接失敗或者成功,而不是等待10秒。
為了定位出問題,我們使用了Sysdig。Sysdig是一個開源Linux視覺化工具,提供對容器的本地可見性,包括Docker、Kubernetes、DC / OS和Mesos等等。將htop,tcpdump,strace,lsof,netstat等的功能組合在一起,Sysdig提供了Kubernetes基礎架構環境中的所有系統呼叫和應用程式資料。Monitoring Kubernetes with Sysdig[4]中很好地介紹瞭如何在Kubernetes中使用這個工具。
為了分析上述問題原因,我們將請求sysdig來dump所有資訊到捕獲檔案中:
$ sudo sysdig -k http://127.0.0.1:8080 -s8192 -zw capture.scap
快速解釋下每個引數:
  • -k http://localhost:8080 連線Kubernetes API

  • -s8192 擴大IO緩衝區,因為我們需要顯示全部內容,否則預設情況下會被切斷,

  • -zw capture.scap 將系統呼叫與元資料資訊壓縮並dump到檔案中

同時,我們將再次執行curl命令來複現這個問題:# curl backend.critical-app.svc.cluster.local。這確保了我們在上面捕獲的檔案中擁有所有資料,以重現場景並解決問題。
一旦curl傳回,我們可以執行Ctrl+C來終止資料捕獲,並且我們將擁有一個10s的捕獲檔案,包括我們的Kubernetes主機中發生的所有事情以及服務發現過程。現在我們可以開始在叢集內或叢集外解決問題,只要在安裝了sysdig的環境中複製檔案:
$ sysdig -r capture.scap -pk -NA "fd.type in (ipv4, ipv6) and (k8s.ns.name=critical-app or proc.name=skydns)" | less
快速解釋下每個引數:
  • -r capture.scap 讀取捕獲檔案

  • -pk 列印Kubernetes部分到stdout中

  • -NA 顯示ASCII輸出

以及雙引號中是過濾內容。Sysdig能夠理解Kubernetes語意,因此我們可以過濾來自名稱空間critical-app中的任何容器或任何名為skydns的行程的套接字IPv4或IPv6上的流量。加入proc.name = skydns因為這是內部Kubernetes DNS解析器,並且作為Kubernetes基礎結構的一部分執行在我們的名稱空間之外。

Sysdig也有一個互動式的ncurses介面。
為了跟隨此服務發現故障排除示例,你可以下載捕獲檔案capture.scap[5]並使用sysdig自行檢視它。
我們立即看到curl如何嘗試解析域名,但在DNS查詢有效載荷上我們有些奇怪(10049):backend.critical-app.svc.cluster.local.critical-app.svc.cluster.local。看上去由於某些原因,curl不識別已經給定的FQDN,並且決定追加一個搜尋域。
[...]
10030 16:41:39.536689965 0 client (b3a718d8b339) curl (22370:13) < socket fd=3(<4>)
10031 16:41:39.536694724 0 client (b3a718d8b339) curl (22370:13) > connect fd=3(<4>)
10032 16:41:39.536703160 0 client (b3a718d8b339) curl (22370:13) < connect res=0 tuple=172.17.0.7:46162->10.0.2.15:53
10048 16:41:39.536831645 1  (36ae6d09d26e) skydns (17280:11) > recvmsg fd=6(<3t>:::53)
10049 16:41:39.536834352 1  (36ae6d09d26e) skydns (17280:11) < recvmsg res=87 size=87 data=
backendcritical-appsvcclusterlocalcritical-appsvcclusterlocal tuple=::ffff:172.17.0.7:46162->:::53
10050 16:41:39.536837173 1  (36ae6d09d26e) skydns (17280:11) > recvmsg fd=6(<3t>:::53)
[...]
SkyDNS傳送請求(10097)到etcd的API(/local/cluster/svc/critical-app/local/cluster/svc/critical-app/backend),顯然etcd不能識別這個service,然後傳回(10167)“Key not found”。這透過DNS查詢響應傳回給curl。
[...]
10096 16:41:39.538247116 1  (36ae6d09d26e) skydns (4639:8) > write fd=3(<4t>10.0.2.15:34108->10.0.2.15:4001) size=221
10097 16:41:39.538275108 1  (36ae6d09d26e) skydns (4639:8) < write res=221 data=
GET /v2/keys/skydns/local/cluster/svc/critical-app/local/cluster/svc/critical-app/backend?quorum=false&recursive;=true&sorted;=false HTTP/1.1
Host: 10.0.2.15:4001
User-Agent: Go 1.1 package http
Accept-Encoding: gzip
10166 16:41:39.538636659 1  (36ae6d09d26e) skydns (4617:1) > read fd=3(<4t>10.0.2.15:34108->10.0.2.15:4001) size=4096
10167 16:41:39.538638040 1  (36ae6d09d26e) skydns (4617:1) < read res=285 data=
HTTP/1.1 404 Not Found
Content-Type: application/json
X-Etcd-Cluster-Id: 7e27652122e8b2ae
X-Etcd-Index: 1259
Date: Thu, 08 Dec 2016 15:41:39 GMT
Content-Length: 112
{"errorCode":100,"message":"Key not found","cause":"/skydns/local/cluster/svc/critical-app/local","index":1259}
[...]
curl沒有放棄並再次嘗試(10242),不過這次用的是backend.critical-app.svc.cluster.local.svc.cluster.local。看起來這次curl嘗試將critical-app這個追加搜尋域去除,使用一個不同的搜尋域。顯然,當請求到達etcd(10247)時,再次失敗(10345)。
[...]
10218 16:41:39.538914765 0 client (b3a718d8b339) curl (22370:13) < connect res=0 tuple=172.17.0.7:35547->10.0.2.15:53
10242 16:41:39.539005618 1  (36ae6d09d26e) skydns (17280:11) < recvmsg res=74 size=74 data=
backendcritical-appsvcclusterlocalsvcclusterlocal tuple=::ffff:172.17.0.7:35547->
:::53
10247 16:41:39.539018226 1  (36ae6d09d26e) skydns (17280:11) > recvmsg fd=6(<3t>:::53)
10248 16:41:39.539019925 1  (36ae6d09d26e) skydns (17280:11) < recvmsg res=74 size=74 data=
0]backendcritical-appsvcclusterlocalsvcclusterlocal tuple=::ffff:172.17.0.7:35547->
:::53
10249 16:41:39.539022522 1  (36ae6d09d26e) skydns (17280:11) > recvmsg fd=6(<3t>:::53)
10273 16:41:39.539210393 1  (36ae6d09d26e) skydns (4639:8) > write fd=3(<4t>10.0.2.15:34108->10.0.2.15:4001) size=208
10274 16:41:39.539239613 1  (36ae6d09d26e) skydns (4639:8) < write res=208 data=
GET /v2/keys/skydns/local/cluster/svc/local/cluster/svc/critical-app/backend?quorum=false&recursive;=true&sorted;=false HTTP/1.1
Host: 10.0.2.15:4001
User-Agent: Go 1.1 package http
Accept-Encoding: gzip
10343 16:41:39.539465153 1  (36ae6d09d26e) skydns (4617:1) >
read fd=3(<4t>10.0.2.15:34108->10.0.2.15:4001) size=4096
10345 16:41:39.539467440 1  (36ae6d09d26e) skydns (4617:1) < read res=271 data=
HTTP/1.1 404 Not Found
[...]
curl會再次嘗試,我們可以看這次的DNS查詢請求(10418),加上了cluster.local變成backend.critical-app.svc.cluster.local.cluster.local。這次etcd請求(10479)同樣失敗(10524)。
[...]
10396 16:41:39.539686075 0 client (b3a718d8b339) curl (22370:13) < connect res=0 tuple=172.17.0.7:40788->10.0.2.15:53
10418 16:41:39.539755453 0  (36ae6d09d26e) skydns (17280:11) < recvmsg res=70 size=70 data=
backendcritical-appsvcclusterlocalclusterlocal tuple=::ffff:172.17.0.7:40788->
:::53
10433 16:41:39.539800679 0  (36ae6d09d26e) skydns (17280:11) > recvmsg fd=6(<3t>:::53)
10434 16:41:39.539802549 0  (36ae6d09d26e) skydns (17280:11) < recvmsg res=70 size=70 data=
backendcritical-appsvcclusterlocalclusterlocal tuple=::ffff:172.17.0.7:40788->
:::53
10437 16:41:39.539805177 0  (36ae6d09d26e) skydns (17280:11) > recvmsg fd=6(<3t>:::53)
10478 16:41:39.540166087 1  (36ae6d09d26e) skydns (4639:8) > write fd=3(<4t>10.0.2.15:34108->10.0.2.15:4001) size=204
10479 16:41:39.540183401 1  (36ae6d09d26e) skydns (4639:8) < write res=204 data=
GET /v2/keys/skydns/local/cluster/local/cluster/svc/critical-app/backend?quorum=false&recursive;=true&sorted;=false HTTP/1.1
Host: 10.0.2.15:4001
User-Agent: Go 1.1 package http
Accept-Encoding: gzip
10523 16:41:39.540421040 1  (36ae6d09d26e) skydns (4617:1) >
read fd=3(<4t>10.0.2.15:34108->10.0.2.15:4001) size=4096
10524 16:41:39.540422241 1  (36ae6d09d26e) skydns (4617:1) < read res=267 data=
HTTP/1.1 404 Not Found
[...]
對於未經訓練的人來說,可能看起來我們發現了這個問題:一堆無效的請求。但實際上這不是事實,如果我們檢視時間戳,第一個etcd請求(10097)和最後一個(10479)之間的差異,第二列中的時間戳相距小於10ms。而我們正在尋找一個秒數問題,而不是毫秒——那麼等待的時間在哪裡?
當我們繼續瀏覽捕獲檔案時,我們可以看到curl不停止嘗試使用DNS查詢到SkyDNS,現在使用backend.critical-app.svc.cluster.local.localdomain(10703)。這個.localdomain不被SkyDNS識別為Kubernetes的內部域,因此它決定將這個查詢轉發給它的上游DNS解析器(10691),而不是去etcd進行服務發現。
[...]
10690 16:41:39.541376928 1  (36ae6d09d26e) skydns (4639:8) > connect fd=8(<4>)
10691 16:41:39.541381577 1  (36ae6d09d26e) skydns (4639:8) < connect res=0 tuple=10.0.2.15:44249->8.8.8.8:53
10702 16:41:39.541415384 1  (36ae6d09d26e) skydns (4639:8) > write fd=8(<4u>10.0.2.15:44249->8.8.8.8:53) size=68
10703 16:41:39.541531434 1  (36ae6d09d26e) skydns (4639:8) < write res=68 data=
Nbackendcritical-appsvcclusterlocallocaldomain
10717 16:41:39.541629507 1  (36ae6d09d26e) skydns (4639:8) >
read fd=8(<4u>10.0.2.15:44249->8.8.8.8:53) size=512
10718 16:41:39.541632726 1  (36ae6d09d26e) skydns (4639:8) < read res=-11(EAGAIN) data=
58215 16:41:43.541261462 1  (36ae6d09d26e) skydns (4640:9) >
close fd=7(<4u>10.0.2.15:54272->8.8.8.8:53)
58216 16:41:43.541263355 1  (36ae6d09d26e) skydns (4640:9) < close res=0
[...]
掃描時間戳列時,我們發現SkyDNS發出請求後第一個較大的間隙,然後等待大約4秒(10718-58215)。鑒於.localdomain不是有效的TLD(頂級域名),上游伺服器將只是忽略此請求。超時後,SkyDNS再次嘗試使用相同的查詢(75923),再等待幾秒鐘(75927-104208)。總的來說,我們一直在等待大約8秒鐘,以查詢不存在並且被忽略的DNS條目。
[...]
58292 16:41:43.542822050 1  (36ae6d09d26e) skydns (4640:9) < write res=68 data=
Nbackendcritical-appsvcclusterlocallocaldomain
58293 16:41:43.542829001 1  (36ae6d09d26e) skydns (4640:9) > read fd=8(<4u>10.0.2.15:56371->8.8.8.8:53) size=512
58294 16:41:43.542831896 1  (36ae6d09d26e) skydns (4640:9) < read res=-11(EAGAIN) data=
75904 16:41:44.543459524 0  (36ae6d09d26e) skydns (17280:11) < recvmsg res=68 size=68 data=
[...]
75923 16:41:44.543560717 0  (36ae6d09d26e) skydns (17280:11) < recvmsg res=68 size=68 data=
Nbackendcritical-appsvcclusterlocallocaldomain tuple=::ffff:172.17.0.7:47441->:::53
75927 16:41:44.543569823 0  (36ae6d09d26e) skydns (17280:11) > recvmsg fd=6(<3t>:::53)
104208 16:41:47.551459027 1  (36ae6d09d26e) skydns (4640:9) > close fd=7(<4u>10.0.2.15:42126->8.8.8.8:53)
104209 16:41:47.551460674 1  (36ae6d09d26e) skydns (4640:9) < close res=0
[...]
但最後,它一切正常!為什麼?curl停止嘗試修補事物並應用搜索域。當我們在命令列中輸入時,它會逐字嘗試域名。SkyDNS透過etcd服務發現API請求解析DNS請求(104406)。開啟一個與服務IP地址(107992)的連線,然後使用iptables將其轉發給Pod,並且HTTP響應傳回到curl容器(108024)。
[...]
104406 16:41:47.552626262 0  (36ae6d09d26e) skydns (4639:8) < write res=190 data=
GET /v2/keys/skydns/local/cluster/svc/critical-app/backend?quorum=false&recursive;=true&sorted;=false HTTP/1.1
[...]
104457 16:41:47.552919333 1  (36ae6d09d26e) skydns (4617:1) < read res=543 data=
HTTP/1.1 200 OK
[...]
{"action":"get","node":{"key":"/skydns/local/cluster/svc/critical-app/backend","dir":true,"nodes":[{"key":"/skydns/local/cluster/svc/critical-app/back
end/6ead029a"
,"value":"{\"host\":\"10.3.0.214\",\"priority\":10,\"weight\":10,\"ttl\":30,\"targetstrip\":0}","modifiedIndex":270,"createdIndex":270}],
"modifiedIndex":270,"createdIndex":270}}
[...]
107992 16:41:48.087346702 1 client (b3a718d8b339) curl (22369:12) < connect res=-115(EINPROGRESS) tuple=172.17.0.7:36404->10.3.0.214:80
108002 16:41:48.087377769 1 client (b3a718d8b339) curl (22369:12) > sendto fd=3(<4t>172.17.0.7:36404->10.3.0.214:80) size=102 tuple=NULL
108005 16:41:48.087401339 0 backend-1440326531-csj02 (730a6f492270) nginx (7203:6) < accept fd=3(<4t>172.17.0.7:36404->172.17.0.5:80) tuple=172.17.0.7:36404->172.17.0.5:80 queuepct=0 queuelen=0 queuemax=128
108006 16:41:48.087406626 1 client (b3a718d8b339) curl (22369:12) < sendto res=102 data=
GET / HTTP/1.1
[...]
108024 16:41:48.087541774 0 backend-1440326531-csj02 (730a6f492270) nginx (7203:6) < writev res=238 data=
HTTP/1.1 200 OK
Server: nginx/1.10.2
[...]
透過系統層面的情況,我們可以得出結論:造成這個問題的根本原因有兩個方面。首先,當我給curl一個FQDN時,它不相信我並試圖應用搜索域演演算法。其次,.localdomain不應該存在,因為它在我們的Kubernetes叢集中是不可路由的。
如果你認為這是透過使用tcpdump也能完成,只是你還沒嘗試。我能100%肯定它不會被安裝在你的容器內,你可以在主機外部執行它,但祝你找到與對應Kubernetes排程容器的網路名稱空間匹配的網路介面。請繼續閱讀:我們還沒有完成故障排除。

Kubernetes故障排除之DNS解析

讓我們看看resolv.conf檔案中的內容。容器可能已經不存在了,或者curl呼叫後檔案可能已經改變。但是我們有一個包含發生了所有事情的捕獲檔案。
通常情況下,容器的執行時間與行程內執行的時間一樣長,當行程結束時容器就消失了。這是對容器進行故障排除最具挑戰性的部分之一。我們如何探索已經消失的東西?我們如何重現發生的事情?在這些情況下,Sysdig捕獲檔案非常有用。
我們來分析捕獲檔案,但不是過濾網路流量,我們將在這次對resolv檔案進行過濾。我們希望看到resolv.conf與curl讀取的完全一樣,以確認我們的想法,它包含localdomain。
$ sysdig -pk -NA -r capture.scap -c echo_fds "fd.type=file and fd.name=/etc/resolv.conf"
------ Read 119B from  [k8s_client.eee910bc_client_critical-app_da587b4d-bd5a-11e6-8bdb-028ce2cfb533_bacd01b6] [b3a718d8b339]  /etc/resolv.conf (curl)
search critical-app.svc.cluster.local svc.cluster.local cluster.local localdomain
nameserver 10.0.2.15
options ndots:5
[...]
這是使用sysdig的一種新的方式:
-c echo_fds 使用Sysdig chisel——附加指令碼——用以聚合資訊並格式化輸出。此外,過濾器僅包含檔案描述符上的IO活動,該檔案描述符是一個檔案,名稱為/etc/resolv.conf,正是我們所要尋找的。
透過系統呼叫,我們看到有一個選項叫做ndots。 此選項是curl不信任我們的FQDN並試圖首先追加所有搜尋域的原因。如果你閱讀了manpage[6],就會知道ndots會強制libc,任何小於5個點的域名解析都不會被視為fqdn,但會嘗試首先追加所有搜尋域。ndots有一個很好的理由,所以我們可以執行 curl backend。但是誰在那裡添加了localdomain?

Kubernetes故障排除之使用Docker執行容器

我們不想在沒有找到這個本地域的罪魁禍首的情況下完成我們的故障排除。 這樣,我們可以責怪軟體而不是人,是Docker加了搜尋域?或者Kubernetes在建立容器時指導Docker這麼做?
既然我們知道Kubernetes和Docker之間的所有控制通訊都是透過Unix套接字完成的,我們可以使用它來過濾掉所有的東西:
$ sudo sysdig -pk -s8192 -c echo_fds -NA "fd.type in (unix) and evt.buffer contains localdomain"
這一次,我們將使用一個非常棒的過濾器的來捕捉現場 evt.buffer containers。 這個過濾器需要所有的事件緩衝區,如果它包含我們正在尋找的字串,將被我們的chisel捕獲並格式化輸出。
現在我需要建立一個新的客戶端來窺探容器編排時發生的情況:
$ kubectl run -it --image=tutum/curl client-foobar --namespace critical-app --restart=Never
可以看到,Kubernetes中的hyperkube使用Docker API在/var/run/docker.sock上寫了一個HTTP POST請求到/containers/create。如果我們通讀它,我們會發現這個請求包含一個選項“DnsSearch”:[“critical-app.svc.cluster.local”,“svc.cluster.local”,“cluster.local”,“localdomain”]。Kubernetes,我們抓到你了!很可能它出於某種原因,就像我的本地開發機器設定了該搜尋域一樣。無論如何,這是一個不同的故事。
[...]
------ Write 2.61KB to  [k8s-kubelet] [de7157ba23c4]   (hyperkube)
POST /containers/create?name=k8s_POD.d8dbe16c_client-foobar_critical-app_085ac98f-bd64-11e6-8bdb-028ce2cfb533_9430448e HTTP/1.1
Host: docker
[...]
  "DnsSearch":["critical-app.svc.cluster.local","svc.cluster.local","cluster.local","localdomain"],
[...]

總結

準確地再現容器內發生的事情可能會非常具有挑戰性,因為它們在行程死亡或剛剛結束時終止。Sysdig捕獲透過系統呼叫包含所有資訊,包括網路流量、檔案系統I/O和行程行為,提供故障排除所需的所有資料。
在容器環境中進行故障排除時,能夠過濾和新增容器背景關係資訊(如Docker容器名稱或Kubernetes元資料)使我們的排查過程更加輕鬆。
Sysdis在所有主流Linux發行版、OSX以及Windows上都能使用,下載[7]它獲得最新版本。Sysdig是一個開源工具,但該專案背後的公司也提供了一個商業產品[8]來監視和排除多個主機上的容器和微服務。
相關連結:
  1. https://github.com/gianlucaborello/healthchecker-kubernetes/blob/master/manifests/backend.yaml

  2. https://sysdig.com/blog/understanding-how-kubernetes-services-dns-work/

  3. http://kubernetes.io/docs/user-guide/services/#dns

  4. http://blog.kubernetes.io/2015/11/monitoring-Kubernetes-with-Sysdig.html

  5. https://github.com/bencer/troubleshooting-docker-kubernetes-sysdig/raw/master/capture.scap

  6. http://man7.org/linux/man-pages/man5/resolv.conf.5.html

  7. http://www.sysdig.org/install/

  8. https://www.sysdig.com/

原文連結:https://sysdig.com/blog/kubernetes-service-discovery-docker/
Kubernetes快速入門實戰培訓

本次培訓內容包括:容器介紹、容器網路、Kubernetes架構基礎介紹、安裝、設計理念、架構詳解、設計原則、常用物件、監控方案、Kubernetes高階設計和實現、微服務、實踐案例分享等,點選瞭解具體培訓內容
5月18日正式上課,點選閱讀原文連結即可報名。
贊(0)

分享創造快樂