Kubesprayでcontainerd on UbuntuのKubenretesクラスタを立てる

(株)日立製作所 研究開発グループ サービスコンピューティング研究部の井出です。

Kubernetes 構築ツールである [Kubespray](https://github.com/kubernetes-sigs/kubespray) を用いて Ubuntu 上に containerd ベースの Kubernetes クラスタを構築する方法を紹介します。バージョンはそれぞれ Kubespray 2.17.0、Kubernetes 1.21.5、Ubuntu 20.04、containerd 1.4.9 の想定です。


Kubernetes v1.21 から [Kuernetes クラスタの構築に Docker を使うことが非推奨](https://kubernetes.io/blog/2020/12/02/dont-panic-kubernetes-and-docker/)となり、2022 年 4 月リリースの v1.24 からは [Docker を用いた Kubernetes クラスタの構築ができなくなる](https://www.publickey1.jp/blog/21/kubernetesdockershim.html)予定です。本件は、まだ[ユーザへの影響度を検討](https://kubernetes.io/blog/2021/11/12/are-you-ready-for-dockershim-removal/)している最中なので、影響の大きさ如何では対応が延期されるかもしれませんが、今のうちに containerd 等を用いた Kubernetes クラスタの構築を試しておくことは有用でしょう。

記事の流れは以下の通りです。

1. [ip_vsの不足エラーを回避してUbunutu上でKubesprayを実行する](#1-ip_vsの不足エラーを回避してubunutu上でkubesprayを実行する)
2. [Kubesprayでcontainerdを用いたKubernetesクラスタを立てる](#2-kubesprayでcontainerdを用いたkubernetesクラスタを立てる)
3. [containerd でコンテナイメージ取得時に Docker Hub の認証情報を付与する](#3-containerd-でコンテナイメージ取得時に-docker-hub-の認証情報を付与する)
4. [まとめ](#まとめ)

## 1. ip_vsのエラーを回避してUbunutu上でKubesprayを実行する

KVM 用など、特定の Ubuntu サーバ上で Kubespray を実行すると、IP Virtual Server が無いとのエラーが出る場合があります。

```sh
FATAL: Module ip_vs not found in directory /lib/modules/5.4.0-1023-kvm
```

これは、`linux-image-kvm`などの Kernel に `ip_vs` モジュールが含まれていないことが原因です。

```sh
uname -a
#Linux foobar 5.4.0-1023-kvm #23-Ubuntu SMP Sat Sep 5 00:49:48 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux

modinfo ip_vs
#modinfo: ERROR: Module ip_vs not found.

sudo modprobe ip_vs
#modprobe: FATAL: Module ip_vs not found in directory /lib/modules/5.4.0-1023-kvm
```

これを解決する場合、 `ip_vs`モジュールをビルドして`/lib/modules/$KERNEL`に置いても良いですが、Kernel の変更が可能なら、以下のように`linux-image-generic`の Kernel を入れると楽です。

```sh
sudo apt update
sudo apt install linux-image-5.8.0-63-generic
```

再起動すると Kernel が切り替わり`ip_vs`を利用可能となります。

```sh
sudo reboot
```

```sh
uname -a
#Linux k8s-master00 5.8.0-63-generic #71~20.04.1-Ubuntu SMP Thu Jul 15 17:46:08 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

# ip_vs が使えることを確認
modinfo ip_vs
filename:       /lib/modules/5.8.0-63-generic/kernel/net/netfilter/ipvs/ip_vs.ko
license:        GPL
srcversion:     C19F642DDCE359C9EDF97ED
depends:        nf_conntrack,nf_defrag_ipv6,libcrc32c
retpoline:      Y
intree:         Y
name:           ip_vs
vermagic:       5.8.0-63-generic SMP mod_unload modversions 
sig_id:         PKCS#7
signer:         Build time autogenerated kernel key
sig_key:        1B:03:37:42:D8:09:C6:B8:56:26:E7:0A:BC:AB:2C:A2:BB:4C:5D:11
sig_hashalgo:   sha512
signature:      86:00:C1:ED:91:71:34:50:0E:3C:49:9A:CB:59:AB:14:6B:1E:27:E9:
                (略)
parm:           conn_tab_bits:Set connections' hash size (int)
```

その後は、以降の手順で Kubespray 内の Ansible を実行すると良いでしょう。

## 2. Kubesprayでcontainerdを用いたKubernetesクラスタを立てる

Kubespray のコンテナランタイムは Docker がデフォルトですが、設定により containerd と CRI-O に切り替えることが可能です(設定方法: [containerd](https://github.com/kubernetes-sigs/kubespray/blob/master/docs/containerd.md)、[CRI-O](https://github.com/kubernetes-sigs/kubespray/blob/master/docs/cri-o.md))。

そこで、Kubespray で containerd を用いた Kubernetes クラスタを構築する手順を以下に示します。

ただし、Docker Hub へのアクセスが多い環境では、Too Many Request でコンテナイメージの取得に失敗する可能性があるため、先に [3-2. kubelet で認証情報を付与する](#1-kubelet-で認証情報を付与する) を実施下さい。

また、OS や CNI の組合せ次第では起動に失敗するため、Ubuntu 20.04 + Calico 以外を利用される場合、[KubesprayのCIテスト結果](https://github.com/kubernetes-sigs/kubespray/blob/master/docs/ci.md)を確認するとよいでしょう。

まず、Kubespray のリポジトリをクローンし、目的のバージョンの tag に移動します

```sh
git clone https://github.com/kubernetes-sigs/kubespray.git
cd kubespray
git checkout v2.17.0
```

デフォルトの設定を任意の名前 (以下では mycluster) でコピーします。

```sh
cp -rfp inventory/sample/ inventory/mycluster
```

Ansible 等のツールをインストールします。

```sh
sudo pip3 install -r requirements.txt
# pip3 がない場合は以下で pip3 をインストール
# sudo apt update && sudo apt install python3-pip -y
```

`inventory/mycluster/hosts.ini`に Node の設定を記載します。
Master 2 台、Node 5 台(うち 2 台 Master 兼用)で構築する場合、例えば以下となります。
IP アドレスやユーザ名、ファイルパスは適宜読み替えて下さい。

```ini:inventory/mycluster/hosts.ini
[all]
k8s-master00 ansible_host=192.168.0.10
k8s-master01 ansible_host=192.168.0.11
k8s-node00 ansible_host=192.168.0.12
k8s-node01 ansible_host=192.168.0.13
k8s-node02 ansible_host=192.168.0.14

[all:vars]
ansible_user=ubuntu ;ユーザ名
ansible_ssh_private_key_file=/home/ubuntu/.ssh/privatekey.pem ;ssh 秘密鍵

[kube-master]
k8s-master00
k8s-master01

[etcd] ;奇数にすること。偶数だと起動エラー(split brain対策)
k8s-master00
k8s-master01
k8s-node00

[kube-node]
k8s-master00
k8s-master01
k8s-node00
k8s-node01
k8s-node02

[k8s-cluster:children]
kube-node
kube-master
```

containerd を利用するために設定を 2 箇所変更します(Kubespray v2.18 からはデフォルトで変更されているようです)。

```diff:inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml
-container_manager: docker
+container_manager: containerd
```

```diff:inventory/mycluster/group_vars/etcd.yml
-etcd_deployment_type: docker
+etcd_deployment_type: host
```

Ansible を実行し、Kubernetes クラスタを構築します(15 分ほどかかります)。

```sh
ansible-playbook -i inventory/mycluster/hosts.ini cluster.yml -b -v
```

### 2-2. 動作確認

kubectl で Node リソースを取得できるか試します。
kubectl がない場合は snap や asdf で kubectl を取得できます。例えば snap なら以下です。

```sh
sudo snap install kubectl
```

Master サーバから kubectl の認証ファイル(`/etc/kubernetes/admin.conf`)を取得し、`~/.kube`配下に格納後、IP アドレスを修正します。

```sh
ssh -i <ssh_private_key> <user>@<master_server> "sudo cat /etc/kubernetes/admin.conf" > /tmp/kube.config
sed 's|server: https://127.0.0.1:6443|server: https://<master_server>:6443|g' /tmp/kube.config > ~/.kube/kube.config
```

その後、kubectl を実行すると構築した Kubernetes クラスタの Node リソースが取得できるようになります。

```sh
kubectl get nodes
# NAME           STATUS   ROLES                  AGE   VERSION
# k8s-master00   Ready    control-plane,master   20m   v1.21.5
# k8s-master01   Ready    control-plane,master   19m   v1.21.5
# k8s-node00     Ready    <none>                 17m   v1.21.5
# k8s-node01     Ready    <none>                 17m   v1.21.5
# k8s-node02     Ready    <none>                 17m   v1.21.5
```

## 3. containerd で Docker Hub の認証をする

Docker Hub は、[匿名アカウントによる同一 IP アドレスからのイメージ取得数を制限](https://www.docker.com/increase-rate-limits)します。これを回避するにはイメージの取得リクエストに Docker Hub の認証情報を付与する必要があります。

では containerd の場合、どのように認証情報を付与すれば良いのでしょうか？ 結論から言うと、Kubernetes の場合は認証情報の付与も Kubernetes が行うので、Docker でも contained でも設定方法は変わりません。

Kubernetes にてコンテナイメージの取得時にアカウント情報を付与するには Pod の`imagePullSecret`が有名ですが、クラスタ全体に認証を適用させる場合は、以下 2 種の方法が便利です。

1. kubelet で認証情報を付与する
2. ServiceAccount の imagePullSecrets を使う

以降でそれぞれ紹介します。

### 3-1. kubelet で認証情報を付与する

Kubernetes クラスタの構築時に便利な方法です。また、Kubernetes のマニフェストで認証情報を意識したくない場合にも使えます。

kubelet は`$HOME/.docker/config.json`(`$HOME`は kubelet 起動ユーザのホームディレクトリなので一般に`/root`となる)など、特定パスに置かれた Docker の設定ファイルを参照し、イメージ取得時に認証情報を付与してくれます([参考](https://kubernetes.io/ja/docs/concepts/containers/images/#%E3%83%97%E3%83%A9%E3%82%A4%E3%83%99%E3%83%BC%E3%83%88%E3%83%AC%E3%82%B8%E3%82%B9%E3%83%88%E3%83%AA%E3%81%B8%E3%81%AE%E8%AA%8D%E8%A8%BC%E3%82%92node%E3%81%AB%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B))。

このため、以下のような設定ファイルを各 Node に配置すれば裏で認証処理を行ってくれるようになります。このファイルは適当なサーバで docker login をすれば、`$HOME/.docker/config.json`として生成されるため、それをコピーしてくると良いでしょう。

```json:/root/.docker/config.json
{
    "auths": {
        "https://index.docker.io/v1/": {
            "auth": "<username:password をbase64エンコードした値>"
        }
    }
}
```

[追記] なお、kubelet の認証設定ならば、`/var/lib/kubelet/.dockercfg`などの設定ファイルも認識されますが、Kubespray v2.18 以降は`$HOME/.docker/config.json`に認証設定を配置した方が良いと思われます。というのも、Kubespray v2.18 以降、ansible の実行時に containerd の cli である [nerdctl を用いた pull テスト](https://github.com/kubernetes-sigs/kubespray/blob/release-2.18/roles/download/tasks/prep_download.yml#L19)が追加されたため、[nerdctl からも認証設定を認識させる](https://github.com/containerd/nerdctl/blob/master/docs/registry.md)必要があるためです。

### 3-2. ServiceAccount の imagePullSecrets を使う

ServiceAccount リソースに [imagePullSecrets](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#serviceaccount-v1-core) を設定すると、ServiceAccount 配下の Pod へ動的に`imagePullSecret`を付与してくれます。Node ごとに認証設定を行うことが困難な場合などに便利な方法です。

以下のように`default` Service Account 等に`imagePullSecrets`を設定しておくと便利です([参考](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#add-image-pull-secret-to-service-account))。

```sh
kubectl patch serviceaccount default -p '{"imagePullSecrets": [{"name": "myregistrykey"}]}'
```

ただし、この方法は一度 Kubernetes が起動してからしか適用できないため、Kubernetes を構成するコンテナのイメージ取得には利用できません。起動済みクラスタでアクセス制限の問題が発生し始めたときなどに活用すると良いでしょう。

## 4. まとめ

本記事では、Kubespray にて Ubuntu 上に containerd を用いた Kubernetes クラスタを構築する方法を紹介しました。

[Docker の非推奨化](https://kubernetes.io/blog/2020/12/02/dont-panic-kubernetes-and-docker/)を受け、今後は containerd 活用の機会も増えていくと思います。本記事が皆様のお役に立てば幸いです。