1. 程式人生 > 其它 >K8S之Service詳解

K8S之Service詳解

K8S之Service詳解

簡介

在K8S中,Pod是容器執行的載體,我們可以訪問Pod內部的容器來訪問相對應的服務,但是Pod的IP地址不是固定的,因此這也就意味我們不方便直接採用IP地址對Pod進行訪問管理。

在K8S中提供我們 service 服務來解決上述問題, service 對一組pod進行聚合,並且提供一個統一的外部介面地址,我們通過訪問該介面地址就可以訪問其所對應的pod服務。

本質上來說其實 service 只是一個概念,真正起到轉發資料的是 kube-proxy 服務程序,每個節點之上都執行一個 kube-proxy 服務程序,當建立 service 的時候 API Service

會監聽 service 然後向 etcd 寫入 service 的資訊, kube-proxy 會監聽 etcdservice 的資訊並且將 service 資訊轉發成對應的訪問規則。

kube-proxy工作模式

userspace

在該模式下 kube-proxy 會為每一個 service 建立一個監聽埠,傳送給 Cluseter IP 請求會被 iptable 重定向給 kube-proxy 監聽的埠上,其中 kube-proxy 會根據 LB 演算法將請求轉發到相應的pod之上。

該模式下,kube-proxy充當了一個四層負載均衡器的角色。由於kube-proxy執行在userspace中,在進行轉發處理的時候會增加核心和使用者空間之間的資料拷貝,雖然比較穩定,但是效率非常低下。

iptables

iptables模式下 kube-proxy 為每一個pod建立相對應的 iptables 規則,傳送給 ClusterIP 的請求會被直接傳送給後端pod之上

在該模式下 kube-proxy 不承擔負載均衡器的角色,其只會負責建立相應的轉發策略,該模式的優點在於較userspace模式效率更高,但是不能提供靈活的LB策略,當後端Pod不可用的時候無法進行重試。

ipvs模式

ipvs模式與iptable模式型別, kube-proxy 會根據pod的變化建立相應的 ipvs 轉發規則,ipvs相對iptable來說轉發效率更加高效,同時提供了大量的負責均衡演算法。

使用ipvs模式必須安裝ipvs核心模組,否則會自動降級為iptables

# 編輯配置檔案 搜尋43行mode將其修改為ipvs
[root@master k8s]# kubectl edit cm kube-proxy -n kube-system

# 刪除原有的代理
[root@master k8s]# kubectl delete pod -l k8s-app=kube-proxy -n kube-system

# 檢視
[root@master k8s]# ipvsadm -Ln

service型別

資源清單

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

service應用

Clusterip service

cat > service-clusterip.yaml << EOF
apiVersion: v1
kind: Service
metadata:
  name: service-clusterip
  namespace: dev
spec:
  selector:
    app: nginx-pod
  type: ClusterIP
  # service IP地址 如果不寫預設會生成一個
  clusterIP: 10.97.97.97
  ports:
    # service埠
    - port: 80
      # 目標pod埠
      targetPort: 80
EOF

# 建立service
[root@master k8s]# kubectl create -f service-clusterip.yaml 

# 檢視service
[root@master k8s]# kubectl get -n dev service service-clusterip -o wide

建立pod

cat > deployment.yaml << EOF
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
              protocol: TCP
EOF

# 建立pod
[root@master k8s]# kubectl create -f deployment.yaml 

# 檢視pod
[root@master k8s]# kubectl get pod -n dev -o wide --show-labels
# 任意節點發起請求
[root@master k8s]# curl 10.244.2.64
# 由於pod節點都是nginx預設介面都是一樣的為了方便測試修改預設介面
# 進入容器
[root@master k8s]# kubectl exec -it -n dev pc-deployment-7d7dd5499b-j5hnp /bin/sh

# 三個節點寫入資料
echo "Current Request is 10.244.1.64" > /usr/share/nginx/html/index.html
echo "Current Request is 10.244.1.63" > /usr/share/nginx/html/index.html
echo "Current Request is 10.244.1.62" > /usr/share/nginx/html/index.html


# 發起請求
[root@master k8s]# curl 10.244.2.64
[root@master k8s]# curl 10.244.2.63
[root@master k8s]# curl 10.244.2.62

建立services

cat > service-clusterip.yaml << EOF
apiVersion: v1
kind: Service
metadata:
  name: service-clusterip
  namespace: dev
spec:
  selector:
    app: nginx-pod
  type: ClusterIP
  # service IP地址 如果不寫預設會生成一個
  clusterIP: 10.97.97.97
  ports:
    # service埠
    - port: 80
      # 目標pod埠
      targetPort: 80
EOF

# 建立service
[root@master k8s]# kubectl create -f service-clusterip.yaml 

# 檢視service
[root@master k8s]# kubectl get -n dev service service-clusterip -o wide
# 檢視詳情
[root@master k8s]# kubectl describe svc service-clusterip -n dev
# 多次訪問後端節點
[root@master k8s]# curl 10.97.97.97

Endpoint

Endpoint是kubernetes中的一個資源物件,儲存在etcd中,用來記錄一個service對應的所有Pod的訪問地址,它是根據service配置檔案中的selector描述產生的。

一個service由一組Pod組成,這些Pod通過Endpoints暴露出來,Endpoints是實現實際服務的端點集合。換言之,service和Pod之間的聯絡是通過Endpoints實現的。

# 檢視endpoints
[root@master k8s]# kubectl get endpoints -n dev 

負載分發策略

對Service的訪問被分發到了後端的Pod上去,目前kubernetes提供了兩種負載分發策略:

  • 如果不定義,預設使用kube-proxy的策略,比如隨機、輪詢等。
  • 基於客戶端地址的會話保持模式,即來自同一個客戶端發起的所有請求都會轉發到固定的一個Pod上,這對於傳統基於Session的認證專案來說很友好,此模式可以在spec中新增sessionAffinity: ClusterIP選項。
# 修改分發策略
apiVersion: v1
kind: Service
metadata:
  name: service-clusterip
  namespace: dev
spec:
  selector:
    app: nginx-pod
  clusterIP: 10.97.97.97 # service的IP地址,如果不寫,預設會生成一個
  type: ClusterIP
  sessionAffinity: ClientIP # 修改分發策略為基於客戶端地址的會話保持模式
  ports:
    - port: 80 # Service的埠
      targetPort: 80 # Pod的埠    
[root@master k8s]# kubectl create -f service-clusterip.yaml

# 迴圈測試
[root@master k8s]# while true;do curl 10.97.97.97:80; sleep 5; done;

HeadLiness services

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

建立service

cat > service-headliness.yaml << EOF
apiVersion: v1
kind: Service
metadata:
  name: service-headliness
  namespace: dev
spec:
  selector:
    app: nginx-pod
  clusterIP: None # 將clusterIP設定為None,即可建立headliness Service
  type: ClusterIP
  ports:
    - port: 80 # Service的埠
      targetPort: 80 # Pod的埠
EOF

[root@master k8s]# kubectl create -f service-headliness.yaml

# 檢視service
[root@master k8s]# kubectl get svc service-headliness -n dev -o wide
# 檢視詳情
[root@master k8s]# kubectl describe svc service-headliness -n dev
# 檢視pod
[root@master k8s]# kubectl get pod -n dev
# 進入Pod
[root@master k8s]# kubectl exec -it -n dev pc-deployment-7d7dd5499b-jc84t /bin/bash
# 檢視域名
root@pc-deployment-7d7dd5499b-jc84t:/# cat /etc/resolv.conf 
# 通過Service的域名進行查詢:
# 預設訪問規則service名稱.名稱空間.svc.cluster.local

# 安裝dig
[root@master k8s]# yum -y install bind-utils

# 訪問
[root@master k8s]# dig @10.96.0.10 service-headliness.dev.svc.cluster.local

NodePort service

在上述案例之中 service 暴露的IP 只能供叢集內部訪問,但是我們建立資源即暴露給使用者使用,因此 K8S 為我們提供了 NodePort service

其會將service 的埠與 node 的埠進行對映,當我們訪問 node 的 IP + Port 即為訪問 service 所對應的資源

建立 service

# 建立 service
cat > cat service-nodeport.yaml << EOF
apiVersion: v1
kind: Service
metadata:
  name: service-nodeport
  namespace: dev
spec:
  selector:
    app: nginx-pod
  type: NodePort # Service型別為NodePort
  ports:
    - port: 80 # Service的埠
      targetPort: 80 # Pod的埠
      nodePort: 30002 # 指定繫結的node的埠(預設取值範圍是30000~32767),如果不指定,會預設分配
EOF

# 建立pod
[root@master k8s]# kubectl create -f service-nodeport.yaml

# 檢視詳情
[root@master k8s]# kubectl get svc -n dev -o wide
# 訪問測試 由於更換機器此時 master 的地址由 10.1.1.2 更改為 172.16.137.128
curl 172.16.137.128:30002

LoadBalancer service

通過上圖可以看到使用 NodePort 模式中對負載均衡能力不是很友好,external 型別與 nodeport 類似都是對於外部暴露一個訪問埠

區別在於使用該模式會在叢集外部新增一個負載均衡裝置,外部訪問負載均衡設定,由負載均衡裝置在根據一定的演算法轉發到後端服務節點

ExternalName service

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

建立 service

cat > service-externalname.yaml << EOF
apiVersion: v1
kind: Service
metadata:
  name: service-externalname
  namespace: dev
spec:
  type: ExternalName # Service型別為ExternalName
  externalName: www.baidu.com # 改成IP地址也可以
EOF

[root@master k8s]# kubectl create -f service-externalname.yaml

# 檢視 service
[root@master k8s]# kubectl get svc service-externalname -n dev
# 安裝域名解析
[root@master k8s]# yum install bind-utils

# 域名解析
[root@master k8s]# dig @10.96.0.10 service-externalname.dev.svc.cluster.local

Ingress

簡介

在上述中 service 對外提供負載均衡主要有 nodeport 與 loadblancer 兩種方式,但是這兩種方式各自都有一定的缺點,在 nodeport 方式中 service 會與 node 節點進行對映,這樣會佔用大量的埠,當 service 過多的時候可能會導致節點埠不夠,在 loadblancer 中每一個service 都需要一個 LB,並且需要外部的負載均衡裝置進行支援

基於上述問題,在 K8S 中提出了 ingress 資源物件,該資源物件只需要一個 nodeport 或者一個 LB 就可以滿足暴露多個 service 需求

實際上 Ingress 類似於一個七層的負載均衡器,是由 K8S 對反向代理的抽象,其工作原理類似於 Nginx 可以理解為Ingress裡面建立了諸多對映規則,Ingress Controller通過監聽這些配置規則並轉化為Nginx的反向代理配置,然後對外提供服務。

  • Ingress:kubernetes中的一個物件,作用是定義請求如何轉發到Service的規則。

  • Ingress Controller:具體實現反向代理及負載均衡的程式,對Ingress定義的規則進行解析,根據配置的規則來實現請求轉發,實現的方式有很多,比如Nginx,Contour,Haproxy等。

  • 其工作原理如下

    • 使用者編寫 Ingress 規則說明那個域名對應那個 service
    • Ingress Contoller 動態感知 ingress 編寫的規則,然後生成對應的反向代理規則
    • ingress 控制器會根據生成代理規則寫入到代理服務中
    • 客戶端請求代理服務,由代理服務轉發到後端 pod 節點

Ingress 使用

環境搭建

mkdir ingress-controller && cd ingress-controller

# 下載service 與控制器
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/mandatory.yaml
  
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/provider/baremetal/service-nodeport.yaml
  
# 更換網路源
sed -i 's#quay.io/kubernetes-ingress-controller/nginx-ingress-controller#registry.cn-qingdao.aliyuncs.com/kubernetes_xingej/nginx-ingress-controller#g' mandatory.yaml

# 使用配置檔案
kubectl apply -f ./

# 檢視pod
[root@master ~]# kubectl get -n ingress-nginx pod

# 檢視svc
[root@master ~]# kubectl get -n ingress-nginx svc

準備Service和pod

cat > tomcat-nginx.yaml << EOF
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
EOF

# 建立pod
[root@master ~]# kubectl create -f tomcat-nginx.yaml

# 檢視
[root@master ~]# kubectl get svc,pod -n dev

HTTP代理

cat > ingress-http.yaml << EOF
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-http
  namespace: dev
spec:
    # 代理規則
  rules:
    # 繫結域名
  - host: nginx.sr.com
    http:
      paths:
        # 繫結路徑
      - path: /
        backend:
          serviceName: nginx-service
           # service暴露埠
          servicePort: 80
  - host: tomcat.sr.com
    http:
      paths:
      - path: /
        backend:
          serviceName: tomcat-service
          servicePort: 8080
EOF
 
# 建立
[root@master ~]# kubectl create -f ingress-http.yaml

# 檢視
[root@master ~]# kubectl get pod -n dev -o wide
# 檢視詳情
[root@master ~]# kubectl describe ingress ingress-http -n dev

配置本地host檔案將master的IP地址與上述域名繫結

# 訪問nginx
curl nginx.sr.com:30771
# 訪問tomcat
curl tomcat.sr.com:30378

HTTPS 代理

# 生成證書
[root@master ~]# 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=sr.com"

# 生成祕鑰
[root@master ~]# kubectl create secret tls tls-secret --key tls.key --cert tls.crt

# 生成配置檔案
cat > ingress-https.yaml << EOF
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-https
  namespace: dev
spec:
  tls:
    - hosts:
      - nginx.sr.com
      - tomcat.sr.com
      secretName: tls-secret # 指定祕鑰需要與上述生成的祕鑰名稱相同
  rules:
  - host: nginx.sr.com
    http:
      paths:
      - path: /
        backend:
          serviceName: nginx-service
          servicePort: 80
  - host: tomcat.sr.com
    http:
      paths:
      - path: /
        backend:
          serviceName: tomcat-service
          servicePort: 8080
EOF

# 載入配置檔案
[root@master k8s]# kubectl create -f ingress-https.yaml

# 檢視配置檔案
[root@master k8s]# kubectl get ingress ingress-https -n dev
# 檢視詳情
[root@master k8s]# kubectl describe -n dev ingress ingress-https
# 檢視service
[root@master k8s]# kubectl get -n ingress-nginx service