將服務暴露給外部客戶端 P136
有以下三種方式可以在外部訪問服務:
- 將服務的型別設定成
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
使用 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 介紹過相關處理及踩過的坑)。
LoadBalancer
型別的服務是一個具有額外的基礎設施提供的負載均衡器 NodePort
服務。使用 kubectl describe service kubia-loadbalancer
命令可以發現該服務選擇了一個節點埠。 P141
瞭解外部連線的特性 P142
瞭解並防止不必要的網路跳數:當外部客戶端通過節點埠連線到服務時,隨機選擇的 pod 並不一定在接收連線的同一節點上。可能需要額外的網路跳轉才能到達 pod 。可配置 Service.spec.externalTrafficPolicy
的值為 Local
指定僅將外部通訊重定向到接收連線的節點上執行的 pod 。
- 如果接收連線的節點上沒有執行對應的 pod ,那麼連線將掛起,所以需要確保負載均衡器將連線轉發給至少具有一個 pod 的節點
- 假設有一個兩節點三個 pod 的叢集(節點 A 執行一個 pod ,節點 B 執行兩個 pod),如果負載均衡器在兩個節點間均勻分佈連線,那麼 pod 的負載分佈不均衡
客戶端 IP 是不記錄的:當通過節點埠接收到連線時,由於對資料包執行了源網路地址轉換 (SNAT) ,因此資料包對源 IP 將發生更改。如果配置 Service.spec.externalTrafficPolicy
的值為 Local
,那麼將會保留客戶端 IP ,因為在接收連線的節點和託管目標 pod 的節點之間沒有額外的跳躍(不執行 SNAT )。 P143
通過 Ingress
暴露服務 P143
為什麼需要 Ingress
P144
每個 LoadBalancer
服務都需要自己的負載均衡器,以及獨有的公有 IP 地址,而 Ingress
只需要一個公網 IP 就能為許多服務提供訪問。當客戶端向 Ingress
傳送 HTTP 請求時, Ingress
會根據請求的主機名和路徑決定請求轉發到的服務。 P144
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
- 客戶端對
kubia.example.com
執行 DNS 查詢,本地作業系統返回了Ingress
控制器的 IP - 客戶端向
Ingress
控制器傳送 HTTP 請求,並在 Host 頭中指定kubia.example.com
- 控制器從頭部確定客戶端嘗試訪問哪個服務,通過與該服務關聯的
Endpoint
物件檢視 pod IP ,並將客戶端的請求轉發給其中一個 pod
Ingress
控制器不會將請求轉發給服務,只用它來選擇一個 pod 。大多數控制器都是這樣工作的。 P147
通過相同的 Ingress
暴露多個服務 P147
Ingerss
的 rules
和 paths
都是陣列,所以它們可以包含多個條目,因此一個 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
就緒探測器和存活探測器的區別 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