1. 程式人生 > >Kubernetes Service

Kubernetes Service

ont 生成 缺省 時間 spec 目標 a記錄 ssi 連續

Service 的作用

參考鏈接

雖然每個Pod都有自己的IP地址,但即使這些IP地址不能長期保持穩定。這導致了一個問題:如果一些Pod(稱為它們的後端)為Kubernetes集群內的其他Pod(我們稱之為前端)提供了功能,那麽這些前端如何發現並跟蹤哪些後端位於該集合中?

通過Service。

Kubernetes的 service是一個抽象概念,它定義了Pod的邏輯集合以及訪問它們的策略 - 有時稱為微服務。service所針對的Pod集(通常)由標簽選擇器決定(請參閱下面為什麽您可能需要沒有選擇器的服務)。

舉一個例子,考慮一個運行3個副本的應用處理後端。這些副本是可替代的 - 前端不關心他們使用的後端。雖然構成後端集合的實際Pod可能會發生變化,但前端客戶端不需要知道該事件,也不需要跟蹤後端列表本身。服務抽象使這種解耦成為可能。

對於Kubernetes原生應用程序,Kubernetes提供了一個簡單的Endpoints API,只要服務中的Pod集合發生更改,它就會更新。對於非本機應用程序,Kubernetes提供了一個基於虛擬IP的網橋,用於重定向到後端Pod的服務。

定義Service

Serive 可以通過兩種方式定義,yaml 文件方式和使用命令行創建的方式。

使用yaml方式

使用yaml文件定義個service:

kind: Service
apiVersion: v1
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
  - protocol: TCP
    port: 80
    targetPort: 9376

此規範將創建一個名為“my-service”的新服務對象,該服務對象將任何Pod上的TCP端口9376以“app = MyApp”標簽作為目標。 該服務還將被分配一個IP地址(有時稱為“群集IP”),服務代理使用該地址(見下文)。 服務的選擇器將被連續評估,結果將被發送到一個名為“my-service”的端點對象。

請註意,服務可以將傳入端口映射到任何目標端口。 默認情況下,targetPort將設置為與端口字段相同的值。 也許更有趣的是,targetPort可以是一個字符串,指的是後端Pod中端口的名稱。 分配給該名稱的實際端口號可以在每個後端Pod中不同。 這為部署和發展您的服務提供了很大的靈活性。 例如,您可以在不中斷客戶端的情況下,更改後續版本後端軟件中的端口號。

Kubernetes 的 Services 支持TCP和UDP協議,默認支持TCP。

使用命令方式

使用kubectl expose命令也可以創建一個Services:

 kubectl  expose deployment php-apache

創建不帶標簽選擇器的service

我們有時候也可以定義一個不帶標簽選擇器的Service,即無法選擇後端的Pod,系統不會自動創建Endpoint,當要正式使用的時候再手動創建一個和該Service同名的Endpoint,用於指向實際的後端訪問地址。
這種方式一般有如下的應用場景:

  • 在生產環境中,需要連接一個外部的數據庫,但是在測試的階段,我們會使用自己的測試數據庫。
  • 需要將service指向其他namespace的服務或外部集群中。
  • 您正在將工作負載遷移到Kubernetes,而您的一些後端運行在Kubernetes之外。

這樣定義一個不含選擇器標簽的service:

kind: Service
apiVersion: v1
metadata:
  name: my-service
spec:
  ports:
  - protocol: TCP
    port: 80                       # services 的端口
    targetPort: 9376               # endpoint 的端口

在使用的時候還需要手動創建一個同名的Endpoint:

kind: Endpoints
apiVersion: v1
metadata:
  name: my-service
subsets:
  - addresses:
      - ip: 1.2.3.4       # 外部服務的IP和端口,這裏相當於Pod 的IP,只不過是外部的一個不變的地址
    ports:
      - port: 9376

虛擬IP和服務代理

Kubernetes群集中的每個節點都運行一個kube-proxy。 kube-proxy負責為ExternalName以外的其他類型的服務提供一個虛擬IP。 在Kubernetes v1.0中,服務是“第4層”(TCP / UDP over IP)構造,代理純粹在用戶空間中。 在Kubernetes v1.1中,添加了(測試版)Ingress API以表示“第7層”(HTTP)服務,還添加了iptables代理,並成為自Kubernetes v1.2以來的默認操作模式。 在Kubernetes v1.8.0-beta.0中,添加了ipvs代理。

用戶空間代理模式

在此模式下,kube-proxy監視Kubernetes主服務器以添加和刪除Service和Endpoints對象。 對於每個服務,它在本地節點上打開一個端口(隨機選擇)。 與此“代理端口”的任何連接都將代理到Service的後端Pod之一(如端點中所報告)。 根據服務的SessionAffinity決定使用哪個後端Pod。 最後,它安裝iptables規則,捕獲流量到服務的clusterIP(虛擬)和端口,並將該流量重定向到代理後端Pod的代理端口。 默認情況下,後端的選擇是輪詢模式。

技術分享圖片

iptables 代理模式

在此模式下,kube-proxy監視Kubernetes主服務器以添加和刪除Service和Endpoints對象。 對於每個服務,它都會安裝iptables規則,這些規則將流量捕獲到服務的clusterIP(這是虛擬的)和端口,並將該流量重定向到服務的後端集合之一。 對於每個Endpoints對象,它都會安裝選擇後端Pod的iptables規則。 默認情況下,後端的選擇是隨機的。

顯然,iptables不需要在用戶空間和內核空間之間切換,它應該比用戶空間代理更快,更可靠。 但是,與用戶空間連接器不同,如果iptables連接器最初選擇的連接器不響應,iptables連接器不能自動重試另一個連接,因此它依賴於正在工作的準備就緒探測器。

技術分享圖片

IPVS代理模式

在這種模式下,Kubernetes Services和Endpoints調用netlink接口來相應地創建ipvs規則,並定期與Kubernetes Services和Endpoints同步ipvs規則,以確保ipvs狀態與預期一致。 當訪問服務時,流量將被重定向到其中一個後端Pod。

與iptables類似,Ipvs基於netfilter鉤子函數,但使用散列表作為基礎數據結構並在內核空間中工作。 這意味著ipvs可以更快地重定向流量,並且在同步代理規則時具有更好的性能。 此外,ipvs為負載均衡算法提供了更多選項,例如:

  • rr:輪詢
  • lc:最少連接
  • dh:目標哈希
  • sh:源哈希
  • sed:預計的最短延遲
  • nq:從不排隊

在運行kube-proxy之前,ipvs模式假定在節點上安裝了IPVS內核模塊。 當kube-proxy以ipvs代理模式啟動時,kube-proxy會驗證節點上是否安裝了IPVS模塊,如果未安裝,kube-proxy將回退到iptables代理模式。

技術分享圖片

在任何這些代理模型中,為服務的IP:端口綁定的任何流量都會代理到適當的後端,而客戶端不知道任何關於Kubernetes或服務或Pod的信息。 通過將service.spec.sessionAffinity設置為“ClientIP”(缺省值為“None”),可以選擇基於Client-IP的會話關聯,並且可以通過設置字段service.spec.sessionAffinityConfig.clientIP來設置最大會話粘滯時間。 timeoutSeconds如果您已經將service.spec.sessionAffinity設置為“ClientIP”(默認值為“10800”)

多端口service

有時一個容器也可以映射多個端口服務,在service的定義中也可以相應的設置多端口的對應到多個應用服務器上:

kind: Service
apiVersion: v1
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 9376
  - name: https
    protocol: TCP
    port: 443
    targetPort: 9377

在映射多個端口的時候,需要給每一個端口指定名稱。

服務發現機制

在Kubernetes 集群中是如何進行服務發現的呢? Kubernetes為我們提供了兩種方式:

  • 環境變量
  • DNS

環境變量

當 Pod 運行在 Node 上,kubelet 會為每個活躍的 Service 添加一組環境變量。 它同時支持 Docker links兼容 變量(查看 makeLinkVariables)、簡單的 {SVCNAME}_SERVICE_HOST 和 {SVCNAME}_SERVICE_PORT 變量,這裏 Service 的名稱需大寫,橫線被轉換成下劃線。

舉個例子,一個名稱為 "redis-master" 的 Service 暴露了 TCP 端口 6379,同時給它分配了 Cluster IP 地址 10.0.0.11,這個 Service 生成了如下環境變量:

REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11

使用環境變量需要有順序的要求 —— Pod 想要訪問的任何 Service 必須在 Pod 自身之前被創建,否則這些環境變量就不會被賦值。但是DNS 並沒有這個限制。

DNS

一個可選(盡管強烈推薦)集群插件 是 DNS 服務器。 DNS 服務器監視著創建新 Service 的 Kubernetes API,從而為每一個 Service 創建一組 DNS 記錄。 如果整個集群的 DNS 一直被啟用,那麽所有的 Pod 應該能夠自動對 Service 進行名稱解析。

例如,有一個名稱為 "my-service" 的 Service,它在 Kubernetes 集群中名為 "my-ns" 的 Namespace 中,為 "my-service.my-ns" 創建了一條 DNS 記錄。 在名稱為 "my-ns" 的 Namespace 中的 Pod 應該能夠簡單地通過名稱查詢找到 "my-service"。 在另一個 Namespace 中的 Pod 必須限定名稱為 "my-service.my-ns"。 這些名稱查詢的結果是 Cluster IP。

Kubernetes 也支持對端口名稱的 DNS SRV(Service)記錄。 如果名稱為 "my-service.my-ns" 的 Service 有一個名為 "http" 的 TCP 端口,可以對 "_http._tcp.my-service.my-ns" 執行 DNS SRV 查詢,得到 "http" 的端口號。

Kubernetes DNS 服務器是唯一的一種能夠訪問 ExternalName 類型的 Service 的方式。 更多信息可以查看DNS Pod 和 Service。

Headless Service

有時不需要或不想要負載均衡,以及單獨的 Service IP。 遇到這種情況,可以通過指定 Cluster IP(spec.clusterIP)的值為 "None" 來創建 Headless Service。

這個選項允許開發人員自由尋找他們自己的方式,從而降低與 Kubernetes 系統的耦合性。 應用仍然可以使用一種自註冊的模式和適配器,對其它需要發現機制的系統能夠很容易地基於這個 API 來構建。

對這類 Service 並不會分配 Cluster IP,kube-proxy 不會處理它們,而且平臺也不會為它們進行負載均衡和路由。 DNS是否能夠自動配置,依賴於 Service 是否定義了 selector。

帶Selectors 的Headless Service

如果在Services定義了一個Selectors, K8S將會為每個Pod創建一個Endpoint,並配置到DNS,訪問此Service時,會將Pod對應的所有A記錄地址返回。
這對於分布式的集群創建非常有用,可以通過使用這種方式獲得集群的列表。

定義:

cat nginx-headless-service.yaml 

kind: Service
apiVersion: v1
metadata:
  name: nginx-service
  labels:
    app: nginx-service
spec:
  selector:
    app: nginx
  clusterIP: None
  ports:
  - protocol: TCP
    port: 80

創建一個nginx 的deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.10.3
        ports:
        - containerPort: 80

查看對應的serivce和pod 信息:

# kubectl  get svc nginx-service
NAME            TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
nginx-service   ClusterIP   None         <none>        80/TCP    27m

# kubectl  get pod -o wide |grep nginx
nginx-deployment-75d56bb955-pw7mm   1/1       Running   0          40m       10.2.61.9   10.0.0.2
nginx-deployment-75d56bb955-qprjw   1/1       Running   0          40m       10.2.39.3   10.0.0.3
nginx-deployment-75d56bb955-xfgbk   1/1       Running   0          40m       10.2.39.2   10.0.0.3

在pod中訪問此服務,可以發現DNS上直接綁定了Pod的IP列表,不再綁定Cluster IP:

# kubectl  run  busybox --image=busybox -it sh  --rm

/ # nslookup nginx-service
Server:    10.1.0.100
Address 1: 10.1.0.100 coredns.kube-system.svc.cluster.local

Name:      nginx-service
Address 1: 10.2.39.2 10-2-39-2.nginx-service.default.svc.cluster.local
Address 2: 10.2.39.3 10-2-39-3.nginx-service.default.svc.cluster.local
Address 3: 10.2.61.9 10-2-61-9.nginx-service.default.svc.cluster.local

使用headless service 和 Stateful Sets 部署Cassandra分布式集群實例參考此鏈接:https://kubernetes.io/docs/tutorials/stateful-application/cassandra/

發布服務-集群外部訪問Pod或Service

在kubernetes中,發布服務有有如下幾種方式:

  • ClusterIP:通過集群的內部 IP 暴露服務,選擇該值,服務只能夠在集群內部可以訪問,這也是默認的 ServiceType。
  • NodePort:通過每個 Node 上的 IP 和靜態端口(NodePort)暴露服務。NodePort 服務會路由到 ClusterIP 服務,這個 ClusterIP 服務會自動創建。通過請求 <NodeIP>:<NodePort>,可以從集群的外部訪問一個 NodePort 服務。
  • LoadBalancer:使用雲提供商的負載局衡器,可以向外部暴露服務。外部的負載均衡器可以路由到 NodePort 服務和 ClusterIP 服務。
  • ExternalName:通過返回 CNAME 和它的值,可以將服務映射到 externalName 字段的內容(例如, foo.bar.example.com)。 沒有任何類型代理被創建,這只有 Kubernetes 1.7 或更高版本的 kube-dns 才支持。
  • 如果是實現外部訪問內部服務,還可以將容器的端口映射到宿主機上。

將容器端口映射到宿主機

通過設置hostPort來設置容器端口到物理機:

apiVersion: v1
kind: Pod
metadata: 
  name: redis-php
  labels:
    name: redis-php
spec:
  hostNetwork: true                        # 指定可以通過宿主機訪問pod中的服務
  containers:
  - name: frontend
    image: kubeguide/guestbook-php-frontend:localredis
    ports:
    - containerPort: 80
    # 指定宿主機映射端口。在不與hostNetwork: true 同時使用時可以指定任意端口,但是在某些使用CNI插件的情況下可能不會生效。
    # 與hostNetwork使用的時候,只能與容器端口一致,且可以省略,一般只在測試時使用。
      hostPort: 80                          
  - name: redis
    image: kubeguide/redis-master
    ports:
    - containerPort: 6379
      hostPort: 6379

使用nodePort將Service的端口映射到物理機

如果設置 type 的值為 "NodePort",Kubernetes master 將從給定的配置範圍內(默認:30000-32767)分配端口,每個 Node 將從該端口(每個 Node 上的同一端口)代理到 Service。配置如下參數指定端口範圍:

# grep -ir "20000-40000" /usr/lib/systemd/system/

/usr/lib/systemd/system/kube-apiserver.service:  --service-node-port-range=20000-40000 

該端口將通過 Service 的 spec.ports[*].nodePort 字段被指定。

如果需要指定的端口號,可以配置 nodePort 的值,系統將分配這個端口,否則調用 API 將會失敗(比如,需要關心端口沖突的可能性)。並且指定的端口要在配置文件指定的端口範圍內。
從kubernetes 1.10開始,支持指定IP, 通過如下參數:

--nodeport-addresses=127.0.0.0/8

也可以支持指定多個IP段,用逗號分隔的IP塊列表(例如10.0.0.0/8,1.2.3.4/32)用於過濾本節點的地址。例如,如果您使用標誌--nodeport-addresses = 127.0.0.0 / 8啟動kube-proxy,則kube-proxy將僅為NodePort服務選擇環回接口。 --nodeport地址默認為空([]),這意味著選擇所有可用的接口並符合當前的NodePort行為。

這可以讓開發人員自由地安裝他們自己的負載均衡器,並配置 Kubernetes 不能完全支持的環境參數,或者直接暴露一個或多個 Node 的 IP 地址。

需要註意的是,Service 將能夠通過 <NodeIP>:spec.ports[].nodePort 和 spec.clusterIp:spec.ports[].port 而對外可見。

定義示例:

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  type: NodePort
  selector:
    app: nginx
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
    nodePort: 28888

創建此service後,所有安裝有kube-proxy的節點上都會映射28888的端口,供外部訪問。

Kubernetes Service