1. 程式人生 > 其它 >一次由 Kubernetes HostPort 引發的服務故障排錯記實

一次由 Kubernetes HostPort 引發的服務故障排錯記實

  最近排查了一個 Kubernetes 中使用了 hostport 後遇到比較坑的問題,奇怪的知識又增加了。

1.問題背景

  叢集環境為 K8s v1.15.9,cni 指定了 flannel-vxlan 跟 portmap, kube-proxy 使用 mode 為 ipvs,叢集 3 臺 master,同時也是 node,這裡以 node-1,node-2,node-3 來表示。

  叢集中有 2 個 mysql, 部署在兩個 ns 下,mysql 本身不是問題重點,這裡就不細說,這裡以 mysql-A,mysql-B 來表示。

  mysql-A 落在 node-1 上,mysql-B 落在 node-2 上,兩個資料庫svc名跟使用者、密碼完全不相同

  出現詭異的現象這裡以一張圖來說明會比較清楚一些:

  其中綠線的表示訪問沒有問題,紅線表示連線Mysql-A提示使用者名稱密碼錯誤

  特別詭異的是,當在 Node-2 上通過 svc 訪問 Mysql-A 時,輸入 Mysql-A 的使用者名稱跟密碼提示密碼錯誤,密碼確認無疑,但當輸入 Mysql-B 的使用者名稱跟密碼,居然能夠連線上,看了下資料,連上的是 Mysql-B 的資料庫,給人的感覺就是請求轉到了 Mysql-A, 最後又轉到了 Mysql-B,當時讓人大跌眼鏡。

  碰到詭異的問題那就排查吧,排查的過程倒是不費什麼事,最主要的是要通過這次踩坑機會挖掘一些奇怪的知識出來。

2.排查過程

  既然在 Node-1 上連線 Mysql-A/Mysql-B 都沒有問題,那基本可以排查是 Mysql-A 的問題

  經實驗,在 Node-2 上所有的服務想要連 Mysql-A 時,都有這個問題,但是訪問其它的服務又都沒有問題,說明要麼是 mysql-A 的 3306 這個埠有問題,通過上一步應該排查了 mysql-A 的問題,那問題只能出在 Node-2 上

  在 k8s 中像這樣的請求轉發出現詭異現象,當排除了一些常見的原因之外,最大的嫌疑就是 iptables 了,作者遇到過多次

  這次也不例外,雖然當前叢集使用的 ipvs, 但還是照例看下 iptables 規則,檢視 Node-2 上的 iptables 與 Node-1 的 iptables 比對,結果有蹊蹺, 在 Node-2 上發現有以下的規則在其它節點上沒有

-A CNI-DN-xxxx -p tcp -m tcp --dport 3306 -j DNAT --to-destination 10.224.0.222:3306
-A CNI-HOSTPORT-DNAT -m comment --comment "dnat name": \"cni0\" id: \"xxxxxxxxxxxxx\"" -j CNI-DN-xxx
-A CNI-HOSTPORT-SNAT -m comment --comment "snat name": \"cni0\" id: \"xxxxxxxxxxxxx\"" -j CNI-SN-xxx
-A CNI-SN-xxx -s 127.0.0.1/32 -d 10.224.0.222/32 -p tcp -m tcp --dport 80 -j MASQUERADE

  其中 10.224.0.222 為 Mysql-B 的 pod ip, xxxxxxxxxxxxx 經查實為 Mysql-B 對應的 pause 容器的id

  從上面的規則總結一下就是目的為 3306 埠的請求都會轉發到 10.224.0.222 這個地址,即 Mysql-B

  看到這裡,作者明白了為什麼在 Node-2 上去訪問 Node-1 上 Mysql-A 的 3306 會提示密碼錯誤而輸入 Mysql-B 的密碼卻可以正常訪問

  雖然兩個 mysql 的 svc 名不一樣,但上面的 iptables 只要目的埠是 3306 就轉發到 Mysql-B 了,當請求到達 mysql 後,使用正確的使用者名稱密碼自然可以登入成功

  原因是找到了,但是又引出來了更多的問題?

1.這幾條規則是誰入到 iptables 中的?

2.怎麼解決呢,是不是刪掉就可以?

3.問題復現

  同樣是 Mysql,為何 Mysql-A 沒有呢? 那麼比對一下這兩個 Mysql 的部署差異

  比對發現, 除了使用者名稱密碼,ns 不一樣外,Mysql-B 部署時使用了 hostPort=3306, 其它的並無異常

  難道是因為 hostPort ?

  作者日常會使用 NodePort,倒卻是沒怎麼在意 hostPort,也就停留在 hostPort 跟 NodePort 的差別在於 NodePort 是所有 Node 上都會開啟埠,而 hostPort 只會在執行機器上開啟埠,由於 hostPort 使用的也少,也就沒太多關注,網上短暫搜了一番,描述的也不是很多,看起來大家也用的不多

  那到底是不是因為 hostPort 呢?

  Talk is cheap, show me the code

  通過實驗來驗證,這裡簡單使用了三個nginx來說明問題, 其中兩個使用了 hostPort,這裡特意指定了不同的埠,其它的都完全一樣,釋出到叢集中,yaml 檔案如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-hostport2
  labels:
    k8s-app: nginx-hostport2
spec:
  replicas: 1
  selector:
    matchLabels:
      k8s-app: nginx-hostport2
  template:
    metadata:
      labels:
        k8s-app: nginx-hostport2
    spec:
      nodeName: spring-38
      containers:
        - name: nginx
          image: nginx:latest
          ports:
            - containerPort: 80
              hostPort: 31123

  Finally,問題復現:

  可以肯定,這些規則就是因為使用了 hostPort 而寫入的,但是由誰寫入的這個問題還是沒有解決?

4.罪魁禍首

  作者開始以為這些 iptables 規則是由 kube-proxy 寫入的, 但是檢視 kubelet 的原始碼並未發現上述規則的關鍵字

  再次實驗及結合網上的探索,可以得到以下結論:

  首先從 kubernetes 的官方發現以下描述:

  The CNI networking plugin supportshostPort. You can use the official portmap plugin offered by the CNI plugin team or use your own plugin with portMapping functionality.

  If you want to enablehostPortsupport, you must specifyportMappings capabilityin yourcni-conf-dir. For example:

{
  "name": "k8s-pod-network",
  "cniVersion": "0.3.0",
  "plugins": [
    {
        # ...其它的plugin
    }
    {
      "type": "portmap",
      "capabilities": {"portMappings": true}
    }
  ]
}

  參考: https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/

  也就是如果使用了 hostPort, 是由 portmap 這個 cni 提供 portMapping 能力,同時,如果想使用這個能力,在配置檔案中一定需要開啟 portmap,這個在作者的叢集中也開啟了,這點對應上了

  另外一個比較重要的結論是:

  The CNI ‘portmap’ plugin, used to setup HostPorts for CNI, inserts rules at the front of the iptables nat chains; which take precedence over the KUBE- SERVICES chain. Because of this, the HostPort/portmap rule could match incoming traffic even if there were better fitting, more specific service definition rules like NodePorts later in the chain

  參考: https://ubuntu.com/security/CVE-2019-9946

  翻譯過來就是使用 hostPort 後,會在 iptables 的 nat 鏈中插入相應的規則,而且這些規則是在 KUBE- SERVICES 規則之前插入的,也就是說會優先匹配 hostPort 的規則,我們常用的 NodePort 規則其實是在 KUBE- SERVICES 之中,也排在其後

  從 portmap 的原始碼中果然是可以看到相應的程式碼

  感興趣的可以的 plugins 專案的 meta/portmap/portmap.go 中檢視完整的原始碼

  所以,最終是呼叫portmap寫入的這些規則.

5.端口占用

  進一步實驗發現,hostport 可以通過 iptables 命令檢視到, 但是無法在 ipvsadm 中檢視到

  使用 lsof/netstat 也檢視不到這個埠,這是因為 hostport 是通過 iptables 對請求中的目的埠進行轉發的,並不是在主機上通過埠監聽

  既然 lsof 跟 netstat 都查不到埠資訊,那這個埠相當於沒有處於 listen 狀態?

  如果這時再部署一個 hostport 指定相同埠的應用會怎麼樣呢?

  結論是:使用 hostPort 的應用在排程時無法排程在已經使用過相同 hostPort 的主機上,也就是說,在排程時會考慮 hostport

  如果強行讓其排程在同一臺機器上,那麼就會出現以下錯誤,如果不刪除的話,這樣的錯誤會越來越多,嚇的作者趕緊刪了.

  如果這個時候建立一個 nodePort 型別的 svc, 埠也為 31123,結果會怎麼樣呢?

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-nodeport2
  labels:
    k8s-app: nginx-nodeport2
spec:
  replicas: 1
  selector:
    matchLabels:
      k8s-app: nginx-nodeport2
  template:
    metadata:
      labels:
        k8s-app: nginx-nodeport2
    spec:
      nodeName: spring-38
      containers:
        - name: nginx
          image: nginx:latest
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-nodeport2
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: 80
    nodePort: 31123
  selector:
    k8s-app: nginx-nodeport2

  

  可以發現,NodePort 是可以成功建立的,同時監聽的埠也出現了.

  從這也可以說明使用 hostposrt 指定的埠並沒有 listen 主機的埠,要不然這裡就會提示埠重複之類

  那麼問題又來了,同一臺機器上同時存在有 hostPort 跟 nodePort 的埠,這個時候如果 curl 31123 時, 訪問的是哪一個呢?

  經多次使用 curl 請求後,均是使用了 hostport 那個 nginx pod 收到請求

  原因還是因為 KUBE-NODE-PORT 規則在 KUBE-SERVICE 的鏈中是處於最後位置,而 hostPort 通過 portmap 寫入的規則排在其之前

  因此會先匹配到 hostport 的規則,自然請求就被轉到 hostport 所在的 pod 中,這兩者的順序是沒辦法改變的,因此無論是 hostport 的應用釋出在前還是在後都無法影響請求轉發

  另外再提一下,hostport 的規則在 ipvsadm 中是查詢不到的,而 nodePort 的規則則是可以使用 ipvsadm 查詢得到

6.問題解決

  要想把這些規則刪除,可以直接將 hostport 去掉,那麼規則就會隨著刪除,比如下圖中去掉了一個 nginx 的 hostport

  另外使用較多的 port-forward 也是可以進行埠轉發的,它又是個什麼情況呢? 它其實使用的是 socat 及 netenter 工具,網上看到一篇文章,原理寫的挺好的,感興趣的可以看一看

  參考: https://vflong.github.io/sre/k8s/2020/03/15/how-the-kubectl-port-forward-command-works.html

7.生產建議

  一句話,生產環境除非是必要且無他法,不然一定不要使用hostport,除了會影響排程結果之外,還會出現上述問題,可能造成的後果是非常嚴重的。

8.參考文章

https://www.qikqiak.com/post/how-to-use-ipvs-in-kubernetes/

https://serenafeng.github.io/2020/03/26/kube-proxy-in-iptables-mode/

https://zhuanlan.zhihu.com/p/94418251

https://ronaknathani.com/blog/2020/07/kubernetes-nodeport-and-iptables-rules/

https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/

https://vflong.github.io/sre/k8s/2020/03/15/how-the-kubectl-port-forward-command-works.html

轉載: https://izsk.me/2021/08/01/Kubernetes-hostport/

作者:小家電維修

相見有時,後會無期。