Kubesprayで構築したK8sクラスターをcri-dockerdからcontainerdへ移行した時の作業メモ

# はじめに

Kubesprayを利用して構築したk8sクラスターがいくつかありますが、これらのほぼ全てがdockerdをcontainer_managerとして利用しています。

Kubesprayのv2.18.0 (kubernetes v1.22.8)からはデフォルトがdockerdからcontainerdに変更されており、kubernetes本家はv1.20.0からdockerdの利用を非推奨にし、v1.24.0からはdockershimを削除しています。

Kubesprayなどでは、dockershimはcri-dockerdに置き換わって引き続きdockerdの利用がサポートされていますが、kube-state-metricsでの不具合などを経験し、いよいよcontainerdへ以降することにしました。

この記事を作成する前に、実験環境や壊れてもいい環境では移行が完了して無事に動作しているので、ここではその際に気がついたことなどをメモに残していきます。

# 移行対象の環境

* Hw - Fujitsu TX1310 M3、TX1320 M4
* Kubespray v2.21.0 (Kubernetes v1.25.6)

## 導入しているサービス等

* Rook/Ceph v1.9.13 (Ceph v16.2.10)
* Minio RELEASE.2023-07-07T07-13-57Z
* GitLab v15.11.13
* RabbitMQ v3.8.19
* Apache Solr v8.11.2

## 作業前の状況

* Kubespray v2.21.0にアップグレードするタイミングで、既にetcdはホスト上で稼動している 
  * etcdノード上で、``sudo docker ps | grep etcd``を実行し出力されないことを確認済み
* 同様に **resolvconf_mode:** は **host_resolvconf** となっている。
* ansible-playbookはvenv環境を利用している

# 参考資料

* [移行の作業手順 - https://github.com/kubernetes-sigs/kubespray/blob/master/docs/upgrades/migrate_docker2containerd.md](https://github.com/kubernetes-sigs/kubespray/blob/master/docs/upgrades/migrate_docker2containerd.md)

# 移行作業の概要

参考資料に挙げた内容を確認し、ここに書かれていることだけを頼りにしないでください。

また作業はkubesprayの作業用ディレクトリ、control-plane、移行を実施するノードの3つの端末を切り替えて行うため、自分がどのホストで作業を実施しているか確認しながら進めてください。

## 実際の作業手順

```bash:
##
## ホスト1. kubesprayの作業用ディレクトリでの準備作業
##

## kubesprayのトップディレクトリに移動
$ cd ~/ansible/.../kubespray/

## branchを確認し、v2.21.0の作業ブランチにいることを確認する
$ git branch

## k8s-cluster.yml, etcd.yml の内容を確認し、必要に応じて編集する
## container_manager:行のみを docker から containerd に書き換えているはず
$ vi inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml
$ vi inventory/mycluster/group_vars/all/etcd.yml

## ここまででcontainerdを利用するようにkubesprayが構成されていることを確認する

##
## ホスト2. control-planeノードでの作業
##

## node番号の若い方から順にcordon, drainを行い、移行作業を実施する
$ sudo kubectl cordon node1
$ sudo kubectl drain node1 --force --ignore-daemonsets

## 移行対象nodeの状態が"Ready,SchedulingDisabled"であることを確認する
$ sudo kubectl get node

##
## ホスト3. 移行を行うホスト上での作業 (最初はkubectlを実行しているcontrol-planeと同じ)
##

## dockerパッケージが導入されていることを確認 (存在しない場合は違うノードでないか確認すること)
$ dpkg -l |grep docker

## サービスの停止
$ sudo service kubelet stop
$ sudo service docker stop

## dockerパッケージの削除
$ sudo apt-get remove -y --allow-change-held-packages containerd.io docker-ce docker-ce-cli docker-ce-rootless-extras
$ sudo apt-get install pigz

## docker関連のパッケージの削除を確認 ("rc"ステータスを確認)
$ dpkg -l |grep docker

##
## ホスト1. kubesprayの作業用ディレクトリでの準備作業
##

## cluster.ymlの実行、ansible-playbookはfailed=0で完了していること
$ . venv/k8s/bin/activate
$ ansible-playbook -i inventory/mycluster/hosts.yaml -b cluster.yml --limit=node1 --skip-tags=multus

##
## ホスト2. control-planeノードでの作業
##

## cri-socketをcontainerdに変更する
$ sudo kubectl annotate node node1 --overwrite kubeadm.alpha.kubernetes.io/cri-socket=/var/run/containerd/containerd.sock

## cri-socketが変更されていることを確認する (未実施のノードはdockershim/cri-dockerdのはず)
$ sudo kubectl get node -o yaml | grep cri-socket

##
## ホスト3. 移行を行うホスト上での作業
##

## 再起動
$ sudo shutdown -r now

## pingで再起動の状況を確認する
$ ping 192.168.1.11

## 再度ログインし、再起動からの経過時間で再起動した事を確認する
$ ssh node1
$ uptime

##
## ホスト2. control-planeノードでの作業
##

## 確認のためノードの状態を確認し、再起動したノードが"Ready,SchedulingDisabled"である事を確認する
$ sudo kubectl get node

## PodがRunningとなっていることを確認する (JobはCompleted、Cephなど一部のノードがPendingであるのは最上)
## 起動中のPodは起動が完了するまで待ち、起動に失敗しているPod現時点では放置する
$ sudo kubectl get pod -A

## 移行対象のホストが再起動して、全てのPodが起動したことを確認してから、uncordonする
$ sudo kubectl uncordon node1

## 
## 以下はDomain固有の確認手順なので無視してOK
##

## Rook/Cephの状態を確認し、HEALTH_OKになるまで待機 ##
$ sudo kubectl -n rook-ceph exec -it $(sudo kubectl -n rook-ceph get pod -l app=rook-ceph-tools -o jsonpath='{.items[*].metadata.name}') -- ceph status

## Rook/Cephが正常に起動した後も起動に失敗しているPodがないか確認し、必要に応じてdeleteして再起動する
$ sudo kubectl get pod -A

##
## 以降、対象ノードをnode2, node3, ... と変更しながらこの手順を先頭から再度実施する
## 
```

**node1**は適宜埋め込まれているので環境変数を利用して書き直した方がより安全かもしれません。

## 作業手順の主な変更点

* sudoコマンドを適宜追加
* cordon/drain/uncordonの具体的なコマンドを明記
* venv環境のsource
* ansible-playbookの実行時に"-b"オプション、"--skip=multus""オプションの追記

# 作業時に気がついたこと

あらかじめいくつかの環境で移行作業を試したこともあって、テスト系、本番系での作業は順調に終りました。

またUbuntu 20.04から22.04への移行作業の経験もあり、遭遇したマイナーなエラーにもすぐに対処できました。

これまでの作業範囲では、ansible-playbookの実行時などにエラーが発生してもコマンドラインを修正し、rerunすることでリカバリー可能でした。

## 再起動について

ノードの再起動では様々なケースがありました。再起動せずに停止していた場合、順調に再起動した場合、停止できずにノードが起動状態のまま電源ボタンから強制停止した場合、などです。

無事に再起動が完了した場合では比較的短い時間で処理が完了していました。

手動でリセットを実施したサーバーはpingに数分間は反応し、反応がなくなっ後2〜3分程度待ってから実施しています。

containerdへの移行作業自体は、ノードの再起動さえ確実に行える環境であれば自動化できるのだろうと思います。

この点ではクラウド事業者はノードの強制終了も含めて自動化できるでしょうから、自前でノードを

## サービスへの影響について

適切にcordon,drain処理が行われていれば、基本的にはサービス自体は継続されるはずです。

もちろんPodDisruptionBudgetの設定などに依るわけですが、今回の作業では、意図しないサービス停止は確認できませんでした。

想定内の影響に留まったのは作業を進める上で心理的な負荷が軽減したのは間違いありません。

## Rook/Cephのhealth状態について

作業を進めていくと、Rook/Cephのhealth状態がいろいろ変化します。

例えば、2台目を再起動した直後では次のようになりました。

```text:再起動後のceph statusの出力例
  cluster:
    id:     74a67737-7c7c-4418-9c97-a770e717e15b
    health: HEALTH_WARN
            1 OSDs or CRUSH {nodes, device-classes} have {NOUP,NODOWN,NOIN,NOOUT} flags set
 
  services:
    mon: 3 daemons, quorum d,f,g (age 14m)
    mgr: a(active, since 4d)
    mds: 1/1 daemons up, 1 hot standby
    osd: 8 osds: 8 up (since 5m), 8 in (since 33m); 9 remapped pgs
 
  data:
    volumes: 1/1 healthy
    pools:   4 pools, 97 pgs
    objects: 2.42M objects, 19 GiB
    usage:   92 GiB used, 29 TiB / 29 TiB avail
    pgs:     646059/7266459 objects misplaced (8.891%)
             88 active+clean
             8  active+remapped+backfill_wait
             1  active+remapped+backfilling
 
  io:
    client:   767 B/s rd, 1.8 MiB/s wr, 1 op/s rd, 99 op/s wr
    recovery: 7.3 KiB/s, 9 objects/s
 
  progress:
    Global Recovery Event (33m)
      [=========================...] (remaining: 3m)
```

この**1 OSDs or CRUSH**の状態は強制的に解除することもできますが、Cephが大丈夫と判断した時点で解除されるので辛抱強く待ちましょう。

HEALTH_OKであれば、Global Recovery Eventの終りは待たずに作業を進めています。

## kubeletが起動してこない現象について

再起動後にkubeletが起動せずにget nodeの出力が**NotReady**のままになっていたノードがありました。

swapパーティションが残っていたのでswapが有効になってしまったことが原因で、``swapoff -a``を実行してからfdiskを使ってswapパーティションそのものを削除して対応しています。


# さいごに

ノードの再起動やパッケージ管理など、Kubesprayの標準機能としてはansibleで安全に管理できる範囲外のことがいろいろと発生してしまうため自動化してサービスとして提供することは難しいのだと思います。

主にRook/Cephの回復処理の待ち時間が全体の作業時間の大部分を占めていたと思います。
4台ノードなクラスターの移行作業に朝から取り掛かって、完了したのは午後に入ってからでランチの時間は微妙に逃しました。

とはいえ、containerdへの移行自体はそれほど危険な作業というわけではありませんでした。

以上