<xmp id="63nn9"><video id="63nn9"></video></xmp>

<xmp id="63nn9"></xmp>

<wbr id="63nn9"><ins id="63nn9"></ins></wbr>

<wbr id="63nn9"></wbr><video id="63nn9"><ins id="63nn9"><table id="63nn9"></table></ins></video>

【Kubernetes】K8s筆記(十):Service 解決服務發現的關鍵問題

在云原生時代,微服務無疑是應用的主流形態。為了更好地支持微服務以及服務網格這樣的應用架構,Kubernetes 又專門定義了一個新的對象:Service,它是集群內部的負載均衡機制,用來解決服務發現的關鍵問題。在 Kubernetes Service 文檔中,Service 被定義為將運行在一組 Pods 上的應用程序公開為網絡服務的抽象方法。

0. 打造 Service 對象的動機

有了 Deployment 和 DaemonSet,我們在集群里發布應用程序的工作輕松了很多。借助 Kubernetes 強大的自動化運維能力,我們可以把應用的更新上線頻率由以前的月、周級別提升到天、小時級別,讓服務質量更上一層樓。

在 Kubernetes 集群里 Pod 的生命周期是比較“短暫”的,雖然 Deployment 和 DaemonSet 可以維持 Pod 總體數量的穩定,但在運行過程中,難免會有 Pod 銷毀又重建,這就會導致 Pod 集合處于動態的變化之中。

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

業內早就有解決方案來針對這樣“不穩定”的后端服務,那就是“負載均衡”,典型的應用有 LVS、Nginx 等等。它們在前端與后端之間加入了一個“中間層”,屏蔽后端的變化,為前端提供一個穩定的服務。

因此 Kubernetes 定義了一個新的 API 對象:Service。

1. Service 的工作原理

Service 的工作原理和 LVS、Nginx 差不多,Kubernetes 會給它分配一個靜態 IP 地址,然后它再去自動管理、維護后面動態變化的 Pod 集合,當客戶端訪問 Service,它就根據某種策略,把流量轉發給后面的某個 Pod。

這張圖展示的是 Service 的 iptables 代理模式。每個節點上的 kube-proxy 組件自動維護 iptables 規則,客戶不再關心 Pod 的具體地址,只要訪問 Service 的固定 IP 地址,Service 就會根據 iptables 規則轉發請求給它管理的多個 Pod,這是典型的負載均衡架構。此外,使用 iptables 處理流量具有較低的系統開銷,因為流量由 Linux netfilter 處理, 而無需在用戶空間和內核空間之間切換。 這種方法也可能更可靠。

Service 并不是只能使用 iptables 來實現負載均衡,它還有另外兩種實現技術:性能更差的 userspace 和性能更好的 ipvs,但這些都屬于底層細節,我們不需要刻意關注。

2. 使用 YAML 描述 Service

我們還是可以用命令 kubectl api-resources 查看它的基本信息,可以知道它的簡稱是 svc,apiVersion 是 v1。注意,這說明它與 Pod 一樣,屬于 Kubernetes 的核心對象,不關聯業務應用,與 Job、Deployment 是不同的。

這里 Kubernetes 又表現出了行為上的不一致。雖然它可以自動創建 YAML 樣板,但不是用命令 kubectl create,而是另外一個命令 kubectl expose,也許 Kubernetes 認為 expose 能夠更好地表達 Service “暴露”服務地址的意思吧。

使用 kubectl expose 指令時還需要用參數 --port--target-port 分別指定映射端口和容器端口,而 Service 自己的 IP 地址和后端 Pod 的 IP 地址可以自動生成,用法上和 Docker 的命令行參數 -p 很類似,只是略微麻煩一點。

例如,我們對 Deployment 筆記中的 ngx-dep 對象生成 Service ,命令就應該這樣寫:

$ export out="--dry-run=client -o yaml"
$ kubectl expose deploy ngx-dep --port=80 --target-port=80 $out

下面是剔除一些字段后的 YAML:

apiVersion: v1
kind: Service
metadata:
  name: ngx-svc

spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: ngx-dep

可以發現,Service 的定義非常簡單,spec 中只有兩個關鍵字 selectorports。

  • selector 和 Deployment/DaemonSet 里的作用是一樣的,用來過濾出要代理的那些 Pod。因為我們指定要代理 Deployment,所以 Kubernetes 就為我們自動填上了 ngx-dep 的標簽,會選擇這個 Deployment 對象部署的所有 Pod。

  • ports 就很好理解了,里面的三個字段分別表示外部端口、內部端口和使用的協議,在這里就是內外部都使用 80 端口,協議是 TCP。\

3. 在 Kubernetes 中使用 Service

在 YAML 創建 Service 對象之前,我們要先對 Deployment 筆記中的 ngx-dep 做一點改造:方便觀察 Service 的效果。

首先,創建一個 ConfigMap,定義一個 Nginx 的配置片段,它會相應服務器地址、主機名、請求的 URI 等信息:

# ngx-conf.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: ngx-conf

data:
  default.conf: |
    server {
      listen 80;
      location / {
        default_type text/plain;
        return 200
          'srv : $server_addr:$server_port\nhost: $hostname\nuri : $request_method $host $request_uri\ndate: $time_iso8601\n';
      }
    }
$ kubectl apply -f ngx-conf.yaml

然后在 Deployment 中的 template.volumes 里定義存儲卷,再用 volumeMounts 將配置文件加載到容器中:

# ngx-dep.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ngx-dep

spec:
  replicas: 3
  selector:
    matchLabels:
      app: ngx-dep

  template:
    metadata:
      labels:
        app: ngx-dep
    spec:
      volumes:
      - name: ngx-conf-vol
        configMap:
          name: ngx-conf

      containers:
      - image: nginx:alpine
        name: nginx
        ports:
        - containerPort: 80

        volumeMounts:
        - mountPath: /etc/nginx/conf.d
          name: ngx-conf-vol
$ kubectl apply -f ngx-conf.yaml

部署這個 Deployment 之后,我們就可以創建 Service 對象了:

# ngx-svc.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app: ngx-dep
  name: ngx-dep
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: ngx-dep
$ kubectl apply -f ngx-svc.yaml

創建之后,我們就可以查看該 Service 對象的狀態:

$ kubelet get svc -o wide
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE   SELECTOR
kubernetes   ClusterIP   10.96.0.1      <none>        443/TCP   2d    <none>
ngx-svc      ClusterIP   10.96.21.182   <none>        80/TCP    10s   app=ngx-dep

Kubernetes 為 Service 對象自動分配了一個 IP 地址 10.96.21.182,這個地址段是獨立于 Pod 地址段的(10.10.xx.xx)。而且 Service 對象的 IP 地址還有一個特點,它是一個“虛地址”,不存在實體,只能用來轉發流量。

想要看 Service 代理了哪些后端的 Pod,可以使用 describe 子命令:

$ kubectl describe svc ngx-svc
Name:              ngx-svc
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          app=ngx-dep
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.96.21.182
IPs:               10.96.21.182
Port:              <unset>  80/TCP
TargetPort:        80/TCP
Endpoints:         10.10.1.34:80,10.10.1.35:80,10.10.1.36:80
Session Affinity:  None
Events:            <none>

顯示 Service 對象管理了 3 個 endpoint10.10.1.34:80, 10.10.1.35:80, 10.10.1.36:80。

下面查看一下 Pod 詳情:

$ kubectl get pods -o wide 
NAME                       READY   STATUS    RESTARTS       AGE   IP           NODE         NOMINATED NODE   READINESS GATES
ngx-dep-545884c69c-9q6gd   1/1     Running   0              39m   10.10.1.34   worker1      <none>           <none>
ngx-dep-545884c69c-rbfpn   1/1     Running   0              39m   10.10.1.35   worker1      <none>           <none>
ngx-dep-545884c69c-vzdf2   1/1     Running   0              39m   10.10.1.36   worker1      <none>           <none>
redis-ds-8zmdb             1/1     Running   1 (140m ago)   23h   10.10.0.39   k8s-master   <none>           <none>
redis-ds-tp9sd             1/1     Running   1 (140m ago)   23h   10.10.1.30   worker1      <none>           <none>

可以看到 Service 確實用一個靜態 IP 地址代理了 3 個 Pod 的動態 IP 地址。

接下來測試負載均衡的效果,因為 Service、 Pod 的 IP 地址都是 Kubernetes 集群的內部網段,所以我們需要用 kubectl exec 進入到 Pod 內部(或者 ssh 登錄集群節點),再用 curl 等工具來訪問 Service:

$ kubectl exec -it ngx-dep-545884c69c-9q6gd -- sh
/ # curl 10.96.21.182
srv : 10.10.1.34:80
host: ngx-dep-545884c69c-9q6gd
uri : GET 10.96.21.182 /
date: 2022-10-20T03:04:55+00:00
/ # curl 10.96.21.182
srv : 10.10.1.36:80
host: ngx-dep-545884c69c-vzdf2
uri : GET 10.96.21.182 /
date: 2022-10-20T03:04:59+00:00
/ # curl 10.96.21.182
srv : 10.10.1.36:80
host: ngx-dep-545884c69c-vzdf2
uri : GET 10.96.21.182 /
date: 2022-10-20T03:05:02+00:00

在 Pod 里,用 curl 訪問 Service 的 IP 地址,就會看到它把數據轉發給后端的 Pod,輸出信息會顯示具體是哪個 Pod 響應了請求,就表明 Service 確實完成了對 Pod 的負載均衡任務。

再試著刪除一個 Pod,看看 Service 是否會更新后端 Pod 的信息,實現自動化的服務發現:

$ kubectl delete pod ngx-dep-545884c69c-9q6gd 
pod "ngx-dep-545884c69c-9q6gd" deleted
$ kubectl describe svc ngx-svc
Name:              ngx-svc
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          app=ngx-dep
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.96.21.182
IPs:               10.96.21.182
Port:              <unset>  80/TCP
TargetPort:        80/TCP
Endpoints:         10.10.1.35:80,10.10.1.36:80,10.10.1.37:80
Session Affinity:  None
Events:            <none>

可以看到一個 IP 地址為 10.10.1.37 的 Pod 被添加進來了。

由于 Pod 被 Deployment 對象管理,刪除后會自動重建,而 Service 又會通過 controller-manager 實時監控 Pod 的變化情況,所以就會立即更新它代理的 IP 地址。

4. 以域名方式使用 Service

Service 還有一些高級特性值得了解。

首先是 DNS 域名。Service 對象的 IP 地址是靜態的,保持穩定,這在微服務里確實很重要,不過數字形式的 IP 地址用起來還是不太方便。這個時候 Kubernetes 的 DNS 插件就派上了用處,它可以為 Service 創建易寫易記的域名,讓 Service 更容易使用。

使用 DNS 域名之前,我們要先了解一個新的概念:名字空間 namespace,它被用來在集群里實現對 API 對象的隔離和分組。

namespace 的簡寫是 ns,使用命令 kubectl get ns 來查看當前集群里都有哪些名字空間,也就是說 API 對象有哪些分組:

$ kubectl get ns 
NAME              STATUS   AGE
default           Active   2d
kube-flannel      Active   2d
kube-node-lease   Active   2d
kube-public       Active   2d
kube-system       Active   2d

Kubernetes 有一個默認的名字空間 default,如果不顯式指定,API 對象都會在這個 default 名字空間里。而其他的名字空間都有各自的用途,比如 kube-system 就包含了 apiserver、etcd 等核心組件的 Pod。

因為 DNS 是一種層次結構,為了避免太多的域名導致沖突,Kubernetes 就把名字空間作為域名的一部分,減少了重名的可能性。

Service 對象的域名完全形式是:

Object.namespace.svc.cluster.local

但很多時候也可以省略后面的部分,直接寫 object.namesapce 甚至 object 就足夠了,默認會使用對象所在的名字空間(比如這里就是 default)。

現在我們來試驗一下 DNS 域名的用法,還是先 kubectl exec 進入 Pod,然后用 curl 訪問 ngx-svc、ngx-svc.default 等域名:

$ kubectl exec -it ngx-dep-545884c69c-rbfpn -- sh
/ # curl ngx-svc
srv : 10.10.1.37:80
host: ngx-dep-545884c69c-mmrnm
uri : GET ngx-svc /
date: 2022-10-20T07:04:40+00:00
/ # curl ngx-svc.default
srv : 10.10.1.35:80
host: ngx-dep-545884c69c-rbfpn
uri : GET ngx-svc.default /
date: 2022-10-20T07:04:45+00:00

可以看到,現在我們就不再關心 Service 對象的 IP 地址,只需要知道它的名字,就可以用 DNS 的方式去訪問后端服務。

順便說一下,Kubernetes 也為每個 Pod 分配了域名,形式是 IP Address.namespace.pod.cluster.local,但需要把 IP 地址里的 . 改成 - 。比如地址 10.10.1.87,它對應的域名就是 10-10-1-87.default.pod。

這里是 Kubernetes 文檔 - Service 與 Pod 的 DNS。

5. 讓 Service 對外暴露服務

由于 Service 是一種負載均衡技術,所以它不僅能夠管理 Kubernetes 集群內部的服務,還能夠擔當向集群外部暴露服務的重任。

Service 對象有一個關鍵字段 type,表示 Service 是哪種類型的負載均衡。

  • ClusterIP - 對集群內部 Pod 的負載均衡,Service 的靜態 IP 地址只能在集群內訪問

  • ExternalName - 通過返回 CNAME 和對應值,可以將服務映射到 externalName 字段的內容(例如 foo.bar.example.com)。 無需創建任何類型代理 查看文檔

  • LoadBalancer - 一般依賴云服務提供商 查看文檔

  • NodePort - 通過每個節點上的 IP 和靜態端口(NodePort)暴露服務

在實驗環境里我們使用 NodePort:

如果在使用命令 kubectl expose 的時候加上參數 --type=NodePort,或者在 YAML 里添加字段 type:NodePort,那么 Service 除了會對后端的 Pod 做負載均衡之外,還會在集群里的每個節點上創建一個獨立的端口,用這個端口對外提供服務,這也正是 NodePort 這個名字的由來。

下面我們給 Service 的 YAML 文件加上 type 字段:

apiVersion: v1
kind: Service
metadata:
  name: ngx-svc

spec:
  type: NodePort
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: ngx-dep
$ kubectl apply -f ngx-svc.yaml
service/ngx-svc configured
$ kubectl get svc -o wide 
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE     SELECTOR
kubernetes   ClusterIP   10.96.0.1      <none>        443/TCP        2d5h    <none>
ngx-svc      NodePort    10.96.21.182   <none>        80:30952/TCP   5h25m   app=ngx-dep

就會看到 TYPE 變成了 NodePort,而在 PORT 列里的端口信息也不一樣,除了集群內部使用的 80 端口,還多出了一個 30952 端口,這就是 Kubernetes 在節點上為 Service 創建的專用映射端口。

因為這個端口號屬于節點,外部能夠直接訪問,所以現在我們就可以不用登錄集群節點或者進入 Pod 內部,直接在集群外使用任意一個節點的 IP 地址,就能夠訪問 Service 和它代理的后端服務了。

比如我使用宿主機 IP: 172.16.63.1 訪問集群內的 Worker1 節點 IP: 172.16.63.129:30952 就可以得到 Nginx Pod 的響應數據:

$ curl 172.16.63.129:30952
srv : 10.10.1.36:80
host: ngx-dep-545884c69c-vzdf2
uri : GET 172.16.63.129 /
date: 2022-10-20T08:20:21+00:00

NodePort 類型的 Service 有下面幾個缺點:

  • 端口數量很有限。Kubernetes 為了避免端口沖突,默認只在“30000~32767”這個范圍內隨機分配,只有 2000 多個,而且都不是標準端口號,這對于具有大量業務應用的系統來說根本不夠用

  • 它會在每個節點上都開端口,然后使用 kube-proxy 路由到真正的后端 Service,這對于有很多計算節點的大集群來說就帶來了一些網絡通信成本,不是特別經濟

  • 它要求向外界暴露節點的 IP 地址,這在很多時候是不可行的,為了安全還需要在集群外再搭一個反向代理,增加了方案的復雜度

posted @ 2022-10-20 16:28  joexu01  閱讀(183)  評論(0編輯  收藏  舉報
人碰人摸人爱免费视频播放

<xmp id="63nn9"><video id="63nn9"></video></xmp>

<xmp id="63nn9"></xmp>

<wbr id="63nn9"><ins id="63nn9"></ins></wbr>

<wbr id="63nn9"></wbr><video id="63nn9"><ins id="63nn9"><table id="63nn9"></table></ins></video>