kubernetes - day13

September 3, 2020

ReplicaSet 縮寫為 RS,是一種 POD 控制器。用來確保所管控的 POD 副本數在任一時間都能保證用戶所期望的需求。下圖演示了 RS 操作,RS 觸發後會去找匹配 selectorPOD,當前啟動的數量與期望的數量不符合時,會進行一些操作,像是多的數量則刪除,少的話透過 POD 模板建立。當期數量符合用戶定義時則不斷的循環。

from Kubernetes in action

RS 的建立通常以三個字段定義,selectorreplicastemplate,如下圖所示。當中 selectorreplicastemplate 隨時可按照需求做更改。replicas 的定義數量為其直接影響;selector 可能讓當前的標籤不再匹配,讓 RS 不再對當前的 POD 進行控制;template 的修改是對後續創建新 POD 才產生影響。

from Kubernetes in action

對於一般手動建立 PODRS 帶來的好處有以下

必要時可透 HPA(HroizontalPodAutoscaler) 控制器針對資源來自動縮放

建立 RS

再前面描述有說明 RS 重要的資源創建字段有哪些。這邊再說明一個字段 minReadySecond 預設值為 0 秒,在建立新 POD 時,啟動後有多長時間無出現異常就可被視為READY 狀態。以下為建立一個 RS 的範例。

# rs-demo.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myapp
  namespace: default
spec:
  replicas: 2 # 期望數量
  selector: # 標籤選擇器
    matchLabels:
      app: myapp
      release: canary
  template: # POD 模板定義
    metadata:
      name: myapp-pod
      labels:
        app: myapp
        release: canary
        env: qa
    spec:
      containers:
      - name: myapp-container
        image: nginx:1.10
        ports:
        - name: http
          containerPort: 80

使用 apply 部署 yaml 定義的資源

$ kubectl apply -f rs-demo.yaml

查看 POD 清單,有趣的是在 NAME 欄位,只要 PODmyapp 這個 RS 控制,該 POD 名稱前綴會有該 RS 名稱。

$ kubectl get pods -l app=myapp,release=canary -o wide
NAME          READY   STATUS    RESTARTS   AGE   IP          NODE                                       NOMINATED NODE   READINESS GATES
myapp-9xkht   1/1     Running   0          98s   10.4.0.13   gke-cluster-1-default-pool-7dc8b11b-z2gx   <none>           <none>
myapp-mvkjk   1/1     Running   0          98s   10.4.2.12   gke-cluster-1-default-pool-7dc8b11b-r7rg   <none>           <none>

查看 RS 清單,當前按照想要的副本數建立了 2 個 POD

$ kubectl get rs -o wide
NAME    DESIRED   CURRENT   READY   AGE   CONTAINERS        IMAGES       SELECTOR
myapp   2         2         2       63s   myapp-container   nginx:1.10   app=myapp,release=canary

RS 控管 POD

這邊會用幾個場景來說明 RS 的能力。

缺少 POD

可能某些原因導致 RS 控管的 POD 少了幾個,然而 RS 自動的將其補齊。

這邊實作的話要用 tmux 或多開終端機來觀察。

$ kubectl delete pod myapp-9xkht # 嘗試刪除 RS 中一個 POD
pod "myapp-9xkht" deleted
$ kubectl get rs
NAME    DESIRED   CURRENT   READY   AGE
myapp   2         2         2       12m
$ kubectl get pods -l app=myapp,release=canary
NAME          READY   STATUS    RESTARTS   AGE
myapp-flv45   1/1     Running   0          2m53s # 自動新增了
myapp-mvkjk   1/1     Running   0          14m 

利用 -w 持續監控,這邊是監控 RS 所控制的 POD

$ kubectl get pods -l app=myapp,release=canary -o wide -w
NAME          READY   STATUS    RESTARTS   AGE   IP          NODE                                       NOMINATED NODE   READINESS GATES
myapp-9xkht   1/1     Running   0          11m   10.4.0.13   gke-cluster-1-default-pool-7dc8b11b-z2gx   <none>           <none>
myapp-mvkjk   1/1     Running   0          11m   10.4.2.12   gke-cluster-1-default-pool-7dc8b11b-r7rg   <none>           <none>
myapp-9xkht   1/1     Terminating   0          12m   10.4.0.13   gke-cluster-1-default-pool-7dc8b11b-z2gx   <none>           <none>
myapp-flv45   0/1     Pending       0          0s    <none>      <none>                                     <none>           <none>
myapp-flv45   0/1     Pending       0          0s    <none>      gke-cluster-1-default-pool-7dc8b11b-z2gx   <none>           <none>
myapp-flv45   0/1     ContainerCreating   0          0s    <none>      gke-cluster-1-default-pool-7dc8b11b-z2gx   <none>           <none>
myapp-9xkht   0/1     Terminating         0          12m   10.4.0.13   gke-cluster-1-default-pool-7dc8b11b-z2gx   <none>           <none>
myapp-9xkht   0/1     Terminating         0          12m   10.4.0.13   gke-cluster-1-default-pool-7dc8b11b-z2gx   <none>           <none>
myapp-flv45   1/1     Running             0          2s    10.4.0.14   gke-cluster-1-default-pool-7dc8b11b-z2gx   <none>           <none>
myapp-9xkht   0/1     Terminating         0          12m   10.4.0.13   gke-cluster-1-default-pool-7dc8b11b-z2gx   <none>           <none>
myapp-9xkht   0/1     Terminating         0          12m   10.4.0.13   gke-cluster-1-default-pool-7dc8b11b-z2gx   <none>           <none>

假設我們嘗試將 RS 下的 POD 強制移除標籤。

$ kubectl label pods myapp-flv45 app= --overwrite # --overwrite 表示當前要修改的標籤存在,需用此選項選擇覆蓋
pod/myapp-flv45 labeled
$ kubectl get pods -l app=myapp,release=canary
NAME          READY   STATUS    RESTARTS   AGE
myapp-mvkjk   1/1     Running   0          20m
myapp-xt9wr   1/1     Running   0          14s
$ kubectl get pods # myapp-flv45 雖然不被 RS 控管,但它還是回存在
NAME                READY   STATUS    RESTARTS   AGE
myapp-flv45         1/1     Running   0          12m
myapp-mvkjk         1/1     Running   0          24m
myapp-xt9wr         1/1     Running   0          3m45s
pod-nodename-demo   1/1     Running   0          4d4h

從結果來看,myapp-flv45 這個 PODapp 標籤被移除,這不符合 RS 所定義要控管的 POD 標籤,因此該 POD 不再被 RS 控管,同時間少了一個 POD,所以 RS 又建立了 myapp-xt9wr。從這些結果來看,對於刪除或節點故障都會導致永久性消失。

多出 Pod

我們嘗試將 myapp-flv45 的標籤打回去,這樣 RS 就會控管 3 份 POD

$ kubectl label pods myapp-flv45 app=myapp --overwrite
$ kubectl get pods -l app=myapp,release=canary
NAME          READY   STATUS    RESTARTS   AGE
myapp-flv45   1/1     Running   0          18m
myapp-mvkjk   1/1     Running   0          30m
$ kubectl get pods -l app=myapp,release=canary -w
NAME          READY   STATUS    RESTARTS   AGE
myapp-mvkjk   1/1     Running   0          28m
myapp-xt9wr   1/1     Running   0          7m40s
myapp-flv45   1/1     Running   0          17m
myapp-flv45   1/1     Running   0          17m
myapp-xt9wr   1/1     Terminating   0          9m22s
myapp-xt9wr   0/1     Terminating   0          9m22s
myapp-xt9wr   0/1     Terminating   0          9m23s
myapp-xt9wr   0/1     Terminating   0          9m23s

上面結果來說,3 份 POD 對於 myapp 來說多了一份,因此這邊看到它終止了 myapp-xt9wr。這說明了自主式的 POD 或屬於其它控制器的 POD,當標籤資源被做了變動一不小心可能就終止了其它 POD 資源。

查看 RS 資源事件

這邊使用 describe,去查看 RS。其內容如下面結果所式,有名稱、所屬 namespaceselector 定義等。在 Events 中可以看出過往新增或刪除了什麼 PODRS 之所以能做出這些的反應,是因為它有向 API Server 註冊 watch,這樣就可以有相關資源變動訊息,API Server 在變動觸發時會通知給相關有註冊 watch 的客戶端。

$ kubectl describe rs myapp
Name:         myapp
Namespace:    default
Selector:     app=myapp,release=canary
Labels:       <none>
Annotations:  <none>
Replicas:     2 current / 2 desired
Pods Status:  2 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:  app=myapp
           env=qa
           release=canary
  Containers:
   myapp-container:
    Image:        nginx:1.10
    Port:         80/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:
  Type    Reason            Age    From                   Message
  ----    ------            ----   ----                   -------
  Normal  SuccessfulCreate  37m    replicaset-controller  Created pod: myapp-9xkht
  Normal  SuccessfulCreate  37m    replicaset-controller  Created pod: myapp-mvkjk
  Normal  SuccessfulCreate  25m    replicaset-controller  Created pod: myapp-flv45
  Normal  SuccessfulCreate  16m    replicaset-controller  Created pod: myapp-xt9wr
  Normal  SuccessfulDelete  7m22s  replicaset-controller  Deleted pod: myapp-xt9wr

更新 RS

一開始 RS 控管的 POD 使用 nginx:1.10

$ kubectl get rs -o wide
NAME    DESIRED   CURRENT   READY   AGE   CONTAINERS        IMAGES       SELECTOR
myapp   2         2         2       63s   myapp-container   nginx:1.10   app=myapp,release=canary

更新 Template

edit

使用 edit 進行修改,將 nginx:1.10nginx:1.18

$ kubectl edit rs myapp
$ kubectl get rs -o wide
NAME    DESIRED   CURRENT   READY   AGE   CONTAINERS        IMAGES       SELECTOR
myapp   2         2         2       54m   myapp-container   nginx:1.18   app=myapp,release=canary
$ kubectl get pods -l app=myapp,release=canary -o \
> custom-columns=Name:metadata.name,Image:spec.containers[0].image
Name          Image
myapp-flv45   nginx:1.10
myapp-mvkjk   nginx:1.10
apply
$ kubectl apply -f rs-demo.yaml # 將鏡像改 1.15 版
replicaset.apps/myapp configured
$ kubectl get pods -l app=myapp,release=canary -o custom-columns=Name:metadata.name,Image:spec.containers[0].image
Name          Image
myapp-flv45   nginx:1.10
myapp-mvkjk   nginx:1.10
$ kubectl get rs -o wide
NAME    DESIRED   CURRENT   READY   AGE   CONTAINERS        IMAGES       SELECTOR
myapp   2         2         2       59m   myapp-container   nginx:1.15   app=myapp,release=canary

最後發現還是 1.10 版,但 RS 資源卻顯示我們要改的版本。這需要將當前的 POD 進行刪除或修改標籤選擇器,才會觸發新模板的建立,過程如下圖

如果對於新舊的交替也就是更新會有以下操作可能

雖然這樣可以達到以手動方式更新,但 K8s 提供了一個 Deployment 控制器(下一篇會說明),其能夠更完善的實現滾動更新和回滾,同時也可讓客戶端定義更新策略。

POD 伸縮

只要更改 replicas 的數量即可達到 POD 的水平擴充。可用 scale 做更改,也可使用 edit 等方式。

使用 scale 擴展成 5 個 POD

$ kubectl scale rs myapp --replicas=5
$ kubectl get rs myapp
NAME    DESIRED   CURRENT   READY   AGE
myapp   5         5         5       7h34m
$ kubectl get pods -l app=myapp,release=canary
NAME          READY   STATUS    RESTARTS   AGE
myapp-fh6cp   1/1     Running   0          31s
myapp-flv45   1/1     Running   0          7h22m
myapp-mvkjk   1/1     Running   0          7h34m
myapp-ts7zd   1/1     Running   0          31s
myapp-vbtnn   1/1     Running   0          31s

使用 edit 減少至 3 個 POD

$ kubectl edit rs myapp # 找到 rcplicas 字段,進行數量修改
$ kubectl get rs myapp
NAME    DESIRED   CURRENT   READY   AGE
myapp   3         3         3       7h37m
$ kubectl get pods -l app=myapp,release=canary
NAME          READY   STATUS    RESTARTS   AGE
myapp-flv45   1/1     Running   0          7h25m
myapp-mvkjk   1/1     Running   0          7h37m
myapp-ts7zd   1/1     Running   0          3m56s

刪除 RS 資源

可以使用 delete 方式進行 RS 資源刪除。一般使用 deleteRS 會將其控管的 POD 也一併刪除,當中有 --cascade=false 可選,可將 RS 控管的 POD,變成自主式的 POD,同時也少了 RS 控管的優點。

$ kubectl delete -f rs-demo.yaml # 刪除 RS 資源
$ kubectl delete rs myapp # 刪除 RS 資源
$ kubectl delete rs myapp --cascade=false
replicaset.extensions "myapp" deleted
$ kubectl get rs # RS 資源被刪除
No resources found in default namespace.
$ kubectl get pods # 設置了 --cascade=false,讓 POD 變成自主式
NAME                READY   STATUS    RESTARTS   AGE
myapp-flv45         1/1     Running   0          7h32m
myapp-mvkjk         1/1     Running   0          7h44m
myapp-ts7zd         1/1     Running   0          10m

Other

因為實驗環境使用 GKE 這邊實驗將一個節點關閉,觀察 RS 的處裡,這邊先將 replicas 更改為 5 個並重新部署。

$ kubectl get pods -l app=myapp,release=canary -o wide
NAME          READY   STATUS    RESTARTS   AGE     IP          NODE                                       NOMINATED NODE   READINESS GATES
myapp-5t5qf   1/1     Running   0          3m56s   10.4.0.17   gke-cluster-1-default-pool-7dc8b11b-z2gx   <none>           <none>
myapp-dnbf6   1/1     Running   0          3m56s   10.4.1.11   gke-cluster-1-default-pool-7dc8b11b-cxs1   <none>           <none>
myapp-flv45   1/1     Running   0          7h38m   10.4.0.14   gke-cluster-1-default-pool-7dc8b11b-z2gx   <none>           <none>
myapp-mvkjk   1/1     Running   0          7h50m   10.4.2.12   gke-cluster-1-default-pool-7dc8b11b-r7rg   <none>           <none>
myapp-tmft2   1/1     Running   0          3m56s   10.4.2.14   gke-cluster-1-default-pool-7dc8b11b-r7rg   <none>           <none>

這邊將 gke-cluster-1-default-pool-7dc8b11b-r7rg 節點進行網卡關閉,最後結果也是一樣,兩個狀態變為 UnknownRS 又新增兩個,該節點網路如果又恢復,想必又終止 2 個 POD,這過程完全不用人為干預。

$ gcloud compute ssh gke-cluster-1-default-pool-7dc8b11b-r7rg --zone=us-central1-c
$ sudo ifconfig eth0 down
$ kubectl get node
NAME                                       STATUS     ROLES    AGE   VERSION
gke-cluster-1-default-pool-7dc8b11b-cxs1   Ready      <none>   11d   v1.15.12-gke.2
gke-cluster-1-default-pool-7dc8b11b-r7rg   NotReady   <none>   11d   v1.15.12-gke.2
gke-cluster-1-default-pool-7dc8b11b-z2gx   Ready      <none>   11d   v1.15.12-gke.2
$ kubectl get pods -l app=myapp,release=canary
NAME          READY   STATUS    RESTARTS   AGE
myapp-5t5qf   1/1     Running   0          14m
myapp-dnbf6   1/1     Running   0          14m
myapp-flv45   1/1     Running   0          7h49m
myapp-gjpp6   1/1     Running   0          3m8s # 新增
myapp-mvkjk   1/1     Unknown   0          8h # this
myapp-t82p4   1/1     Running   0          3m8s # 新增
myapp-tmft2   1/1     Unknown   0          14m # this

將網路恢復

$ gcloud compute instances reset gke-cluster-1-default-pool-7dc8b11b-r7rg --zone=us-central1-c
Updated [https://www.googleapis.com/compute/v1/projects/sunny-catwalk-286908/zones/us-central1-c/instances/gke-cluster-1-default-pool-7dc8b11b-r7rg].
$ kubectl get node
NAME                                       STATUS   ROLES    AGE   VERSION
gke-cluster-1-default-pool-7dc8b11b-cxs1   Ready    <none>   11d   v1.15.12-gke.2
gke-cluster-1-default-pool-7dc8b11b-r7rg   Ready    <none>   8h    v1.15.12-gke.2
gke-cluster-1-default-pool-7dc8b11b-z2gx   Ready    <none>   11d   v1.15.12-gke.2

當節點網路恢復時,其狀態回到 Ready,並且狀態為 UnknownPOD 將被刪除。

結論

透過本篇文章,詳細的知道 ReplicaSet 的原理以及操作。也比較說與自主式 POD 的差異。而 Replica 能夠實現出對於相同的 POD 的負載均衡和保持 POD 想要維持的數量。但要注意的是 ReplicaSet 只是負責 POD 數量有無符合期望值,並不會知道 POD 的容器是掛掉的還是怎樣。