Service 概述
kubernetes 中的pod是有生生滅滅的,時刻都有可能被新的pod所代替,而不可復活(pod的生命周期)。一旦一個pod生命終止,通過ReplicaSets動態創建和銷毀pod(Pod的動態擴縮容,滾動升級 等)。 每個pod都有自己的IP,這IP隨着pod的生生滅滅而變化,不能被依賴。這樣導致一個問題,如果這個POD作為后端(backend)提供一些功能供給一些前端POD(frontend),在kubernete集群中是如何實現讓這些前台能夠持續的追蹤到這些后台的?所以之間需要一個服務作為后端的服務負載------service
Kubernetes Service 是一個定義了一組Pod的策略的抽象,這些被服務標記的Pod都是(一般)通過label Selector實現的
舉個例子,考慮一個圖片處理 backend,它運行了3個副本。這些副本是可互換的 —— frontend 不需要關心它們調用了哪個 backend 副本。 然而組成這一組 backend 程序的 Pod
實際上可能會發生變化,frontend 客戶端不應該也沒必要知道,而且也不需要跟蹤這一組 backend 的狀態。 Service
定義的抽象能夠解耦這種關聯。
Service 實現的三種方式
在 Kubernetes 集群中,每個 Node 運行一個 kube-proxy
進程。kube-proxy
負責為 Service
實現了一種 VIP(虛擬 IP)的形式,而不是 ExternalName
的形式,在 Kubernetes v1.0 版本,代理完全在 userspace。在 Kubernetes v1.1 版本,新增了 iptables 代理,但並不是默認的運行模式。 從 Kubernetes v1.2 起,默認就是 iptables 代理。在Kubernetes v1.8.0-beta.0中,添加了ipvs代理。在 Kubernetes v1.0 版本,Service
是 “4層”(TCP/UDP over IP)概念。 在 Kubernetes v1.1 版本,新增了 Ingress
API(beta 版),用來表示 “7層”(HTTP)服務。
kube-proxy 這個組件始終監視着apiserver中有關service的變動信息,獲取任何一個與service資源相關的變動狀態,通過watch監視,一旦有service資源相關的變動和創建,kube-proxy都要轉換為當前節點上的能夠實現資源調度規則(例如:iptables、ipvs)
userspace 代理模式
這種模式,當客戶端Pod請求內核空間的service iptables后,把請求轉到給用戶空間監聽的kube-proxy 的端口,由kube-proxy來處理后,再由kube-proxy打請求轉給內核空間的 service iptalbes,再由service iptalbes根據請求轉給各節點中的的service pod。由此可見這個模式有很大的問題,由客戶端請求先進入內核空間的,又進去用戶空間訪問kube-proxy,由kube-proxy封裝完成后再進去內核空間的iptables,再根據iptables的規則分發給各節點的用戶空間的pod。這樣流量從用戶空間進出內核帶來的性能損耗是不可接受的
iptables 代理模式
客戶端IP請求時,直接求情本地內核service ip,根據iptables的規則求情到各pod上,因為使用iptable NAT來完成轉發,也存在不可忽視的性能損耗。另外,如果集群中存在上萬的Service/Endpoint,那么Node上的iptables rules將會非常龐大,性能還會再打折扣。
ipvs 代理模式
客戶端IP請求時,直接求情本地內核service ipvs,根據ipvs的規則求情到各pod上。kube-proxy會監視Kubernetes Service
對象和Endpoints
,調用netlink
接口以相應地創建ipvs規則並定期與Kubernetes Service
對象和Endpoints
對象同步ipvs規則,以確保ipvs狀態與期望一致。訪問服務時,流量將被重定向到其中一個后端Pod。
與iptables類似,ipvs基於netfilter 的 hook 功能,但使用哈希表作為底層數據結構並在內核空間中工作。這意味着ipvs可以更快地重定向流量,並且在同步代理規則時具有更好的性能。此外,ipvs為負載均衡算法提供了更多選項,例如:
- rr:
輪詢調度
- lc:最小連接數
dh
:目標哈希sh
:源哈希sed
:最短期望延遲nq
:不排隊調度
注意: ipvs模式假定在運行kube-proxy之前在節點上都已經安裝了IPVS內核模塊。當kube-proxy以ipvs代理模式啟動時,kube-proxy將驗證節點上是否安裝了IPVS模塊,如果未安裝,則kube-proxy將回退到iptables代理模式。
如果某個服務后端pod發生變化,標簽選擇器適應的pod有多一個,適應的信息會立即放映到apiserver上,而kube-proxy一定可以watch到etc中的信息變化,而將他立即轉為ipvs或者iptables中的規則,這一切都是動態和實時的,刪除一個pod也是同樣的原理。
service 定義
kubectl explain svc.spec
- ports 建立哪些端口,暴露的端口是哪些
- selector 把哪些容器通過這個service暴露出去
- type 有四種 (ExternalName ClusterIP NodePort LoadBalancer) 默認是ClusterIP
ports 的定義
kubectl explain svc.spec.ports
- name 指定的port的名稱
- nodePort 指定節點上的端口
- port 暴露給服務的端口
- targetPort 容器的端口
- protocol 執行協議(TCP or UDP)
ClusterIP方式
apiVersion: v1 kind: Service metadata: name: redis namespace: default spec: selector: app: redis role: log-store type: ClusterIP ports: - port: 6379 targetPort: 6379
查看一下詳細
$ kubectl describe svc redis Name: redis Namespace: default Labels: <none> Annotations: kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"redis","namespace":"default"},"spec":{"ports":[{"port":6379,"targetPort":6379}... Selector: app=redis,role=log-store Type: ClusterIP IP: 10.43.164.114 Port: <unset> 6379/TCP Endpoints: 10.42.0.219:6379 Session Affinity: None Events: <none>
資源記錄格式:
SVC_NAME.NS_NAME.DOMAIN.LTD.
默認的service的a記錄 svc.cluster.local.
剛創建的service的a記錄 redis.default.cluster.local.
NodePort方式
apiVersion: v1 kind: Service metadata: name: myapp namespace: default spec: selector: app: myapp release: dev type: NodePort ports: - port: 80 targetPort: 80 nodePort: 30080
$ kubectl describe svc myapp Name: myapp Namespace: default Labels: <none> Annotations: field.cattle.io/publicEndpoints=[{"addresses":["172.16.138.170"],"port":30080,"protocol":"TCP","serviceName":"default:myapp","allNodes":true}] kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"myapp","namespace":"default"},"spec":{"ports":[{"nodePort":30080,"port":80,"ta... Selector: app=myapp,release=dev Type: NodePort IP: 10.43.162.175 Port: <unset> 80/TCP NodePort: <unset> 30080/TCP Endpoints: 10.42.0.218:80,10.42.1.107:80,10.42.3.210:80 Session Affinity: None Events: <none>
#可以看到他負責均衡的效果
$ for a in {1..10}; do curl http://172.16.138.170:30080/hostname.html && sleep 1s; done
myapp-deploy-869b888f66-4l4cv
myapp-deploy-869b888f66-7shh9
myapp-deploy-869b888f66-4l4cv
myapp-deploy-869b888f66-7shh9
myapp-deploy-869b888f66-4l4cv
myapp-deploy-869b888f66-7shh9
myapp-deploy-869b888f66-vwgj2
myapp-deploy-869b888f66-7shh9
myapp-deploy-869b888f66-4l4cv
LoadBalancer類型
使用支持外部負載均衡器的雲提供商的服務,設置 type
的值為 "LoadBalancer"
,將為 Service
提供負載均衡器。 負載均衡器是異步創建的,關於被提供的負載均衡器的信息將會通過 Service
的 status.loadBalancer
字段被發布出去。
來自外部負載均衡器的流量將直接打到 backend Pod
上,不過實際它們是如何工作的,這要依賴於雲提供商。 在這些情況下,將根據用戶設置的 loadBalancerIP
來創建負載均衡器。 某些雲提供商允許設置 loadBalancerIP
。如果沒有設置 loadBalancerIP
,將會給負載均衡器指派一個臨時 IP。 如果設置了 loadBalancerIP
,但雲提供商並不支持這種特性,那么設置的 loadBalancerIP
值將會被忽略掉。
ExternalName 類型
提供訪問發布服務的,像使用集群內部一樣使用外部服務。
會話粘性(常說的會話保持)
kubectl explain svc.spec.sessionAffinity
支持ClientIP和None 兩種方式,默認是None(隨機調度) ClientIP是來自於同一個客戶端的請求調度到同一個pod中
apiVersion: v1 kind: Service metadata: name: myapp namespace: default spec: selector: app: myapp release: dev sessionAffinity: ClientIP type: NodePort ports: - port: 80 targetPort: 80 nodePort: 30080
查看來自同一客戶端的請求始終訪問同一個Pod
$ kubectl describe svc myapp Name: myapp Namespace: default Labels: <none> Annotations: field.cattle.io/publicEndpoints=[{"addresses":["172.16.138.170"],"port":30080,"protocol":"TCP","serviceName":"default:myapp","allNodes":true}] kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"myapp","namespace":"default"},"spec":{"ports":[{"nodePort":30080,"port":80,"ta... Selector: app=myapp,release=dev Type: NodePort IP: 10.43.162.175 Port: <unset> 80/TCP NodePort: <unset> 30080/TCP Endpoints: 10.42.0.218:80,10.42.1.107:80,10.42.3.210:80 Session Affinity: ClientIP Events: <none> $ for a in {1..10}; do curl http://172.16.138.170:30080/hostname.html && sleep 1s; done myapp-deploy-869b888f66-4l4cv myapp-deploy-869b888f66-4l4cv myapp-deploy-869b888f66-4l4cv myapp-deploy-869b888f66-4l4cv myapp-deploy-869b888f66-4l4cv myapp-deploy-869b888f66-4l4cv myapp-deploy-869b888f66-4l4cv myapp-deploy-869b888f66-4l4cv myapp-deploy-869b888f66-4l4cv myapp-deploy-869b888f66-4l4cv
Headless service(就是沒有Cluster IP 的Service
)
有時不需要或不想要負載均衡,以及單獨的 Service IP。 遇到這種情況,可以通過指定 Cluster IP(spec.clusterIP
)的值為 "None"
來創建 Headless
Service。它會給一個集群內部的每個成員提供一個唯一的DNS域名來作為每個成員的網絡標識,集群內部成員之間使用域名通信
這個選項允許開發人員自由尋找他們自己的方式,從而降低與 Kubernetes 系統的耦合性。 應用仍然可以使用一種自注冊的模式和適配器,對其它需要發現機制的系統能夠很容易地基於這個 API 來構建。
對這類 Service
並不會分配 Cluster IP,kube-proxy 不會處理它們,而且平台也不會為它們進行負載均衡和路由。 DNS 如何實現自動配置,依賴於 Service
是否定義了 selector。
apiVersion: v1 kind: Service metadata: name: myapp-headless namespace: default spec: selector: app: myapp release: dev clusterIP: "None" ports: - port: 80 targetPort: 80
驗證
$ dig -t A myapp-headless.default.svc.cluster.local. @10.42.0.5 ; <<>> DiG 9.9.4-RedHat-9.9.4-61.el7 <<>> -t A myapp-headless.default.svc.cluster.local. @10.42.0.5 ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 55062 ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;myapp-headless.default.svc.cluster.local. IN A ;; ANSWER SECTION: myapp-headless.default.svc.cluster.local. 30 IN A 10.42.0.218 myapp-headless.default.svc.cluster.local. 30 IN A 10.42.1.107 myapp-headless.default.svc.cluster.local. 30 IN A 10.42.3.210 ;; Query time: 2 msec ;; SERVER: 10.42.0.5#53(10.42.0.5) ;; WHEN: Fri Aug 31 11:40:46 EDT 2018 ;; MSG SIZE rcvd: 106