kubernetes系列08—service資源詳解
本文收錄在 容器技術學習系列文章總目錄
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 NAMETYPECLUSTER-IPEXTERNAL-IPPORT(S)AGE kubernetesClusterIP10.96.0.1<none>443/TCP142d redisClusterIP10.99.99.99<none>6380/TCP12s ---查詢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 NAMETYPECLUSTER-IPEXTERNAL-IPPORT(S)AGE kubernetesClusterIP10.96.0.1<none>443/TCP145d myappNodePort10.97.97.97<none>80:31180/TCP39s redisClusterIP10.99.99.99<none>6380/TCP2d [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 NAMETYPECLUSTER-IPEXTERNAL-IPPORT(S)AGE kubernetesClusterIP10.96.0.1<none>443/TCP145d myappNodePort10.97.97.97<none>80:31180/TCP2h myapp-svcClusterIPNone<none>80/TCP6s redisClusterIP10.99.99.99<none>6380/TCP2d
( 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 A10.244.1.96 myapp-svc.default.svc.cluster.local. 5 IN A10.244.2.101 myapp-svc.default.svc.cluster.local. 5 IN A10.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 INA10.97.97.97 ... ...