Kubesprayで構築したk8sクラスターを強引にUbuntu 22.04にアップグレードしてみた

# はじめに

Kubesprayで構築したk8sクラスターのOSは、全て Ubuntu 20.04 です。

まだしばらくは20.04のサポートは続きますが、22.04へのアップグレードはどこかで検討しなければいけません。

幸いにも試すことができるクラスターがあったので、workerの1ノードをアップグレードしてみました。

:::note
Ubuntu 22.04.5から24.04.2へのアップグレードを試していますが、ここに記載しているような手順(cordon→drain→ansible-playbook→uncordo)といった流れでkubernetesのシステム自体は問題なくアップグレードできます。

しかしrook/cephについてはCSIドライバが``rbd.ko.zst``を正しくロードできず正常に動作しないPodがありました。

この問題はRook/Cephを事前にv1.15の最新に更新することで回避できるはずです。

またOS再起動のタイミングでRX100 S7pが停止してしまったので現地で強制リセットの必要がありました。

これは特定ハードウェアの特性ではあると思いますが、KVMや富士通製であればiRMCなどで遠隔操作できないのであればモートだけで作業するのは避けた方が良いでしょう。
:::

# 環境

Kubespray v2.20.0 (Kubernetes v1.24.6) 以降では、OSとしてUbuntu 22.04がサポートされています。

今回は以下の環境でアップグレードを行なっています。

* Kubespray: v2.21.0 (Kubernetes: v1.25.6)
* OS: Ubuntu 20.04.5 LTS (最新版)
* ノード数: 4

# 確認作業 (PoC)

:::note alert
このセクションの内容は必要な作業を洗い出すための準備作業です。

アップグレード手順としては利用せず、後述の[#作業手順](#作業手順)を参照してください。
:::

とりあえずcordonして新規にPodが割り当てられないようにしておきます。

```bash:node4をcordonする
$ sudo kubectl cordon node4
node/node4 cordoned
```

状態を確認します。

```bash:
$ sudo kubectl get node
NAME    STATUS                     ROLES           AGE    VERSION
node1   Ready                      control-plane   555d   v1.25.6
node2   Ready                      control-plane   555d   v1.25.6
node3   Ready                      <none>          555d   v1.25.6
node4   Ready,SchedulingDisabled   <none>          555d   v1.25.6
```

この状態で、node4をdo-release-upgradeしようとするとエラーになります。

```bash:
$ sudo do-release-upgrade -d
Checking for a new Ubuntu release
Please install all available updates for your release before upgrading.

$ sudo apt dist-upgrade
...
The following packages have been kept back:
  containerd.io docker-ce docker-ce-cli
0 upgraded, 0 newly installed, 0 to remove and 3 not upgraded.
```

Kubesprayはpackageを保留状態(hold)にしています。

```bash:保留状態のエラー
$ dpkg -l |grep ^h
hi  containerd.io                         1.6.4-1                           amd64        An open and reliable container runtime
hi  docker-ce                             5:20.10.20~3-0~ubuntu-focal       amd64        Docker: the open-source application container engine
hi  docker-ce-cli                         5:20.10.20~3-0~ubuntu-focal       amd64        Docker CLI: the open-source application container engine
```

これらをunholdすれば、作業自体は進めることが可能ですが、はたしてどうするべきなのか、検討してみます。

# 参考資料

* [Kubernetes Part 18: How-To Upgrade Kubernetes Nodes from Ubuntu 20.04 to 22.04 LTS](https://www.debontonline.com/2022/04/kubernetes-part-18-how-to-kubernetes.html)
* [Kubesprayを利用してKubernetesをデプロイ・アップグレードした時のメモ - v2.15.1でcontainerd.ioパッケージが更新されてしまう問題について](https://qiita.com/YasuhiroABE/items/3aaf7ceb314f47cd62d9#v2151%E3%81%A7containerdio%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E3%81%8C%E6%9B%B4%E6%96%B0%E3%81%95%E3%82%8C%E3%81%A6%E3%81%97%E3%81%BE%E3%81%86%E5%95%8F%E9%A1%8C%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)

とりあえず壊れても問題のないクラスターなので、単純にunholdしてdo-release-upgradeする方法で進めてみます。

# 作業手順

:::note warn
OSのアップグレードはworkerノードから実施しています。
:::

あらかじめcordonしてからdrainまで実施しましょう。

```bash:ノードの一時停止
$ sudo kubectl cordon node4
$ sudo kubectl drain node4 --force --ignore-daemonsets
## ↑で失敗した場合はメッセージを確認して"--delete-local-data"オプション付きで実行する
$ sudo kubectl drain node4 --force --ignore-daemonsets --delete-emptydir-data
```

grace-periodは指定しません。

まず3つのパッケージをunholdしておきます。

```bash:node4で実行
$ sudo apt-mark unhold containerd.io docker-ce docker-ce-cli
Canceled hold on containerd.io.
Canceled hold on docker-ce.
Canceled hold on docker-ce-cli.
```

パッケージ全体をアップグレードします。

```bash:node4で実行
$ sudo apt update
$ sudo apt dist-upgrade
```

パッケージを最新にした状態でノードを再起動し、最新のカーネルで起動している状態にします。

```bash:node4で実行
$ sudo shutdown -r now
```

Ubuntu 22.04にアップグレードします。
この中でGNU screenが起動するので、別のターミナルからsshでnode4にログインし、作業を進めます。

```bash:node4で実行
$ sudo do-release-upgrade -d
```

あとは、基本的に'y'キーなどで作業を進め、設定ファイルは現状のまま変更しない'N'を選択しながら見守ります。

そのまま再起動して様子を確認します。

## 再起動後の作業

起動後、数分してからcontrol-planeからノードの状況を確認して、**Ready**になるまで待機します。

```bash:
$ kcg node
NAME    STATUS                     ROLES           AGE    VERSION
node1   Ready                      control-plane   555d   v1.25.6
node2   Ready                      control-plane   555d   v1.25.6
node3   Ready                      <none>          555d   v1.25.6
node4   Ready,SchedulingDisabled   <none>          555d   v1.25.6
```

表面上は問題なく動いている様子です。

続いて、kubesprayのupdate-cluster.ymlを--limitを利用してnode4だけに適用します。**これはエラーになるので実行の際には、最後に"--skip-tags=multus"を加えて実行してください。**

```bash:ansibleを実行するホストに移動して実行(エラーが発生する例。実施しないこと)
$ . venv/k8s/bin/activate
(k8s) $ grep kube_version inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml 
kube_version: v1.25.6
(k8s) $ ansible-playbook upgrade-cluster.yml -b -i inventory/mycluster/hosts.yaml -e kube_version=v1.25.6 --limit=node4
...
```

もしこれを実行してしまうと、エラーで停止します。

```bash:
...
TASK [kubernetes-apps/network_plugin/multus : Multus | Start resources] ***************************************************
failed: [node4 -> {{ groups['kube_control_plane'][0] }}] (item=None) => {"ansible_loop_var": "item", "changed": false, "item": null, "msg": "Failed to template loop_control.label: 'None' has no attribute 'item'", "skip_reason": "Conditional result was False"}

NO MORE HOSTS LEFT ********************************************************************************************************

PLAY RECAP ****************************************************************************************************************
node4                   : ok=312  changed=23   unreachable=0    failed=1    skipped=451  rescued=0    ignored=0  
```

GitHubのIssuesには同様の事例が登録されていて、[Node-based upgrade fails with: "Failed to template loop_control.label: 'None' has no attribute 'item'" #9703](https://github.com/kubernetes-sigs/kubespray/issues/9703) に従って **--skip-tags=multus** を追加して再度実行します。

```bash:これもまだエラーになる
(k8s) $ ansible-playbook upgrade-cluster.yml -b -i inventory/mycluster/hosts.yaml -e kube_version=v1.25.6 --limit=node4 --skip-tags=multus
...
```

続いて別のエラーが発生します。

```bash:
...
TASK [container-engine/docker : ensure docker packages are installed] *****************************************************
fatal: [node4]: FAILED! => {"attempts": 4, "cache_update_time": 1678337172, "cache_updated": true, "changed": false, "msg": "'/usr/bin/apt-get -y -o \"Dpkg::Options::=--force-confdef\" -o \"Dpkg::Options::=--force-confold\"       install 'containerd.io=1.6.4-1' 'docker-ce-cli=5:20.10.20~3-0~ubuntu-jammy' 'docker-ce=5:20.10.20~3-0~ubuntu-jammy'' failed: E: Packages were downgraded and -y was used without --allow-downgrades.\n", "rc": 100, "stderr": "E: Packages were downgraded and -y was used without --allow-downgrades.\n", "stderr_lines": ["E: Packages were downgraded and -y was used without --allow-downgrades."], "stdout": "Reading package lists...\nBuilding dependency tree...\nReading state information...\nThe following packages were automatically installed and are no longer required:\n  docker-buildx-plugin docker-compose-plugin libpython2-stdlib\n  libpython2.7-minimal libpython2.7-stdlib python2 python2-minimal python2.7\n  python2.7-minimal\nUse 'sudo apt autoremove' to remove them.\nSuggested packages:\n  aufs-tools cgroupfs-mount | cgroup-lite\nThe following packages will be DOWNGRADED:\n  containerd.io docker-ce docker-ce-cli\n0 upgraded, 0 newly installed, 3 downgraded, 0 to remove and 4 not upgraded.\n", "stdout_lines": ["Reading package lists...", "Building dependency tree...", "Reading state information...", "The following packages were automatically installed and are no longer required:", "  docker-buildx-plugin docker-compose-plugin libpython2-stdlib", "  libpython2.7-minimal libpython2.7-stdlib python2 python2-minimal python2.7", "  python2.7-minimal", "Use 'sudo apt autoremove' to remove them.", "Suggested packages:", "  aufs-tools cgroupfs-mount | cgroup-lite", "The following packages will be DOWNGRADED:", "  containerd.io docker-ce docker-ce-cli", "0 upgraded, 0 newly installed, 3 downgraded, 0 to remove and 4 not upgraded."]}    
```

これは参考資料に上げたv2.15.1で遭遇した問題と同じなので、*force: true* を roles/container-engine/docker/tasks/main.yml に追加して、再度実行します。

```bash:成功した手順
## venv環境の導入
$ . venv/k8s/bin/activate

## kube_versionの確認
(k8s) $ grep kube_version inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml 
kube_version: v1.25.6

## dockerインストール時にforce: trueを設定
(k8s) $ vi roles/container-engine/docker/tasks/main.yml

## kube_versionを指定して、--skip-tags=multusオプションも指定して実行
(k8s) $ ansible-playbook upgrade-cluster.yml -b -i inventory/mycluster/hosts.yaml -e kube_version=v1.25.6 --limit=node4 --skip-tags=multus
```

最終的には無事に完了しました。

```bash:
PLAY RECAP ****************************************************************************************************************
node4                   : ok=451  changed=19   unreachable=0    failed=0    skipped=855  rescued=0    ignored=1   
```

最後には忘れずに *uncordon* します。

```bash:control-planeでuncordonする。drain状態からもこれだけで復活する
$ sudo kubectl uncordon node4
```

これで問題なく動作するようになりました。

node4のcontainerd.io docker-ce docker-ce-cliの状態は次のようになっています。

```bash:node4で実行
$ dpkg -l containerd.io docker-ce docker-ce-cli
Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend
|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
||/ Name           Version                     Architecture Description
+++-==============-===========================-============-========================================================
hi  containerd.io  1.6.4-1                     amd64        An open and reliable container runtime
hi  docker-ce      5:20.10.20~3-0~ubuntu-jammy amd64        Docker: the open-source application container engine
hi  docker-ce-cli  5:20.10.20~3-0~ubuntu-jammy amd64        Docker CLI: the open-source application container engine
```

バージョンはアップグレード前と同じになっています。

/etc/apt/sources.list.d/ の中は次のようになっています。

```bash:node4の/etc/apt/sources.list.d/の状況
$ $ ls /etc/apt/sources.list.d
download_docker_com_linux_ubuntu.list  download_docker_com_linux_ubuntu.list.distUpgrade

$ cat /etc/apt/sources.list.d/download_docker_com_linux_ubuntu.list
deb [arch=amd64] https://download.docker.com/linux/ubuntu jammy stable # disabled on upgrade to jammy

$ cat /etc/apt/sources.list.d/download_docker_com_linux_ubuntu.list.distUpgrade 
deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable
```

この状態で upgrade を実行すると次のようになって、docker関連のパッケージが少し導入されますが、このままインストールしています。

```bash:node4で実行
$ sudo apt dist-upgrade
...
The following packages have been kept back:                                                    
  containerd.io docker-ce docker-ce-cli                                                                     
The following packages will be upgraded:                                               
  docker-buildx-plugin docker-ce-rootless-extras docker-compose-plugin docker-scan-plugin           
4 upgraded, 0 newly installed, 0 to remove and 3 not upgraded. 
```

このまま全ノードをUbuntu 22.04にアップグレードしたら、dockerからcontainerdに乗り換える方法を検討しようと思います。

## legacy trusted.gpg keyring への対応

しばらくして次のようなワーニングに気がつきました。

```bash:node4の様子
$ sudo apt update
Hit:1 https://download.docker.com/linux/ubuntu jammy InRelease
...
W: https://download.docker.com/linux/ubuntu/dists/jammy/InRelease: Key is stored in legacy trusted.gpg keyring (/etc/apt/trusted.gpg), see the DEPRECATION section in apt-key(8) for details.
```

docker.comの手順に従えば、再度gpg鍵を設定することはできます。

* [Install Docker Engine on Ubuntu](https://docs.docker.com/engine/install/ubuntu/)

これだけでは不十分で、加えてtrusted.gpgから削除する必要があります。
docker自体はアップグレードする必要はないのでしばらく放っておくことにしました。

## 複数台を更新していく作業のテンポについて

複数のノードを順番にアップグレードしていく場合には、分散DBのように複数ノードに渡るクラスターを運用している場合にはその健全性に注意しましょう。

基本的にはn-1台でサービスが継続できるように構成されていると思われるため、1ノードずつ停止している分には問題は発生しないはずです。

しかし短期間に繰り返し作業をしてしまうと問題が発生するかもしれません。

1台の作業が終る度にRook/Cephであれば``ceph status``などで健全性を確認するようにしてください。

## control-planeノードのアップグレード

この記事を投稿してから、control-planeのノードもUbuntu 20.04から、22.04にアップグレードしました。

特にworkerノードと違う点はありませんでしたが、再起動に非常に長い時間がかかりました。
原因は不明ですが作業時間は想定よりも2倍程度かかった点が印象的です。

# まとめ

作業自体はそれほど危険な感じはしませんが、再起動などをしてからクラスター全体が安定するまでにはしばらく時間がかかります。

またcordonしている間にkubesprayを実行し、ノードを再び再起動するなど確認の手間はおしまない方が良さそうです。

結果としては Rook/Ceph の動作にも影響はないですし、以前 docker や containerd.io がアップグレードされてしまった状態から戻したこともあったので、それほど混乱なく作業を進めることができました。

K8sクラスターは安定していると、特に日本の風土では、アップグレードなどをしないまま放置してしまうことがありそうだなと感じています。

しかしk8sの各バージョンのサポート期間はとても短いので、End-of-Life(EOL)情報は定期的に確認し、メンテナンスウィンドウを設けてk8sクラスターをアップグレードし続けるようにしましょう。

* [https://endoflife.date/kubernetes](https://endoflife.date/kubernetes)

# 後日談: 24.04へのアップグレード時に遭遇した問題点

## Rook/Cephで"Still connection to unix:///csi/csi.sock"メッセージが出る

OSアップグレード後のノードでcsi-rbdpluginが上記のメッセージを出力して正常に稼動しない問題が発生しました。

状況は違いますが、同様のメッセージは下記のissuesでも報告されています。

https://github.com/rook/rook/issues/14507

https://github.com/rook/rook/issues/14431

結果的にRook/Cephのバージョンが古いことでこの問題に遭遇してしまったので、OSを更新したノードで``modprobe rbd``を実行して全体を正常にしてからRookのバージョンをv1.15.9に、Cephをv18.2.4に更新しました。

v1.14.12ではCSIのバージョンがv3.10.1と古いままで問題は解決しませんでした。

以上