Kubernetes 實戰 —— 05. 服務:讓客戶端發現 pod 並與之通信(下)


將服務暴露給外部客戶端 P136

圖 5.5 將服務暴露給外部客戶端

有以下三種方式可以在外部訪問服務:

  • 將服務的類型設置成 NodePort
  • 將服務的類型設置為 LoadBalance
  • 創建一個 Ingress 資源

使用 NodePort 類型的服務 P137

通過創建一個 NodePort 服務,可以讓 Kubernetes 在其所有節點上保留一個端口(所有節點上都使用相同端口號),並將傳入的連接轉發給作為服務部分的 pod 。 P137

創建 NodePort 類型的服務 P137

可以使用如下描述文件 kubia-svc-nodeport.yaml 創建一個 NodePort 類型的服務。

# 遵循 v1 版本的 Kubernetes API
apiVersion: v1
# 資源類型為 Service
kind: Service
metadata:
  # Service 的名稱
  name: kubia-nodeport
spec:
  # 指定服務類型為 NodePort
  type: NodePort
  # 該服務可用的端口
  ports:
    # 第一個可用端口的名字
    - name: http
      # 可用端口為 80
      port: 80
      # 服務將連接轉發到容器的 8080 端口
      targetPort: 8080
      # 通過集群節點的 30000 端口可以訪問該服務
      nodePort: 30000
    # 第二個可用端口的名字
    - name: https
      # 可用端口為 443
      port: 443
      # 服務將連接轉發到容器的 8443 端口
      targetPort: 8443
      # 通過集群節點的 32767 端口可以訪問該服務
      nodePort: 32767
  # 具有 app=kubia 標簽的 pod 都屬於該服務
  selector:
    app: kubia

nodePort 屬性不是強制的,如果忽略就會隨機選擇一個端口。 P137

kubectl get services kubia-nodeport: 查看該服務的基礎信息

NAME               TYPE           CLUSTER-IP       EXTERNAL-IP       PORT(S)                      AGE
kubia-nodeport     NodePort       10.111.59.156    <none>            80:30000/TCP,443:32767/TCP   2s

PORT(S) 列顯示集群 IP 內部端口 (80, 443) 和節點端口 (30000, 32767) ,可通過 10.111.59.156:80<any-node-ip>:30000 等訪問服務。 P138

圖 5.6 外部客戶端通過節點 1 或者節點 2 連接到 NodePort 服務

使用 JSONPath 輸出需要的信息:通過指定 kubectl 的 JSONPath ,我們可以只輸出需要的信息。例如: kubectl get nodes -o jsonpath='{.items[*].status.addresses[0].address}' 將輸出所有節點的 IP 地址。

通過負載均衡器將服務暴露出來 P140

負載均衡器擁有自己獨一無二的可公開訪問的 IP 地址,並將所有連接重定向到服務。 P140

創建 LoadBalance 服務 P140

可以使用如下描述文件 kubia-svc-loadbalancer.yaml 創建一個 LoadBalancer 類型的服務。

# 遵循 v1 版本的 Kubernetes API
apiVersion: v1
# 資源類型為 Service
kind: Service
metadata:
  # Service 的名稱
  name: kubia-loadbalancer
spec:
  type: LoadBalancer
  # 該服務可用的端口
  ports:
    # 第一個可用端口的名字
    - name: http
      # 可用端口為 80
      port: 80
      # 服務將連接轉發到容器的 8080 端口
      targetPort: 8080
    # 第二個可用端口的名字
    - name: https
      # 可用端口為 443
      port: 443
      # 服務將連接轉發到容器的 8443 端口
      targetPort: 8443
  # 具有 app=kubia 標簽的 pod 都屬於該服務
  selector:
    app: kubia

使用 minikube 會發現服務的 EXTERNAL-IP 一直為 <pending> ,我們可以使用 minikube 自帶的 minikube tunnel 命令可以完成暴露(02. 開始使用 Kubernetes 和 Docker 介紹過相關處理及踩過的坑)。

圖 5.7 外部客戶端連接一個 LoadBalancer 服務.png

LoadBalancer 類型的服務是一個具有額外的基礎設施提供的負載均衡器 NodePort 服務。使用 kubectl describe service kubia-loadbalancer 命令可以發現該服務選擇了一個節點端口。 P141

了解外部連接的特性 P142

了解並防止不必要的網絡跳數:當外部客戶端通過節點端口連接到服務時,隨機選擇的 pod 並不一定在接收連接的同一節點上。可能需要額外的網絡跳轉才能到達 pod 。可配置 Service.spec.externalTrafficPolicy 的值為 Local 指定僅將外部通信重定向到接收連接的節點上運行的 pod 。

  • 如果接收連接的節點上沒有運行對應的 pod ,那么連接將掛起,所以需要確保負載均衡器將連接轉發給至少具有一個 pod 的節點
  • 假設有一個兩節點三個 pod 的集群(節點 A 運行一個 pod ,節點 B 運行兩個 pod),如果負載均衡器在兩個節點間均勻分布連接,那么 pod 的負載分布不均衡

圖 5.8 使用 Local 外部流量策略的服務可能會導致 pod 的負載分布不均衡

客戶端 IP 是不記錄的:當通過節點端口接收到連接時,由於對數據包執行了源網絡地址轉換 (SNAT) ,因此數據包對源 IP 將發生更改。如果配置 Service.spec.externalTrafficPolicy 的值為 Local ,那么將會保留客戶端 IP ,因為在接收連接的節點和托管目標 pod 的節點之間沒有額外的跳躍(不執行 SNAT )。 P143

通過 Ingress 暴露服務 P143

為什么需要 Ingress P144

每個 LoadBalancer 服務都需要自己的負載均衡器,以及獨有的公有 IP 地址,而 Ingress 只需要一個公網 IP 就能為許多服務提供訪問。當客戶端向 Ingress 發送 HTTP 請求時, Ingress 會根據請求的主機名和路徑決定請求轉發到的服務。 P144

圖 5.9 通過一個 Ingress 暴露多個服務

Ingress 在網絡棧 (HTTP) 的應用層操作,並且可以提供一些服務不能實現的功能,例如基於 cookie 的會話親和性 (session affinity) 等功能。 P144

Ingress 控制器是必不可少的 P144

只有 Ingress 控制器在集群中運行, Ingress 資源才能正常工作。 P144

在 minikube 上啟動 Ingress 的擴展功能 P145

minikube addons list: 可以列出所有的插件及其啟用狀態

minikube addons enable ingress: 啟用 ingress 插件

kubectl get pods -n kube-system: 查看 kube-system 命名空間下的 pod ,可以發現 Ingress 控制器 pod

創建 Ingress 資源 P145

可以使用如下描述文件 kubia-ingress.yaml 創建一個 Ingress 資源。

# 遵循 extensions/v1beta1 版本的 Kubernetes API
apiVersion: extensions/v1beta1
# 資源類型為 Ingress
kind: Ingress
metadata:
  # Ingress 的名稱
  name: kubia
spec:
  # Ingress 的規則列表
  rules:
    # 第一條規則匹配的域名為 kubia.example.com
    - host: kubia.example.com
      # 匹配 http
      http:
        # 匹配的路徑列表
        paths:
          # 第一條路徑為 /
          - path: /
            # 該路徑將被轉發到的后端服務
            backend:
              # 將被轉發到 kubia-nodeport 服務
              serviceName: kubia-nodeport
              # 對應服務的端口為 80
              servicePort: 80

minikube 下創建 Ingress 資源時報錯了,提示超時。后來找到一種解決方案:使用 kubectl edit ValidatingWebhookConfiguration/ingress-nginx-admission 進行編輯,找到 failurePolicy: Fail 這行,並將 Fail 改為 Ignore ,然后就能成功創建 Ingress 資源了,等一段時間后就可以看見其分配了一個 IP 地址 (192.168.64.70) 。

為了能將指定的域名 kubia.example.com 指向分配的 IP 地址 (192.168.64.70),可以使用 SwitchHosts 這個軟件進行快速切換。

此時我們在主機上就可以通過 curl kubia.example.com 訪問 kubia-nodeport 服務了。

了解 Ingress 的工作原理 P147

  1. 客戶端對 kubia.example.com 執行 DNS 查找,本地操作系統返回了 Ingress 控制器的 IP
  2. 客戶端向 Ingress 控制器發送 HTTP 請求,並在 Host 頭中指定 kubia.example.com
  3. 控制器從頭部確定客戶端嘗試訪問哪個服務,通過與該服務關聯的 Endpoint 對象查看 pod IP ,並將客戶端的請求轉發給其中一個 pod

圖 5.10 通過 Ingress 訪問 pod

Ingress 控制器不會將請求轉發給服務,只用它來選擇一個 pod 。大多數控制器都是這樣工作的。 P147

通過相同的 Ingress 暴露多個服務 P147

Ingerssrulespaths 都是數組,所以它們可以包含多個條目,因此一個 Ingress 可以將多個域名和路徑映射到多個服務。 P147

配置 Ingress 處理 TLS 傳輸 P149

Ingress 創建 TLS 認證 P149

當客戶端創建到 Ingress 控制器到 TLS 連接時,控制器將終止 TLS 連接。客戶端和控制器之間到通信是加密的,而控制器和后端 pod 之間的通信則未加密。運行在 pod 上的應用程序不需要支持 TLS 。 P149

為了讓 Ingress 控制器負責處理與 TLS 相關的所有內容,需要將證書和私鑰附加到 Ingress 。這兩個必須資源存儲在稱為 Secret 的 Kubernetes 資源中(將在第 7 章中詳細介紹 Secret),然后在 Ingress 的描述文件中引用它。 P149

openssl genrsa -out tls.key 2048: 創建私鑰

openssl req -new -x509 -key tls.key -out tls.cert -days 365 -subj /CN=kubia.example.com: 創建證書

kubectl create secret tls tls-secret --cert=tls.cert --key=tls.key: 創建 Secret 資源

然后我們就可以改寫 kubia-ingress.yaml 得到 kubia-ingress-tls.yaml 描述文件:

...
spec:
  # 配置 TLS
  tls:
    # 第一條配置的域名列表
    - hosts:
      - kubia.example.com
      # 這些域名使用 tls-secret 獲得私鑰和證書
      secretName: tls-secret
  ...

然后我們就可以使用 curl -k -v https://kubia.example.com 通過 HTTPS 訪問服務了。( minikube 下未進行上述操作前也可以訪問,不過可以發現是 Ingress 控制器使用了假證書) P150

pod 就緒后發出信號 P150

與存活探測器(04. 副本機制和其他控制器:部署托管的 pod 中介紹過)類似, Kubernetes 還允許為容器定義就緒探測器。就緒探測器會定期調用,並確保特定的 pod 是否接收客戶端請求。當容器的就緒探測器返回成功時,表示容器已准備好接收請求。 P151

就緒探測器的類型P151

  • Exec 探測器:在容器內執行任意命令,並檢查命令的退出狀態碼。如果狀態碼是 0 ,則探測成功,認為容器已經就緒,所有其他狀態碼都被認為失敗
  • HTTP GET 探測器:對容器的 IP 地址(指定的端口和路徑)執行 HTTP GET 請求。如果探測器收到響應,並且響應狀態碼不代表錯誤(狀態碼為 2xx 或 3xx ),則認為探測成功,認為容器已經就緒。如果服務器返回錯誤響應狀態碼或者沒有響應,那么探測就被認為是失敗的
  • TCP Socket探測器:嘗試與容器指定端口建立 TCP 連接。如果連接成功建立,則探測成功,認為容器已經就緒

了解就緒探測器的操作 P151

啟動容器時,可以為 Kubernetes 配置一個等待時間,經過等待時間后才可以執行第一次准備就緒檢查。之后,它會周期性地調用探測器,並根據就緒探測器的結果采取行動。如果某個 pod 報告它尚未准備就緒,那么就會從服務中刪除該 pod ;如果這個 pod 再次准備就緒,那么就會將給 pod 重新添加到服務中。 P151

圖 5.11 就緒探測失敗的 pod 從服務的 endpoint 中移除

就緒探測器和存活探測器的區別 P151

存活探測器通過殺死異常的容器並用新的正常容器替代它們來保持 pod 正常工作,而就緒探測器確保只有准備好處理請求的 pod 才可以接收請求,並不會終止或重新啟動容器。 P151

就緒探測器的重要性:確保客戶端只與正常的 pod 交互,並且永遠不會知道系統存在的問題。 P152

了解就緒探測器的實際作用 P154

務必定義就緒探測器 P155

應該始終定義一個就緒探測器,即使它只是向基准 URL 發送 HTTP 請求一樣簡單。如果沒有將就緒探測器添加到 pod 中,那么它們啟動后幾乎立即成為服務端點。 P155

不要將停止 pod 的邏輯納入到就緒探測器中 P155

當一個容器關閉時,運行在其中的應用程序通常會在收到終止信號后立即停止接收連接。但在啟動關機程序后,沒有必要讓就緒探測器返回失敗以達到從所有服務中移除 pod 目的,因為在該容器刪除后, Kubernetes 就會自動從所有服務中移除該容器。 P155

使用 headless 服務來發現獨立的 pod P155

要讓客戶端連接到所有 pod ,需要找出每個 pod 的 IP 。 Kubernetes 允許客戶通過 DNS 查找發現 pod IP 。通常,當執行服務的 DNS 查找時, DNS 服務器會返回單個 IP —— 服務的集群 IP 。但是,如果告訴 Kubernetes ,不需要為服務提供集群 IP (通過在服務 spec 中將 clusterIP 字段設置為 None 來完成此操作),則 DNS 服務器將會返回 pod IP 而不是單個服務 IP 。 P155

DNS 服務器不會返回單個 DNS A 記錄,而是會為該服務返回多個 A 記錄,每個記錄指向當時支持該服務的單個 pod 的 IP 。客戶端因此可以做一個簡單的 DNS A 記錄查詢並獲取屬於該服務的所有 pod 的 IP 。客戶端可以使用該信息連接到其中的一個、多個或全部。 P155

創建 headless 服務 P156

可以使用如下描述文件 kubia-svc-headless.yaml 創建一個 headless 的 Service 資源。

# 遵循 v1 版本的 Kubernetes API
apiVersion: v1
# 資源類型為 Service
kind: Service
metadata:
  # Service 的名稱
  name: kubia-headless
spec:
  # 該服務的集群 IP 為 None ,使其變為 headless 的
  clusterIP: None
  # 該服務可用的端口
  ports:
    # 第一個可用端口的名字
    - name: http
      # 可用端口為 80
      port: 80
      # 服務將連接轉發到容器的 8080 端口
      targetPort: 8080
    # 第二個可用端口的名字
    - name: https
      # 可用端口為 443
      port: 443
      # 服務將連接轉發到容器的 8443 端口
      targetPort: 8443
  # 具有 app=kubia 標簽的 pod 都屬於該服務
  selector:
    app: kubia

通過 DNS 發現 pod P156

kubia 容器鏡像不包含 nslookup 二進制文件,所以需要用一個新的容器鏡像來執行相應的命令。 P156

kubectl run dnsutils --image=tutum/dnsutils --generator=run-pod/v1 --command -- sleep infinity: 創建一個可以執行 nslookup 命令的 pod

kubectl exec dnsutils nslookup kubia-headless: 在 dnsutils pod 內執行 nslookup kubia-headless 命令,可以發現 DNS 服務器為 kubia-headless.default.svc.cluster.local FQDN 返回了多個 IP ,且它們都是 pod 的 IP ,可以通過 kubectl get pods -o wide 進行確認

kubectl exec dnsutils nslookup kubia: 在 dnsutils pod 內執行 nslookup kubia 命令,可以發現 DNS 服務器為 kubia.default.svc.cluster.local FQDN 返回了一個 IP ,該 IP 是服務的集群 IP

盡管 headless 服務看起來可能與常規服務不同,但是在客戶的視角上它們並無不同。對於 headless 服務,由於 DNS 返回了 pod 的 IP ,客戶端直接連接到該 pod ,而不是通過服務代理(注意這里是直接訪問的 pod ,所以對應的端口要改成 pod 的端口)。 P157

注意: headless 服務仍然提供跨 pod 的負載均衡,但是通過 DNS 輪循機制不是通過服務代理 P157

發現所有的 pod —— 包括未就緒的 pod P157

可以通過在 Service.metadata.annotations 下面增加一條 service.alpha.kubernetes.io/tolerate-unready-endpoints: "true" 告訴 Kubernetes 無論 pod 的准備狀態如何,希望將所有 pod 添加到服務中 P158

排除服務故障 P158

如果無法通過服務訪問 pod ,應根據下面的列表進行排查: P158

  • 確保從集群內連接到服務的集群 IP
  • 不要通過 ping 服務 IP 來判斷服務是否可訪問(服務的集群 IP 是虛擬 IP ,是無法 ping 通的)
  • 如果已經定義了就緒探測器,請確保它返回成功;否則該 pod 不會成為服務的一部分
  • 要確認某個容器是服務的一部分,請使用 kubectl get endpoints 來檢查相應的端點對象
  • 如果嘗試通過 FQDN 或其中一部分來訪問服務,但並不起作用,請查看是否可以使用其集群 IP 而不是 FQDN 來訪問服務
  • 檢查是否連接到服務公開的端口,而不是目標端口
  • 嘗試直接連接到 pod IP 以確認 pod 正在接收正確端口上的連接
  • 如果甚至無法通過 pod 的 IP 訪問應用,請確保應用不是僅綁定到 localhost (127.0.0.1)

本文首發於公眾號:滿賦諸機(點擊查看原文) 開源在 GitHub :reading-notes/kubernetes-in-action


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM