iT邦幫忙

2024 iThome 鐵人賽

DAY 10
1

在 Kubernetes 中,Service 是一種抽象資源,它定義了一組提供相同服務的 Pod 的邏輯集合,並且能夠確保這些 Pod 能夠被持續地訪問。Service 提供了一個穩定的網絡端點,無論後端的 Pod 如何變動,使用者都能通過這個穩定的端點訪問服務。

為什麼需要 Service

在 Kubernetes 中,Pod 是有生命周期的,可能會被刪除或重新創建,這會導致它們的 IP 地址發生變化。如果外部應用或其他 Pod 需要與這些 Pod 通信,直接依賴 Pod 的 IP 地址是不可行的。Service 解決了這個問題,它提供了一個固定的訪問點,通過負載均衡來管理後端 Pod 的流量,確保服務的穩定性和可靠性。

Service 的類型

Kubernetes 提供了多種 Service 類型,以適應不同的使用場景:

  1. ClusterIP:該 Service 只在集群內部可達,為一組 Pod 提供一個內部訪問點。適用於集群內部的服務通信。ClusterIP 是 Service 的默認類型。

  2. NodePort:該 Service 將服務暴露在每個節點的固定端口上,這個端口範圍通常是 30000-32767。NodePort 允許外部流量通過這些節點端口訪問服務。

  3. LoadBalancer:在雲提供商(如 AWS、GCP)中使用時,該 Service 會創建一個外部負載均衡器,並將流量分發到後端的 Pod。這適用於需要暴露服務給外部客戶端的場景。

  4. ExternalName:該 Service 將服務映射到一個 DNS 名稱,而不直接映射到 Kubernetes 集群內的 Pod。適用於需要指向集群外部服務的情況。

應用場景

  1. 內部服務通信:使用 ClusterIP Service 來管理和暴露集群內部的微服務,使得不同的應用服務可以通過固定的服務名稱來相互通信。

  2. 外部訪問:使用 NodePort 或 LoadBalancer 來將集群內的服務暴露給集群外部的用戶或系統,特別是在需要對外提供 API、Web 服務等時。

  3. 跨區域或跨集群的服務:使用 ExternalName 將集群內的流量指向外部 DNS 名稱,從而與外部系統或不同區域的服務進行通信。

實作

建立 ClusterIP 類型服務

組態檔案: pod.yaml

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    app: foo
    group: hello
  name: foo
spec:
  containers:
  - image: lofairy/foo
    name: foo
    resources: {}
    ports:
      - containerPort: 8080
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}
---
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    app: bar
    group: hello
  name: bar
spec:
  containers:
  - image: lofairy/bar
    name: bar
    resources: {}
    ports:
      - containerPort: 8080
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}
  • 建立 Pod
kubectl apply -f pod.yaml
  • 進入 foo Pod,試著訪問 foobar 的應用:
kubectl exec -it foo -- bin/sh
/ # wget -q -O - foo:8080
---
foo
----------
/ # wget -q -O - bar:8080
---
wget: bad address 'bar:8080'

可以看到 Pod 之間無法互相訪問

組態檔案: clusterip-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: bar-service
spec:
  selector:
    app: bar
  ports:
    - protocol: TCP
      port: 9527
      targetPort: 8080

建立該 bar-service 會被指派並暴露 Cluster-IP(內部IP) 和 9527 端口,訪問這個 Cluster-IP:PORT 流量將會轉發到目標 Pod 的 8080 端口

  • 建立 bar-service
kubectl apply -f clusterip-service.yaml
  • 查詢叢集分配給 bar-service 的 ClusterIP
kubectl get svc
---
NAME          TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
bar-service   ClusterIP   10.96.123.76   <none>        9527/TCP   12s
kubernetes    ClusterIP   10.96.0.1      <none>        443/TCP    5h
  • 再次進入 foo Pod,並訪問 Cluster-IP:PORT
kubecl exec -it foo -- bin/sh
/ # wget -q -O - 10.96.123.76:9527
---
bar

透過 clusterIP:<port> ,可以訪問到其他 Pod 的應用

也可以使用 [[DNS name]] 訪問

  • foo Pod 中,使用 nslookup 指令進行 DNS 解析
/ # nslookup bar-service
---
Server:         10.96.0.10
Address:        10.96.0.10:53

** server can't find bar-service.cluster.local: NXDOMAIN


** server can't find bar-service.svc.cluster.local: NXDOMAIN

** server can't find bar-service.cluster.local: NXDOMAIN

Name:   bar-service.default.svc.cluster.local
Address: 10.96.123.76

** server can't find bar-service.svc.cluster.local: NXDOMAIN

問題排除

如果遇到 Cluster-IP 可以訪問,但 DNS name 連線卻被拒絕的情況,可以刪除 kube-dns Pod 來重啟 coredns:

## 刪除 dns pod 
kubectl delete pod -n kube-system -l k8s-app=kube-dns
## 查看 dns pod
kubectl get pod -n kube-system -l k8s-app=kube-dns

建立 NodePort 類型服務

  • 刪除之前的 Service
kubectl delete -f clusterip-service.yaml

組態檔案: nodeport-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: bar-service
spec:
  type: NodePort
  selector:
    app: bar
  ports:
  - port: 9527
    targetPort: 8080
    nodePort: 30000
  • 建立 Service
kubectl apply -f nodeport-service.yaml
  • 查詢 bar Pod 所在的節點
kubectl get pod -o wide
---
NAME   READY   STATUS    RESTARTS   AGE   IP           NODE              NOMINATED NODE   READINESS GATES
bar    1/1     Running   0          25m   10.244.2.2   wslkind-worker2   <none>           <none>
foo    1/1     Running   0          25m   10.244.1.2   wslkind-worker    <none>           <none>

可以得知,bar Pod 位於 wslkind-worker2 節點

  • 查詢 wslkind-worker2 節點
kubectl get node -o wide
---
NAME                    STATUS   ROLES           AGE     VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE                         KERNEL-VERSION                       CONTAINER-RUNTIME
wslkind-control-plane   Ready    control-plane   8m24s   v1.30.0   172.18.0.4    <none>        Debian GNU/Linux 12 (bookworm)   5.15.153.1-microsoft-standard-WSL2   containerd://1.7.15
wslkind-worker          Ready    <none>          8m4s    v1.30.0   172.18.0.2    <none>        Debian GNU/Linux 12 (bookworm)   5.15.153.1-microsoft-standard-WSL2   containerd://1.7.15
wslkind-worker2         Ready    <none>          8m3s    v1.30.0   172.18.0.3    <none>        Debian GNU/Linux 12 (bookworm)   5.15.153.1-microsoft-standard-WSL2   containerd://1.7.15

可以得知,wslkind-worker2 節點的內部 IP 為 172.18.0.3

我們可以使用 docker inspect 命令來檢查 wslkind-worker2 節點容器,得知 Docker 容器分配給該節點的 IP

docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' 4aa30c0afd06
---
172.18.0.3

可以看到,docker inspect 查到的容器 IP 與 Kubernetes 節點的 internal IP 是相同的

由於使用 KinD 建立了集群,節點並不對容器外暴露 IP。不過我們可以使用 Docker 指令進入任意節點容器,繼續進行測試。

  • 在 Docker Container 中,測試服務
docker exec -it wslkind-control-plane /bin/sh
# curl 172.18.0.3:30000
---
bar

建立 LoadBalancer 類型服務

LoadBalancer 服務旨在使用雲端供應商提供的負載平衡器基礎架構。我們不在雲端運行,但我們需要平衡負載器為我們分配外部 IP。作為代替,我們使用第三方的 MetalLB 負載平衡器實作。

準備 MetalLB

  • 安裝 metallb
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.7/config/manifests/metallb-native.yaml

組態檔案: IP-Pool.yaml

apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: first-pool
  namespace: metallb-system
spec:
  addresses:
  # 可分配的 IP 地址,可以指定多個,包括 ipv4、ipv6
  - 172.20.175.140-172.20.175.150
  • 建立 IP-Pool
kubectl apply -f IP-Pool.yaml

建立 LoadBalancer

  • 刪除之前的 Service
kubectl delete -f nodeport-service.yaml

組態檔案: lb-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: lb-service
spec:
  type: LoadBalancer
  selector:
    group: hello
  ports:
    - protocol: TCP
      port: 9527
      targetPort: 8080
  • 建立 Service
kubectl apply -f lb-service.yaml
  • 查詢 service 資訊
kubectl get svc
---
NAME         TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)          AGE
kubernetes   ClusterIP      10.96.0.1      <none>           443/TCP          148m
lb-service   LoadBalancer   10.96.82.109   172.20.175.140   9527:32216/TCP   5s

由於使用 KinD 建立了集群,儘管 MetalLB 分配了外部 IP 給我們,整個叢集對容器外還是不暴露 IP。不過我們可以使用 Docker 指令進入任一節點容器,繼續進行測試。

  • 在 Docker Container 中,測試服務
docker exec -it wslkind-control-plane /bin/sh
# curl 172.20.175.140:9527
bar# curl 172.20.175.140:9527
foo# curl 172.20.175.140:9527
foo# curl 172.20.175.140:9527
foo# curl 172.20.175.140:9527
bar#

建立 ExternalName 類型服務

  • 刪除之前的 Service
kubectl delete -f lb-service.yaml

組態檔案: externalname-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  type: ExternalName
  externalName: www.google.com
  • 建立 Service
kubectl apply -f externalname-service.yaml
  • 查詢 service 資訊
kubectl get svc
---
NAME         TYPE           CLUSTER-IP   EXTERNAL-IP      PORT(S)   AGE
kubernetes   ClusterIP      10.96.0.1    <none>           443/TCP   28d
my-service   ExternalName   <none>       www.google.com   <none>    9s
  • 創建並進入測試用 Pod
kubectl run -it --image busybox:1.36 dns-test --restart=Never --rm
  • 查詢 Service DNS Record
nslookup my-service.default.svc.cluster.local
---
Server:         10.96.0.10
Address:        10.96.0.10:53

my-service.default.svc.cluster.local    canonical name = www.google.com
Name:   www.google.com
Address: 2404:6800:4012:3::2004

my-service.default.svc.cluster.local    canonical name = www.google.com
Name:   www.google.com
Address: 142.251.43.4

特別的服務: Headless Service

在 Kubernetes 中,Headless Service 是一種特別的 Service,它與普通 Service 的主要區別在於它不會分配一個 Cluster IP。當創建 Headless Service 時,Kubernetes 不會配置負載均衡或代理,只是簡單地根據服務的 selector 創建 DNS 條目,將 Pod 的 IP 地址直接暴露給客戶端。這樣,客戶端可以直接與後端的 Pod 通信,而不需要通過 Service 代理。

為什麼需要 Headless Service

Headless Service 的主要用途是為那些需要直接處理每個 Pod 的應用程序提供支持,而不是通過一個統一的 IP 和負載均衡器。例如,在某些分佈式系統中,如資料庫分片或狀態一致性協議,每個節點(Pod)可能需要直接被訪問,以便進行節點之間的通信或數據分布。

應用場景

  1. 分布式數據庫集群:在分布式數據庫系統(如 Cassandra、Elasticsearch)中,每個節點(Pod)都是獨立的,並且需要直接與其他節點通信。Headless Service 允許客戶端直接獲取每個 Pod 的 IP,從而進行點對點通信。

  2. 有狀態應用:一些有狀態的應用程序(如 StatefulSet 管理的應用)可能需要直接訪問特定的 Pod,Headless Service 提供了這種靈活性。

  3. 自定義負載均衡:如果應用程序需要自定義的負載均衡策略,而不是依賴 Kubernetes 的內建負載均衡,則可以使用 Headless Service 來實現。

組態檔案說明

以下是一個簡單的 Headless Service 組態檔範例:

apiVersion: v1
kind: Service
metadata:
  name: my-headless-service
spec:
  clusterIP: None
  selector:
    app: MyApp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080

只要設定 spec.clusterIP: None 就可以建立 Headless Service。

實作: Headless Service

Headless Service 只有在與 StatefulSet 搭配時,才能為每個 Pod 副本提供獨立的 DNS 名稱。不過,由於我們尚未介紹 StatefulSet,接下來我們將使用 Deployment 來展示 Headless Service 與一般 ClusterIP Service 的差異。

組態檔案: headless-service-demo.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: foo
  template:
    metadata:
      labels:
        app: foo
    spec:
      containers:
      - name: foo
        image: lofairy/foo
---
apiVersion: v1
kind: Service
metadata:
  name: foo-headless-svc
spec:
  clusterIP: None
  selector:
    app: foo
  ports:
    - protocol: TCP
      port: 8080 
      targetPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: foo-clusterip-svc
spec:
  selector:
    app: foo
  ports:
    - protocol: TCP
      port: 8080 
      targetPort: 8080

這個組態檔案包含了一個有 3 個副本的 Deployment 以及兩個類型的 Service:

  • foo-clusterip-svc 是上面展示過的 ClusterIP 類型

  • foo-clusterip-svc 是 Headless 類型。

  • 創建資源

kubectl apply -f headless-service-demo.yaml
  • 查詢由 Deployment 建立副本的 IP
kubectl get pod -o wide
---
NAME                             READY   STATUS    RESTARTS   AGE     IP            NODE              NOMINATED NODE   READINESS GATES
foo-deployment-66587db9f-285gj   1/1     Running   0          5h50m   10.244.2.31   wslkind-worker2   <none>           <none>
foo-deployment-66587db9f-89s7m   1/1     Running   0          5h50m   10.244.2.32   wslkind-worker2   <none>           <none>
foo-deployment-66587db9f-kfl85   1/1     Running   0          5h50m   10.244.1.13   wslkind-worker    <none>           <none>
  • 查詢剛剛建立的服務
kubectl get svc
---
NAME                TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
foo-clusterip-svc   ClusterIP   10.96.109.160   <none>        8080/TCP   56m
foo-headless-svc    ClusterIP   None            <none>        8080/TCP   56m

可以看到 foo-headless-svc 沒有被分配叢集 IP。

  • 查詢 K8s 裡的 CoreDNS Service IP
kubectl get svc -n kube-system
---
NAME                TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
foo-clusterip-svc   ClusterIP   10.96.109.160   <none>        80/TCP    62m
foo-headless-svc    ClusterIP   None            <none>        80/TCP    62m
kubernetes          ClusterIP   10.96.0.1       <none>        443/TCP   28d

CoreDNS Service IP 是 10.96.0.10

  • 創建並進入測試用 Pod
kubectl run -it --image busybox:1.36 dns-test --restart=Never --rm
  • 查看 foo-clusterip-svc DNS Name
nslookup foo-clusterip-svc.default.svc.cluster.local 10.96.0.10
---
Server:         10.96.0.10
Address:        10.96.0.10:53

Name:   foo-clusterip-svc.default.svc.cluster.local
Address: 10.96.109.160
  • 查看 foo-headless-svc DNS Name
nslookup foo-headless-svc.default.svc.cluster.local 10.96.0.10
---
Server:         10.96.0.10
Address:        10.96.0.10:53


Name:   foo-headless-svc.default.svc.cluster.local
Address: 10.244.2.32
Name:   foo-headless-svc.default.svc.cluster.local
Address: 10.244.1.13
Name:   foo-headless-svc.default.svc.cluster.local
Address: 10.244.2.31

可以看到 foo-headless-svc 服務找到了三個 Endpoint,正好對應了副本數量,也對上了 Pod 副本分配的 IP。我們試著訪問這三個 Endpoint。

/ # wget -q -O - 10.244.2.32:8080/hostname
{"hostname":"foo-deployment-66587db9f-89s7m"}/ #
---
/ # wget -q -O - 10.244.1.13:8080/hostname
{"hostname":"foo-deployment-66587db9f-kfl85"}/ #
---
/ # wget -q -O - 10.244.2.31:8080/hostname
{"hostname":"foo-deployment-66587db9f-285gj"}/ #

當我們使用 Deployment 搭配 Headless Service 時,無法為 Pod 提供個別的 DNS 名稱。通常,如果需要這種功能,會選擇使用 StatefulSet 搭配 Headless Service。在之後的 StatefulSet 章節中,我們會展示這一特性。


上一篇
學 Kubernetes 的第九天 - Pod - 基礎概念與核心特性
下一篇
學 Kubernetes 的第十一天 - Networking - DNS 條目
系列文
都什麼年代了,還在學 Kubernetes13
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言