K8s on ラズパイ(ARM64)にcri-o+podmanでworkerを追加してみた

# はじめに
2020/12末時点で、kubernetesのworkerノードを「cri-o」と「podman」で構築してみた結果です。

# 環境

- Raspberry Pi 4B
 - OS: RaspiOS Lite(ARM64) beta 2020/8/24 (※本手順では最終的にkernelの入れ替えをしています)
 - kubernetes: v1.20.1 (※タイミング悪くv1.20.0のマスターにv1.20.1を追加してます)

# 構築した手順
メモ書き程度ですが、ご参考まで。
RaspiOS Lite(ARM64)をインストール後、ホスト名やネットワークの設定後の状態からです。
https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2020-08-24/

## 1. スワップ無効化
swap有効化のままでもkubeletがインストールできる方法もあるようですが、私は無効化してます。

```
dphys-swapfile swapoff
systemctl disable dphys-swapfile
```

## 2. cgroupsの設定
cgroups...kernelによってsysfs上の階層構成が変わったりしてたり、よくわかってません。

```
perl -pi -e "s/^/cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1 /" /boot/cmdline.txt 
```

## 3. cri-o,podmanのインストール
arm64のパッケージが含まれる最新バージョンが「1.18.3」のようです。コンパイルすればもっと最新が使えそうですがやってません。

```
OS=Debian_Unstable
VERSION=1.18:1.18.3

curl -L https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable:cri-o:$VERSION/$OS/Release.key | sudo apt-key --keyring /etc/apt/trusted.gpg.d/libcontainers.gpg add -
curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/Release.key | sudo apt-key --keyring /etc/apt/trusted.gpg.d/libcontainers.gpg add -

cat <<EOF | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable:cri-o:$VERSION.list
deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/$VERSION/$OS/ /
EOF

cat <<EOF | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/ /
EOF
```

ここまでは公式の手順で良いのですが、1.18.3は「1.18:/1.18.3」という階層にあるので修正します。
一旦「apt update」してエラーが出ないか確認しましょう。

```
perl -pi -e 's/18:/18:\//' /etc/apt/sources.list.d/devel\:kubic\:libcontainers\:stable\:cri-o\:1.18\:1.18.3.list
apt update
```

Debian10用にバックポートされた「libseccomp2」パッケージ用のリポジトリ追加です。

```
echo 'deb http://deb.debian.org/debian buster-backports main' >> /etc/apt/sources.list
apt update
```

パッケージのインストールをします。
dockerコマンドの代替えである「podman」はkubernetes関連のパッケージと競合しているようなので、「podman-rootless」をインストールします。
https://github.com/containers/podman/issues/5296

```
apt -t buster-backports install libseccomp2
apt install cri-o cri-o-runc podman-rootless
```

インストールできたらcrioサービスを有効化します。

```
systemctl enable crio
```

## 4. kubelet,kubeadm,kubectlのインストール
定番なので説明は書きませんが、「apt update」が失敗する事があるので、成功するまで「apt update」してます。

```
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" >> /etc/apt/sources.list.d/kubernetes.list
apt update
apt install kubelet kubeadm kubectl
```

バージョン指定でインストールする場合は以下のように指定できます。
apt install kubelet=1.20.0-00 kubeadm=1.20.0-00 kubectl=1.20.0-00

## 5. その他設定
必要なカーネルパラメータを追加しておきます。

```
cat <<EOF > /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
EOF
```

必要な前提カーネルモジュールを定義しておきます。

```
echo "br_netfilter" >> /etc/modules
```

一応、nftablesを使わないようにします。詳しく調べてませんが、kube-proxyはnftablesで良くても、flannelがiptablesじゃなきゃダメなのかなと思ってます。

```
update-alternatives --set iptables /usr/sbin/iptables-legacy
```

dockerを利用しないので、iptablesのFORWARDのデフォルトポリシーはACCEPTのままなはずですが、DROPの場合はACCEPTに変更してください。

kubeletがcgroup-driverとしてsystemdを使うように設定します。

```
echo "KUBELET_EXTRA_ARGS=--cgroup-driver=systemd" >> /etc/default/kubelet
```

本記事前提OSのカーネル以外の場合は、この状態でrebootして「kubeadm join」すればうまく動くかもしれません。

## 6. kernelの入れ替え
RaspiOS Lite(ARM64)の場合は、カーネルがよろしくないようでkubernetesクラスタに参加できてもPodがデプロイされませんでした。
具体的には「〜cpu.cfs_period_us: permission denied」と出てPodのデプロイに失敗します。
調べると以下の記事を発見しました。(最初からここ見ればよかったなぁ〜)
https://gist.github.com/dmesser/b110d717b372e36607b3fb75b52ab0ca

ただ、実際カーネルを調べると「CONFIG_CFS_BANDWIDTH」は有効になっているはずなのですが、/procや/sysの階層がkubeletが想定しているような階層になっていないように思えました。
どちらにせよ、おそらくカーネルを再コンパイルしなくてはならないと思われますが、Cephを導入した際にrbdを有効にしたカスタムカーネルがあったので、試しに入れ替えたらうまく動きました。

カーネルをコンパイルした手順は以下になります。
https://qiita.com/Y-Shikase/items/c6fffbbd11a3d039b380#kernel%E3%83%93%E3%83%AB%E3%83%89

上記手順でカーネルを入れ替えて再起動後、「kubeadm join」でうまくクラスタへの参加とPodのデプロイができました。

```:masterノードでの作業
# kubeadm token create
cbrhlc.ckwxfkt2zfv75b4b
# openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'
9dce510f61f13a841dc94fd43d27c5264a76c671deae350363069c6e43c04879
```

tokenとhashをメモる。

```:workerノードでの作業
kubeadm join 10.0.0.1:6443 --token cbrhlc.ckwxfkt2zfv75b4b --discovery-token-ca-cert-hash sha256:9dce510f61f13a841dc94fd43d27c5264a76c671deae350363069c6e43c04879
```

# 注意点

###自炊イメージを使用している場合
dockerでコンテナイメージを自炊して、kubernetes上で使っている場合には少し注意が必要です。
dockerではリポジトリに階層を付けない事ができるのですが、podmanでは「localhost/」という階層が必ず付きますので、dockerとpodmanで混在している場合はpodman側に合わせなければなりません。
dockerのworkerでは自炊のイメージにエイリアスで「localhost/」のリポジトリ名を付けて、kubernetesのyamlも「image: lcoalhost/〜」としました。

https://github.com/containers/buildah/issues/1034

###kubeletのエラー
動いてはいますが、kubeletがエラーを吐いてます。
![スクリーンショット 2020-12-28 18.22.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/c4e8e83e-e069-9297-7db0-d8cfe0ca2c6b.png)

###コンテナのデバッグ
cri-oの不具合でexecが実行できません。
https://github.com/cri-o/cri-o/issues/4280

![スクリーンショット 2020-12-28 20.15.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/511a61a7-8c11-0e04-ca4d-b7be583e4c8b.png)

ARM64向けの新バージョンのcri-oが出るのを待つか、最新を自分でコンパイルする必要がありそうです。

[追記]
再構築する前にダメ元でxUbuntu用のcri-o 1.20.0をインストールしたらデバッグできるようになりました。

```
wget http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/1.20/xUbuntu_20.04/arm64/cri-o_1.20.0~0_arm64.deb
dpkg -i cri-o_1.20.0~0_arm64.deb
```

また、1.20.0にしたからかはちゃんと確認してませんが、イベントに以下のWarningが定期的に出るようになりました。

```
$ kubectl get events -A -w
       :
default          0s          Warning   ImageGCFailed             node/maya                                       failed to get imageFs info: non-existent label "crio-images"
```

kubeletのログは以下のようなログが出ます。

```
Jan  3 00:00:52 maya kubelet[455]: E0103 00:00:52.287957     455 log_metrics.go:66] failed to get pod stats: failed to get imageFs info: non-existent label "crio-images"
```

対策は、以下のissuesに書かれているように、サービス起動ファイルの[Unit]に「Before=kubelet.service」を追記しましょう。
https://github.com/cri-o/cri-o/issues/4437



# dockershim vs cri-o
大袈裟な書き方ですが、同じHW構成でコンテナランタイムだけ違うworkerの無風状態での負荷を比較しました。
Loadがdockerは1.2くらいの平均で、cri-oは0.6くらいの平均になりました。cri-oは100MBくらいメモリ使用量も少なかったです。
![スクリーンショット 2020-12-26 15.24.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/6b31490b-8f49-31cf-130e-146bfb5c528a.png)
![スクリーンショット 2020-12-26 18.05.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/47430562-fa61-8215-8880-0f7f1f23458a.png)
![スクリーンショット 2020-12-26 18.14.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/c8543a4b-0d6d-271f-eb9f-fa461a4f7ed5.png)

# podmanについて
まだ使い込んでないのですが、Dockerfileからのbuildができたので、docker互換として使えるようです。

「images」はそのまま使えましたし、「load/save」も使えそうです。
ただ、「ps」は表示されません。
![スクリーンショット 2020-12-28 18.02.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/97f0de41-8a29-ca01-3e45-305c18a02458.png)

CRI経由で作成されたものだからかもしれませんが、crictlコマンドで表示できました。
![スクリーンショット 2021-01-04 14.38.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/03b7b941-63fc-cd6e-9fab-01e29c40c218.png)

# おわりに
色々試しましたが以下のような結果になりました。

・podmanでDockerfileからのbuild、イメージのsave/loadはできたので、脱dockerはできそう
・負荷はdockerより軽い
・cri-o 1.18.3では不具合によりコンテナのデバッグできない→xUbuntu用のcri-o 1.20.0で動いた!?

新バージョンのcri-oをコンパイルする元気が無かったので、個人的には「もう少し待った方がいいかなー」という感じでした。