五、Service詳解


### 一、Service簡介

1.1 service作用

作用:

  • 使集群內部能訪問pod,或者集群外訪問pod

  • 用於pod的服務發現與負載均衡(TCP/UDP 4層)

  • 通過selector指定某一類pod的標簽相關聯pod

  • 底層原理是通過iptables和IPVS二種網絡模式來實現的服務發現跟負載均衡

為什么要用到service,因為Pod是不穩定的,隨時可能停止在被控制器拉起,這樣ip就會發生變化,所以需要service提供一個固定的ip來訪問這些pod

pod的服務發現:

通過deployment或者replicas控制器創建的pod,指定了pod的副本個數,當pod的副本個數擴容或裁剪的時候,service是自動發現新增的pod並給他流量,自動發現裁剪的pod,不再給這類下線的pod流量,也就訪問不到下線的pod了。

負載均衡:

使用service關聯多個pod,流量是負載均衡到每個pod上的。

底層原理:

Service工作模式有三種:userspace(k8s 1.1版本之前使用),iptables(k8s 1.10版本之前使用),ipvs(k8s 1.11版本之后一直默認使用)

目前k8s默認使用ipvs模式來轉發流量的,Service也是通過這種模式來找pod的。

參考資料:Service三種工作模式

Iptables實現流量轉發

image-20211120192517593

可使用命令查看規則

iptables -L -n #查看

ipvs實現流量轉發

image-20211120192923243

可使用命令查看規則

ipvsadm -ln

參考資料:iptables跟ipvs區別

1.2 service類型

service類型分為:

  • ClusterIP

​ 默認,分配一個只有集群內部節點可以訪問的虛擬IP,集群內部節點指的是master跟node節點

  • NodePort

​ 在每個集群節點上分配一個端口作為外部訪問入口,集群外部節點也能訪問

  • LoadBalancer

​ 負載均衡器,工作在特定的Cloud Provider(雲平台)上,例如Google Cloud,AWS,OpenStack

  • ExternalName

​ 表示把集群外部的服務引入到集群內部中來,即實現了集群內部pod和集群外部的服務進行通信;也可用於不同命名空間之間的通信。

參考資料:LoadBalancer詳解ExternalName詳解

補充:

ClusterIP根據是否生成ClusterIP又可分為普通Service和Headless Service(無頭服務)

  • 普通Service:

    為Kubernetes的Service分配一個集群內部可訪問的固定的虛擬IP(Cluster IP), 實現集群內的訪問。

  • Headless Service: (無頭服務)

    該服務不會分配Cluster IP, 也不通過kube-proxy做反向代理和負載均衡。而是通過DNS為每個pod提供一個域名,這樣我們能訪問到每個單獨的pod。

參考資料:Headless Service詳解

二、使用Service

2.1 ClusterIP類型

集群IP模式,只能在集群內部進行訪問,如master跟node節點上,將某一類相同標簽的pod映射出來,分配一個service的ip來進行訪問。

如下圖所示,使用Deployment資源創建了3個pod,將這三個pod的80端口映射為8000端口,並提供一個Service的ip來訪問這些pod,這時我們在集群中的任意節點(即Master或者Node節點上)通過Service的IP加端口就能負載均衡訪問到這3個pod。

image-20211120171542597

下面我們來創建一個該類型的Service服務體驗一下。

1.使用命令創建方式

定義一個名為my-dep的Deployment資源,里面包含有3個pod副本,每個pod中含有一個nginx容器

kubectl create deployment my-dep --image=nginx --replicas=3
kubectl get deploy

創建Service,默認就是--type=ClusterIP模式,這里也可以不寫--type=ClusterIP

kubectl expose deploy my-dep --port=8000 --target-port=80 --type=ClusterIP --protocol=TCP

查看當前的service

 kubectl get services
 kubectl get svc

可以看到該Service的集群IP為10.96.100.203,暴露的端口為8000,我們在任意集群節點上都可訪問curl http://10.96.100.203:8000 負載均衡訪問到3個pod中。

什么,你還不信是負載均衡訪問到后面的pod的?那么我們分別修改這3個pod中的index.html文件

#查看創建的pod名
kubectl get pods

#依次修改每個pod的html頁面,注意進入容器后使用ctrl+d退出容器
kubectl exec -it my-dep-5b7868d854-bntks -- /bin/bash
echo 'this is 1 page' > /usr/share/nginx/html/index.html

#修改第二個pod
...
echo 'this is 2 page' > /usr/share/nginx/html/index.html

#修改第三個pod
...
echo 'this is 3 page' > /usr/share/nginx/html/index.html

#訪問Service服務
curl http://10.96.100.203:8000

image-20211121152315038

2.使用yaml文件創建方式

1.創建deployment的yaml文件

[root@k8s-master01 ~]# vim nginx-dep.yml
apiVersion: apps/v1  #版本信息使用kubectl explain deployment查看
kind: Deployment
metadata:
  name: nginx-dep

spec:               
  replicas: 2     #定義2個pod副本             
  selector:
    matchLabels:
      app: nginx-dep  #匹配的pod標簽名
      
  template:               
    metadata:
      name: nginx          
      labels:
        app: nginx-dep         # pod的標簽名,要跟matchLabels里定義的一致
    spec:
      containers:              
      - name: nginx          
        image: nginx:1.15-alpine
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80

#應用yaml清單
kubectl apply -f nginx-dep.yml

2.創建Service的yaml文件

這里關聯的是標簽名為nginx-dep的pod

[root@k8s-master01 ~]# vim nginx-svc.yml
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
  namespace: default
  labels:
    app: nginx-svc
    
spec:
  clusterIP: 10.96.139.8     #指定ClusterIP,不寫此行會自動分配一個Service的ip
  type: ClusterIP   #可以不寫此行,默認為ClusterIP模式
  selector:
    app: nginx-dep #關聯的pod的標簽名
  ports:
  - port: 8000
    protocol: TCP
    targetPort: 80
    
#應用yaml清單
kubectl apply -f nginx-svc.yml

查看Pod的標簽名

kubectl get pods --show-labels
kubectl get pod -l app=nginx-dep

3.查看service,跟deployment,endpoints節點信息

kubectl get svc,deploy,ep

image-20211121171454976

這里的endpoints指的是pod,可以查看pod的IP地址

導出yaml文件

kubectl get svc nginx-svc -o yaml> nginx-svc.yaml

編輯修改資源

kubectl edit svc nginx-svc

Service服務發現

關於Service的服務發現,我們來做個實驗。

這里就着前面搞的那個deloyment資源,原來是3個pod副本,我們改成2個副本

kubectl get deploy

#編輯my-dep資源
kubectl edit deploy my-dep
...
replicas: 2 #修改副本數為2
...

#訪問Service服務
curl http://10.96.100.203:8000

這時發現還是能正常訪問Service服務,這就是Service的服務發現功能,自動感知pod擴容和裁剪,無需我們人工操作

使用Service名訪問

前面我們是通過訪問service的IP發現是可以負載均衡到后面的pod上的,我們也可以在容器內部訪問service的名字達到一樣的效果。

kubectl get svc #得到Service名字my-dep
kubectl get pods #查看pod
kubectl exec -it my-dep-5b7868d854-crh94 -- /bin/bash #進入容器
curl my-dep.default.svc:8000
curl my-dep:8000

image-20211121161100466

此時的my-dep.default.svc:8000中的my-dep為服務名,default為命名空間,svc表示service,可簡寫為my-dep:8000,需要進入容器中訪問才能用這種方式,因為有coredns解析,但在集群節點上無法使用,集群節點只能用Service_ip:8000的方式訪問,因為沒有dns解析

2.2 NodePort類型

在每台集群節點上都映射端口,這樣訪問集群任意節點的端口就能訪問到pod提供的服務,這就不限於集群中了,集群外也能訪問。

下面我們來創建一個該類型的Service服務體驗一下。

1.使用命令創建方式

定義一個名為my-dep的Deployment資源,里面包含有3個pod副本,每個pod中含有一個nginx容器。

kubectl create deployment my-dep --image=nginx --replicas=3
kubectl get deploy

創建Service,指定類型為NodePort

kubectl expose deploy my-dep --port=8000 --target-port=80 --type=NodePort --protocol=TCP

查看當前的service

 kubectl get services
 kubectl get svc

這里可以看到名為my-dep的Service使用了10.96.27.163的集群IP,暴露了一個8000端口,用於集群內部訪問;還有一個38780端口,可用於集群外部訪問,這個38780端口是在每個集群節點上都開了

訪問

#在集群內部訪問
curl 10.96.27.163:8000

#10.154.0.111是我的master的IP地址,其他兩台是我的node節點
#在集群外部使用瀏覽器訪問http://10.154.0.111:38780
#在集群外部使用瀏覽器訪問http://10.154.0.112:38780
#在集群外部使用瀏覽器訪問http://10.154.0.113:38780

注意,這個38780端口在任何一台集群節點上都開放了,該端口是隨機暴露的,范圍在30000-32767之間

如果覺得訪問集群節點加端口的方式不很方便,可以使用Nginx做負載均衡,或者Ingress控制器做負載均衡。

2.使用yaml文件創建方式

這里使用我們之前創建的Deployment關聯的那2個pod,對了,每個pod可以關聯多個Service,名為nginx-dep的pod之前關聯了ClusterIP類型的Service,這里我們繼續用它關聯NodePort類型的Service。

1.創建NodePort的Service

[root@k8s-master01 ~]# vim nginx-svc2.yml
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc2
  namespace: default
  labels:
    app: nginx-svc2
    
spec:
  type: NodePort   #這里改為NodePort模式
  selector:
    app: nginx-dep #關聯的pod的標簽名
  ports:
  - port: 8000
    protocol: TCP
    targetPort: 80
    
#應用yaml清單
kubectl apply -f nginx-svc2.yml

2.查看service,跟deployment,endpoints節點信息

kubectl get svc,deploy,ep

image-20211122235313577

如果不想使用集群IP加端口的方式訪問,可以部署一台Nginx作為負載均衡器,這樣能更加規范化。

補充:

我們也可以編輯之前ClusterIP類型創建的名為my-dep的Service,將其改為ClusterIP模式

#編輯名為nginx-svc的Service
kubectl edit service nginx-svc
type: NodePort #修改type為NodePort

#或者
kubectl patch service nginx-svc -p '{"spec": {"type":"NodePort"}}'

2.3 loadbalancer類型

使用雲廠商的提供的負載均衡器,關聯到 loadbalancer類型的Service,這樣我們就能在集群外訪問雲服務廠商的LB,訪問到我們的pod服務了。

訪問流程:用戶-->域名-->雲服務提供端提供LB-->NodeIP:Port(serviceIP)-->Pod IP:端口

image-20211115103559869

具體方式就是修改Service的type: LoadBalancer,然后在雲平台上創建一個LB,關聯到該Service即可,我沒有使用過廠商的雲平台,這里可以自行查看文檔,應該很簡單的。

參考資料:騰訊雲LoadBalancer華為雲LoadBalancer

2.4 ExternalName類型

ExternalName類型的Service,就是將該Service名跟集群外部服務地址做一個映射,使之訪問Service名稱就是訪問外部服務。

這里使用兩個案例來介紹,一個是綁定外部域名,也可以綁定其他命名空間的域名。

1.綁定集群外部域名

把外部的服務綁定到集群內部的ExternalName類型的Service上,這樣訪問該Service名就能訪問到外部服務,先來看一個小例子

1.先創建一個帶有curl命令的busybox的pod

[root@k8s-master01 ~]# vim busybox.yml
apiVersion: v1
kind: Pod
metadata:
  name: busybox
spec:
  containers:
  - name: liveness
    image: yauritux/busybox-curl
    imagePullPolicy: IfNotPresent
    args:
    - /bin/sh
    - -c
    - sleep 30000000

2.再創建一個ExternalName類型的Service,綁定的是外部域名www.k8sec.com

[root@k8s-master01 ~]# vim externelname.yml
apiVersion: v1
kind: Service
metadata:
  name: k8sec        
  namespace: default
spec:
  type: ExternalName
  externalName: www.k8sec.com    # 對應的外部域名為www.k8sec.com
  
#應用YAML文件
kubectl apply -f externelname.yml

3.查看Service

kubectl get svc

image-20211123150415292

4.訪問

這時我們就能通過訪問名為k8sec.default.svc的Service訪問到www.k8sec.com

#進入第一步創建的busybox
kubectl exec -it busybox -- /bin/sh
curl -I k8sec
curl -I k8sec.default.svc

image-20211123150558281

5, 查看名為k8sec的Service的dns解析

yum install -y bind-utils
dig -t A k8sec.default.svc.cluster.local. @10.96.0.10 #這里10.96.0.10是集群dns地址

可以看出其實是做了一個CNAME,CNAME就是域名映射,將名為k8sec.default.svc的Service映射到www.k8sec.com,我們訪問這個Service名,就相當於訪問到了www.k8sec.com

這么做的好處就是,一旦外部服務發生了地址變更,如www.k8sec.com掛了,只需要修改Service中的externalName字段即可,對於集群內部是無感知的。

同樣我們也可以換成別的服務,如mysql服務,externalName字段改為mysql服務的VIP,這樣集群內部就能通過Service名訪問到外部的mysql服務了。

2.綁定其他命名空間的域名

這里要注意一點的是,不同命名空間的pod是可以直接通信的,我們使用ExternalName類型的Service本質上就是創建一個用於訪問的別名。

1.創建test命名空間

kubectl create ns test

2.在test命名空間中創建nginx的Service

kubectl create deployment nginx --image=nginx -n test
kubectl expose deploy nginx --port=80 --target-port=80 -n test

3.在默認命名空間中創建一個busybox

[root@k8s-master01 ~]# vim busybox.yml
apiVersion: v1
kind: Pod
metadata:
  name: busybox
spec:
  containers:
  - name: liveness
    image: yauritux/busybox-curl
    imagePullPolicy: IfNotPresent
    args:
    - /bin/sh
    - -c
    - sleep 30000000

4.創建ExternalName類型的Service綁定test命名空間的Service

apiVersion: v1
kind: Service
metadata:
  name: nginx-external
spec:
  type: ExternalName
  externalName: nginx.test.svc.cluster.local 
  ports:
  - name: http
    port: 80
    targetPort: 80

5.驗證

#進入處於默認命名空間的busybox中,訪問nginx-external
kubectl exec -it busybox -- /bin/sh
curl -I nginx-external
curl -I nginx-external.default.svc

image-20211123154033953

6.查看dns解析

dig -t A nginx-external.default.svc.cluster.local.  @10.96.0.10

還有一點要注意: 集群內的Pod會繼承Node上的DNS解析規則。所以只要Node可以訪問的服務,Pod中也可以訪問到, 這就實現了集群內服務訪問集群外服務

參考資料:ExternalNames說明ExternalNames案例

三、補充

3.1 sessionAffinity

可以將來自同一個客戶端的請求始終轉發至同一個后端的Pod對象,用於pod的會話保持,就是第一次訪問誰,后面都訪問這個節點。

舉個例子,如果你使用賬號密碼登錄,不使用會話保持,那么每次刷新頁面就會重新登錄一次,因為http是無狀態的,每次請求都沒有聯系。

使用了會話保持就能基於客戶端IP地址識別客戶端身份,不再每次訪問就登錄一次。

跟nginx的ip_hash算法類似。

1.設置sessionAffinity為Clientip,啟用sessionAffinity

#直接修改service里的參數
kubectl patch svc my_service -p '{"spec": {"sessionAffinity":"ClientIP"}}'

#或者
kubectl edit svc my-service
...
sessionAffinity: ClientIP

#取消sessionAffinity 
kubectl patch svc my_service -p '{"spec": {"sessionAffinity":"None"}}'

3.2 headless service

普通的ClusterIP service是service name解析為cluster ip,然后cluster ip對應到后面的pod ip

而無頭service是指service name 直接解析為后面的pod ip,也就是說沒有ClusterIP了

1, 編寫YAML文件

[root@k8s-master01 ~]# vim headless-service.yml
apiVersion: v1
kind: Service
metadata:
   name: headless-service
   namespace: default
spec:
   clusterIP: None                                  # None就代表是無頭service
   type: ClusterIP                                  # ClusterIP類型,也是默認類型
   ports:                                                # 指定service 端口及容器端口
   - port: 80                                          # service ip中的端口
     protocol: TCP
     targetPort: 80                                 # pod中的端口
   selector:                                           # 指定后端pod標簽
     app: nginx                     # 可通過kubectl get pod -l app=nginx查看哪些pod在使用此標簽

注意這里的 clusterIP: None 是沒有IP的

2, 應用YAML文件創建無頭服務

kubectl apply -f headless-service.yml

3, 驗證

kubectl get svc

可以看到headless-service沒有CLUSTER-IP,用None表示

image-20211123161235281

我們查看headless-service的DNS解析發現解析到了如下兩個IP地址

dig -t A headless-service.default.svc.cluster.local. @10.96.0.10

image-20211123161335174

接着查看pod的ip會發現,這兩個IP是一致的,也就是說headless service做dns解析是直接解析到pod的

 kubectl get pods -o wide

image-20211123161553372

需要注意的是對這類 Service 並不會分配 Cluster IP,kube-proxy 不會處理它們,而且集群也不會為它們進行負載均衡和路由。

這種Service主要用於statefulset控制器,用於部署有狀態的pod應用,以后學到了再說吧。

參考資料:Service詳解

3.3 kube-DNS

DNS服務監視Kubernetes API,為每一個Service創建DNS記錄用於域名解析

headless service需要DNS來解決訪問問題

1, 查看kube-dns服務的IP

kubectl get svc -n kube-system
#查看到coreDNS的服務地址是10.96.0.10

image-20211123162412708

四、參考資料

黑馬Linux-k8s第三天視頻

尚硅谷雲原生課程-p54-p56


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM