帶着問題學 Kubernetes 抽象對象 Service

帶着問題學 Kubernetes 抽象對象 Service

摘要:本文屬於原創,歡迎轉載,轉載請保留出處:https://github.com/jasonGeng88/blogphp

當前環境

  1. Mac OS 10.11.xdocker

  2. kubectl == v1.6.4api

  3. minikube == v0.19.1架構

  4. docker == 1.11.1app

知識點

  • Service 的 Selector 與 Label 匹配機制負載均衡

  • Service 與 Pods 的地址映射關係性能

  • kube-proxy 的 iptables 代理機制學習

  • Service 的服務發現機制

  • Service 的服務暴露方式

前言

上一篇講述了 Pod 的相關內容,瞭解了 Pod 的定義、生命週期以及通訊機制等。正如上文說的,Pod 是存在生命週期的,它的崩潰、更新都是以建立新 Pod 替換原有的 Pod 的方式進行的,因此經過固定 Pod 地址的訪問變得不太可行。咱們須要經過一種上層調用的方式,來解決底層 Pod 的動態變化的場景。

慶幸,K8S 引入了 Service 這個抽象的概念。Service 會建立一個虛擬的服務,由它來整合集羣內的 Pod。Service 會虛擬出一個 VIP,並在它銷燬以前保持該 VIP 地址保持不變。經過對它的訪問,以代理的方式負載到對應的 Pod 上,同時 Pod 生命週期的變換,也會及時反應在代理上。

下面咱們幾種常見的場景,來具體看看 Service 是如何工做的。

環境準備

演示鏡像

  • 鏡像名:jasonn/php-echoserver

  • 做用:打印當前容器的 IP

K8S Pod 建立

  • 文件名:deploy-echoserver.yml (這裏以 Deployment 的方式來建立與管理 Pod

  • 文件內容:

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  # Deployment 實例名稱
  name: echoserver
spec:
  # 設置 Pod 個數
  replicas: 2
  template:
    metadata:
      # 設置 Pod 標籤
      labels:
        app: echoserver
    spec:
      # 運行 docker 鏡像
      containers:
      - name: echoserver 
        image: jasonn/php-echoserver
  • 啓動命令:

kubectl create -f deploy-echoserver.yml

至此,準備工做所有完成。短暫的等待後,Pod 建立成功,而且也由 deployment 管理着。

查看 deployment 啓動狀況:

對 Pod 的訪問狀況以下(經過kubectl describe pods獲取 Pod 的 IP 地址):

問題

場景1:如今 K8S 上運行着2個 Pod。咱們但願經過上述所說的 Service 來整合這兩個 Pod 的訪問,完成對它們的統一訪問,而不用向具體的 Pod 發出請求。

Q1: Service 如何對 Pod 進行整合

這裏所說的 Pod 默認都是帶有標籤(label)的,咱們以前建立的兩個 Pod 所賦予的標籤是 app: echoserver,因此咱們在建立 Service 時,就要經過選擇器(selector)來獲取符合條件的 Pod 進行整合。

Service 建立腳本內容以下(service-echoserver.yml):

apiVersion: v1
kind: Service
metadata:
  # Service 實例名稱
  name: svc-echoserver
spec:
  ports:
    - protocol: TCP
      # Service 端口地址
      port: 8080
      # Pod 端口地址
      targetPort: 80
  selector:
    # 匹配符合標籤條件的 Pod
    app: echoserver

建立 Service 命令:

kubectl create -f service-echoserver.yml

由此,咱們建立好了一個 Service,同時也生成了一個對應的 VIP。

查看 Serivce 建立狀況:

下面,咱們來驗證下是否如以前所說,對 VIP 的訪問能訪問到 Pod 的內容。

咱們發現不只能成功訪問,並且還提供了負載均衡的功能。後面會講負載是怎麼實現的

PS: 標籤篩選查找範圍僅在同個命名空間(namespace)內。


場景2:瞭解了 Service 是經過 label & selecor 來進行整合 Pod 的。那若是 Pod 不存在標籤,又或者是在不一樣 Namespace 下,也多是 K8S 集羣外的一個服務。現實狀況每每更加複雜,這樣的狀況下,Service 又該如何整合。

Q2: Service 與 Pod 的地址映射關係由誰管理?

這裏引出了另外一個概念 Endpoints。咱們先來看看它的一個具體狀況。

發如今 Service 建立的同時,還生成了一個 Endpoints。 該 Endpoints 與 Service 同名,它所暴露的地址信息正是對應 Pod 的地址。由此猜想是 Endpoints 維護了 Service 與 Pod 的映射關係。

爲了驗證咱們的猜想,咱們手動刪除 Endpoints,發現以前能成功訪問到 Pod 的 VIP,如今已經已經訪問不到了。

咱們在手動把 Endpoints 建立回來,建立腳本以下(endpoint-echoserver.yml):

apiVersion: v1
kind: Endpoints
metadata:
  # Endpoints 實例的名稱
  name: svc-echoserver
subsets:
  - addresses:
    - ip: 172.17.0.5
    - ip: 172.17.0.6
    ports:
    - port: 80

建立命令:

kubectl create -f endpoint-echoserver.yml

注意:Endpoints 與 Service 的綁定關係經過名稱來關聯的,因此這二者的名稱(name)必定要一致。

若是建立失敗,出現的錯誤信息是「...endpoints "svc-echoserver" already exists」,說明 Service 已經更新了 Endpoints。這裏就說到了 Service 會按期去檢查 Pod 的狀態,而且將結果更新到 Endpoints 上。

VIP 再次訪問時又能成功訪問到,如圖:

如今咱們已經能輕鬆的解決場景2的問題了,在建立 Service 時,只要不設置 Selector 屬性,那麼將不會自動建立 Endpoints,這是咱們能夠根據需求手動的建立指定地址(address)的 Endpoints,來解決標籤沒法實現的整合。


場景3:知道了 Service、Endpoints、Pod 的三者關係後,咱們來具體看看所說的代理究竟是如何實現的。從以前 K8S 的架構中,咱們知道 Service 的代理是由 kube-proxy 實現的。而它的代理模式(Proxy mode)主要有兩種:userspace 與 iptables。自 K8S v1.2 開始,默認的代理模式就是 iptables,而且它的性能也是要高於 userspace 的,因此在這兒只討論 iptables 的實現。

Q3:kube-proxy 是如何使用 iptables 作到服務代理的(對於 iptables 不瞭解的同窗能夠直接跳過)?

咱們如今要作的呢,是將 VIP 請求給轉發到對應的 Pod 上。而實現此的正是 iptables。

瞭解 iptables 的同窗都知道四表五鏈的概念,而作端口地址轉發的呢,主要是在 nat 表中實現。咱們下面看一下一個 VIP 請求在 nat 表中是如何一步步被轉發到 Pod 上的。

    1. 根據 iptables 的機制,請求是先到 nat 表的 PREROUTING 鏈(chain)上的,它的規則以下:

從圖中發現,請求首先通過 KUBE-SERVICE 鏈,其次再到 DOCKER 鏈上的。

    1. 咱們看一下 KUBE-SERVICE 的狀況:

咱們發現 KUBE-SERVICE 中包含了一系列 Service 的規則。根據咱們請求的 VIP 的目的地址,對應到了下一個名叫 KUBE-SVC-PRQ3AXYQLQGIVVIU 的 Service 鏈上。

    1. KUBE-SVC-PRQ3AXYQLQGIVVIU 規則以下:

從規則的名字上能夠看出,該條 Service 鏈上記錄的是2個 Endpoints 鏈,具體的選擇是經過 50% 的隨機性的進行決定(這也是它的一個負載規則)。

    1. 咱們來看第一個名叫 KUBE-SEP-JSFY3ZFM2EVD64VQ 的 Endpoints 鏈的狀況:

從圖中,咱們已經很清晰的看到了它轉發到 Pod 的具體規則。

    1. 下面以一張簡單的流程圖,看一下請求的轉發狀況:

關於 DOCKER 鏈的跟蹤,方法是差很少的,請求 從 nat 表結束後,在到 filter 表中,這裏就不加以說明了。

而這些規則的實現正是由 Service、Endpoints 來完成的。咱們在建立、更新、以及其自身的檢測機制,都會對這些規則進行更新。


場景4:Service 的建立、內部結構以及映射關係,咱們都瞭解了。下面咱們就要關心如何優雅的使用它,上面咱們都是經過 Service 的 VIP 進行訪問的。這存在的問題是,若是有服務與服務之間的調用,難道我還要知道所調用服務的 VIP 不成,對於 VIP 的訪問可否更通用一些。

Q4:Service 的服務發現機制是怎樣的?

對於服務與服務間的調用,實際上就是 Pod 對 Servie 的調用。而 Pod 是如何發現 Service 的,這裏可選擇的方式有2種。

咱們經過啓動一個名爲 busybox 的 Pod 來觀察這兩種方式:

kubectl run -i --tty busybox --image=busybox --restart=Never -- sh
  • 環境變量:

在 Pod 中,集羣中的 Service 會以環境變量的方式賦值在容器中,咱們能夠經過 {SERVICE_NAME}_SERVICE_HOST{SERVICE_NAME}_SERVICE_PORT 進行獲取(對於有多個 Port 的,能夠經過帶指定 PORT 名稱的變量得到。)。

busybox 中 環境變量以下:

查看訪問狀況:

  • dns 解析:

第二種方式是經過 kube-dns 對 Service 進行域名解析,一樣能達到服務發現的目的。

查看 DNS 域名解析配置:

經過 nslookup 查詢 dns 記錄:

查看訪問結果:


場景5:集羣間的服務調用解決了,可說到底仍是經過的 VIP 進行的訪問。VIP 對於集羣外的終端用戶,是沒法訪問的。因此咱們得經過服務暴露的方式,讓終端用戶能與集羣內的服務進行通訊。

Q5:Service 是如何對外暴露服務的?

在 Service 的配置文件中,屬性spec.type就是用來設置服務暴露的方式,它提供的三種方式以下:

  • ClusterIP: 提供一個集羣內部的虛擬IP以供Pod訪問(默認類型,咱們上述使用的正是這種方式)。

  • NodePort: 在每一個Node上打開一個端口以供外部訪問。

  • LoadBalancer: 經過外部的負載均衡器來訪問(通常須要雲提供商提供 LB 支持)。

咱們這裏簡單起見,仍是經過 NodePort 方式進行。

修改 Service 配置文件,並從新啓動:

apiVersion: v1
kind: Service
metadata:
  # Service 實例名稱
  name: svc-echoserver
spec:
  ports:
    - protocol: TCP
      # Service 端口地址
      port: 8080
      # Pod 端口地址
      targetPort: 80
  selector:
    # 匹配符合標籤條件的 Pod
    app: echoserver
  type: NodePort

注意:這裏若是要以kubecrl replace -f service-echoserver.yml方式進行平滑更新,配置中需添加spec.clusterIP屬性,值爲當前 Service 的 VIP,不然更新會失敗。這也符合了一開始說的 Service 在它終止以前,VIP 是不會改變的。

查看 Service 更新狀況:

外部訪問(該 Node 地址是:192.168.64.6):

總結

文本從 Service 的標籤與選擇器開始,講了 Service 整合 Pod 的過程,引出了 Service, Endpoints, Pods 三者的關係狀況。隨後又經過 iptables 詳細展開了 kube-proxy 的代理機制。最後,以 Service 的集羣內與集羣外的訪問設置,講述了 Service 的服務發現與服務暴露機制。

關於 Service 的有遺漏重要的知識點,或者有講的不對的地方,也歡迎提出和指正!最後,但願本篇對你學習 K8S 有所幫助~

相關文章
相關標籤/搜索