Service詳解

羅家龍 發表於 2022-12-04

Service詳解

Service介紹

在kubernetes中,pod是應用程式的載體,我們可以透過pod的ip來訪問應用程式,但是pod的ip地址不是固定的,這也就意味著不方便直接採用pod的ip對服務進行訪問。
為了解決這個問題,kubernetes提供了Service資源,Service會對提供同一個服務的多個pod進行聚合,並且提供一個統一的入口地址。透過訪問Service的入口地址就能訪問到後面的pod服務。
Service詳解

Service在很多情況下只是一個概念,真正起作用的其實是kube-proxy服務程式,每個Node節點上都執行著一個kube-proxy服務程式。當建立Service的時候會透過api-server向etcd寫入建立的service的資訊,而kube-proxy會基於監聽的機制發現這種Service的變動,然後它會將最新的Service資訊轉換成對應的訪問規則。
Service詳解

# 10.97.97.97:80 是service提供的訪問入口# 當訪問這個入口的時候,可以發現後面有三個pod的服務在等待呼叫,# kube-proxy會基於rr(輪詢)的策略,將請求分發到其中一個pod上去# 這個規則會同時在叢集內的所有節點上都生成,所以在任何一個節點上訪問都可以。
[[email protected] ~]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConnTCP  10.97.97.97:80 rr
  -> 10.244.1.39:80               Masq    1      0          0
  -> 10.244.1.40:80               Masq    1      0          0
  -> 10.244.2.33:80               Masq    1      0          0
//檢視iptables防火牆規則
[[email protected] ~]# iptables -t nat -nvL

kube-proxy目前支援三種工作模式:

userspace 模式

userspace模式下,kube-proxy會為每一個Service建立一個監聽埠,發向Cluster IP的請求被Iptables規則重定向到kube-proxy監聽的埠上,kube-proxy根據LB演算法選擇一個提供服務的Pod並和其建立連結,以將請求轉發到Pod上。 該模式下,kube-proxy充當了一個四層負責均衡器的角色。由於kube-proxy執行在userspace中,在進行轉發處理時會增加核心和使用者空間之間的資料複製,雖然比較穩定,但是效率比較低。
Service詳解

iptables 模式

iptables模式下,kube-proxy為service後端的每個Pod建立對應的iptables規則,直接將發向Cluster IP的請求重定向到一個Pod IP。 該模式下kube-proxy不承擔四層負責均衡器的角色,只負責建立iptables規則。該模式的優點是較userspace模式效率更高,但不能提供靈活的LB策略,當後端Pod不可用時也無法進行重試。
Service詳解

ipvs 模式

ipvs模式和iptables類似,kube-proxy監控Pod的變化並建立相應的ipvs規則。ipvs相對iptables轉發效率更高。除此以外,ipvs支援更多的LB演算法。
Service詳解

# 此模式必須安裝ipvs核心模組,否則會降級為iptables# 開啟ipvs
//使用kubectl  api-resources可以檢視cm是什麼?
[[email protected] ~]# kubectl edit cm kube-proxy -n kube-system
# 修改mode: "ipvs"
[[email protected] ~]# kubectl delete pod -l k8s-app=kube-proxy -n kube-system
[[email protected] ~]# ipvsadm -LnIP Virtual Server version 1.2.1 (size=4096)Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConnTCP  10.97.97.97:80 rr
  -> 10.244.1.39:80               Masq    1      0          0
  -> 10.244.1.40:80               Masq    1      0          0
  -> 10.244.2.33:80               Masq    1      0          0

Service型別

Service的資源清單檔案:

kind: Service  # 資源型別
apiVersion: v1  # 資源版本
metadata: # 後設資料
  name: service # 資源名稱
  namespace: dev # 名稱空間
spec: # 描述
  selector: # 標籤選擇器,用於確定當前service代理哪些pod
    app: nginx
  type: # Service型別,指定service的訪問方式
  clusterIP:  # 虛擬服務的ip地址
  sessionAffinity: # session親和性,支援ClientIP、None兩個選項
  ports: # 埠資訊
    - protocol: TCP 
      port: 3017  # service埠
      targetPort: 5003 # pod埠
      nodePort: 31122 # 主機埠
  • ClusterIP:預設值,它是Kubernetes系統自動分配的虛擬IP,只能在叢集內部訪問
  • NodePort:將Service透過指定的Node上的埠暴露給外部,透過此方法,就可以在叢集外部訪問服務
  • LoadBalancer:使用外接負載均衡器完成到服務的負載分發,注意此模式需要外部雲環境支援
  • ExternalName: 把叢集外部的服務引入叢集內部,直接使用

Service使用

實驗環境準備

在使用service之前,首先利用Deployment建立出3個pod,注意要為pod設定app=nginx-pod的標籤
建立deployment.yaml,內容如下:

apiVersion: apps/v1
kind: Deployment      
metadata:
  name: pc-deployment
  namespace: dev
spec: 
  replicas: 3
  selector:
    matchLabels:
      app: nginx-pod
  template:
    metadata:
      labels:
        app: nginx-pod
    spec:
      containers:
      - name: nginx
        image: nginx:1.17.1
        ports:
        - containerPort: 80
[[email protected] ~]# kubectl create -f deployment.yaml
deployment.apps/pc-deployment created
# 檢視pod詳情
[[email protected] ~]# kubectl get pods -n dev -o wide --show-labels
NAME                             READY   STATUS     IP            NODE     LABELS
pc-deployment-66cb59b984-8p84h   1/1     Running    10.244.1.39   node1    app=nginx-pod
pc-deployment-66cb59b984-vx8vx   1/1     Running    10.244.2.33   node2    app=nginx-pod
pc-deployment-66cb59b984-wnncx   1/1     Running    10.244.1.40   node1    app=nginx-pod
# 為了方便後面的測試,修改下三臺nginx的index.html頁面(三臺修改的IP地址不一致)# kubectl exec -it pc-deployment-66cb59b984-8p84h -n dev /bin/sh# echo "10.244.1.39" > /usr/share/nginx/html/index.html
#修改完畢之後,訪問測試
[[email protected] ~]# curl 10.244.1.3910.244.1.39
[[email protected] ~]# curl 10.244.2.3310.244.2.33
[[email protected] ~]# curl 10.244.1.4010.244.1.40

ClusterIP型別的Service

建立service-clusterip.yaml檔案

apiVersion: v1
kind: Service
metadata:
  name: service-clusterip
  namespace: dev
spec:
  selector:
    app: nginx-pod
  clusterIP: 10.97.97.97 # service的ip地址,如果不寫,預設會生成一個
  type: ClusterIP
  ports:
  - port: 80  # Service埠       
    targetPort: 80 # pod埠
# 建立service
[[email protected] ~]# kubectl create -f service-clusterip.yaml
service/service-clusterip created
# 檢視service
[[email protected] ~]# kubectl get svc -n dev -o wide
NAME                TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE   SELECTOR
service-clusterip   ClusterIP   10.97.97.97   <none>        80/TCP    13s   app=nginx-pod
# 檢視service的詳細資訊# 在這裡有一個Endpoints列表,裡面就是當前service可以負載到的服務入口
[[email protected] ~]# kubectl describe svc service-clusterip -n dev
Name:              service-clusterip
Namespace:         dev
Labels:            <none>
Annotations:       <none>
Selector:          app=nginx-pod
Type:              ClusterIP
IP:                10.97.97.97
Port:              <unset>  80/TCP
TargetPort:        80/TCP
Endpoints:         10.244.1.39:80,10.244.1.40:80,10.244.2.33:80
Session Affinity:  NoneEvents:            <none>
# 檢視ipvs的對映規則
[[email protected] ~]# ipvsadm -Ln
TCP  10.97.97.97:80 rr
  -> 10.244.1.39:80               Masq    1      0          0
  -> 10.244.1.40:80               Masq    1      0          0
  -> 10.244.2.33:80               Masq    1      0          0
# 訪問10.97.97.97:80觀察效果
[[email protected] ~]# curl 10.97.97.97:8010.244.2.33

Endpoint

Endpoint是kubernetes中的一個資源物件,儲存在etcd中,用來記錄一個service對應的所有pod的訪問地址,它是根據service配置檔案中selector描述產生的。
一個Service由一組Pod組成,這些Pod透過Endpoints暴露出來,Endpoints是實現實際服務的端點集合。換句話說,service和pod之間的聯絡是透過endpoints實現的。
Service詳解

負載分發策略

對Service的訪問被分發到了後端的Pod上去,目前kubernetes提供了兩種負載分發策略:
如果不定義,預設使用kube-proxy的策略,比如隨機、輪詢
基於客戶端地址的會話保持模式,即來自同一個客戶端發起的所有請求都會轉發到固定的一個Pod上
此模式可以使在spec中新增sessionAffinity:ClientIP選項

# 檢視ipvs的對映規則【rr 輪詢】
[[email protected] ~]# ipvsadm -Ln
TCP  10.97.97.97:80 rr
  -> 10.244.1.39:80               Masq    1      0          0
  -> 10.244.1.40:80               Masq    1      0          0
  -> 10.244.2.33:80               Masq    1      0          0
# 迴圈訪問測試
[[email protected] ~]# while true;do curl 10.97.97.97:80; sleep 5; done;
10.244.1.4010.244.1.3910.244.2.3310.244.1.4010.244.1.3910.244.2.33
# 修改分發策略----sessionAffinity:ClientIP
# 檢視ipvs規則【persistent 代表持久】
[[email protected] ~]# ipvsadm -Ln
TCP  10.97.97.97:80 rr persistent 10800
  -> 10.244.1.39:80               Masq    1      0          0
  -> 10.244.1.40:80               Masq    1      0          0
  -> 10.244.2.33:80               Masq    1      0          0
# 迴圈訪問測試
[[email protected] ~]# while true;do curl 10.97.97.97; sleep 5; done;10.244.2.3310.244.2.3310.244.2.33
  # 刪除service
[[email protected] ~]# kubectl delete -f service-clusterip.yaml
service "service-clusterip" deleted

HeadLiness型別的Service

在某些場景中,開發人員可能不想使用Service提供的負載均衡功能,而希望自己來控制負載均衡策略,針對這種情況,kubernetes提供了HeadLiness Service,這類Service不會分配Cluster IP,如果想要訪問service,只能透過service的域名進行查詢。
建立service-headliness.yaml

apiVersion: v1
kind: Servicemetadata:
  name: service-headliness
  namespace: devspec:
  selector:
    app: nginx-pod
  clusterIP: None # 將clusterIP設定為None,即可建立headliness Service
  type: ClusterIP
  ports:
  - port: 80    
    targetPort: 80
# 建立service
[[email protected] ~]# kubectl create -f service-headliness.yaml
service/service-headliness created
# 獲取service, 發現CLUSTER-IP未分配
[[email protected] ~]# kubectl get svc service-headliness -n dev -o wide
NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE   SELECTOR
service-headliness   ClusterIP   None         <none>        80/TCP    11s   app=nginx-pod
# 檢視service詳情
[[email protected] ~]# kubectl describe svc service-headliness  -n dev
Name:              service-headliness
Namespace:         dev
Labels:            <none>
Annotations:       <none>
Selector:          app=nginx-pod
Type:              ClusterIP
IP:                None
Port:              <unset>  80/TCP
TargetPort:        80/TCP
Endpoints:         10.244.1.39:80,10.244.1.40:80,10.244.2.33:80
Session Affinity:  None
Events:            <none>
# 檢視域名的解析情況
[[email protected] ~]# kubectl exec -it pc-deployment-66cb59b984-8p84h -n dev /bin/sh
/ # cat /etc/resolv.conf
nameserver 10.96.0.10
search dev.svc.cluster.local svc.cluster.local cluster.local

[[email protected] ~]# dig @10.96.0.10 service-headliness.dev.svc.cluster.local
service-headliness.dev.svc.cluster.local. 30 IN A 10.244.1.40
service-headliness.dev.svc.cluster.local. 30 IN A 10.244.1.39
service-headliness.dev.svc.cluster.local. 30 IN A 10.244.2.33

NodePort型別的Service

在之前的樣例中,建立的Service的ip地址只有叢集內部才可以訪問,如果希望將Service暴露給叢集外部使用,那麼就要使用到另外一種型別的Service,稱為NodePort型別。NodePort的工作原理其實就是將service的埠對映到Node的一個埠上,然後就可以透過NodeIp:NodePort來訪問service了。
Service詳解

建立service-nodeport.yaml

apiVersion: v1
kind: Servicemetadata:
  name: service-nodeport
  namespace: devspec:
  selector:
    app: nginx-pod
  type: NodePort # service型別
  ports:
  - port: 80
    nodePort: 30002 # 指定繫結的node的埠(預設的取值範圍是:30000-32767), 如果不指定,會預設分配
    targetPort: 80
# 建立service
[[email protected] ~]# kubectl create -f service-nodeport.yaml
service/service-nodeport created
# 檢視service
[[email protected] ~]# kubectl get svc -n dev -o wide
NAME               TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)       SELECTOR
service-nodeport   NodePort   10.105.64.191   <none>        80:30002/TCP  app=nginx-pod
# 接下來可以透過電腦主機的瀏覽器去訪問叢集中任意一個nodeip的30002埠,即可訪問到pod

LoadBalancer型別的Service

LoadBalancer和NodePort很相似,目的都是向外部暴露一個埠,區別在於LoadBalancer會在叢集的外部再來做一個負載均衡裝置,而這個裝置需要外部環境支援的,外部服務傳送到這個裝置上的請求,會被裝置負載之後轉發到叢集中。
Service詳解

ExternalName型別的Service

ExternalName型別的Service用於引入叢集外部的服務,它透過externalName屬性指定外部一個服務的地址,然後在叢集內部訪問此service就可以訪問到外部的服務了。
Service詳解

apiVersion: v1
kind: Servicemetadata:
  name: service-externalname
  namespace: devspec:
  type: ExternalName # service型別
  externalName: www.baidu.com  #改成ip地址也可以
# 建立service
[[email protected] ~]# kubectl  create -f service-externalname.yaml
service/service-externalname created
# 域名解析
[[email protected] ~]# dig @10.96.0.10 service-externalname.dev.svc.cluster.local
service-externalname.dev.svc.cluster.local. 30 IN CNAME www.baidu.com.
www.baidu.com.          30      IN      CNAME   www.a.shifen.com.
www.a.shifen.com.       30      IN      A       39.156.66.18
www.a.shifen.com.       30      IN      A       39.156.66.14

Ingress介紹

在前面課程中已經提到,Service對叢集之外暴露服務的主要方式有兩種:NotePort和LoadBalancer,但是這兩種方式,都有一定的缺點:

  • NodePort方式的缺點是會佔用很多叢集機器的埠,那麼當叢集服務變多的時候,這個缺點就愈發明顯
  • LB方式的缺點是每個service需要一個LB,浪費、麻煩,並且需要kubernetes之外裝置的支援
    基於這種現狀,kubernetes提供了Ingress資源物件,Ingress只需要一個NodePort或者一個LB就可以滿足暴露多個Service的需求。工作機制大致如下圖表示:
    Service詳解

實際上,Ingress相當於一個7層的負載均衡器,是kubernetes對反向代理的一個抽象,它的工作原理類似於Nginx,可以理解成在Ingress裡建立諸多對映規則,Ingress Controller透過監聽這些配置規則並轉化成Nginx的反向代理配置 , 然後對外部提供服務。在這裡有兩個核心概念:

  • ingress:kubernetes中的一個物件,作用是定義請求如何轉發到service的規則
  • ingress controller:具體實現反向代理及負載均衡的程式,對ingress定義的規則進行解析,根據配置的規則來實現請求轉發,實現方式有很多,比如Nginx, Contour, Haproxy等等
    Ingress(以Nginx為例)的工作原理如下:
    1.使用者編寫Ingress規則,說明哪個域名對應kubernetes叢集中的哪個Service
    2.Ingress控制器動態感知Ingress服務規則的變化,然後生成一段對應的Nginx反向代理配置
    3.Ingress控制器會將生成的Nginx配置寫入到一個執行著的Nginx服務中,並動態更新
    4.到此為止,其實真正在工作的就是一個Nginx了,內部配置了使用者定義的請求轉發規則
    Service詳解

Ingress使用

環境準備

搭建ingress環境

# 建立資料夾
[[email protected] ~]# mkdir ingress-controller
[[email protected] ~]# cd ingress-controller/
# 獲取ingress-nginx,本次案例使用的是0.30版本
[[email protected] ingress-controller]# wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/mandatory.yaml
[[email protected] ingress-controller]# wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/provider/baremetal/service-nodeport.yaml
# 修改mandatory.yaml檔案中的倉庫# 修改quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.30.0# 為quay-mirror.qiniu.com/kubernetes-ingress-controller/nginx-ingress-controller:0.30.0# 建立ingress-nginx
[[email protected] ingress-controller]# kubectl apply -f ./
# 檢視ingress-nginx
[[email protected] ingress-controller]# kubectl get pod -n ingress-nginx
NAME                                           READY   STATUS    RESTARTS   AGE
pod/nginx-ingress-controller-fbf967dd5-4qpbp   1/1     Running   0          12h
# 檢視service
[[email protected] ingress-controller]# kubectl get svc -n ingress-nginx
NAME            TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx   NodePort   10.98.75.163   <none>        80:32240/TCP,443:31335/TCP   11h

準備service和pod
為了後面的實驗比較方便,建立如下圖所示的模型
Service詳解

建立tomcat-nginx.yaml

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

apiVersion: apps/v1
kind: Deployment
metadata:
  name: tomcat-deployment
  namespace: dev
spec:
  replicas: 3
  selector:
    matchLabels:
      app: tomcat-pod
  template:
    metadata:
      labels:
        app: tomcat-pod
    spec:
      containers:
      - name: tomcat
        image: tomcat:8.5-jre10-slim
        ports:
        - containerPort: 8080
---

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  namespace: dev
spec:
  selector:
    app: nginx-pod
  clusterIP: None
  type: ClusterIP
  ports:
  - port: 80
    targetPort: 80
---

apiVersion: v1
kind: Service
metadata:
  name: tomcat-service
  namespace: dev
spec:
  selector:
    app: tomcat-pod
  clusterIP: None
  type: ClusterIP
  ports:
  - port: 8080
    targetPort: 8080
# 建立
[[email protected] ~]# kubectl create -f tomcat-nginx.yaml
# 檢視
[[email protected] ~]# kubectl get svc -n dev
NAME             TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
nginx-service    ClusterIP   None         <none>        80/TCP     48s
tomcat-service   ClusterIP   None         <none>        8080/TCP   48s

Http代理

建立ingress-http.yaml

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-http
  namespace: dev
spec:
  ingressClassName: nginx
  rules:
  - host: nginx.itwangqing.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: nginx-service
            port: 80
  - host: tomcat.itwangqing.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: tomcat-service
            port: 8080
# 建立
[[email protected] ~]# kubectl create -f ingress-http.yaml
ingress.extensions/ingress-http created
# 檢視
[[email protected] ~]# kubectl get ing ingress-http -n dev
NAME           HOSTS                                  ADDRESS   PORTS   AGE
ingress-http   nginx.itwangqing.com,tomcat.itwangqing.com             80      22s
# 檢視詳情
[[email protected] ~]# kubectl describe ing ingress-http  -n dev
...Rules:
Host                Path  Backends
----                ----  --------
nginx.itwangqing.com   / nginx-service:80 (10.244.1.96:80,10.244.1.97:80,10.244.2.112:80)
tomcat.itwangqing.com  / tomcat-service:8080(10.244.1.94:8080,10.244.1.95:8080,10.244.2.111:8080)
...
# 接下來,在本地電腦上配置host檔案,解析上面的兩個域名到192.168.109.100(master)上# 然後,就可以分別訪問tomcat.itwangqing.com:32240  和  nginx.itwangqing.com:32240 檢視效果了

Https代理

建立證書

# 生成證書
openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/C=CN/ST=BJ/L=BJ/O=nginx/CN=itwangqing.com"
# 建立金鑰
kubectl create secret tls tls-secret --key tls.key --cert tls.crt

建立ingress-https.yaml

apiVersion: extensions/v1beta1
kind: Ingressmetadata:
  name: ingress-https
  namespace: devspec:
  tls:
    - hosts:
      - nginx.itwangqing.com
      - tomcat.itwangqing.com
      secretName: tls-secret # 指定秘鑰
  rules:
  - host: nginx.itwangqing.com
    http:
      paths:
      - path: /
        backend:
          serviceName: nginx-service
          servicePort: 80
  - host: tomcat.itwangqing.com
    http:
      paths:
      - path: /
        backend:
          serviceName: tomcat-service
          servicePort: 8080
# 建立
[[email protected] ~]# kubectl create -f ingress-https.yaml
ingress.extensions/ingress-https created
# 檢視
[[email protected] ~]# kubectl get ing ingress-https -n dev
NAME            HOSTS                                  ADDRESS         PORTS     AGE
ingress-https   nginx.itwangqing.com,tomcat.itwangqing.com   10.104.184.38   80, 443   2m42s
# 檢視詳情
[[email protected] ~]# kubectl describe ing ingress-https -n dev
...TLS:
  tls-secret terminates nginx.itwangqing.com,tomcat.itwangqing.comRules:
Host              Path Backends
----              ---- --------
nginx.itwangqing.com  /  nginx-service:80 (10.244.1.97:80,10.244.1.98:80,10.244.2.119:80)
tomcat.itwangqing.com /  tomcat-service:8080(10.244.1.99:8080,10.244.2.117:8080,10.244.2.120:8080)
...
# 下面可以透過瀏覽器訪問https://nginx.itwangqing.com:31335 和 https://tomcat.itwangqing.com:31335來檢視了