《kubernetes in action》 學習筆記——5、讓客戶端發現Pod並與之通信

Service

Service 是kubernetes提供一種爲一組功能相同的pod提供單一不變的接入點的資源。當服務存在時,它的IP地址和端口都不會改變。客戶端通過IP地址和端口建立連接。這些連接會被路由到提供該服務的任意一個pod上。這樣客戶端就不要知道每個單獨的提供服務的pod地址,這些pod就可以在集羣中隨時創建與註銷。

service通過標籤來確定哪些pod是屬於服務,與前面rc、rs、ds一樣都通過pod控制器中的標籤選擇器來進行指定。

service-demo.yaml

apiVersion: v1
kind:Service
metadata:
  name: svc-demo
spec:
  selector:
    app: svc-backend  # 具有app=svc-backend 標籤的pod屬於服務
  ports:
  - port: 80     # 該服務可用的端口
   targetPort: 8080        # 服務鏈轉發到容器的端口

在運行的容器中遠程執行命令
可用使用kubectl exec 命令遠程地在一個已存在的pod容器上指定任何命令
例如:kubectl exec podName – curl -s http://x.x.x.x
“–” 表示kubectl 命令項結束。在雙橫槓之後表示是在容器中要執行的命令。如果沒有使用雙橫槓來隔開,後續的參數可能會被解析成kubectl命令的參數,這樣會造成結果的歧義。

設置會話親和性
每次連接都會被執行到不同的pod上,即使是同一個客戶端發來的請求。如果希望特定客戶端產生的所有的請求每次都指向同一個pod,可以設置服務的sessionAffinity屬性值爲clientIP(默認值是nano)

apiVersion: v1
kind: Service
spec:
    sessionAffinity: clinetIP

kubernetes 服務處理的是TCP和UDP包,而不是應用層的HTTP,所以不支持cookie的會話親和性

同一個服務暴露多個端口

apiVersion: v1
kind: Service
metadata:
  name: svc-demo
spec:
  ports:
  - name: http  # 在創建同時有多個端口的時候,必須爲每個端口設置名字
    port: 80
    targetPort: 8080
 - name: https
   port: 443
   targetPort: 8443
 selector:
   app: svc-backend  

即可同時暴露兩個對外的端口,80會被映射到後端pod的8080端口上,443會被映射到後端pod的8443端口上

使用命名的端口
假如在後端pod上端口命名,則可在service中使用端口名進行映射

kind: Pod
spec:
  containes:
  - name: http
    containerPort: 8080   # 端口8080 被命名爲http
  - name: https
    containerPort: 8443   # 端口8443 被命名爲https
       
kind:Service
spec:
  ports:
  - name: port1
    port: 80
    targetPort: http  # 即可與pod中命名爲http的端口進行映射
  - name: port2
    port: 443
    targetPort: https  # 即可與pod中命名爲https的端口進行映射  

採用命名的方式會讓以後更改pod的端口更方便,假如以後pod的端口該爲其他值,service中的映射端口值就不需要改變

服務發現

通過環境變量發現服務
pod開始運行時,kubernetes會初始化一系列的環境變量指向現在的服務。這樣pod就能通過環境變量來知道服務的IP地址與端口,但前提是服務必須在pod之前創建
可通過kubectl 命令查看pod當前的環境變量
kubectl exec podName env
服務轉換成環境變量時遵循以下原則

  • 服務名稱中的橫槓會轉換成下劃線
  • 服務名稱會作爲環境變量的前綴,且都會轉換成大寫

DNS服務發現
kubernetes 通過修改每個容器的 /etc/resolv.conf 文件來實現將集羣中運行的dns-pod 作爲所有容器的DNS服務。運行在pod上的進程DNS查詢都會被kubernetes自身的DNS服務響應,該服務知道集羣中運行的所有服務。
注意:pod是否使用內部的DNS服務器是根據pod中spec的dnsPolicy屬性來決定。
每個服務從內部DNS服務器中獲得一個DNS條目,客戶端的pod在指定服務名稱的情況下可以通過全限定域名(FQDN)來訪問,而不是通過環境變量。

通過FQDN連接服務
例:
前端pod可以通過打開一下FQDN連接訪問後端的服務
backend-database.default.svc.cluster.local
其中 backend-databash 爲服務名稱
default 表示服務所在的命名空間
svc.cluster.local 是在所有集羣本地服務名稱中使用的可配置集羣域後綴

注意:客戶端然要知道服務的端口號,如果服務使用默認的端口號(例如HTTP爲80,postgres爲5432等),這樣是沒問題的,如果不是標準的端口,則可以從環境變量中獲得

連接時,如果前端pod與後端服務在同一個命名空間,則可省略svc.cluster.local,甚至命名空間也可以省略。
注意:服務正常工作時,ping不通服務的IP,因爲服務的集羣IP是一個虛擬IP,只有當與服務端口結合時纔有意義。

連接集羣外部的服務

endpoint介紹
服務並不是和pod直接兩聯,相反,有一種介於兩者之間——就是Endpoint資源。Endpoint資源就是暴露一個服務的IP地址和端口的列表。儘管在spec服務中定義了pod選擇器,但是在重定向傳入連接時不會直接使用它,相反,選擇器用於構建IP和端口列表,然後存入Endpoint資源中。當客戶端連接到服務時,服務代理選擇這些IP端口對中的一個,並接入連接重定向到現在爲止監聽的服務器。

手動配置服務的Endpoint
服務的endpoint與服務解耦後,可以分別手動配置個更新它們
如果創建了不帶選擇器的服務,kubernetes將不會爲服務創建Endpoint資源,這樣就需要創建Endpoint資源來指定該服務的endpoint列表。
創建沒有選擇器的服務

apiVersion: v1
kind: Service
metadata:
  name: test-svc    #服務的名稱必須與Endpoint對象的名稱一致
spec:     # 沒有指定選擇器
  ports:
  - port: 80 

爲沒有選擇器的服務創建Endpoint資源

apiVersion: v1
kind: Endpoints
metadata:
  name: test-svc    #endpoint的名稱必須與服務的名稱一致
subsets:
  - addresses:
    - ip: 1.1.1.1   # 服務將連接重定向到endpoint的IP地址
    - ip: 2.2.2.2
    ports:
    - port: 80      # endpoint的目標端口

Endpoint必須與服務具有相同的名稱,且需要包含該服務的目標IP地址與端口列表。服務和Endpoint資源都發布到服務器後,服務就可像具有pod選擇器一樣的正常使用。在服務創建後創建的容器將包含服務的環境變量,並且與其IP:port對的所有連接都在服務端點之間進行負載均衡

也可以根據需要爲服務添加選擇器,從而對Endpoint進行自動管理。相反,也可將選擇器從服務端移除,kubernetes將停止更新Endpoint。這意味着服務的IP地址可以保持不變,同時服務的實際實現卻發生了改變。

爲外部服務創建別名
除了使用Endpoint來代替公開外部服務方法外,還可以通過其完全限定域名訪問外部服務
要創建一個具有別名的外部服務時,要將創建的服務資源的一個type字段設置爲ExternalName。
external-svc.yaml

apiVersion: v1
kind: Service
metadata:
  name: external-svc
spec:
  type: ExternalName    # 設置type類型
  externalName: www.baidu.com   # 外部服務的完全限定域名
  ports:
  - port: 80    

服務創建後,pod可以通過external-svc。default.svc.cluster.local 域名連接到外部服務(如果pod與服務是在同一命名空間下,則可通過external-svc來訪問),而不是使用服務實際的FQDN,這樣隱藏了實際服務的名稱及其使用該服務的pod位置,可以修改externalName屬性來將其指向不同的服務。也可以修改類型重新變回clusterIP,或手動創建Endpoint資源,或添加選擇器。

將服務暴露給外部客戶端

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

  1. 將服務類型設置成
    NodePort——每個集羣節點都會在節點上打開一個端口,對於NodePort服務,每個集羣節點都在節點本身上打開一個端口,並將在該端口上接收的流量重定向到基礎服務。該服務僅在內部集羣IP和端口上纔可以訪問,但也可以通過所有節點上的專用端口訪問
  2. 將服務類型設置成LoadBalance,NodePort的一種擴展——這使得服務可以通過一個專用的負責均衡器來訪問,這是有kubernetes中正在運行的雲基礎設施提供的。負載均衡器將流量重定向到跨所有節點的節點端口,接護短通過負載均衡器的IP連接到服務。
  3. 創建一個Ingress資源,這是一個完全不同的機制,通過一個IP地址公開多個服務——它運行在HTTP層上,因此可以提供比工作在第四層的服務更多的功能。

使用NodePort類型服務
將一組pod的公開個外部服務客戶端的一種辦法是創建一個服務並將其類型設置爲NodePort。通過創建NodePort服務,可以讓kubernetes在所有的節點上保留一個端口(所有節點上都使用相同的端口號),並將傳入的連接轉發給作爲服務器部分的pod。
不僅可以通過服務內部集羣IP訪問NodePort服務,還可以通過任何節點的IP和預留節點端口訪問NodePort服務。

svc-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
  name: svc-nodeport
spec:
  type: NodePort      # 爲NodePort 設置服務類型
  ports:
  - port: 80          # 服務集羣IP的端口號
    targePort: 8080   # 背後pod的目標端口號
   nodePort: 30123   # 通過集羣節點的30123端口可以訪問到該服務
  selector:          # 選擇器
    app: test    

其中nodePort:30123 不是必要的,可以不指定節點端口,kubernetes會隨機分配一個端口
kubectl get svc svc-nodeport 獲取服務信息
查看EXTERNAL-IP 列 將顯示爲 nodes
查看PORT(S) 列顯示集羣IP的內部端口(80)和幾點端口(30123)
訪問過程:
外部客戶端 -> 節點 -> 服務 -> pod

通過JSONPath獲取所有節點的IP
可以通過節點的json或yaml描述中找到IP,但並不是在很大的驚悚中篩選,而是可以利用
kubectl命令值打印出節點IP而不是整個服務的定義

kubectl get nodes -o -jsonpath=’{.item[*].status.addresse[?(@.type==“ExternalIP”)].address}’

通過指定kubectl的JSONPath,使得其只輸出需要的信息,上列中的JSONPath指示kubectl執行以下操作
1、瀏覽item屬性
2、對於每個元素,輸入status屬性
3、過濾address屬性的元素,僅包含哪些具有將type屬性設置爲ExternalIP的元素
4、最後打印過濾元素的address屬性
知道節點IP後,就可以通過 curl http://nodeIP:30123 對服務進行訪問

正如所看到的,整個互聯網上都可以通過任何節點上的30123端口訪問到你的pod,但是,若果客戶端指向的節點發生故障,客戶端將無法再訪問服務。這就是爲什麼要將負載均衡器放在節點前面,以確保發送請求傳播到所有健康的節點上,且不會連接到處於脫機的節點的原因。
如果kubernetes集羣支持它(當kubernetes部署在雲基礎設施上時,一般都會支持負載均衡器),name可以創建一個Load Balance 而不是NodePort服務自動生成的負載均衡器。

通過負載均衡器將服務暴露出來
在雲提供商上運行的kubernetes集羣通常都支持從雲基礎架構自動提供負載均衡。只要將服務類型設置爲Load Balance而不是NodePort,負載均衡器擁有自己獨一無二的可公開訪問的IP地址,並將所有連接重定向到服務,可以通過負載均衡的IP地址訪問服務。
如果kubernetes在不支持Load Balance的環境中運行,則不會調用負載均衡,但是服務任然將表現得像一個NodePort服務,這是因爲Load Balance是NodePort的一種擴展。

loadBalance-demo.yaml
apiVersion: v1
kind: Service
metadata:
  name: loadBalance
spec:
  type: LoadBalance
  selector:
    app: test
  ports:
 - port: 80
   targetPort: 8080

創建服務後,雲基礎架構需要一段時間才能創建負載均衡器並將IP地址寫入服務對象,這樣IP地址就被列爲服務的外部IP地址。
kubectl get svc loadBalance
查看負載均衡器的地址,並且可以通過該地址訪問服務

瞭解並防止不必要的網絡跳數
但外部客戶端通過服務連接到服務時,隨機選擇的pod的並不一定在接收連接的節點上,因此可能需要額外的網絡跳轉才能到達pod,我們可以通過設置服務的屬性來避免該情況的產生,讓外部連接只重定向到接收連接的節點上運行的pod。設置spec.externalTrafficPolicy=Local 即可。但同樣存在問題,如果接收連接的節點上沒有運行pod,則連接會被掛起,而不會重定向到其他的pod,因此需要確保負載均衡器將連接轉發給至少具有一個pod的節點
同時該方法還有一個缺點,當節點A上有一個pod,節點B上有兩個pod,如果負載均衡器在兩個節點間均勻的分佈連接,則A節點會承擔50%的負載,而B上的pod各承擔25%的負載

使用headless服務來發現獨立的pod
服務一般提供穩定的IP地址,讓客戶端來連接支持服務的每個pod。到服務的每個鏈接只會被轉發到一個隨機的pod上,但如果想訪問所有的pod呢?
如果客戶端要鏈接每一個pod,那需要知道pod的IP地址,可以讓客戶端調用kubernetes API服務器,通過api獲取pod及其IP地址列表。但是我們應該儘量保證,應用程序與kubernetes無關,所有儘量不要採用該種方法。
kubernetes運行客戶端通過DNS來查找發現podIP。通過執行DNS服務時,kubernetes會返回單個IP——服務的集羣IP。但可以告訴kubernetes,我們不需要集羣IP,則DSN將會返回podIP,而不是單個服務的IP。只要將服務spec中的cluster字段設置爲None即可。
DNS會返回多條記錄,每條記錄會指向一個支持該服務的單個pod的IP。客戶端可以通過這些記錄連接到其中一個、多個或全部。需要注意的是,這裏只會返回部分的記錄,那是因爲沒有就緒的pod沒有添加到服務中,所以它們的地址不會被返回。
將服務的clusterIP設置爲none,會使服務變成headless服務。此時kubernetes不會爲服務分配集羣IP。
kube-svc-headless.yaml

apiVersion: v1
kind: Service
metadata:
  name: svc-headless
spec:
  clusterIP: None
  Ports:
  - port: 80
    targetPort: 8080
 selecter:
   app:headless-pod

有時候我們需要發現所有匹配服務標籤選擇器的pod,不管是否就緒

kind: Service
metadata:
    annotations:
        service.alpha.kubernetes.io/tolerrate-unready-endpoints: "true"

(本文章是學習《kubernetes in action》 一書時做的筆記,由於本人是剛學習k8s,且自學方式就是把它敲一遍,然後加一些自己的理解。所以會發現很多內容是跟書中內容一模一樣,如果本文是對本書的侵權,敬請諒解,告知後會刪除。如果引用或轉載,請註明出處——謝謝)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章