5.1 介紹服務

5.1 介紹服務

Kubernetes服務是一種為一組功能相同的pod提供單一不變的接入點的資源。當服務存在時,它的IP地址和端口不會改變。客戶端通過IP地址和端口號建立連接,這些連接會被路由到提供該服務的任意一個pod上。通過這種方式,客戶端不需要知道每個單獨的提供服務的pod的地址,這樣這些pod就可以在集群中隨時被創建或移除。

結合實例解釋服務

回顧一下有前端web服務器和后端數據庫服務器的例子。有很多pod提供前端服務,而只有一個pod提供后臺數據庫服務。需要解決兩個問題才能使系統發揮作用。

外部客戶端無須關心服務器數量而連接到前端pod上。

前端的pod需要連接后端的數據庫。由于數據庫運行在pod中,它可能會在集群中移來移去,導致IP地址變化。當后臺數據庫被移動時,無須對前端pod重新配置。

通過為前端pod創建服務,并且將其配置成可以在集群外部訪問,可以暴露一個單一不變的IP地址讓外部的客戶端連接pod。同理,可以為后臺數據庫pod創建服務,并為其分配一個固定的IP地址。盡管pod的IP地址會改變,但是服務的IP地址固定不變。另外,通過創建服務,能夠讓前端的pod通過環境變量或DNS以及服務名來訪問后端服務。

5.1.1 創建服務

服務的后端可以有不止一個pod。服務的連接對所有的后端pod是負載均衡的。但是要如何準確地定義哪些pod屬于服務哪些不屬于呢?

或許還記得在ReplicationController和其他的pod控制器中使用標簽選擇器來指定哪些pod屬于同一組。服務使用相同的機制。

在前面的章節中,通過創建ReplicationController運行了三個包含Node.js應用的pod。再次創建ReplicationController并且確認pod啟動運行,在這之后將會為這三個pod創建一個服務。

通過kubectl expose創建服務

創建服務的最簡單的方法是通過kubectl expose,在第2章中曾使用這種方法來暴露創建的ReplicationController。像創建ReplicationController時使用的pod選擇器那樣,利用expose命令和pod選擇器來創建服務資源,從而通過單個的IP和端口來訪問所有的pod。

現在,除了使用expose命令,可以通過將配置的YAML文件傳遞到Kubernetes API服務器來手動創建服務。

通過YAML描述文件來創建服務

使用以下代碼清單中的內容創建一個名為kubia-svc.yaml的文件。

代碼清單5.1 服務的定義:kubia-svc.yaml

apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  ports:
  - port: 80  #服務可用端口
    targetPort: 8080 #轉發的容器端口
  selector:
    app: kubia #具有app=kubia標簽的pod都屬于該服務

附:deployment文件

apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubia
  labels:
    app: kubia
spec:
  replicas: 3
  selector:
    matchLabels:
      app: kubia
  template:
    metadata:
      labels:
        app: kubia
    spec:
      containers:
      - name: kubia
        image: luksa/kubia:1.0
        ports:
        - containerPort: 8080

創建了一個名叫kubia的服務,它將在端口80接收請求并將連接路由到具有標簽選擇器是app=kubia的pod的8080端口上。

接下來通過使用kubectl create發布文件來創建服務。

檢測新的服務

在發布完YAML文件后,可以在命名空間下列出來所有的服務資源,并可以發現新的服務已經被分配了一個內部集群IP。

$ kubectl get svc
NAME    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
kubia   ClusterIP   10.111.56.158   <none>        80/TCP    11s

列表顯示分配給服務的IP地址是 10.111.56.158 。因為只是集群的IP地址,只能在集群內部可以被訪問。服務的主要目標就是使集群內部的其他pod可以訪問當前這組pod,但通常也希望對外暴露服務。如何實現將在之后講解。現在,從集群內部使用創建好的服務并了解服務的功能。

從內部集群測試服務

可以通過以下幾種方法向服務發送請求:

顯而易見的方法是創建一個pod,它將請求發送到服務的集群IP并記錄響應。可以通過查看pod日志檢查服務的響應。

使用ssh遠程登錄到其中一個Kubernetes節點上,然后使用curl命令。

可以通過 kubectl exec 命令在一個已經存在的pod中執行curl命令。

我們來學習最后一種方法——如何在已有的pod中運行命令。

在運行的容器中遠程執行命令

可以使用kubectl exec命令遠程地在一個已經存在的pod容器上執行任何命令。這樣就可以很方便地了解pod的內容、狀態及環境。用 kubectl get pod 命令列出所有的pod,并且選擇其中一個作為exec命令的執行目標(在下述例子中,選擇kubia-7nog1 pod作為目標)。也可以獲得服務的集群IP(比如使用 kubectl get svc 命令),當執行下述命令時,請確保替換對應pod的名稱及服務IP地址。

$ kubectl exec kubia-7d5b548867-fnvl5 -- curl -s http://10.111.56.158

如果之前使用過ssh命令登錄到一個遠程系統,會發現kubectl exec沒有特別大的不同之處。

為什么是雙橫杠?

雙橫杠(--)代表著kubectl命令項的結束。在兩個橫杠之后的內容是指在pod內部需要執行的命令。如果需要執行的命令并沒有以橫杠開始的參數,橫杠也不是必需的。

回顧一下在運行命令時發生了什么。在一個pod容器上,利用Kubernetes去執行curl命令。curl命令向一個后端有三個pod服務的IP發送了HTTP請求,Kubernetes服務代理截取的該連接,在三個pod中任意選擇了一個pod,然后將請求轉發給它。Node.js在pod中運行處理請求,并返回帶有pod名稱的HTTP響應。接著,curl命令向標準輸出打印返回值,該返回值被kubectl截取并打印到宕主機的標準輸出。

在之前的例子中,在pod主容器中以獨立進程的方式執行了curl命令。這與容器真正的主進程和服務通信并沒有什么區別。

配置服務上的會話親和性

如果多次執行同樣的命令,每次調用執行應該在不同的pod上。因為服務代理通常將每個連接隨機指向選中的后端pod中的一個,即使連接來自于同一個客戶端。

另一方面,如果希望特定客戶端產生的所有請求每次都指向同一個pod,可以設置服務的sessionAffinity屬性為ClientIP(而不是None,None是默認值),如下面的代碼清單所示。

userspace 代理模式

代碼清單5.2 會話親和性被設置成ClientIP的服務的例子

apiVersion: v1
kind: Service
spec:
  sessionAffinity: ClientIP
......

這種方式將會使服務代理將來自同一個client IP的所有請求轉發至同一個pod上。作為練習,創建額外的服務并將會話親和性設置為ClientIP,并嘗試向其發送請求。

Kubernetes僅僅支持兩種形式的會話親和性服務:None和ClientIP。你或許驚訝竟然不支持基于cookie的會話親和性的選項,但是你要了解Kubernetes 服務不是在HTTP層面上工作。服務處理TCP和UDP包,并不關心其中的載荷內容。因為cookie是HTTP協議中的一部分,服務并不知道它們,這就解釋了為什么會話親和性不能基于cookie。

同一個服務暴露多個端口

創建的服務可以暴露一個端口,也可以暴露多個端口。比如,你的pod監聽兩個端口,比如HTTP監聽8080端口、HTTPS監聽8443端口,可以使用一個服務從端口80和443轉發至pod端口8080和8443。在這種情況下,無須創建兩個不同的服務。通過一個集群IP,使用一個服務就可以將多個端口全部暴露出來。

注意 在創建一個有多個端口的服務的時候,必須給每個端口指定名字。

以下代碼清單中展示了多端口服務的規格。

代碼清單5.3 在服務定義中指定多端口

apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  selector:
    app: kubia  #標簽選擇器適用于整個服務
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 8080
    - name: https
      protocol: TCP
      port: 443
      targetPort: 8081

注意 標簽選擇器應用于整個服務,不能對每個端口做單獨的配置。如果不同的pod有不同的端口映射關系,需要創建兩個服務。

之前創建的kubia pod不在多個端口上偵聽,因此可以練習創建一個多端口服務和一個多端口pod。

使用命名的端口 在這些例子中,通過數字來指定端口,但是在服務spec中也可以給不同的端口號命名,通過名稱來指定。這樣對于一些不是眾所周知的端口號,使得服務spec更加清晰。在服務spec中按名稱引用這些端口,如下面的代碼清單所示。

代碼清單5.5 在服務中引用命名pod

apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  selector:
    app: kubia  #標簽選擇器適用于整個服務
  ports:
    - name: http
      port: 80
      targetPort: http #將80端口映射到容器中被稱為http的端口
    - name: https
      port: 443
      targetPort: https #將443端口映射到容器中被稱為https的端口

為什么要采用命名端口的方式?最大的好處就是即使更換端口號也無須更改服務spec。你的pod現在對http服務用的是8080,但是假設過段時間你決定將端口更換為80呢?

如果你采用了命名的端口,僅僅需要做的就是改變spec pod 中的端口號(當然你的端口號的名稱沒有改變)。在你的pod向新端口更新時,根據pod收到的連接(8080端口在舊的pod上、80端口在新的pod上),用戶連接將會轉發到對應的端口號上。

5.1.2 服務發現

通過創建服務,現在就可以通過一個單一穩定的IP地址訪問到pod。在服務整個生命周期內這個地址保持不變。在服務后面的pod可能刪除重建,它們的IP地址可能改變,數量也會增減,但是始終可以通過服務的單一不變的IP地址訪問到這些pod。

但客戶端pod如何知道服務的IP和端口?是否需要先創建服務,然后手動查找其IP地址并將IP傳遞給客戶端pod的配置選項?當然不是。Kubernetes還為客戶端提供了發現服務的IP和端口的方式。

通過環境變量發現服務

在pod開始運行的時候,Kubernetes會初始化一系列的環境變量指向現在存在的服務。如果你創建的服務早于客戶端pod的創建,pod上的進程可以根據環境變量獲得服務的IP地址和端口號。

在一個運行pod上檢查環境,去了解這些環境變量。現在已經了解了通過kubectl exec命令在pod上運行一個命令,但是由于服務的創建晚于pod的創建,那么關于這個服務的環境變量并沒有設置,這個問題也需要解決。

在查看服務的環境變量之前,首先需要刪除所有的pod使得ReplicatSet創建全新的pod。在無須知道pod的名字的情況下就能刪除所有的pod,就像這樣:

$ kubectl delete po --all

現在列出所有新的pod,然后選擇一個作為kubectl exec命令的執行目標。一旦選擇了目標pod,通過在容器中運行env來列出所有的環境變量,如下面的代碼清單所示。

代碼清單5.6 容器中和服務相關的環境變量

$ k exec kubia-7d5b548867-4qn2x -- env

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
KUBIA_SERVICE_HOST=10.111.56.158 # 服務的集群IP
KUBIA_SERVICE_PORT=80 #服務所在的端口

在集群中定義了兩個服務:kubernetes和kubia(之前在用kubectl get svc命令的時候應該見過);所以,列表中顯示了和這兩個服務相關的環境變量。在本章開始部分,創建了kubia服務,在和其有關的環境變量中有 KUBIA_SERVICE_HOSTKUBIA_SERVICE_PORT ,分別代表了kubia服務的IP地址和端口號。

回顧本章開始部分的前后端的例子,當前端pod需要后端數據庫服務pod時,可以通過名為 backend-database 的服務將后端pod暴露出來,然后前端pod通過環境變量 BACKEND_DATABASE_SERVICE_HOSTBACKEND_DATABASE_SERVICE_PORT 去獲得IP地址和端口信息。

注意 服務名稱中的橫杠被轉換為下畫線,并且當服務名稱用作環境變量名稱中的前綴時,所有的字母都是大寫的。

環境變量是獲得服務IP地址和端口號的一種方式,為什么不用DNS域名?為什么Kubernetes中沒有DNS服務器,并且允許通過DNS來獲得所有服務的IP地址?事實證明,它的確如此!

通過DNS發現服務

還記得第3章中在kube-system命名空間下列出的所有pod的名稱嗎?其中一個pod被稱作kube-dns,當前的kube-system的命名空間中也包含了一個具有相同名字的響應服務。

k get svc -A

就像名字的暗示,這個pod運行DNS服務,在集群中的其他pod都被配置成使用其作為dns(Kubernetes通過修改每個容器的/etc/resolv.conf文件實現)。運行在pod上的進程DNS查詢都會被Kubernetes自身的DNS 服務器響應,該服務器知道系統中運行的所有服務。

注意 pod是否使用內部的DNS服務器是根據pod中spec的dnsPolicy屬性來決定的。

每個服務從內部DNS 服務器中獲得一個DNS條目,客戶端的pod在知道服務名稱的情況下可以通過全限定域名(FQDN)來訪問,而不是訴諸于環境變量。

通過FQDN連接服務

再次回顧前端-后端的例子,前端pod可以通過打開以下FQDN的連接來訪問后端數據庫服務:

backend-database.default.svc.cluster.local

backend-database對應于服務名稱,default表示服務在其中定義的名稱空間,而svc.cluster.local是在所有集群本地服務名稱中使用的可配置集群域后綴。

注意 客戶端仍然必須知道服務的端口號。如果服務使用標準端口號(例如,HTTP的80端口或Postgres的5432端口),這樣是沒問題的。如果并不是標準端口,客戶端可以從環境變量中獲取端口號。

連接一個服務可能比這更簡單。如果前端pod和數據庫pod在同一個命名空間下,可以省略 svc.cluster.local 后綴,甚至命名空間。因此可以使用 backend-database 來指代服務。這簡單到不可思議,不是嗎?

嘗試一下。嘗試使用FQDN來代替IP去訪問kubia服務。另外,必須在一個存在的pod上才能這樣做。已經知道如何通過 kubectl exec 在一個pod的容器上去執行一個簡單的命令,但是這一次不是直接運行curl命令,而是運行bash shell,這樣可以在容器上運行多條命令。在第2章中,當想進入容器啟動Docker時,調用 docker exec-it bash 命令,這與此很相似。

在pod容器中運行shell

可以通過kubectl exec命令在一個pod容器上運行bash(或者其他形式的shell)。通過這種方式,可以隨意瀏覽容器,而無須為每個要運行的命令執行kubectl exec。

注意 shell的二進制可執行文件必須在容器鏡像中可用才能使用。

為了正常地使用shell,kubectl exec 命令需要添加–it選項:

$ kubectl exec -it kubia-3inly -- bash

現在進入容器內部,根據下述的任何一種方式使用curl命令來訪問kubia服務:

# curl http://kubia.custom.svc.cluster.local
Hey there, this is kubia-7d5b548867-8wnls. Your IP is ::ffff:172.18.0.1.
# curl http://kubia.custom
Hey there, this is kubia-7d5b548867-8wnls. Your IP is ::ffff:172.18.0.1.
# curl http://kubia
Hey there, this is kubia-7d5b548867-m4bfz. Your IP is ::ffff:172.18.0.1.

在請求的URL中,可以將服務的名稱作為主機名來訪問服務。因為根據每個pod容器DNS解析器配置的方式,可以將命名空間和svc.cluster.local后綴省略掉。查看一下容器中的/etc/resilv.conf文件就明白了。

# cat /etc/resolv.conf
nameserver 10.96.0.10
search test.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

無法ping通服務IP的原因

在繼續之前還有最后一問題。了解了如何創建服務,很快地去自己創建一個。但是,不知道什么原因,無法訪問創建的服務。

大家可能會嘗試通過進入現有的pod,并嘗試像上一個示例那樣訪問該服務來找出問題所在。然后,如果仍然無法使用簡單的curl命令訪問服務,也許會嘗試ping 服務 IP以查看服務是否已啟動。現在來嘗試一下:

# ping kubia
PING kubia.test.svc.cluster.local (10.99.93.78): 56 data bytes
^C--- kubia.test.svc.cluster.local ping statistics ---
91 packets transmitted, 0 packets received, 100% packet loss

嗯,curl這個服務是工作的,但是卻ping不通。這是因為服務的集群IP是一個虛擬IP,并且只有在與服務端口結合時才有意義。將在第11章中解釋這意味著什么,以及服務是如何工作的。在這里提到這個問題,因為這是用戶在嘗試調試異常服務時會做的第一件事(ping服務的IP),而服務的IP無法ping通會讓大多數人措手不及。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,936評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,744評論 3 421
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,879評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,181評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,935評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,325評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,384評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,534評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,084評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,892評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,623評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,322評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,735評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,990評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,800評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,084評論 2 375