k8s-svc

張鐵牛發表於2022-01-03

1. 簡介

kubernets service 是將執行一組pods上的應用程式公開為網路服務的抽象方法。

有了 kubernets service,你就無需修改應用程式即可使用服務發現機制,kubernets 為 pods 提供自己的ip地址,併為一組pod提供相同的DNS名,並且可以在它們之間進行負載均衡。

2. 為什麼要用services

建立和銷燬 kubernets pod 以匹配叢集狀態。pod 是非永久性資源。如果你使用 Deployment 來執行你的應用程式,則它可以動態建立和銷燬 Pod。

每個 Pod 都有自己的 IP 地址,但是在 Deployment 中,在同一時刻執行的 Pod 集合可能與稍後執行該應用程式的 Pod 集合不同。

這導致了一個問題: 如果一組 Pod(稱為“後端”)為叢集內的其他 Pod(稱為“前端”)提供功能, 那麼前端如何找出並跟蹤要連線的 IP 地址,以便前端可以使用提供工作負載的後端部分?

3. quick start

3.1 建立svc

資源模板 svc-deploy-nginx.yaml資訊如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx-deploy-test
  name: nginx-test-1
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx-test
  template:
    metadata:
      labels:
        app: nginx-test
    spec:
      containers:
      - image: nginx:latest
        name: nginx
        imagePullPolicy: IfNotPresent

---

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    # must be match pod template .spec.template.labels
    app: nginx-test
  ports:
    - protocol: TCP
      port: 8000
      targetPort: 80

建立一個deploy,和一個 svc

注意 svc selector 對應的是 deploy 的 .spec.template.labels

建立資源

$ kubectl create -f svc-deploy-nginx.yaml

3.2 檢視svc

  1. 使用資原始檔檢視

    $ kubectl get  -f svc-deploy-nginx.yaml -o wide
    # 輸出內容如下
    # deploy 資訊
    NAME                           READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES         SELECTOR
    deployment.apps/nginx-test-1   3/3     3            3           42m   nginx        nginx:latest   app=nginx-test
    # svc 資訊
    NAME                 TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)    AGE   SELECTOR
    service/my-service   ClusterIP   10.96.112.5   <none>        8000/TCP   42m   app=nginx-test
    
  2. 使用命令檢視

    檢視svc 資訊

    $ kubectl get svc/my-service -owide
    NAME         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)    AGE   SELECTOR
    my-service   ClusterIP   10.96.112.5   <none>        8000/TCP   45m   app=nginx-test
    

    檢視endpoints資訊

    可以看到成功的繫結了三個pod endpoint

    $ kubectl get ep/my-service
    NAME         ENDPOINTS                                               AGE
    my-service   10.100.132.133:80,10.100.132.139:80,10.100.132.140:80   46m
    

    檢視svc 詳細資訊

    $ kubectl describe svc/my-service
    Name:              my-service
    Namespace:         default
    Labels:            <none>
    Annotations:       <none>
    Selector:          app=nginx-test
    Type:              ClusterIP
    IP:                10.96.112.5
    Port:              <unset>  8000/TCP
    TargetPort:        80/TCP
    Endpoints:         10.100.132.133:80,10.100.132.139:80,10.100.132.140:80
    Session Affinity:  None
    Events:            <none>
    

3.3 訪問svc

在上面的例項中svc 並沒有指定 svc type,其預設為ClusterIP型別(從上面的詳情資訊中也能看出)。

我們現在訪問下例項中的svc

  1. 在叢集內部訪問

    在叢集機器上可以使用ClusterIp 訪問服務,但是無法通過 svc name訪問

    在叢集pod中可以使用任意方式訪問服務

  2. 在叢集外部訪問

    叢集外部 沒有辦法直接訪問 svc type=ClusterIp 的svc

4. svc type

svc 有以下的幾種型別:

  • ClusterIP:通過叢集的內部 IP 暴露服務,選擇該值時服務只能夠在叢集內部訪問。 這也是預設的 ServiceType

  • NodePort:通過每個節點上的 IP 和靜態埠(NodePort)暴露服務。 NodePort 服務會路由到自動建立的 ClusterIP 服務。 通過請求 <節點 IP>:<節點埠>,你可以從叢集的外部訪問一個 NodePort 服務。

  • LoadBalancer:使用雲提供商的負載均衡器向外部暴露服務。 外部負載均衡器可以將流量路由到自動建立的 NodePort 服務和 ClusterIP 服務上。

  • ExternalName:通過返回 CNAME 和對應值,可以將服務對映到 externalName 欄位的內容(例如,foo.bar.example.com)。 無需建立任何型別代理。

    說明: 需使用 kube-dns 1.7 及以上版本或者 CoreDNS 0.0.8 及以上版本才能使用 ExternalName 型別。

4.1 ClusterIp

  1. ClusterIp是預設的svc型別,在建立該資源時,kubernetes會預設分配一個 cluster ip。
  2. svc埠為.spec.ports.port,其代理的後端服務的目標埠為.spec.ports.targetPort
  3. 其cluster ip 只能在叢集內部訪問,如果想通過svc name 訪問其代理的服務,只能在pod中訪問。其驗證過程可參考3.3 訪問svc
  4. 叢集外部的資源無法訪問ClusterIp型別的svc。

4.2 NodePort

node(節點)port(埠),顧名思義 NodePort型別的svc是通過在叢集的宿主機上開放訪問的埠,並通過 <節點 IP>:<節點埠>(叢集任意節點ip:nodeport 都可以訪問)來實現從叢集外部訪問其svc的。

nodePort 的原理在於在 node 上開了一個埠,將向該埠的流量匯入到 kube-proxy,然後由 kube-proxy 進一步到給對應的 pod。(能夠將內部服務暴露給外部的一種方式)

  1. 建立資源

    一個deploy 和 一個 nodeport svc,且指定了node 埠為30007(如果不指定,k8s會預設分配一個)

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      labels:
        app: nginx-deploy-test
      name: nginx-test-2
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: nginx-test-02
      template:
        metadata:
          labels:
            app: nginx-test-02
        spec:
          containers:
          - image: nginx:latest
            name: nginx
            imagePullPolicy: IfNotPresent
    
    ---
    
    apiVersion: v1
    kind: Service
    metadata:
      name: my-service-2
    spec:
      type: NodePort
      selector:
        app: nginx-test-02
      ports:
        - protocol: TCP
          port: 8000
          targetPort: 80
          # 可選欄位
          # 預設情況下,為了方便起見,Kubernetes 會從範圍內分配一個埠號(預設:30000-32767)
          nodePort: 30007
    
  2. 訪問svc

4.3 LoadBalancer

使用雲提供商的負載均衡器向外部暴露服務。 外部負載均衡器可以將流量路由到自動建立的 NodePort 服務和 ClusterIP 服務上。

4.4 ExternalName

型別為 ExternalName 的服務將服務對映到 DNS 名稱,而不是典型的選擇器,例如 其他svc 或者 公網域名。 你可以使用 spec.externalName 引數指定這些服務。

  1. 建立服務

    例如,以下 Service 定義將 default 名稱空間中的 my-service-2 服務對映到 my-service-1.test.svc.cluster.local(test 名稱空間中的 my-service-1 service):

    apiVersion: v1
    kind: Service
    metadata:
      name: my-service-2
    spec:
      type: ExternalName
      externalName: my-service-1.test.svc.cluster.local
    

    服務資訊如下:

    服務是在default namespace 下

    沒有分配CLUSTER-IP,但是有我們指定的 EXTERNAL-IP

    $ kubectl get svc -owide
    
    NAME           TYPE           CLUSTER-IP   EXTERNAL-IP                           PORT(S)   AGE    SELECTOR
    kubernetes     ClusterIP      10.96.0.1    <none>                                443/TCP   170d   <none>
    my-service-2   ExternalName   <none>       my-service-1.test.svc.cluster.local   <none>    41s    <none>
    

    為了驗證對映效果,我們再建立一個my-service-1.test service

    注意 服務都是在test namespace 下

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      labels:
        app: nginx-deploy-test
      name: nginx-test-1
      namespace: test
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: nginx-test
      template:
        metadata:
          labels:
            app: nginx-test
        spec:
          containers:
          - image: nginx:latest
            name: nginx
            imagePullPolicy: IfNotPresent
    
    ---
    
    apiVersion: v1
    kind: Service
    metadata:
      name: my-service-1
      namespace: test
    spec:
      selector:
        app: nginx-test
      ports:
        - protocol: TCP
          port: 8000
          targetPort: 80
    

    服務資訊如下:

    $ kubectl get -f app-nginx.yaml -owide
    
    NAME                           READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES         SELECTOR
    deployment.apps/nginx-test-1   3/3     3            3           48s   nginx        nginx:latest   app=nginx-test
    
    NAME                   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE   SELECTOR
    service/my-service-1   ClusterIP   10.96.188.235   <none>        8000/TCP   47s   app=nginx-test
    
  2. 訪問svc

    首先我們建立一個pod 用來訪問 驗證 svc

    服務資訊如下:

    $ kubectl get po -owide
    NAME         READY   STATUS    RESTARTS   AGE     IP               NODE           NOMINATED NODE   READINESS GATES
    helloworld   1/1     Running   0          7d23h   10.100.132.161   k8s-woker-01   <none>           <none>
    

    ok,到這裡 資源都以就緒,如果我們進入到 pod/helloworld 去訪問 svc/my-service-2,如果能成功訪問到naginx 服務,則證明ExternalName svc這種方式是可行的:

4.5 Headless Services

準確來說 Headless Services 並不屬於svc的type,而是 clusterip型別的變種

有時不需要或不想要負載均衡,以及單獨的 Service IP。 遇到這種情況,可以通過指定 Cluster IP(spec.clusterIP)的值為 "None" 來建立 Headless Service。

對這無頭 Service 並不會分配 Cluster IP,kube-proxy 不會處理它們, 而且平臺也不會為它們進行負載均衡和路由。 DNS 如何實現自動配置,依賴於 Service 是否定義了選擇算符。

**無選擇算符的服務 **

對沒有定義選擇算符的無頭服務,Endpoint 控制器不會建立 Endpoints 記錄。 然而 DNS 系統會查詢和配置,無論是:

  • 對於 ExternalName型別的服務,查詢其 CNAME 記錄
  • 對所有其他型別的服務,查詢與 Service 名稱相同的任何 Endpoints 的記錄

關於Headless Services 在k8s-statefulset博文中 有詳細的記錄,感興趣的可以訪問了解下。

5. externalIPs

Service 的 externalIPs(spec.externalIPs)可以設定一組外部的 IP 地址,並且將流量匯入到叢集內部。

如圖:

啟動一個springboot專案 並初始化一個介面,訪問後返回 Hello World~

在本機上訪問:

接下來我們就使用externalIPs192.168.0.101流量匯入到叢集內部

目標:叢集pod中訪問192.168.0.101:8080時不是訪問此時的這個springboot專案,而是訪問叢集內的externalIPs svc

  1. 首先到叢集master節點訪問一下springboot專案

    訪問成功

    $ curl 192.168.0.101:8080
    Hello World~
    
  2. 建立externalIPs svc,和想匯入到叢集的目標pod

    本例項主要是為了展示流量的切換,但其實spec.externalIPs 可以指定符合ip 規則的任意地址(即使設定的ip不可達),k8s都會幫你 在訪問設定地址的服務時,將流量匯入到spec.selector的服務中去

    實際上就是 如果你把spec.externalIPs地址設定成123.123.123.123,當你在服務中訪問123.123.123.123時,其實訪問的是spec.selector指定的服務

    其實更像是 給spec.selector到服務指定了一個 ipv4 的別名

    apiVersion: v1
    kind: Service
    metadata:
      name: nginx
      labels:
        app: nginx
    spec:
      ports:
      - port: 8080
        targetPort: 80
        name: web
      externalIPs:
      - "192.168.0.101"
      # 將 192.168.0.101 流量匯入到 下面的Deployment中
      selector:
        app: nginx
        
    ---
    
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: web
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: nginx
      template:
        metadata:
          labels:
            app: nginx
        spec:
          containers:
          - name: nginx
            image: nginx
            ports:
            - containerPort: 80
              name: web
    

    服務資訊如下:

    $ kubectl get deploy -owide
    NAME   READY   UP-TO-DATE   AVAILABLE   AGE     CONTAINERS   IMAGES   SELECTOR
    web    2/2     2            2           3h25m   nginx        nginx    app=nginx
    
    $ kubectl get svc/nginx -owide
    NAME         TYPE        CLUSTER-IP     EXTERNAL-IP     PORT(S)    AGE    SELECTOR
    nginx        ClusterIP   10.96.41.170   192.168.0.101   8080/TCP   4m     app=nginx
    
  3. 建立一個pod 並進入pod ,在pod中訪問192.168.0.101:8080檢視訪問的是外部的springboot專案還是叢集內的deploy/web

    很顯然:k8s成功的將流量匯入到了叢集內部

    如果在叢集節點上訪問192.168.0.101:8080,是不能將結果匯入到deploy上的,只有在叢集服務內部訪問才有效

  4. 此時我們將svc/nginx 刪除掉,看下訪問的結果

    符合預期

6. 管理外部的服務

場景:如果我們的中介軟體或者資料庫服務不是在k8s叢集內的,而是在其他的伺服器上,但是我們還是想通過訪問svc的方式去訪問這些外部的服務,我們應該怎麼做呢?

  1. 宣告一個沒有選擇符的svc
  2. 建立一個同名(和第一步中的svc name 相同)的 endpoints 服務去管理這些外部的服務地址

例項:

例項通過建立svc 和 endpoints,實現訪問svc-name,svc 將流量匯出到外部的springboot(專案資訊如5.0中提到的)專案中

  1. 建立服務

    apiVersion: v1
    kind: Service
    metadata:
      name: my-service
    spec:
      ports:
        - protocol: TCP
          port: 80
          targetPort: 8080
    
    ---
    
    apiVersion: v1
    kind: Endpoints
    metadata:
      # must be match svc-name
      name: my-service
    subsets:
      - addresses:
          # 指向外部的springboot 服務
          - ip: 192.168.0.101
        ports:
          - port: 8080
    
  2. 訪問測試

7. 選擇自己的 IP 地址

Service 建立的請求中,可以通過設定 spec.clusterIP 欄位來指定自己的叢集 IP 地址。