K8s Service 介紹

[文章目录]
  1. 建立服務
    1. 透過 kubectl expose 建立 service
      1. 提前概要:
      2. Lab案例:
      3. Lab案例觀察
    2. 開放 service 對外存取
      1. 方式:kubectl port-forward 轉發 tcp 連線
      2. 方式:變更現有 service type
      3. 方式:kubectl expose –type=NodePort
  2. 服務探索
    1. 關於 Kube-dns 與 Service CLUSTER-IP 的血緣關係
    2. 關於 Endpoint
    3. 手動探索服務
  3. kube-proxy 與 CLUSTER-IP 愛恨情仇
  4. 服務探測、檢查功能
    1. 探測器定義要點
    2. 三探測模式應用場景
    3. Liveness 定義方式
    4. Readiness 定義方式
    5. 配置說明
      1. HTTP 應用中進行 httpGet 檢查,有其他配置選項:

K8s Service 是為了所部署的應用程式,能夠開放對外連線,確保可提供應用服務使用,發揮其應用價值。
這篇,來了解複雜的 Service 細節,對於部署應用有基本的幫助。


建立服務

透過 kubectl expose 建立 service

提前概要:

  • 從現有的 Deployment、ReplicaSet 物件中,建立 Service
  • kubectl expose 執行條件:物件中必須要有 selector、port flag

相關 log:

1
2
3
4
5
6
$ kubectl expose pod alpaca-prod
error: could not retrieve selectors via --selector flag or introspection: the pod has no labels and cannot be exposed
See 'kubectl expose -h' for help and examples.

$ kubectl expose replicasets alpaca-prod
error: could not find port via --port flag or introspection

Lab案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 建立 replicaset 物件
[afu@k8s ~]$ kubectl apply -f 7-78-kuard-rs.yaml
replicaset.extensions/alpaca-prod created

# Get pod 物件資訊 、 --show-labels
[afu@k8s ~]$ kubectl get pod -o wide --show-labels
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE LABELS
alpaca-prod-5bbdx 1/1 Running 0 15s 172.17.0.5 minikube <none> app=alpaca,env=prod,ver=1
alpaca-prod-88gdw 1/1 Running 0 15s 172.17.0.6 minikube <none> app=alpaca,env=prod,ver=1
alpaca-prod-p749d 1/1 Running 0 15s 172.17.0.7 minikube <none> app=alpaca,env=prod,ver=1


[afu@k8s ~]$ kubectl get replicaset -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
alpaca-prod 3 3 3 27s kuard gcr.io/kuar-demo/kuard-amd64:2 app=alpaca,env=prod,ver=1


[afu@k8s ~]$ kubectl get daemonset -o wide
No resources found.

[afu@k8s ~]$ kubectl get deployment -o wide
No resources found.

# 透過 kubectl expose 建立 service
[afu@k8s ~]$ kubectl expose replicasets alpaca-prod
service/alpaca-prod exposed

Lab案例觀察

1
2
3
4
[afu@k8s ~]$ kubectl get service -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
alpaca-prod ClusterIP 10.103.75.220 <none> 8080/TCP 13s app=alpaca,env=prod,ver=1
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 65m <none>

觀察重點:

  1. 從上面建立 alpaca-prod service 範例當中,觀察 SELECTOR 欄位 app=alpaca,env=prod,ver=1
    kubectl expose 指令是從上述的 replicaset 物件中擷取 label、port 兩參數作為 service flag
  2. 此 service type 是 ClusterIP,並提供一個 CLUSTER-IP 虛擬IP。
  3. 系統會透過 SELECTOR 條件識別,進行 service -> pod 負載平衡。
  4. 目前此 service 無法對外開放存取,僅限於 K8s Cluster。

開放 service 對外存取

從上述 alpaca-prod service 範例中直接進行後續說明
上述範例中,alpaca-prod service 預設採用 CLUSTER-IP 賦予叢集中可以….我還不是很懂此用途
在此要透過另一個 service type:NodePort 方式,開放服務對外連線

方式:kubectl port-forward 轉發 tcp 連線

有關於 port-forward:

  1. 可以轉發 TCP 連線,相對於 kube-proxy 僅能轉發 http 流量,有所不同。
  2. 可參考 K8s port-forward 官方教學頁
1
2
3
4
5
6
7
8
9
10
11
# 針對 service 物件進行 port-forward
[afu@k8s ~]$ kubectl port-forward --address 0.0.0.0 service/alpaca-prod 30111:8080
Forwarding from 0.0.0.0:30111 -> 8080
Handling connection for 30111
Handling connection for 30111

# 針對 pod 物件進行 port-forward
[afu@k8s ~]$ kubectl port-forward --address 0.0.0.0 kuard-config 30333:8080
Forwarding from 0.0.0.0:30333 -> 8080
Handling connection for 30333
Handling connection for 30333

方式:變更現有 service type

將 type: ClusterIP 修改成 type: NodePort

1
2
[afu@k8s ~]$ kubectl edit service alpaca-prod
service/alpaca-prod edited

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
kind: Service
metadata:
creationTimestamp: "2019-01-15T09:05:36Z"
labels:
app: alpaca
env: prod
ver: "1"
name: alpaca-prod
namespace: default
resourceVersion: "5202"
selfLink: /api/v1/namespaces/default/services/alpaca-prod
uid: b87c02f5-18a4-11e9-8813-08002730aeb3
spec:
clusterIP: 10.103.75.220
ports:
- port: 8080
protocol: TCP
targetPort: 8080
selector:
app: alpaca
env: prod
ver: "1"
sessionAffinity: None
type: NodePort
# ^ 將 type: ClusterIP 修改成 type: NodePort
status:
loadBalancer: {}

變更後觀察:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[afu@k8s ~]$ kubectl get service -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
alpaca-prod NodePort 10.103.75.220 <none> 8080:30044/TCP 37m app=alpaca,env=prod,ver=1
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 102m <none>


[afu@k8s ~]$ kubectl describe service alpaca-prod
Name: alpaca-prod
Namespace: default
Labels: app=alpaca
env=prod
ver=1
Annotations: <none>
Selector: app=alpaca,env=prod,ver=1
Type: NodePort
IP: 10.103.75.220
Port: <unset> 8080/TCP
TargetPort: 8080/TCP
NodePort: <unset> 30044/TCP
Endpoints: 172.17.0.5:8080,172.17.0.6:8080,172.17.0.7:8080
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>

方式:kubectl expose –type=NodePort

關於 kubectl expose –type=NodePort,可參考官方說明頁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 建立第二範例 alpaca-prod22
[afu@k8s ~]$ kubectl apply -f 7-78-kuard22-rs.yaml
replicaset.extensions/alpaca-prod22 created

# kubectl expose + --type=NodePort
[afu@k8s ~]$ kubectl expose replicaset alpaca-prod22 --type=NodePort
service/alpaca-prod22 exposed

[afu@k8s ~]$ kubectl get service -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
alpaca-prod NodePort 10.103.75.220 <none> 8080:30044/TCP 53m app=alpaca,env=prod,ver=1
alpaca-prod22 NodePort 10.105.98.135 <none> 8080:30142/TCP 20s app=alpaca,env=prod,ver=2
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 118m <none>

[afu@k8s ~]$ kubectl describe service alpaca-prod22
Name: alpaca-prod22
Namespace: default
Labels: app=alpaca
env=prod
ver=2
Annotations: <none>
Selector: app=alpaca,env=prod,ver=2
Type: NodePort
IP: 10.105.98.135
Port: <unset> 8080/TCP
TargetPort: 8080/TCP
NodePort: <unset> 30142/TCP
Endpoints: 172.17.0.10:8080,172.17.0.8:8080,172.17.0.9:8080
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>

透過 NodePort 開放服務連線,仍需要環境中的 public ip 賦予連線
如果是 GCP 環境,可下指令:gcloud compute instances list
如果是 Minikube,可下指令:kubectl cluster-info


服務探索

服務探索有助於也有必要去發現哪些服務程序正在執行、位於何處執行、IP\Port~等等資訊
如何快速找到正確的物件對象,去探索及解決問題、能夠快速上線服務等等,都是需要依賴服務探索
以下介紹三個關於服務探索的介紹。

關於 Kube-dns 與 Service CLUSTER-IP 的血緣關係

在上述中,有提到 CLUSTER-IP,但不是很清楚有何作用,現在透過 kube-dns 服務,可以利用 FQDN 方式取得與連線 service。
kube-dns 服務是在建立 K8s 環境時,一併預設就會安裝的服務,可以透過此 DNS 服務查詢到 service 與 CLUSTER-IP 關係~

從上述 Lab kuard 服務中,可以透過 Kuard 網頁中 DNS query 功能查詢相關 A 紀錄
kubectl port-forward --address 0.0.0.0 service/alpaca-prod 30111:8080

  • 查詢預設 namespace,只需輸入 server name

    • 例如:alpaca-prod
      ;; QUESTION SECTION:
      ;alpaca-prod.default.svc.cluster.local. IN A

      ;; ANSWER SECTION:
      alpaca-prod.default.svc.cluster.local. 5 IN A 10.103.75.220

  • 查詢不同 namespace,需要輸入 server.namespace

    • 例如:kubernetes-dashboard.kube-system
      ;; QUESTION SECTION:
      ;kubernetes-dashboard.kube-system.svc.cluster.local. IN A

      ;; ANSWER SECTION:
      kubernetes-dashboard.kube-system.svc.cluster.local. 5 IN A 10.109.91.186

關於 Endpoint

K8s 在於建立服務物件時,會一同建立 endpoint 物件
例如稍早 Lab alpaca-prod 服務。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[afu@k8s ~]$ kubectl describe ep alpaca-prod
Name: alpaca-prod
Namespace: default
Labels: app=alpaca
env=prod
ver=1
Annotations: <none>
Subsets:
Addresses: 172.17.0.5,172.17.0.6,172.17.0.7
NotReadyAddresses: <none>
Ports:
Name Port Protocol
---- ---- --------
<unset> 8080 TCP

Events: <none>

[afu@k8s ~]$ kubectl get ep alpaca-prod
NAME ENDPOINTS AGE
alpaca-prod 172.17.0.5:8080,172.17.0.6:8080,172.17.0.7:8080 6h55m

另外也可以用 –watch 持續觀察服務 endpoint 的變化~

1
2
3
4
5
[afu@k8s ~]$ kubectl get ep alpaca-prod22 --watch
NAME ENDPOINTS AGE
alpaca-prod22 172.17.0.10:8080,172.17.0.8:8080,172.17.0.9:8080 4s
alpaca-prod22 172.17.0.10:8080,172.17.0.8:8080,172.17.0.9:8080 2m11s
alpaca-prod22 172.17.0.10:8080,172.17.0.8:8080,172.17.0.9:8080 0s

手動探索服務

稍早提到 label 應用於 service、replicaset、pod
透過 –show-labels 可以顯示出每個物件的 Label 資訊。

1
2
3
4
5
6
7
8
9
# Get pod 物件資訊 、 --show-labels
[afu@k8s ~]$ kubectl get pod -o wide --show-labels
NAME READY STATUS ··· AGE IP NODE ··· LABELS
alpaca-prod-5bbdx 1/1 Running ··· 7h11m 172.17.0.5 minikube ··· app=alpaca,env=prod,ver=1
alpaca-prod-88gdw 1/1 Running ··· 7h11m 172.17.0.6 minikube ··· app=alpaca,env=prod,ver=1
alpaca-prod-p749d 1/1 Running ··· 7h11m 172.17.0.7 minikube ··· app=alpaca,env=prod,ver=1
alpaca-prod22-9zrwf 1/1 Running ··· 21m 172.17.0.8 minikube ··· app=alpaca,env=prod,ver=2
alpaca-prod22-fh44h 1/1 Running ··· 21m 172.17.0.10 minikube ··· app=alpaca,env=prod,ver=2
alpaca-prod22-zdtrv 1/1 Running ··· 21m 172.17.0.9 minikube ··· app=alpaca,env=prod,ver=2

基於 pod 具有 Labels 資訊,在眾多的服務 pods 中,就可以善用 --selector 進行篩選探索出所需要的 pod 物件對象

  • -l, --selector='': Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)
    1
    2
    3
    4
    5
    6
    # 篩選出 ver=2 的 alpaca pod 物件
    [afu@k8s ~]$ kubectl get pod -o wide --selector=ver=2,app=alpaca
    NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
    alpaca-prod22-9zrwf 1/1 Running 0 33m 172.17.0.8 minikube <none>
    alpaca-prod22-fh44h 1/1 Running 0 33m 172.17.0.10 minikube <none>
    alpaca-prod22-zdtrv 1/1 Running 0 33m 172.17.0.9 minikube <none>

回顧下,replicaset、service 也都同樣利用到 selector 去定義、篩選出本身與 pod 物件。


kube-proxy 與 CLUSTER-IP 愛恨情仇

  1. 我對 Service CLUSTER-IP 的見解是摸不著邊,不存在於 Host、CRI network 中,是被包裹在 iptable 中。
  2. CLUSTER-IP 是無法直接存取,需透過kube-proxy、kubectl port-forward等方式存取其中的服務。
  3. kube-proxy 作用於 service 中,每當 service 建立時一同建立 endpoint 紀錄,並依此紀錄進行 iptable 規則改寫,
    所以每當變動pod、endpoint紀錄異動皆會改寫 iptable 規則。
  • 引用官方敘述
    1
    2
    3
    --service-cluster-ip-range ipNet     Default: 10.0.0.0/24
    A CIDR notation IP range from which to assign service cluster IPs.
    This must not overlap with any IP ranges assigned to nodes for pods.

CLUSTER-IP 環境變數,我們可以透過 Kuard 服務來觀察

  • 啟動 kuard [afu@k8s ~]$ kubectl port-forward --address 0.0.0.0 service/alpaca-prod 30111:8080

服務探測、檢查功能

K8s 運行應用程式的容器時,K8s 會利用健康檢查進程方式促進容器維持運作中~
健康檢查進程只是確保 Container 內的應用程式進程能夠一直運行,如沒運行就進行重啟。
但是,僅檢查程序 “是否運行” 是不足夠的,例如進程 deadlock 了,雖進程運行中但無法回應請求,這在健康檢查程序是無法判斷出問題的,且仍認為正常運行中…囧

K8s 支援了兩種探測器:Liveness、Readiness
這兩者 Probe 功能是有差異的~

  • Liveness 是在 pod 已經運行狀態下並且開放請求流量時,進行 pod check 任務,如檢查失敗則進行 pod 重啟,嘗試恢復服務。
  • Readiness 是反過來,就在 Pod 未接受請求流量前就開始進行 pod check,並確認 check ok 之後,pod 才加入 service 行列中開始接受請求流量。
    Liveness 探測器功能,可以針對應用程式的邏輯進行請求,並期望獲得正常回應,藉此判斷應用程式是否合理的運作中。
    Readiness 探測器功能,在於服務部署初始化之後,先行檢查 pod 是否已經準備就緒,檢查後才可開放服務流量進來。

共通點就是,確保 pod 在健康狀態下於 K8s 叢集中運行,有關兩者基本介紹如下:

探測器定義要點

  • 探測器功能是定義於 Pod manifest 檔案中。
  • 每個 Pod 的 Liveness、Readiness 功能是分開個別定義的。
  • 支援三種模式:
    • exec:利用命令腳本方式
    • httpGet: 透過 http 協定回應
    • tcpSocket:嘗試進行 TCP Socket 連線

三探測模式應用場景

  1. 可在容器內執行腳本或程式,定義exec執行腳本,探測後回傳 zero 退出狀態碼,則視為探測正常運作。
  2. 針對 http 服務可以定義httpGet請求,藉此獲得 http response 狀態,判斷此 http 程序是否正常運作。
  3. 針對 TCP Socket 連線狀態進行探測,可以定義tcpSocket

Liveness 定義方式

1
2
3
4
5
6
7
8
9
10
11
12
# Manifest 檔案
spec:
containers:
- image: k8s.gcr.io/echoserver:1.10
livenessProbe:
httpGet:
path: /healthy
port: 8080
initialDelaySeconds: 1
periodSeconds: 10
timeoutSeconds: 1
failureThreshold: 3

Readiness 定義方式

其配置方式與 Liveness 是雷同地:

1
2
3
4
5
6
7
8
9
10
11
12
# 以下僅是配置示意範本
spec:
containers:
- image: k8s.gcr.io/echoserver:1.10
readinessProbe:
httpGet:
path: /healthy
port: 8080
initialDelaySeconds: 1
periodSeconds: 10
timeoutSeconds: 1
failureThreshold: 3

配置說明

Probe 配置中有很多依場景需求而定,盡可能滿足 liveness 和 readiness 的檢查功能:

  • initialDelaySeconds:容器啟動後幾秒之後進行第一次探測任務。
  • periodSeconds:執行探測的頻率間隔,默認是10秒,最小1秒。
  • timeoutSeconds:探測超時時間,默認1秒,最小1秒。
  • successThreshold:繼上次失敗後,最少連續探測成功次數才被認定為成功,默認是 1;
    • 對於 liveness 必須是 1,最小值是 1。
  • failureThreshold:連續探測失敗多少次才被認定為失敗,默認是 3,最小值是 1;
    • liveness 達標情況下將重啟 pod。
    • readiness 在此標準下,認定 pod 未準備就緒。

HTTP 應用中進行 httpGet 檢查,有其他配置選項:

  • host:連線的主機名,預設連線到 pod IP。
  • scheme:連線使用的 schema,默認HTTP。
  • path: 請求 HTTP server 的 path。
  • httpHeaders:定義 HTTP request header。
  • port:容器內所啟用的 HTTP TCP Port,介於 1 和 65525 之間。

有關 HTTP probe,由 kubelet 針對所指定的path、port發送 HTTP 請求進行檢查。
預設,Kubelet 將 probe 發送到容器的 IP 地址,除非設置了host欄位而覆蓋了預設值。
在大多數情況下,是不用設置host欄位。

有一種情況下您可以設置它~
假設容器在 127.0.0.1 Listen service,並且 PodhostNetwork欄位是true,此時,httpGet host是該設置為127.0.0.1
如果 pod 依賴於虛擬主機,這可能是更常見的情況,不建議使用host,而應該利用httpHeaders去定義 host header