將服務暴露給外部客戶端 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