k8sノードのスケールアップ検証

# モチベーション

webアプリの運用であればk8sクラスタをスケールアウトするが、HPCジョブやAI学習等の高負荷なワークロードを実行する際はノードをスケールアップしたい要件があると思われるため、検証してみた。

# 検証内容

1. ノードのスペックを超えるPodを作成<br />
このとき、Podはpending状態になる想定
2. ノードをスケールアップ

手順2の実施後、pending状態であったPodが、スケールアップしたノード上で起動するか確認する。

# 環境

* kubernetes
    * バージョン：v1.21.6
    * デプロイツール：kubespray v2.17.1
    * ノード構成：controlplane × 1, worker × 2
    * 各ノードのスペック
        * 仮想CPU数：2
        * RAMサイズ：4GB
* ノード
    * OS：Ubuntu 20.04.3
    * ノードの種類：OpenStackのVM

# 検証結果

k8sから見えているスケールアップ前のノードのスペックは以下の通り。

```
$ k describe nodes kube-worker02
Name:               kube-worker02

... snip ...

Capacity:
  cpu:                2
  ephemeral-storage:  50633164Ki
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             4030532Ki
  pods:               110

... snip ...

```

ノードのスペックを超えるリクエストをもつPodをつくる。

```yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: nginx
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx
    resources:
      requests:
        memory: "5Gi"
        cpu: "3"
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}
```

```
$ k get pod
NAME    READY   STATUS    RESTARTS   AGE
nginx   0/1     Pending   0          4s
$ k describe pod nginx

... snip ...

Events:
  Type     Reason            Age                From               Message
  ----     ------            ----               ----               -------
  Warning  FailedScheduling  22s (x2 over 24s)  default-scheduler  0/3 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate, 2 Insufficient cpu, 2 Insufficient memory.
```

Podのリクエストがノードのスペックを超えるため、Podはpending状態となった。

つづいて、workerノード1台を以下のようにスケールアップする。

* vCPU数：2 => 8
* RAMサイズ：4GB => 12GB

```
# openstack flavor list
+--------------------------------------+--------------------------------------+-------+------+-----------+-------+-----------+
| ID                                   | Name                                 |   RAM | Disk | Ephemeral | VCPUs | Is Public |
+--------------------------------------+--------------------------------------+-------+------+-----------+-------+-----------+

... snip ...

| 8937a33f-7cbd-491d-9b9a-0e5170cc6274 | m1.large                             | 12288 |    0 |         0 |     8 | True      |

... snip ...

+--------------------------------------+--------------------------------------+-------+------+-----------+-------+-----------+
# openstack server resize --flavor 8937a33f-7cbd-491d-9b9a-0e5170cc6274 f127a4db-03b3-4baa-b0e2-b90219370c42
# openstack server show f127a4db-03b3-4baa-b0e2-b90219370c42
+-------------------------------------+----------------------------------------------------------+
| Field                               | Value                                                    |
+-------------------------------------+----------------------------------------------------------+

... snip ...

| OS-EXT-STS:vm_state                 | resized                                                  |

... snip ...

| flavor                              | m1.large (8937a33f-7cbd-491d-9b9a-0e5170cc6274)          |

... snip ...

+-------------------------------------+----------------------------------------------------------+
# openstack server resize --confirm f127a4db-03b3-4baa-b0e2-b90219370c42
# openstack server show f127a4db-03b3-4baa-b0e2-b90219370c42
+-------------------------------------+----------------------------------------------------------+
| Field                               | Value                                                    |
+-------------------------------------+----------------------------------------------------------+

... snip ...

| OS-EXT-STS:vm_state                 | active                                                   |

... snip ...

| flavor                              | m1.large (8937a33f-7cbd-491d-9b9a-0e5170cc6274)          |

... snip ...

+-------------------------------------+----------------------------------------------------------+
```

先程デプロイしたPodを確認する。

```
$ k get pod nginx -o wide
NAME    READY   STATUS    RESTARTS   AGE   IP            NODE            NOMINATED NODE   READINESS GATES
nginx   1/1     Running   0          38m   10.233.71.3   kube-worker02   <none>           <none>
```

スケールアップしたノード上でPodが稼働していることを確認できた。

スケールアップしたノードを確認する。

```
$ k describe nodes kube-worker02
Name:               kube-worker02

... snip ...

Capacity:
  cpu:                8
  ephemeral-storage:  50633164Ki
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             12268324Ki
  pods:               110

... snip ...

Non-terminated Pods:          (6 in total)
  Namespace                   Name                         CPU Requests  CPU Limits  Memory Requests  Memory Limits  Age
  ---------                   ----                         ------------  ----------  ---------------  -------------  ---
  default                     nginx                        3 (37%)       0 (0%)      5Gi (44%)        0 (0%)         39m
  kube-system                 calico-node-tptkn            150m (1%)     300m (3%)   64M (0%)         500M (4%)      20h
  kube-system                 coredns-8474476ff8-g2g7f     100m (1%)     0 (0%)      70Mi (0%)        170Mi (1%)     126m
  kube-system                 kube-proxy-fq6wt             0 (0%)        0 (0%)      0 (0%)           0 (0%)         20h
  kube-system                 nginx-proxy-kube-worker02    25m (0%)      0 (0%)      32M (0%)         0 (0%)         20h
  kube-system                 nodelocaldns-j4x7b           100m (1%)     0 (0%)      70Mi (0%)        170Mi (1%)     20h

... snip ...

```

ノードのスペックはスケールアップにあわせて変更されていることを確認できた。
OpenStackでVMをリサイズすると、裏でVMが再起動される。
VMを再起動せずにスケールアップした場合のk8sの挙動も確認したいので、次にオンラインスケールアップの検証を実施してみる。

# オンラインスケールアップ時の検証

OpenStackではオンラインスケールアップができない（多分）ので、VMが動作するコンピュートノードから`virsh`コマンドを実行することでオンラインスケールアップを実施する。

最初に、検証に使ったnginxのPodを削除する。

```
kube-controlplane01:~$ k get pod
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          5d
kube-controlplane01:~$ k delete pod nginx 
pod "nginx" deleted
kube-controlplane01:~$ k get pod
No resources found in default namespace.
```

現状のworkerノード`kube-worker02`のスペックを確認。

```
kube-worker02:~$ fgrep 'physical id' /proc/cpuinfo | sort -u | wc -l
8
kube-worker02:~$ free -h
              total        used        free      shared  buff/cache   available
Mem:           11Gi       570Mi       8.5Gi       2.0Mi       2.6Gi        10Gi
Swap:            0B          0B          0B
```

workerノード`kube-worker02`を超えるリソース量を要求するPodを作成する。

```yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: nginx-large
  name: nginx-large
spec:
  containers:
  - image: nginx
    name: nginx-large
    resources:
      requests:
        memory: "13Gi"
        cpu: "9"
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}
```

```
kube-controlplane01:~$ k apply -f nginx-large.yaml 
pod/nginx-large created
kube-controlplane01:~$ k get pod
NAME          READY   STATUS    RESTARTS   AGE
nginx-large   0/1     Pending   0          110s
kube-controlplane01:~$ k describe pod nginx-large 

... snip ...

Events:
  Type     Reason            Age                From               Message
  ----     ------            ----               ----               -------
  Warning  FailedScheduling  10s (x2 over 11s)  default-scheduler  0/3 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate, 2 Insufficient cpu, 2 Insufficient memory.
```

Podのリソース要求を満たすノードが存在しないためPending状態となった。

`kube-worker02`が動作するコンピュートノードに乗り込み、`kube-worker02`をスケールアップする。

まずは、`kube-worker02`が動作するコンピュートノードとKVMにおける仮想マシン名を特定する。

```
ctl01:~# openstack server show f127a4db-03b3-4baa-b0e2-b90219370c42
+-------------------------------------+----------------------------------------------------------+
| Field                               | Value                                                    |
+-------------------------------------+----------------------------------------------------------+

... snip ...

| OS-EXT-SRV-ATTR:host                | cmp001                                                   |
| OS-EXT-SRV-ATTR:hypervisor_hostname | cmp001.xxxxxx.local                          |
| OS-EXT-SRV-ATTR:instance_name       | instance-0000335c                                        |

... snip ...

+-------------------------------------+----------------------------------------------------------+
```

コンピュートノードに乗り込む。

```
@cmp001:~# virsh list --all
 Id    Name                           State
----------------------------------------------------
 1     instance-00002b0e              running
 2     instance-0000335c              running

cmp001:~#  virsh dominfo instance-0000335c 
Id:             2
Name:           instance-0000335c
UUID:           f127a4db-03b3-4baa-b0e2-b90219370c42
OS Type:        hvm
State:          running
CPU(s):         8
CPU time:       98780.1s
Max memory:     12582912 KiB
Used memory:    12582912 KiB
Persistent:     yes
Autostart:      disable
Managed save:   no
Security model: apparmor
Security DOI:   0
Security label: libvirt-f127a4db-03b3-4baa-b0e2-b90219370c42 (enforcing)

```

以下を参考にスケールアップする。
https://www.eastforest.jp/centos6/3694

まずはvCPU数を追加する。

```
cmp001:~# virsh vcpucount instance-0000335c
maximum      config         8
maximum      live           8
current      config         8
current      live           8

```

> virsh setvcpusコマンドで仮想マシンに割り当てるCPU数を変更できます。ただし最大CPU数を超えることはできません。
--configを指定すると次回起動時から有効になります。--maximumの指定で最大CPU数を変更できますが、動作中の仮想マシンには適用されません。

まずはVMのvCPU数の上限を増やす必要がある。

```
cmp001:~# virsh setvcpus instance-0000335c 12 --config --maximum
error: invalid argument: CPU topology doesn't match the desired vcpu count

```

失敗した。
`virsh edit`で以下のようにCPUトポロジーとvCPU数を変更することで、vCPU数を変更することができた。

```xml
  <vcpu placement='static' current='8'>12</vcpu>
```

```xml
  <cpu mode='host-passthrough' check='none'>
    <topology sockets='12' cores='1' threads='1'/>
  </cpu>
```


```
cmp001:~# virsh edit instance-0000335c
Domain instance-0000335c XML configuration edited.

root@cmp001:~# virsh vcpucount instance-0000335c
maximum      config        12
current      config        8
```

VM再起動後に反映されるため、この時点ではまだ反映さいない。

続いて、メモリの上限を変更する。

```
root@cmp001:~# virsh dominfo instance-0000335c

... snip ...

Max memory:     12582912 KiB
Used memory:    12582912 KiB

... snip ...

```

現在のメモリサイズの上限は12GiBなので、16GiBに変更する。

```
root@cmp001:~# virsh setmaxmem instance-0000335c 16G --config

```

vCPU数、メモリサイズの上限変更を反映させるためにVMを再起動する。

```
root@ctl01:~# openstack server reboot --soft f127a4db-03b3-4baa-b0e2-b90219370c42
```

**`virsh`コマンドでVMを再起動すると以下の問題があったため、ここではわざわざOpenStackからVMを再起動した。**

* VMを起動すると、vCPU, メモリサイズの上限が反映されずフレーバーと同じになってしまう
* VM起動後、しばらくするとVMが勝手に停止されてしまう

```
root@cmp001:~# virsh vcpucount instance-0000335c
maximum      config        12
maximum      live          12
current      config         8
current      live           8

root@cmp001:~# virsh dominfo instance-0000335c

... snip ...

Max memory:     16777216 KiB
Used memory:    12582912 KiB

... snip ...

```

vCPU数、メモリサイズの上限値が反映された。

続いて、VMをオンラインスケールアップする。


```
root@cmp001:~# virsh setvcpus instance-0000335c 12

root@cmp001:~# virsh vcpucount instance-0000335c
maximum      config        12
maximum      live          12
current      config         8
current      live          12

root@cmp001:~# virsh setmem instance-0000335c 16G

root@cmp001:~# virsh dominfo instance-0000335c

... snip ...

Max memory:     16777216 KiB
Used memory:    16777216 KiB

... snip ...

```

スケールアップできた！

次にVMに乗り込み、スケールアップされているか確認する。

```
ubuntu@kube-worker02:~$ fgrep 'physical id' /proc/cpuinfo | sort -u | wc -l
8
ubuntu@kube-worker02:~$ lscpu |grep "CPU(s)"
CPU(s):                          12
On-line CPU(s) list:             0-7
Off-line CPU(s) list:            8-11
NUMA node0 CPU(s):               0-7
ubuntu@kube-worker02:~$ free -h
              total        used        free      shared  buff/cache   available
Mem:           15Gi       453Mi        14Gi       1.0Mi       1.0Gi        14Gi
Swap:            0B          0B          0B
```

メモリサイズは反映されているが、追加したCPUはオフラインになっているので、オンラインにする。

```
ubuntu@kube-worker02:~$ for i in {8..11}; do echo 1 | sudo tee /sys/devices/system/cpu/cpu${i}/online; done
1
1
1
1
ubuntu@kube-worker02:~$ fgrep 'physical id' /proc/cpuinfo | sort -u | wc -l
12
```

vCPU数も反映された。

次にPodを確認

```
ubuntu@kube-controlplane01:~$ k get pod
NAME          READY   STATUS    RESTARTS   AGE
nginx-large   0/1     Pending   0          8d
```

Pendingのまま。
ノードのリソースを確認。

```
ubuntu@kube-controlplane01:~$ k describe nodes kube-worker02

... snip ...

Capacity:
  cpu:                8
  ephemeral-storage:  50633164Ki
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             12201896Ki
  pods:               110

... snip ...

```

ノードのスケールアップは反映されていない。
対象ノードのkubeletを再起動してみる。

```
ubuntu@kube-worker02:~$ sudo systemctl restart kubelet.service
```

```
ubuntu@kube-controlplane01:~$ k describe nodes kube-worker02

... snip ...

Capacity:
  cpu:                12
  ephemeral-storage:  50633164Ki
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             16396200Ki
  pods:               110

... snip ...

ubuntu@kube-controlplane01:~$ k get pod
NAME          READY   STATUS    RESTARTS   AGE
nginx-large   1/1     Running   0          8d
```

ノードのスケールアップが反映され、Podがrunningとなった！

# 結論

kubeletは起動時に、ノードのスペックを見に行くようである。
そのため、ノードのスペックを変更した際は、kubeletを再起動する必要がある。