.

iT邦幫忙

2022 iThome 鐵人賽

DAY 28
0

本文目標:

  • 認識 Kubernetes
  • 介紹幾個 Kubernetes 的建置方案
  • 簡單的 Kubernetes 使用教學

Recap:什麼是 Image、什麼又是 Pod?

在真正了解 Kubernetes 之前,我們必須定義一下什麼是 image:

  • Image 是一個囊括依賴套件與設定檔的套件(它是靜態的,而非處於執行狀態的程式)

而 Pod 是 K8s 中的最小單位,它可以包含一個以上的 Container,但實務上通常只會包含一個 Container:

apiVersion: v1
kind: Pod
metadata:
  namespace: default
  name: bastion
spec:
  containers:
    - name: centos
      image: centos:latest
      command:
      - sh
      - -c
      - while true; do sleep 60; done
  • 上方程式碼描述了一個運作著 centos 的 pod,它會運作在 default 命名空間。
  • Kubernetes 的物件皆可以使用 yaml 檔案描述,提供一個通用、標準化的開發方式,這麼做還能夠降低物件與物件之間的耦合度。

什麼是 Kubernetes?


Kubernetes(或是 K8s)用於管理、部署、擴充容器化的應用程式,該專案最初由 Google 設計,並捐贈給 CNCF 維護。
本篇文章並不會詳細的介紹如何建構 Kubernetes 環境或是基於 Kubernetes 的應用程式,而是盡可能的加深讀者對於 Kubernetes 的認知。

架構圖

Kubernetes 可以由多個 worker node 組合成一個 cluster,為部署在 Kubernetes 上的服務增加保障,下圖是 Kubernetes 的架構圖:

image

上圖取自 Kubernetes 官方文件

參考上圖,我們可以將它拆成兩個面向來看:

  • Master node (control plane)
  • Worker node

Master node 負責調度所有的 worker node,在 Master node 上會運作四個非常重要的 process,它們分別是:

  1. API server
  2. etcd
  3. Controller
  4. Scheduler

API server

API server 是整個 Kubernetes cluster 的入口點,它負責處理來自 client(使用 kubectl)或是 kubelet 的請求。

etcd

etcd 是一個分散式的資料庫專案,它使用 raft 演算法作為共識機制,能夠保證分散式節點上的資料一致性。

  • Kubernetes 使用它存放最重要的資訊
  • 如果有 Pod 故障,它會以 etcd 上存放的資料復原 Pod
  • etcd 是 key/value based database
  • 被 Hyperledger fabric 採納

Controller

Kubernetes 的內部會有多個 controller,它們分別負責不同的任務。

  • 當使用者使用 kubectl,相關命令會改變 etcd 所儲存的內容,而非直接影響 kubernetes 的狀態。
  • Controller 負責觀察這些狀態的變化,並且以最快的速度響應這些變化

Scheduler

Scheduler 負責管理哪些 Pod 能夠獲得 cpu 資源。

  • 當它收到來自 API server 的請求,如果有 Pod 需要分配到資源,它會請求目標 target node 上的 kubelet 執行這個 Pod。

Worker node 可以是實體機器或是虛擬機器,它是應用程式的執行者,每個 Worker node 會運作:

  1. Container runtime
  2. kubelet
  3. kube-proxy

Container runtime

  • Container runtime 運作在每一個工作節點上
  • Container runtime 負責執行 container

kubelet

  • kubelet 負責節點上 pod 的排程
  • kubelet 能夠與 container 以及 worker node 溝通

kube-proxy

  • kube-proxy 同樣在每個節點上工作
  • 負責流量的轉發:外部流量或是 pod 之間的流量該導向哪(也就是 service 物件)
  • 底層使用 iptables 實現轉發功能,所以在效能上還有進步空間(像是利用先前介紹的 xdp 技術)

建置 K8s

目前市面上有多種建置 K8s 的方法,開發者可以根據不同的使用需求或是預算考量選擇一個最佳的方案。

公有雲

目前 GCP、AWS、Azure 都有提供 K8s cluster service,如果服務不到非常龐大,使用公有雲算是個不錯的選擇(不用花錢花時間維護設備,簡易的服務也不會燒太多錢)。

本地部署

如果是選擇在本地環境架設 Kubernetes,那麼就有非常多種選擇了:

  • Kubeadm
    由 Kubernetes 團隊維護的方案,設定多節點環境時會比較複雜,也需要自己處理 CNI。
  • K3D
    K3S(輕量化的 K8s)的 docker 版本,不需要自己處理 CNI,建置多節點也只需要透過指令就能做到。
  • Minikube
    透過 VM 架設 K8s,同樣不需要處理 CNI,並且 minikube 整合了許多功能包,算是入門 kubernetes 的好選擇。
  • KIND (Kubernetes In Docker)
    KIND 顧名思義是將 Kubernetes 以 container 的形式運作起來,比起 Minikube 它有更高的靈活性(建立多節點叢集),並且不需要自己處理 CNI。

筆者的話:
在這邊想要抱怨一下 kubernetes 的命名想法...筆者當初入門的時候一度搞不清楚 kubeadm、kubectl、kubelet 之間的關係,有時候建置方案的選擇太多對新手來說可能也是一種困擾啊...

如何使用 Kubernetes?

image

圖片來源:Kubernetes — Objects (Resources/Kinds) Overview

使用 kubectl apply 建立物件

kubectl apply -f YOUR_POD.yaml

kubectl apply 屬於 Declarative Management,我們可以使用它動態的調整先前已經套用的設定。
間單來說,如果我們已經建立好一個 Pod,並且我們希望修改它的部分設定,那我們可以再撰寫一個 yaml 檔案說明清楚想要調整的欄位後再使用 kubectl apply

使用 kubectl create 建立物件

kubectl create -f YOUR_POD.yaml

kubectl create 屬於 Imperative Management,使用 kubectl create 修改 Pod 的設定時,它會先刪除現有的全部設定再建立新的,因此,如果重複的使用 kubectl create 套件同一份 yaml 檔案會得到失敗的結果。

Pod 的溝通方式

主要有三種:

  1. 使用 shared volumes
  2. 使用 IPC (Inter-process communications)
  3. 使用 Network

補充一:Namespace

namespace 是一個用來組織、隔離 Cluster 資源的機制:

  1. 使用 RoleBindings 做到分隔租戶以及授權存取
  2. 分割出多個虛擬的 sub-clusters
  3. 執行資源管理策略,可以使用 ResourceQuatas 或是 LimitRanges 實現
  4. 使用 NetworkPolicy 區分網路環境

建立 namespace 的方法:

kubectl create namespace ian

建立一個名為 ian 的 namespace。

取得當前 cluster 的所有 namespaces:

kubectl get namespace

指定某個 namespace 為預設的 namespace:

kubectl config set-context -current -namespace=ian

補充二:Pod 資源管理

  • Request 代表請求的最小資源
  • Limit 代表最大上限

如果一個 pod 要求 500m CPU,並且主機有 2 CPU,那麼這一個 pod 可以獲得 2 CPU 的硬體資源

補充三:Labels & Selector

Labels 是一對可辨識的 key/value 形式的標籤,像是:

  • "release": "GA"
  • "env": "production"
  • "stage": "dev"

等等,key 與 value 可以由我們自由定義。
如果將多個 Label 貼上 Pod,我們就可以清楚地知道這個 Pod 的狀態、功能、開發進度...等細節。

此外,K8S 更是提供了 Label selectors 幫助我們根據 label 關鍵字找到我們想要使用的 Pod:

$ kubectl get pods -l environment=production,tier=frontend

或是使用 set-based 的方式尋找:

kubectl get pods -l 'environment in (production, qa),environment notin (frontend)'

Kubernetes 提供了許多物件,像是 Pod、Service、ReplicaSet、Deployment,為了降低物件之間的耦合性,它們在管理資源皆是使用 label 與 selector 做到的,比如說:
假設我撰寫了一份 replicaSet 規格文件:

# Source: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: frontend
  labels:
    app: guestbook
    tier: frontend
spec:
  # modify replicas according to your case
  replicas: 3
  selector:
    matchLabels:
      tier: frontend
  template:
    metadata:
      labels:
        tier: frontend
    spec:
      containers:
      - name: php-redis
        image: gcr.io/google_samples/gb-frontend:v3

當我使用 kubectl apply -f rs.yaml 後,相關的 controller 會去追蹤符合 tier=frontend 條件的 pod 的數量是否為 3,如果不是,Kubernetes 會為我們控制它的數量。

更多使用方法可以參考 kubernetes.io/docs/

補充三:Annotations

前面提到的 Labels 是具有識別用途的標籤,相對的,K8S 也提供了不具識別用途的標籤 Annotations,我們可以在 Annotations 上面打上開發者的聯絡方式或是其他方便開發者使用的資訊。
最後還是要強調,Annotations 與 Labels 最大的差異就是 Annotations 並不會被 K8S 使用

apiVersion: v1
kind: Pod
metadata:
  namespace: ian
  name: nginx-1
  labels:
    app: nginx
  annotations:
    version: latest
    contact: ychen.desl@gmail.com
spec:
  containers:
    - name: nginx
      image: nginx:latest
      ports:
      - containerPort: 80

為 Pod 打上標籤後,可以使用:

$ kubectl describe pods YOUR_POD

查看 Pod 的細節。

Deployment

a Deployment is a higher-level concept that manages ReplicaSets and provides declarative updates to Pods along with a lot of other useful features.
-- Kubernetes 官方文件

在 Kubernetes 中,Deployment、ReplicaSet 與 Pod 之間的關係是很緊密的,在介紹 Deployment 之前我們先來看看 ReplicaSet 是什麼:

# https://kubernetes.io/docs/concepts/workloads/controllers/deployment/
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: frontend
  labels:
    app: guestbook
    tier: frontend
spec:
  # modify replicas according to your case
  replicas: 3
  selector:
    matchLabels:
      tier: frontend
  template:
    metadata:
      labels:
        tier: frontend
    spec:
      containers:
      - name: php-redis
        image: gcr.io/google_samples/gb-frontend:v3

上方是一個 ReplicaSet 的範例程式碼,它會利用 selector 選擇符合目標的 Pod,並且關注 Pod 的數量,如果 Pod 的數量低於 ReplicaSet 的期望值,那麼 Kubernetes 便會為我們擴展 Pod 的數量。

上面的範例可以讓我們了解一件事情:ReplicaSet 是一個用於管理 Pod 的 Kubernetes 物件。
而 Deployment 物件則是幫助我們管理 ReplicaSet 物件的物件,透過建立 Deployment 我們可以觀察到一些有趣的事情:

  • Deployment 會為我們建立 ReplicaSet
  • 由 Deployment 建立的 ReplicaSet 又會為我們建立 Pod

那麼問題來了:看起來 Deployment 只是幫助我們建立了 ReplicaSet,那麼 Deployment 物件有很重要嗎?
Deployment 最大的用途是幫助開發者做到滾動式的更新,滾動式更新是一種能夠不間斷服務的更新方式。在更新 Deployment 的期間,它會建立兩個 ReplicaSet(新版本與舊版本),並且根據使用者的設定逐步的替換舊有的 Pod。
此外,如果在更新後發現新版本不如之前穩定,我們也可以利用 Kubernetes 的紀錄幫助我們 rollout 到舊版本的 Deployment。

Deployment policy

我們可以將 Deployment policy 分為兩大類:

  • Recreate:將舊有的 ReplicaSet 中的 replica 歸零,並且建立新版本的 ReplicaSet。這樣的做法對於系統的效能要求是最小的,但卻有一個致命的缺點:服務會間斷
  • Rolling update:滾動式的更新會逐步的替換舊有的 Pod,直到舊有的 Pod 數量為 0。

要實現這兩種策略,我們需要仰賴 maxUnavailable 或是 maxSurge

  • maxUnavailable:代表最大的不可用 Pod 數量,它的 value 可以是絕對值或百分比,如果 value 為 1,那麼 K8s 對 Pod 逐一汰換(關掉一個後更新一個)。如果是設定為百分比,那麼公式為 replica of ReplicaSet X maxUnavailable。假設 replica 為 10maxUnavailable20%,那麼 K8s 每次都會為我們更新兩個 Pod。
  • maxSurge:它的工作原理會與 maxUnavailable 有些微的不同,使用它進行更新時,它會先建立新版本的 Pod,等到確定新版本的 Pod 建立完成,再關閉對數量的舊版本 Pod。舉例來說:如果 replica 為 10maxSurge20%,那麼 Pod 總數的變化會是 10(10 個舊版本) -> 12(2 個新版本加上 10 個舊版本) -> 10(2 個新版本加上 8 個舊版本)...以此類推。

作者補充:
這篇文章介紹了如何進行 rolling update 以及 rollout,有興趣的朋友們可以參考看看!

新增 Deployment

新增 Deployment 主要可以透過兩個方式:1. apply yaml2. kubectl run

先來談談第一個方式,我們先建立一個描述 Deployment 的 yaml file:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

上面的範例出自 Kubernetes 的官方文件,它是一個用於部署 Nginx 的 deployment。
有了這份檔案後,使用 kubectl apply -f <YOUR_FILE_NAME>.yaml 就可以幫助我們建立資源啦!

第二種方式不需要預先建立檔案,而是用一行命令搞定:

kubectl run web-depl --image="<image>" --replicas=2 --labels="ver=1, app=web, env=production"

移除 Deployment

移除所有 Deployment:

kubectl delete deployment --all

或是使用 selector 指定刪除符合條件的 Deployment:

kubectl delete deployment --selector="<cond>"

如何重啟 Pod?

Kubernetes 並沒有像 Docker 一樣能夠重啟 Container 的機制,如果 Pod 發生問題,那麼就只有將 Pod 刪除再重建這個方法。不過,這麼做可能會帶來一些問題:

  • 如果沒有保留 Pod 的描述檔案,要怎麼重建?
  • 如果我有很多一樣的 Pod,難道要一個一個手動刪除嗎?

為了解決這個問題,我們可以利用 scale Deployment 達到這個目的:

kubectl scale deployment <DEPLOYMENT> --replicas=0 -n <NAME_SPACE>
kubectl scale deployment <DEPLOYMENT> --replicas=<EXPECTED_NUM> -n <NAME_SPACE>

Service

因為 Pods 會被 K8S 動態的控制,所以具有 volatile 的特性,為了讓服務實體可以穩定的將流量或是請求導向它們,我們會需要 Service 的幫忙。

Service 的種類

Type1: ClusterIP

ClusterIP 類型的 Service 只有 Cluster 內部的成員可見,這可以用於保護某些特定的 Service 不被外網存取

Type2: LoadBalancer

LoadBalancer 類型的 Service 就是一個 load balancer,外部成員可以透過存取這類的 Service 取得想要的服務。
如果是在 GKE 上使用 LoadBalancer,GKE 會為它分配一個 IP Address 並將流量導向該服務。

Type3: NodePort

NodePort 類型的 Service 比 ClusterIP 多了指定端口,若不指定端口則會交給 K8S 去處理。

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  type: NodePort
  selector:
    app: nginx
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80

Ingress

我們可以把 Ingress 想像成一個讓 Service 對外的工具,以上面的案例來看 Ingress 更像是 Service 的 Load balancer。
在預設情況下,Kubernetes 並不會安裝 Ingress,它就像是 CNI 一樣需要交給使用者自行挑選、安裝。

總結

這篇文章快速的帶大家認識 Kubernetes 以及基本的使用介紹,不過 Kubernetes 其實是一個相當複雜的工具,所以有很多概念(configMapsecretRBAC)以及底層的知識(CNICRICSI)是沒有在這篇文章提到的,還請讀者們見諒。
明天的文章會注重在如何建構一個 Kubernetes 環境,並且介紹 Helm 這套用於管理 Kubernetes 資源的工具,最後再以 free5GC 的衍生專案為例安裝到我們的 Kubernetes Cluster 上。

References


上一篇
使用 Prometheus 結合 free5GC 實作告警系統
下一篇
使用 Helm 管理 Kubernetes 資源
系列文
5G 核心網路與雲原生開發之亂彈阿翔36
.

尚未有邦友留言

立即登入留言