RaspberryPi ZeroでおうちkubernetesしようとしたらARMがつらかった話

[ClusterHATを使って世界最小Kubernetesクラスタを構築する - サーバー構築編](https://qiita.com/ShinHashitani/items/24fe88a012e01dfc28d0)という記事を見て、できるんだ、自分もやってみようかなとやってみたら、なかなか出来なかったのでまとめます。

kubeletを動かしたというところが載ってなかったので同じところで詰まってしまったのかな・・・？
それか私の技術力が不足しているせいなのか。
いずれにせよ死ぬほど大変でした。

もし、こんなやり方なら普通にできるよというやり方があったら教えてください......

やったことはキレイにまとまってませんが[こちら](https://gist.github.com/fullsat/f0275009415ca30f21870cbfdb85695b)にまとめています。

この中で、ARM起因で詰まったところだけピックアップしています。
ラインナップは以下のとおりです。

* Etcd(パッケージ版)の罠
* Dockerイメージの罠
* 動かない公式Kubernetes実行ファイル
* 存在しないカーネル機能
* 勝手に立ち上がるコンテナの罠

## ハードウェアの構成

|ClusterHAT上の役割 | kubernetes上の役割 | 機器 |
|:------------------|:-------------------|:-----|
|                   | master             | Raspberry Pi 3b+ |
| 本体              |                    | ClusterHAT |
| controller        |                    | Raspberry Pi b+ |
| node              | node               | Raspberry Pi zero WH * 2 |
| node              | node               | Raspberry Pi zero |

ClusterHATを使ってますがこれはRaspberry Pi Zeroを使ってクラスタが組めるものです。

要はサーバが３つ使えるというだけです。

クラスター...かっこいいじゃん、というモチベーションで購入しました。

なのでkubernetesのホストとしてはZeroシリーズ3台とRaspberryPi3が１台の計4台です。

## Etcd(パッケージ版)の罠

etcdですが...

```
# apt install etcd
```

するとインストールできるくせに起動できません

```
# systemctl edit --full  etcd.service
Environment=ETCD_UNSUPPORTED_ARCH=arm
```

こいつを設定すると動きます。

エラーにこの環境変数がないぞと出るので設定しました。

UNSUPPORTED_ARCHという不吉なワードが並んでいますが、とりあえず動いてるので見て見ぬふりをしました。

## Dockerイメージの罠

dockerをインストールして、動作確認がてらnginxを落としてきましたが、docker runすると一瞬にして落ちます。

ログも一瞬で落ちてるのが分かるだけで、何も残っていませんでした。

2,3時間考えてたら思いつきました、armじゃんと。

```
# docker run -d -P --name ngx arm32v6/nginx:1.15.10-alpine
# docker inspect -f '{{ .NetworkSettings.IPAddress }}' ngx
# docker run --rm -it arm32v6/bash bash
   $ wget http://{{ inspectで出たIP }}/ -S -O - (コンテナ名)
```

で動きました。
ちゃんと動いてたのに、無駄に調べてました。

世の中で出回っているバイナリはだいたいx86_64のものっぽいですね・・・

ちなみにdocker自体も古いバージョンにダウングレードしないと動きませんでした。

## 動かない公式Kubernetes実行ファイル

当初はapiserverもzeroで動かすつもりでしたが、apiserver試してる時に全く動いてくれませんでした。

原因はarm v6アーキテクチャのCPU上で、arm v7の命令を動かしているからのようです。

[この方の記事](https://qiita.com/ShinHashitani/items/ea9ffdefce8ca5786da6) を参考にやりました。

kubernetes(1.14),go(1.12.4)でビルドしました。


```
# go get k8s.io/kubernetes
# cd ${GOPATH}/src/k8s.io/kubernetes
# dd if=/dev/zero of=/swapfile2 bs=1M count=8192 (もともとswap4GBあったけど足りなくてエラーになったから8GB追加(合計12GB)のswap)
# makeswap -f /swapfile2
# swapon /swapfile2
# apt install gcc-arm-linux-gnueabi
# git diff
diff --git a/hack/lib/golang.sh b/hack/lib/golang.sh
index 36f9f437e8..4472ab5bdb 100755
--- a/hack/lib/golang.sh
+++ b/hack/lib/golang.sh
@@ -308,6 +308,7 @@ kube::golang::set_platform_envs() {
 
   export GOOS=${platform%/*}
   export GOARCH=${platform##*/}
+  export GOARM=5
 
   # Do not set CC when building natively on a platform, only if cross-compiling from linux/amd64
   if [[ $(kube::golang::host_platform) == "linux/amd64" ]]; then
@@ -316,7 +317,7 @@ kube::golang::set_platform_envs() {
     case "${platform}" in
       "linux/arm")
         export CGO_ENABLED=1
-        export CC=arm-linux-gnueabihf-gcc
+        export CC=arm-linux-gnueabi-gcc
         ;;
       "linux/arm64")
         export CGO_ENABLED=1
# make(正直なんで１回makeするのか分かってない)
# make all WHAT=cmd/kube-proxy KUBE_VERBOSE=5 KUBE_BUILD_PLATFORMS=linux/arm
# make all WHAT=cmd/kubelet KUBE_VERBOSE=5 KUBE_BUILD_PLATFORMS=linux/arm
# make all WHAT=cmd/kubectl KUBE_VERBOSE=5 KUBE_BUILD_PLATFORMS=linux/arm
# make all WHAT=cmd/kube-controller-manager KUBE_VERBOSE=5 KUBE_BUILD_PLATFORMS=linux/arm
# make all WHAT=cmd/kube-apiserver KUBE_VERBOSE=5 KUBE_BUILD_PLATFORMS=linux/arm
# make all WHAT=cmd/kube-scheduler KUBE_VERBOSE=5 KUBE_BUILD_PLATFORMS=linux/arm
```

kubernetes1.15でmakeしたときはGOARM=6で行けました。
kubernetes1.14でmakeしたときはmake cleanしてなかったせいかGOARM=5じゃないと参考にした記事の通りリンカでエラーが出ました。

## 存在しないカーネル機能

kubeletを実行すると、cpusetが無いと言われます。

cpusetはカーネルの機能ですね。

そのcpusetが最新版Pi Zero用Raspbianのカーネルに含まれてませんでした。

```
# cat /proc/cgroups
#subsys_name    hierarchy       num_cgroups     enabled
cpu     6       51      1
cpuacct 6       51      1
blkio   5       51      1
memory  3       81      1
...
```

raspberrypi zero cpusetで検索すると皆さん苦労してるんだなというのが分かります。

結果的にやったことをリストにすると

* Raspbian-2017-11-29の古いイメージを落とす
* clusterhat用のイメージに変換するツールを使う
* カーネルアップグレードしないようにツールを修正する

です。

だいたい[ここ](https://groups.google.com/forum/#!searchin/clusterhat/cpuset$20zero%7Csort:date/clusterhat/zVDlizwVMM8/V2T-qBCtCgAJ)で言われていることを自分なりに解釈してやってみました。

注意したいのがClusterHAT用イメージ作成ツールは、chrootでapt updateしようとしており、そのaptがarmなので、arm以外のcpu上でイメージを作成しようと思っても動きません。

armのPC持ってないのでcontroller上でイメージを作成しました。

```
# wget http://ftp.jaist.ac.jp/pub/raspberrypi/raspbian_lite/images/raspbian_lite-2017-12-01/2017-11-29-raspbian-stretch-lite.zip
# unzip 2017-11-29-raspbian-stretch-lite.zip
# apt install kpartx
# git clone https://github.com/burtyb/clusterhat-image
# cd clusterhat-image/build
# mkdir {img,mnt,dest,mnt2}
# mv /tmp/2017-11-29-raspbian-stretch-lite.img img/
# vim create.sh # 以下みたいな感じで修正
@@ -64,8 +64,10 @@ if [ "$LITE" = "y" ];then
   mount `echo $LOOP|sed s#dev#dev/mapper#`p1 $MNT/boot
 
   # Get any updates / install and remove pacakges
+  chroot $MNT apt-mark hold raspberrypi-kernel raspberrypi-kernel-headers
   chroot $MNT apt-get update
   chroot $MNT /bin/bash -c 'APT_LISTCHANGES_FRONTEND=none apt-get -y dist-upgrade'
+  chroot $MNT apt-mark unhold raspberrypi-kernel raspberrypi-kernel-headers
   chroot $MNT apt-get -y install bridge-utils wiringpi screen minicom python-smbus
 
   # Setup ready for iptables for NAT for NAT/WiFi use
@@ -327,8 +329,10 @@ if [ "$DESKTOP" = "y" ];then
   mount `echo $LOOP|sed s#dev#dev/mapper#`p1 $MNT/boot
 
   # Get any updates / install and remove pacakges
+  chroot $MNT apt-mark hold raspberrypi-kernel raspberrypi-kernel-headers
   chroot $MNT apt-get update
   chroot $MNT /bin/bash -c 'APT_LISTCHANGES_FRONTEND=none apt-get -y dist-upgrade'
+  chroot $MNT apt-mark unhold raspberrypi-kernel raspberrypi-kernel-headers
   chroot $MNT apt-get -y install bridge-utils wiringpi screen minicom python-smbus
   chroot $MNT apt-get -y purge wolfram-engine 
 
@@ -510,8 +514,10 @@ if [ "$FULL" = "y" ];then
   mount `echo $LOOP|sed s#dev#dev/mapper#`p1 $MNT/boot
 
   # Get any updates / install and remove pacakges
+  chroot $MNT apt-mark hold raspberrypi-kernel raspberrypi-kernel-headers
   chroot $MNT apt-get update
   chroot $MNT /bin/bash -c 'APT_LISTCHANGES_FRONTEND=none apt-get -y dist-upgrade'
+  chroot $MNT apt-mark unhold raspberrypi-kernel raspberrypi-kernel-headers
   chroot $MNT apt-get -y install bridge-utils wiringpi screen minicom python-smbus
   chroot $MNT apt-get -y purge wolfram-engine

# create.sh 2017-11-29
```

## 勝手に立ち上がるコンテナの罠

やっとk8sのコンポーネント全部動いた！

と思ってpodを立ち上げたら立ち上がりませんでした。

kubeletのログが以下のようなやつで埋め尽くされてました。

```
Error response from daemon: OCI runtime create fa
iled: container_linux.go:341: creating new parent process caused \"container_linux.go:1713: running lstat on namespace path \\\"/proc/20093/ns
/ipc\\\" caused \\\"lstat /proc/20093/ns/ipc: no such file or directory\\\"\": unknown"
```

ipcはコンテナ間の通信の話らしいですが、1コンテナしか立ててないはずでした。
それでdocker ps -aするとpauseコンテナが出てきました。
しかもすぐexitしてしまっている模様。

このpauseってやつが原因かもなあと思って、pauseをarmv6用にビルドしてみました。

pauseコンテナのDockerfileなどはk8sのリポジトリ内にありました。

```
$ cd ~/go/src/k8s.io/kubernetes
$ git diff
diff --git a/build/pause/Makefile b/build/pause/Makefile                                                                                      
index 92b0f40b16..b718ef86a1 100644
--- a/build/pause/Makefile
+++ b/build/pause/Makefile
@@ -40,7 +40,7 @@ ifeq ($(ARCH),amd64)
 endif

 ifeq ($(ARCH),arm)
-       TRIPLE ?= arm-linux-gnueabihf
+       TRIPLE ?= arm-linux-gnueabi
 endif
 
# docker build . -t pause-arm 
# docker save pause-arm  > pause-arm.tar
```

## 感動の瞬間

```
$ kubectl get nodes
NAME            STATUS     ROLES    AGE    VERSION
p1              Ready      <none>   37h    v1.14.2-beta.0.9+3c949c7d419670-dirty
$ kubectl get pod  
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   16         36h
```

## まとめ

だいたいZeroのCPUアーキテクチャで詰まってた気がするのでk8s用途でZeroシリーズを使うのはやめたほうが良さそうでした。

よくよく記事を見て回るとPi3×4台くらいで作ってる人がほとんどでした。(みんなお金持ち；；)

最終的に動いたには動きましたが、実際に運用となると、おいそれとアップデートができなさそうなのがちょっと辛いです。

カーネルがそもそも古いバージョン固定じゃないといけないですしね。

まあ趣味だから良いかという感覚です。

そもそも、カーネルコンフィグを正しく設定できる技術力があれば・・・

Pi3を４台くらい余裕で買えるくらいのところに転職したいので何か仕事があったら教えてください。