k8s 中 Pod 的控制器

2022-09-30 09:00:27

k8s 中 Pod 的控制器

前言

Pod 是 Kubernetes 叢集中能夠被建立和管理的最小部署單元。所以需要有工具去操作和管理它們的生命週期,這裡就需要用到控制器了。

Pod 控制器由 master 的 kube-controller-manager 元件提供,常見的此類控制器有 Replication Controller、ReplicaSet、Deployment、DaemonSet、StatefulSet、Job 和 CronJob 等,它們分別以不同的方式管理 Pod 資源物件。

Replication Controller

RC 是 k8s 叢集中最早的保證 Pod 高可用的 API 物件。它的作用就是保證叢集中有指定數目的 pod 執行。

當前執行的 pod 數目少於指定的數目,RC 就會啟動新的 pod 副本,保證執行 pod 數量等於指定數目。

當前執行的 pod 數目大於指定的數目,RC 就會殺死多餘的 pod 副本。

直接上栗子

cat <<EOF >./pod-rc.yaml
apiVersion: v1
kind: ReplicationController
metadata:
  name: nginx
spec:
  replicas: 3
  selector:
    app: nginx
  template:
    metadata:
      name: nginx
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
EOF

在新版的 Kubernetes 中建議使用 ReplicaSet (RS)來取代 ReplicationController。

ReplicaSet 跟 ReplicationController 沒有本質的不同,只是名字不一樣,但 ReplicaSet 支援集合式 selector。

關於 ReplicationController 這裡也不展開討論了,主要看下 ReplicaSet。

ReplicaSet

RS 是新一代 RC,提供同樣的高可用能力,區別主要在於 RS 後來居上,能支援支援集合式 selector。

副本集物件一般不單獨使用,而是作為 Deployment 的理想狀態引數使用。

下面看下 Deployment 中是如何使用 ReplicaSet 的。

Deployment

一個 Deployment 為 Pod 和 ReplicaSet 提供宣告式的更新能力,每一個 Deployment 都對應叢集中的一次部署。

一般使用 Deployment 來管理 RS,可以用來建立一個新的服務,更新一個新的服務,也可以用來捲動升級一個服務。

捲動升級一個服務,捲動升級一個服務,實際是建立一個新的 RS,然後逐漸將新 RS 中副本數增加到理想狀態,將舊 RS 中的副本數減小到 0 的複合操作;這樣一個複合操作用一個 RS 是不太好描述的,所以用一個更通用的 Deployment 來描述。

舉個栗子

cat <<EOF >./nginx-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels: # 這裡定義需要管理的 pod,通過 Pod 的標籤進行匹配
      app: nginx
  template:
    metadata:
      labels: # 執行的 pod 的標籤
        app: nginx
    spec:
      containers: # pod 中執行的容器
      - name: nginx
        image: nginx:1.14.2 
        ports:
        - containerPort: 80
EOF

部署

$ kubectl apply -f nginx-deployment.yaml -n study-k8s
deployment.apps/nginx-deployment created

$ study-k8s kubectl describe deployment nginx-deployment -n study-k8s
Name:                   nginx-deployment
Namespace:              study-k8s
CreationTimestamp:      Mon, 26 Sep 2022 09:06:16 +0800
Labels:                 app=nginx
Annotations:            deployment.kubernetes.io/revision: 1
Selector:               app=nginx
Replicas:               3 desired | 3 updated | 3 total | 1 available | 2 unavailable
StrategyType:           RollingUpdate # 預設是捲動更新  
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge  # 捲動更新的策略,最大 25% 不可用,最大 25% 增加
Pod Template:
  Labels:  app=nginx
  Containers:
   nginx:
    Image:        nginx:1.14.2
    Port:         80/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      False   MinimumReplicasUnavailable
  Progressing    True    ReplicaSetUpdated
OldReplicaSets:  <none>
NewReplicaSet:   nginx-deployment-66b6c48dd5 (3/3 replicas created)
Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  3s    deployment-controller  Scaled up replica set nginx-deployment-66b6c48dd5 to 3

可以看到 Deployment 中預設的更新方式捲動更新,並且預設的捲動更新的策略是 最大 25% 不可用,最大 25% 增加。

更新 Deployment

1、直接更新

$ kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.20.1 -n study-k8s

# 或者  

$ kubectl set image deployment/nginx-deployment nginx=nginx:1.20.1 -n study-k8s

2、對 Deployment 執行 edit 操作

$ kubectl get deployment -n study-k8s
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3/3     3            3           18m

$ kubectl edit deployment -n study-k8s
# 修改對應的映象版本,儲存即可  

    spec:
      containers:
      - image: nginx:1.20.3
        imagePullPolicy: IfNotPresent
        name: nginx

3、直接編輯部署的 YAML 檔案,然後重新 Apply

$ kubectl apply -f nginx-deployment.yaml -n study-k8s
deployment.apps/nginx-deployment configured

$ kubectl get pods -n study-k8s
NAME                                READY   STATUS              RESTARTS   AGE
nginx-deployment-6bcf8f4884-dxxxg   1/1     Running             0          38s
nginx-deployment-6bcf8f4884-plbkh   0/1     ContainerCreating   0          13s
nginx-deployment-cc4b758d6-lzlxl    1/1     Running             0          7m25s
nginx-deployment-cc4b758d6-r5rkb    1/1     Running             0          7m11s

回滾 deployment

瞭解瞭如何更新 deployment,那麼當部署出現問題,如果回滾呢,下面來詳細介紹下

檢視之前部署的版本

$ kubectl rollout history deployment/nginx-deployment -n study-k8s
deployment.apps/nginx-deployment
REVISION  CHANGE-CAUSE
1         <none>
2         <none>
3         <none>
4         <none>
5         <none>
6         <none>
7         image updated to 1.21.1

REVISION:就是之前部署的版本資訊;

CHANGE-CAUSE:變動的備註資訊。

$ kubectl apply -f nginx-deployment.yaml -n study-k8s --record

加上 --record,或者每次部署完之後,使用

$ kubectl annotate deployment/nginx-deployment kubernetes.io/change-cause="image updated to 1.21.1" -n study-k8s

CHANGE-CAUSE 資訊就會被記錄

檢視歷史版本的詳細詳細

$ kubectl rollout history deployment/nginx-deployment --revision=6 -n study-k8s
deployment.apps/nginx-deployment with revision #6
Pod Template:
  Labels:	app=nginx
	pod-template-hash=d985dd8bf
  Containers:
   nginx:
    Image:	nginx:1.20.3
    Port:	80/TCP
    Host Port:	0/TCP
    Environment:	<none>
    Mounts:	<none>
  Volumes:	<none>

回滾

# 回滾到上一個版本
$ kubectl rollout undo deployment/nginx-deployment -n study-k8s

# 回滾到指定的版本
$ kubectl rollout undo deployment/nginx-deployment --to-revision=5 -n study-k8s

縮放 Deployment

$ kubectl scale deployment/nginx-deployment --replicas=10 -n study-k8s

$  kubectl get pods -n study-k8s
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-66b6c48dd5-5scbj   1/1     Running   0          44s
nginx-deployment-66b6c48dd5-7nmtp   1/1     Running   0          44s
nginx-deployment-66b6c48dd5-f5xsg   1/1     Running   0          12m
nginx-deployment-66b6c48dd5-fnpnb   1/1     Running   0          12m
nginx-deployment-66b6c48dd5-gg4ng   1/1     Running   0          44s
nginx-deployment-66b6c48dd5-qd5z7   1/1     Running   0          44s
nginx-deployment-66b6c48dd5-qqh4m   1/1     Running   0          44s
nginx-deployment-66b6c48dd5-xww49   1/1     Running   0          44s
nginx-deployment-66b6c48dd5-zndlh   1/1     Running   0          44s
nginx-deployment-66b6c48dd5-zp45g   1/1     Running   0          12m

關於使用 deployment 實現 k8s 中的幾種部署策略,可參見Kubernetes 部署策略詳解

StatefulSet

StatefulSet 用來管理有狀態的應用。

在 Pods 管理的基礎上,保證 Pods 的順序和一致性。與 Deployment一樣,StatefulSet 也是使用容器的 Spec 來建立Pod,與之不同 StatefulSet 建立的 Pods 在生命週期中會保持持久的標記(例如Pod Name)。

StatefulSet 適用於具有以下特點的應用:

1、穩定的、唯一的網路識別符號;

2、穩定的、持久的儲存;

3、有序的、優雅的部署和擴縮;

4、有序的、自動的捲動更新。

那麼什麼是有狀態服務什麼是無狀態服務呢?

無狀態服務

無狀態服務不會在本地儲存持久化資料。多個服務範例對於同一個使用者請求的響應結果是完全一致的。這種多服務範例之間是沒有依賴關係,比如web應用,在k8s控制器 中動態啟停無狀態服務的pod並不會對其它的pod產生影響。

有狀態服務

有狀態服務需要在本地儲存持久化資料,典型的是分散式資料庫的應用,分散式節點範例之間有依賴的拓撲關係。

比如,主從關係。 如果 K8S 停止分散式叢集中任 一範例 pod,就可能會導致資料丟失或者叢集的 crash。

來個栗子

cat <<EOF >./pod-statefulSet.yaml

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  selector:
    matchLabels:
      app: nginx # 必須匹配 .spec.template.metadata.labels
  serviceName: "nginx"
  replicas: 3 # 預設值是 1
  template:
    metadata:
      labels:
        app: nginx # 必須匹配 .spec.selector.matchLabels
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: nginx
        image: nginx:1.14.2 
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi
EOF

DaemonSet

DaemonSet:主要是用來保證叢集中的每個節點只執行一個 Pod,且保證只有一個 Pod,這非常適合一些系統層面的應用,例如紀錄檔收集、資源監控等,這類應用需要每個節點都執行,且不需要太多範例,一個比較好的例子就是 Kubernetes 的 kube-proxy。

當有節點加入叢集時, 也會為他們新增一個 Pod 。 當有節點從叢集移除時,這些 Pod 也會被回收。

DaemonSet 的應用場景:

1、在每個節點上執行叢集守護行程;

2、在每個節點上執行紀錄檔收集守護行程;

3、在每個節點上執行監控守護行程。

來個栗子

cat <<EOF >./nginx-daemonset.yaml

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: nginx-daemonset
  labels:
    app: nginx-daemonset
spec:
  selector:
    matchLabels:
      app: nginx-daemonset
  template:
    metadata:
      labels:
        app: nginx-daemonset
    spec:
      containers:
      - name: nginx-daemonset
        image: nginx:alpine
        resources:
          limits:
            cpu: 250m
            memory: 512Mi
          requests:
            cpu: 250m
            memory: 512Mi
      imagePullSecrets:
      - name: default-secret
EOF

這樣就能在每個節點中部署一個 Pod 了,不過 DaemonSet 也支援通過 label 來選擇部署的目標節點

cat <<EOF >./nginx-daemonset.yaml

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: nginx-daemonset
  labels:
    app: nginx-daemonset
spec:
  selector:
    matchLabels:
      app: nginx-daemonset
  template:
    metadata:
      labels:
        app: nginx-daemonset
    spec:
      nodeSelector: # 節點選擇,當節點擁有nodeName=node7時才在節點上建立Pod
        nodeName: node7
      containers:
      - name: nginx-daemonset
        image: nginx:alpine
        resources:
          limits:
            cpu: 250m
            memory: 512Mi
          requests:
            cpu: 250m
            memory: 512Mi
      imagePullSecrets:
      - name: default-secret
EOF

Job 和 CronJob

Job 和 CronJob 是負責處理定時任務的。

兩者的區別主要在於:

Job 負責處理一次性的定時任務,即僅需執行一次的任務;

CronJob 是基於時間的 Job,就類似於 Linux 系統的 crontab 檔案中的一行,在指定的時間週期執行指定的 Job。

首先來建立一個 Job

cat <<EOF >./job-demo.yaml

apiVersion: batch/v1
kind: Job
metadata:
  name: job-demo
spec:
  template:
    metadata:
      name: job-demo
    spec:
      restartPolicy: Never
      containers:
      - name: counter
        image: busybox
        command:
        - "bin/sh"
        - "-c"
        - "echo hello"  
EOF

執行

$ kubectl apply -f job-demo.yaml

$ kubectl get pods
NAME             READY   STATUS      RESTARTS   AGE
job-demo-8qrd9   0/1     Completed   0          40s
$ study-k8s kubectl get job
NAME       COMPLETIONS   DURATION   AGE
job-demo   1/1           36s        45s
$ kubectl logs -f job-demo-8qrd9
hello

再來看下 CronJob

cat <<EOF >./cronJob-demo.yaml

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "* * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox:1.28
            imagePullPolicy: IfNotPresent
            command:
            - /bin/sh
            - -c
            - date; echo Hello
          restartPolicy: OnFailure
EOF

通過 kubectl get jobs --watch 就能檢視 CronJob 的排程

$ kubectl get jobs --watch
NAME               COMPLETIONS   DURATION   AGE
hello-1664449860   1/1           2s         4s
hello-1664449920   0/1                      0s
hello-1664449920   0/1           0s         0s
hello-1664449920   1/1           2s         2s

總結

關於 pod 中的幾個控制器最常用的就是 Deployment 和 ReplicaSet;

使用 Deployment 來管理 RS,來實現服務的部署,更新和捲動升級;

StatefulSet 主要用來管理無狀態應用;

DaemonSet:主要是用來保證叢集中的每個節點只執行一個 Pod,且保證只有一個 Pod,這非常適合一些系統層面的應用,例如紀錄檔收集、資源監控等,這類應用需要每個節點都執行,且不需要太多範例,一個比較好的例子就是 Kubernetes 的 kube-proxy;

Job 和 CronJob 是負責處理定時任務的;

Job 負責處理一次性的定時任務,即僅需執行一次的任務;

CronJob 是基於時間的 Job,就類似於 Linux 系統的 crontab 檔案中的一行,在指定的時間週期執行指定的 Job。

參考

【Deployments】https://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/deployment/
【Kubernetes 部署策略詳解】https://www.qikqiak.com/post/k8s-deployment-strategies/
【StatefulSet 和 Deployment 區別及選擇方式】https://blog.csdn.net/nickDaDa/article/details/90401635
【K8S: 有狀態 vs 無狀態服務】https://zhuanlan.zhihu.com/p/390440336
【StatefulSet】https://support.huaweicloud.com/basics-cce/kubernetes_0015.html
【k8s 中 Pod 的控制器】https://boilingfrog.github.io/2022/09/30/k8s中Pod控制器/