0%

Kubernetes 筆記 - Component 1

前言

上篇提到使用 Kubernetes 情境的前因後果以及它的基礎架構。這次本篇主要在紀錄 Kubernetes 常用的部分元件的介紹和如何使用:

  • Pod
  • Label
  • ReplicaSet
  • DaemonSet

kubernetes Component

k8s 物件的概念

k8s 常見的物件像是 PodServicedeploymentingress 等等

使用 Kubernetes API 時,是以 JSON 或 YAML 檔案來定義物件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# This's a yaml file to describe the spec and state of Pod objects.
apiVersion: v1
kind: Pod
metadata:
name: kubia-manual
labels:
app: kubia
spec:
containers:
- image: oliwave/kubia
name: kubia
ports:
- containerPort: 8080
protocol: TCP

k8s 物件和 RESTful API 的關係

  • 任何資源在 k8s 當中都可以用 RESTful 的 URI 來表示
    1
    https://your-k8s.com/api/v1/namespaces/default/pods/my-pod
  • 這些資源在 k8s 當中都叫做 Kubernetes objects 。
  • 每一個 kubernetes objects 都存在 唯一的 HTTP/HTTPS 路徑

取得 Kubernetes objects 最基本的 kubectl 指令是 get

kubectl get <resource-name>

列出在目前 namespaces 底下的所有資源,然而若想要取得特定資源

kubectl get <resource-name> <obj-name>

想要知道該物件的詳細資訊

kubectl describe <resource-name> <obj-name>


-o 參數的意義

在預設的情況下, kubectl 的返回結果是 human-readable ,但這會導致許多詳細的資訊被省略掉。如果想要得到完整的 物件 可以加上 -o 參數並指定想要的格式

-o json/yaml

更進階的做法還可以從返回的物件中指定想要看到的 field

kubectl get pods my-pod -o jsonpath –template={.status.podIP}


Creating, Updating 物件

範例

假設有一個簡單的物件在 obj.yaml 中定義好了。可以透過 kubectl 在 Kubernetes 中 創建和更新 該物件

kubectl apply -f obj.yaml

每個物件會有 metadata 這個欄位,用以定義物件唯一的名字

1
2
3
4
5
#...
kind: Pod
metadata:
name: inventory
#...

Namespaces

Kubernetes 支持由同一 physical cluster 支持的多個 virtual clusters。這些 virtual clusters 稱為 Namespaces

namespaces 提供了名稱範圍。資源名稱在名稱空間內必須唯一,但在各個名稱空間之間是不用的。namespaces 彼此是平行的關係,

物件的名字在一個 namespace 底下是唯一的

  • namespaces 用來組織在叢集中的 物件
  • 可以把每一個 namespaces 都想像成一個 資料夾 管理自己的 物件
  • kubectl 預設是和 default namespaces 互動
  • 如果想列出叢集中所有的 pods ,可以用 --all-namespaces 作為參數
  • 不同的使用者應該有不同的 namespaces ,也需要有權限的管理

創建 Namespace

1
2
3
4
apiVersion: v1
kind: Namespace # 資源的類型
metadata:
name: custom-namespace # Namespace 的名字

在不同的 namespace 創建資源

kubectl create -f kubia-manual.yaml -n custom-namespace

Pods

Pod 基本概念

  1. K8s 將多個 container 變成一組單位 Pod (Pod 有一群鯨魚的意思)
  2. Pod 是 K8s 中可部署的最小單位,也就是所有在 Pod 裡的 containers 都運行在同一個 worker node 上面。
  3. 每一個在 Pod 裡面的 container 都擁有自己的 cgroup ,但卻共用一定數量的 namespaces
  4. 應用程式在同個 Pod 中共享同個 NetworkUTS (hostname)、 IPC namespaces ,因此可以用 http://localhost:\<port_num> 來彼此溝通 (IPC)。相反的應用程式在不同的 Pod 是隔離的,所以有不同的 IP 、 hostname 等等。

Container 的概念

使用 Linux 的兩個技術

  • Namespaces 確保每一個 process 都是由自己的觀點去看整個系統 (files, processes, network interfaces, hostname, and so on) 軟體
  • Cgroups 限制每一個 process 可以使用的資源 (CPU, memory, network bandwidth, and so on) 硬體

有許多種 Namespaces 而每一種都是在隔離不同種的資源


Pod IP

對 K8s 來說,不管 Pod 在哪一個 Node 上都可以把它們想像成在同一個 LAN 底下。因此彼此是可以訪問對方的,這是透過 CNI (overlay network) 來實現的。

如何創建一個 Pod

事實上可以透過 kubectl 用以下幾種 K8s 提供的方式來創造物件

  • Imperative command 立即指令(不建議使用
    • kubectl run kuard –generator=run-pod/v1 –image=gcr.io/kuar-demo/kuard-amd64:blue

  • Declarative configuation => 撰寫 Pod manifest
    • kubectl apply -f pod.yaml

  1. 撰寫簡易的 Pod Manifest
1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Pod
metadata:
name: kuard
spec:
containers:
- image: gcr.io/kuar-demo/kuard-amd64:blue
name: kuard
ports:
- containerPort: 8080
name: http
protocol: TCP

什麼是 Pod Manifest

  • Pod Manifest 就只是一個用 json 或 yaml 格式來描述 Pod 物件 的文字檔案
  • 該文件描述 Pod 的 ideal state ,因此這是一種 declarative configuration 而不是 imperative configuration
  • 事實上撰寫 Pod Manifest (或其他 K8s 物件) 是比較推薦的
    • 當 source code 在維護
    • 更容易閱讀

  1. 把撰寫好的 Pod Manifest 透過 kubectl 丟到 K8s API server

    • kubectl apply -f kuard-pod.yaml

  2. K8s API server 會做兩件事:

    1. 透過 Kube Scheduler 會決定要放在哪個 worker node 上。
    2. Kubelet 屆時會負責把相對應寫在 Pod manifest 裡的 container 都運行起來,並且監控。

以上都步驟都完成後,可以查看所有 Pod 的狀況

kubectl get pods

或是檢查單一 Pod 的詳細資訊

kubectl describe pods kuard

終止 pod

kubectl delete pods kuard

:::info

還有許多可以對 Pod 做的設定…

Grace Period

事實上當我們刪除一個 Pod 時 K8s 會做以下事情

  1. 讓 Pod 拒絕接收新的 request,並消化當前還有的 request
  2. 在預設時間 30 秒過後才把 Pod 刪除掉

以上的 30 秒就是所謂的 Grace period ,這麼做是為了確保服務的可靠性。我們不希望有任何請求是因為要刪除 Pod 而無法回應到的

Health Check

最簡易的 health check 就是 process health check 。也就是直接判斷該程序是否還繼續執行著。但這會有一個很大的問題就是萬一程序進入 dead lock ,事實上該程序仍然運行的但是卻無法再提供服務了。

因此在 K8s 引進了幾種方法來解決

Liveness Probe

就是執行一個任務,如果通過了他就還健在。常見的就是發 http request 檢查在 Pod 裡的 container 是否健康~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: v1
kind: Pod
metadata:
name: kuard
spec:
containers:
- image: gcr.io/kuar-demo/kuard-amd64:blue
name: kuard
livenessProbe:
httpGet:
path: /healthy # 測試該容器是否健康的請求
port: 8080
initialDelaySeconds: 5 # 在 Pod 被初始化後的 5 秒才開始測試
timeoutSeconds: 1 # 限時 1 秒回應
periodSeconds: 10 # 每 10 秒一次
failureThreshold: 3 # 超過三次失敗就重起該 container
ports:
- containerPort: 8080
name: http
protocol: TCP
Readiness Probe

readiness probe 和 liveness probe 最大的不一樣就是 liveness probe 確認應用程式是否正確執行,而它則是 檢查該應用程式是不是已經準備好可以開始服務了

其他的 K8s 所提供的 Health Check

除了 HTTP 以外,還有 tcpSocket health checks 或是讓容器執行一段 script 來檢查。這麼做是因為不是每一個服務都是 http-base appliction.



MISC

以下都是不建議直接使用 kubectl 去操作的 (違反 declarative configuration 精神)

有更適當的工具或方法去完成這些任務。

  1. 取得 Pod 的 Logs (fluentd and elasticsearch)
  2. 在 container 裡面執行指令
  3. 從 container 裡面複製檔案到外面,反之亦然

Resource Management

Pod 在資源(cpu, mem, gpu)的請求上在 K8s 有兩種方式

  • minimum 該容器最少需要多少該種類型的資源 (情況允許,可以多給)
  • limit 該容器最多可以擁有該種類型的資源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: v1
kind: Pod
metadata:
name: kuard
spec:
containers:
- image: gcr.io/kuar-demo/kuard-amd64:blue
name: kuard
resources:
requests:
cpu: "500m"
memory: "128Mi"
limits:
cpu: "1000m"
memory: "256Mi"
ports:
- containerPort: 8080
name: http
protocol: TCP
  1. 記住資源的請求是以 container 為單位,因為不同服務需要的資源種類和數量都不一樣
  2. 一個 Pod 所請求的資源就是其所有 containers 資源請求的總和
  3. 記憶體與 CPU 資源的分配不一樣,因為記憶體一旦分配給一個程序就不能再收回。因此當系統消耗完記憶體的資源時,就會把取得過多的 contaienr 重啟以達重新分配記憶體資源

在 Pod 中不同類型的 volume

  • 生命週期與 Pod 相同的 volume (emptyDir)
    • For Communication/synchronization
      • 容器間可以共用一些資料
    • For Cache
      • 有時容器需要或被重啟,那就可以緩衝一些運算昂貴的資料
  • 長期維持的 Persistent volume Persistent
    • 不管是 private 或 public 的 provider , k8s 都有提供 api
    • e.g. NFS, iSCSI, Amazon’s Elastic Block Store, Azure’s Files and Disk Storage, Google’s Persistent Disk
      1
      2
      3
      4
      5
      volumes:
      - name: "kuard-data"
      nfs:
      server: my.nfs.server.local
      path: "/exports"
    • Mounting the host filesystem (hostPath)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      apiVersion: v1
      kind: Pod
      metadata:
      name: kuard
      spec:
      volumes:
      - name: "kuard-data"
      hostPath:
      path: "/var/lib/kuard"
      containers:
      - image: gcr.io/kuar-demo/kuard-amd64:blue
      name: kuard
      volumeMounts:
      - mountPath: "/data"
      name: "kuard-data"
      ports:
      - containerPort: 8080
      name: http
      protocol: TCP

不像 docker ,沒有簡單的方法~

Labels

  • Labels 是 鍵值對
  • Labels 提供了為 物件分組 的基礎(該物件可以是 pod, replicaSet …)

概念

Labels 提供一群物件可被識別的標記,這為之後分組、操作、查看等動作帶來方便。

在 K8s 中物件要尋找或參考到其他物件的時候就需要用到 selector 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: kubia
spec:
replicas: 3
selector: # ReplicaSet 用 label 來尋找它管理的物件
matchLabels:
app: test
template: # PodTemplate
metadata:
labels: # 將 pod 貼上 `app: test` 這個 label
app: test
spec:
containers:
- name: kubia
image: luksa/kubia



Selector 底下分兩種方式來參考到含有該 label 的物件:

  1. matchLabelsmatchExpressions
    • 只有 JobDeploymentReplicaSet 還有 DaemonSet 有支援
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      selector:
      matchLabels:
      app: nginx
      tier: frontend
      # or
      #matchExpressions:
      #- key: app # 鍵 必須為 'app'
      # operator: In # 值 必須為 'kubia'
      # values:
      # - kubia
  2. 什麼都不加,直接放 label
    1
    2
    3
    selector:
    app: nginx
    tier: frontend

(matchLabels, labels, and selectors 差別)


ReplicaSet & DaemonSet

  • RS 定義一個 Pod 數量讓他們隨機散佈在 K8s cluster 中

  • DS 特殊需求 ,要讓 cluster 中的每個 worker node 都有一個該服務的 Pod 。

    • 例如,像是 log collector 或是 memory monitor 的程式需要在每個節點上。
    • 在 K8s 系統中 kube-proxy Pod 也是運行在每一個節點上。

ReplicaSet

  1. 通常不會直接使用 ReplicaSet 而是用更高層次的 Deployment
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: kubia
spec:
replicas: 3
selector:
matchLabels:
app: kubia
#matchExpressions:
#- key: app # 鍵 必須為 'app'
# operator: In # 值 必須為 'kubia'
# values:
# - kubia
template:
metadata:
labels:
app: kubia
spec:
containers:
- name: kubia
image: luksa/kubia

詳情可見 label selector

DaemonSet

DS 只會在每一個 Node 上執行一個 Pod

因此 DaemonSet 不需要透過 K8s scheduler 來安排 Pod 在哪個節點上

將 Pod 部署到特定的節點上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: ssd-monitor
spec:
selector:
matchLabels:
app: ssd-monitor # 管理 label 為 `app:ssd-monitor` 的 Pod
template:
metadata:
labels:
app: ssd-monitor
spec:
nodeSelector: # PodSpec 定義的 field
disk: ssd # 把 Pod 部署到有 ssd 的節點上
containers:
- name: main
image: luksa/ssd-monitor

  • 如果 nodeSelector 沒有成功找到符合的節點,就不會創建 Pod
  • 刪除 RS DS 都會連帶刪除旗下的 Pods

daemonset 也可以用 label selector 部署 pod 到特定的 node 上