本文收錄在容器技術學習系列文章總目錄
1、認識service
1.1 為什么要使用service
Kubernetes Pod 是有生命周期的,它們可以被創建,也可以被銷毀,然而一旦被銷毀生命就永遠結束。 通過 ReplicationController 能夠動態地創建和銷毀 Pod(例如,需要進行擴縮容,或者執行 滾動升級)。 每個 Pod 都會獲取它自己的 IP 地址,即使這些 IP 地址不總是穩定可依賴的。 這會導致一個問題:在 Kubernetes 集群中,如果一組 Pod(稱為 backend)為其它 Pod (稱為 frontend)提供服務,那么那些 frontend 該如何發現,並連接到這組 Pod 中的哪些 backend 呢?答案是:Service。
1.2 service介紹
Kubernetes Service 定義了這樣一種抽象:一個 Pod 的邏輯分組,一種可以訪問它們的策略 —— 通常稱為微服務。 這一組 Pod 能夠被 Service 訪問到,通常是通過 Label Selector(下面我們會講到我們為什么需要一個沒有label selector的服務)實現的。
舉個例子,考慮一個圖片處理 backend,它運行了3個副本。這些副本是可互換的 —— frontend 不需要關心它們調用了哪個 backend 副本。 然而組成這一組 backend 程序的 Pod 實際上可能會發生變化,frontend 客戶端不應該也沒必要知道,而且也不需要跟蹤這一組 backend 的狀態。 Service 定義的抽象能夠解耦這種關聯。
對 Kubernetes 集群中的應用,Kubernetes 提供了簡單的 Endpoints API,只要 Service 中的一組 Pod 發生變更,應用程序就會被更新。 對非 Kubernetes 集群中的應用,Kubernetes 提供了基於 VIP 的網橋的方式訪問 Service,再由 Service 重定向到 backend Pod。
1.3 三種代理模式
- userspace 代理模式(K8S 1.1之前版本)
- iptables 代理模式(K8S 1.10之前版本)
- ipvs 代理模式(K8S 1.11 之后版本,激活ipvs需要修改配置)
1.3.1 userspace 代理模式
這種模式,kube-proxy 會監視 Kubernetes master 對 Service 對象和 Endpoints 對象的添加和移除。 對每個 Service,它會在本地 Node 上打開一個端口(隨機選擇)。 任何連接到“代理端口”的請求,都會被代理到 Service 的backend Pods 中的某個上面(如 Endpoints 所報告的一樣)。 使用哪個 backend Pod,是基於 Service 的 SessionAffinity 來確定的。 最后,它安裝 iptables 規則,捕獲到達該 Service 的 clusterIP(是虛擬 IP)和 Port 的請求,並重定向到代理端口,代理端口再代理請求到 backend Pod。
網絡返回的結果是,任何到達 Service 的 IP:Port 的請求,都會被代理到一個合適的 backend,不需要客戶端知道關於 Kubernetes、Service、或 Pod 的任何信息。
默認的策略是,通過 round-robin 算法來選擇 backend Pod。 實現基於客戶端 IP 的會話親和性,可以通過設置 service.spec.sessionAffinity 的值為 "ClientIP" (默認值為 "None")。
1.3.2 iptables 代理模式
這種模式,kube-proxy 會監視 Kubernetes master 對 Service 對象和 Endpoints 對象的添加和移除。 對每個 Service,它會安裝 iptables 規則,從而捕獲到達該 Service 的 clusterIP(虛擬 IP)和端口的請求,進而將請求重定向到 Service 的一組 backend 中的某個上面。 對於每個 Endpoints 對象,它也會安裝 iptables 規則,這個規則會選擇一個 backend Pod。
默認的策略是,隨機選擇一個 backend。 實現基於客戶端 IP 的會話親和性,可以將 service.spec.sessionAffinity 的值設置為 "ClientIP" (默認值為 "None")。
和 userspace 代理類似,網絡返回的結果是,任何到達 Service 的 IP:Port 的請求,都會被代理到一個合適的 backend,不需要客戶端知道關於 Kubernetes、Service、或 Pod 的任何信息。 這應該比 userspace 代理更快、更可靠。然而,不像 userspace 代理,如果初始選擇的 Pod 沒有響應,iptables 代理能夠自動地重試另一個 Pod,所以它需要依賴 readiness probes。
1.3.3 ipvs代理模式
ipvs (IP Virtual Server) 實現了傳輸層負載均衡,也就是我們常說的4層LAN交換,作為 Linux 內核的一部分。ipvs運行在主機上,在真實服務器集群前充當負載均衡器。ipvs可以將基於TCP和UDP的服務請求轉發到真實服務器上,並使真實服務器的服務在單個 IP 地址上顯示為虛擬服務。
在kubernetes v1.8 中引入了 ipvs 模式,在 v1.9 中處於 beta 階段,在 v1.11 中已經正式可用了。 iptables 模式在 v1.1 中就添加支持了,從 v1.2 版本開始 iptables 就是 kube-proxy 默認的操作模式,ipvs 和 iptables 都是基於netfilter的, ipvs 模式和 iptables 模式之間的差異:
- ipvs 為大型集群提供了更好的可擴展性和性能
- ipvs 支持比 iptables 更復雜的復制均衡算法(最小負載、最少連接、加權等等)
- ipvs 支持服務器健康檢查和連接重試等功能
同時ipvs 也依賴 iptables,ipvs 會使用 iptables 進行包過濾、SNAT、masquared(偽裝)。具體來說,ipvs 將使用ipset來存儲需要DROP或masquared的流量的源或目標地址,以確保 iptables 規則的數量是恆定的,這樣我們就不需要關心我們有多少服務了
ipvs雖然在v1.1版本中已經支持,但是想要使用,還需激活ipvs:
① 修改配置文件
[root@master ~]# vim /etc/sysconfig/kubelet KUBE_PROXY=MODE=ipvs
② 編寫腳本,讓kubelet所在的主機,啟動時裝入以下幾個模塊:
ip_vs,ip_vs_rr,ip_vs_wrr,ip_vs_sh,nf_conntrack_ipv4
1.4 service定義資源清單幾個字段
- apiVersion: v1 版本
- kind: Service 類型
- metadata 元數據
- spec 期望狀態
- ports:服務公開的端口列表;把哪個端口和后端建立聯系
- port:此服務將公開的端口
- targetPort:要在服務所針對的pod上訪問的端口的編號或名稱
- nodePort:K8S 集群節點上的端口
- selector:標簽選擇器;關聯到哪些pod資源上
- clusterIP:服務的IP地址,通常由主服務器隨機分配
- type:確定服務的公開方式。 默認為ClusterIP
- ClusterIP(默認)
- NodePort
- LoadBalancer
- ExternelName
- sessionAffinity:service負載均衡,默認值是None,根據iptables規則隨機調度;可使用sessionAffinity保持會話連線;
- ports:服務公開的端口列表;把哪個端口和后端建立聯系
- status 當前狀態
1.5 service的4中類型
- ClusterIP(默認):僅用於集群內通信,集群內部可達,可以被各pod訪問,節點本身可訪問;
- NodePort:構建在ClusterIP上,並在路由到clusterIP的每個節點上分配一個端口;
- client ---> NodeIP:NodePort ---> ClusterIP:ServicePort ---> PodIP:containePort
- LoadBalancer:構建在NodePort上,並創建一個外部負載均衡器(如果在當前雲中受支持),它將路由到clusterIP;
- ExternelName:通過CNAME將service與externalName的值(比如:foo.bar.example.com)映射起來. 要求kube-dns的版本為1.7或以上.
2、創建clusterIP類型的service
(1)編寫yaml文件並創建名為redis的service
先創建一個deployment,啟動一個redis pod;在使用service綁定這個pod
[root@master manifests]# vim redis-svc.yaml apiVersion: apps/v1 kind: Deployment metadata: name: redis namespace: default spec: replicas: 1 selector: matchLabels: app: redis role: logstor template: metadata: labels: app: redis role: logstor spec: containers: - name: redis image: redis:4.0-alpine ports: - name: redis containerPort: 6379 --- apiVersion: v1 kind: Service metadata: name: redis namespace: default spec: selector: app: redis role: logstor clusterIP: 10.99.99.99 type: ClusterIP ports: - port: 6380 targetPort: 6379 [root@master manifests]# kubectl apply -f redis-svc.yaml deployment.apps/redis created service/redis created
(2)查詢驗證
[root@master ~]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 142d redis ClusterIP 10.99.99.99 <none> 6380/TCP 12s ---查詢service詳細信息,pod綁定成功 [root@master ~]# 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":{"clusterIP":"10.99.99.99","ports":[{"por... Selector: app=redis,role=logstor Type: ClusterIP IP: 10.99.99.99 Port: <unset> 6380/TCP TargetPort: 6379/TCP Endpoints: 10.244.2.94:6379 Session Affinity: None Events: <none>
3、創建NodePort類型的service
3.1 創建service
(1)編寫yaml文件並創建名為myapp的service
先創建一個deployment,啟動3個myapp pod;在使用service綁定這3個pod
[root@master manifests]# vim myapp-svc.yaml apiVersion: apps/v1 kind: Deployment metadata: name: myapp-deploy namespace: default spec: replicas: 3 selector: matchLabels: app: myapp release: canary template: metadata: labels: app: myapp release: canary spec: containers: - name: myapp image: ikubernetes/myapp:v1 ports: - name: http containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: myapp namespace: default spec: selector: app: myapp release: canary clusterIP: 10.97.97.97 type: NodePort ports: - port: 80 targetPort: 80 nodePort: 31180 [root@master manifests]# kubectl apply -f myapp-svc.yaml deployment.apps/myapp-deploy unchanged service/myapp created
(2)查詢驗證
[root@master ~]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 145d myapp NodePort 10.97.97.97 <none> 80:31180/TCP 39s redis ClusterIP 10.99.99.99 <none> 6380/TCP 2d [root@master ~]# kubectl describe svc myapp Name: myapp Namespace: default Labels: <none> Annotations: kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"myapp","namespace":"default"},"spec":{"clusterIP":"10.97.97.97","ports":[{"nod... Selector: app=myapp,release=canary Type: NodePort IP: 10.97.97.97 Port: <unset> 80/TCP TargetPort: 80/TCP NodePort: <unset> 31180/TCP Endpoints: 10.244.1.96:80,10.244.2.101:80,10.244.2.102:80 Session Affinity: None External Traffic Policy: Cluster Events: <none>
(3)在集群外訪問服務
3.2 使用sessionAffinity保持會話連接
(1)sessionAffinity默認是None,沒有修改前,訪問業務是隨機調度
[root@master ~]# while true; do curl 192.168.10.103:31180/hostname.html; sleep 1; done myapp-deploy-69b47bc96d-mmb5v myapp-deploy-69b47bc96d-wtbx7 myapp-deploy-69b47bc96d-wtbx7 myapp-deploy-69b47bc96d-cj48v ... ...
(2)打補丁修改sessionAffinity為clientip;實現會話連接
也可以使用exec修改;或者直接修改yaml文件也可以;
[root@master ~]# kubectl patch svc myapp -p '{"spec":{"sessionAffinity":"ClientIP"}}' service/myapp patched
(3)查詢驗證
[root@master ~]# kubectl describe svc myapp Name: myapp Namespace: default Labels: <none> Annotations: kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"myapp","namespace":"default"},"spec":{"clusterIP":"10.97.97.97","ports":[{"nod... Selector: app=myapp,release=canary Type: NodePort IP: 10.97.97.97 Port: <unset> 80/TCP TargetPort: 80/TCP NodePort: <unset> 31180/TCP Endpoints: 10.244.1.96:80,10.244.2.101:80,10.244.2.102:80 Session Affinity: ClientIP External Traffic Policy: Cluster Events: <none>
(4)訪問業務查詢驗證;發現同一客戶端的請求始終發往同一pod
[root@master ~]# while true; do curl 192.168.10.103:31180/hostname.html; sleep 1; done myapp-deploy-69b47bc96d-cj48v myapp-deploy-69b47bc96d-cj48v myapp-deploy-69b47bc96d-cj48v myapp-deploy-69b47bc96d-cj48v ... ...
(5)重新打補丁修改為None,立即恢復為隨機調度
[root@master ~]# kubectl patch svc myapp -p '{"spec":{"sessionAffinity":"None"}}' service/myapp patched [root@master ~]# while true; do curl 192.168.10.103:31180/hostname.html; sleep 1; done myapp-deploy-69b47bc96d-cj48v myapp-deploy-69b47bc96d-mmb5v myapp-deploy-69b47bc96d-cj48v myapp-deploy-69b47bc96d-mmb5v
4、創建無頭service
(1)編寫yaml文件並創建名為myapp-svc的service
綁定上面創建myapp的3個pod
[root@master manifests]# vim myapp-svc-headless.yaml apiVersion: v1 kind: Service metadata: name: myapp-svc namespace: default spec: selector: app: myapp release: canary clusterIP: None ports: - port: 80 targetPort: 80 [root@master manifests]# kubectl apply -f myapp-svc-headless.yaml service/myapp-svc created
(2)查詢驗證
[root@master ~]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 145d myapp NodePort 10.97.97.97 <none> 80:31180/TCP 2h myapp-svc ClusterIP None <none> 80/TCP 6s redis ClusterIP 10.99.99.99 <none> 6380/TCP 2d
(3)和有頭正常myapp的service對比
無頭service的解析:
[root@master manifests]# dig -t A myapp-svc.default.svc.cluster.local. @10.96.0.10 ... ... ;; ANSWER SECTION: myapp-svc.default.svc.cluster.local. 5 IN A 10.244.1.96 myapp-svc.default.svc.cluster.local. 5 IN A 10.244.2.101 myapp-svc.default.svc.cluster.local. 5 IN A 10.244.2.102 ... ...
有頭正常myapp的service的解析:
[root@master manifests]# dig -t A myapp.default.svc.cluster.local. @10.96.0.10 ... ... ;; ANSWER SECTION: myapp.default.svc.cluster.local. 5 IN A 10.97.97.97 ... ...