Raspberry Pi 4にk3sを導入してElixirアプリ(Livebook)を動かす
Livebookという、ElixirのためのJupyterLabのようなものをいつでもすぐに使える環境を作りたいという気持ちになりました。Kubernetesの練習のために、Raspberry Pi 4にk3sで環境を構築して動かしてみます。
使用するソフトウェア
Livebook
Livebookは、プログラミング言語ElixirのためのJupyterLabみたいなものです。Elixirのコードを、JupyterLabのようにノートブック上で実行したりドキュメントしたりできるものです。Nerves Livebookというものもあり、IoTデバイスでセンサデータを読み取るようなコードを動的に実行できるという応用もあります。
k3s
k3sは、軽量マシン向けのKubernetesです。Edgeコンピューティングや組込み・IoT等において有効であると謳われています。
Lightweight Kubernetes. Easy to install, half the memory, all in a binary of less than 100 MB.
Rancher Docs: K3s - Lightweight Kubernetes
日本語での解説としては「IoT向けの軽量なKubernnetes(?) k3sとはなにか」という記事がわかりやすいでしょう。
ハードウェアとOS
今回は、Rapsberry Pi 4を1台のみ用います。つまり、複数ノードによるクラスタ環境を作りません(それもそのうちやってみたい)。
OSのバージョンは以下の通りです。
$ uname -a
Linux raspberrypi 5.10.52-v7l+ #1441 SMP Tue Aug 3 18:11:56 BST 2021 armv7l GNU/Linux
$ lsb_release -a
No LSB modules are available.
Distributor ID: Raspbian
Description: Raspbian GNU/Linux 10 (buster)
Release: 10
Codename: buster
k3sの準備
Quick-Start Guideの通りインストールします。
$ curl -sfL https://get.k3s.io | sh -
[INFO] Finding release for channel stable
[INFO] Using v1.21.3+k3s1 as release
[INFO] Downloading hash https://github.com/k3s-io/k3s/releases/download/v1.21.3+k3s1/sha256sum-arm.txt
[INFO] Downloading binary https://github.com/k3s-io/k3s/releases/download/v1.21.3+k3s1/k3s-armhf
[INFO] Verifying binary download
[INFO] Installing k3s to /usr/local/bin/k3s
[INFO] Creating /usr/local/bin/kubectl symlink to k3s
[INFO] Creating /usr/local/bin/crictl symlink to k3s
[INFO] Creating /usr/local/bin/ctr symlink to k3s
[INFO] Creating killall script /usr/local/bin/k3s-killall.sh
[INFO] Creating uninstall script /usr/local/bin/k3s-uninstall.sh
[INFO] env: Creating environment file /etc/systemd/system/k3s.service.env
[INFO] systemd: Creating service file /etc/systemd/system/k3s.service
[INFO] systemd: Enabling k3s unit
Created symlink /etc/systemd/system/multi-user.target.wants/k3s.service → /etc/systemd/system/k3s.service.
[INFO] systemd: Starting k3s
Job for k3s.service failed because the control process exited with error code.
See "systemctl status k3s.service" and "journalctl -xe" for details.
インストール自体はできたようですが、k3sがエラーで起動しないようです。指示のある通り、 journalctl -xe
を見てみます。長いログが表示されるのですが、その中にこんな行がありました。
8月 15 15:36:48 raspberrypi k3s[2805]: time="2021-08-15T15:36:48.763067811+09:00" level=error msg="Failed to find memory cgroup, you may need to add \"cgroup_memory=1 cgroup_enable=memory\" to your linux cmdline (/boot/cmdline.txt on a Raspberry Pi)"
/boot/cmdline.txt
というファイルに cgroup_memory=1 cgroup_enable=memory
という記述を足すようにということです。その後、一度リブートします。
リブート後にsystemctl status k3s.service
をみてみると、ちゃんと動いているようですね。
$ systemctl status k3s.service
● k3s.service - Lightweight Kubernetes
Loaded: loaded (/etc/systemd/system/k3s.service; enabled; vendor preset: enabled)
Active: activating (start) since Sun 2021-08-15 15:46:28 JST; 32s ago
Docs: https://k3s.io
Process: 903 ExecStartPre=/bin/sh -xc ! /usr/bin/systemctl is-enabled --quiet nm-cloud-setup.service (code=exited, status=0/SUCCESS)
Process: 905 ExecStartPre=/sbin/modprobe br_netfilter (code=exited, status=0/SUCCESS)
Process: 906 ExecStartPre=/sbin/modprobe overlay (code=exited, status=0/SUCCESS)
Main PID: 907 (k3s-server)
Tasks: 57
Memory: 287.0M
CGroup: /system.slice/k3s.service
├─ 907 /usr/local/bin/k3s server
└─1000 containerd
8月 15 15:46:59 raspberrypi k3s[907]: time="2021-08-15T15:46:59.959320143+09:00" level=info msg="Creating CRD helmcharts.helm.cattle.io"
8月 15 15:47:00 raspberrypi k3s[907]: time="2021-08-15T15:47:00.016548162+09:00" level=info msg="Creating CRD helmchartconfigs.helm.cattle.io"
8月 15 15:47:00 raspberrypi k3s[907]: time="2021-08-15T15:47:00.146302088+09:00" level=info msg="Waiting for CRD addons.k3s.cattle.io to become available"
8月 15 15:47:00 raspberrypi k3s[907]: time="2021-08-15T15:47:00.454083421+09:00" level=info msg="Waiting for node raspberrypi CIDR not assigned yet"
8月 15 15:47:00 raspberrypi k3s[907]: I0815 15:47:00.531727 907 shared_informer.go:240] Waiting for caches to sync for tokens
8月 15 15:47:00 raspberrypi k3s[907]: I0815 15:47:00.599097 907 controller.go:611] quota admission added evaluator for: serviceaccounts
8月 15 15:47:00 raspberrypi k3s[907]: I0815 15:47:00.609180 907 controllermanager.go:574] Started "endpointslice"
8月 15 15:47:00 raspberrypi k3s[907]: I0815 15:47:00.609264 907 endpointslice_controller.go:256] Starting endpoint slice controller
8月 15 15:47:00 raspberrypi k3s[907]: I0815 15:47:00.609458 907 shared_informer.go:240] Waiting for caches to sync for endpoint_slice
8月 15 15:47:00 raspberrypi k3s[907]: I0815 15:47:00.632186 907 shared_informer.go:247] Caches are synced for tokens
サービスがちゃんと動いてるか確認します。
$ kubectl get services
WARN[2021-08-15T15:49:16.927347038+09:00] Unable to read /etc/rancher/k3s/k3s.yaml, please start server with --write-kubeconfig-mode to modify kube config permissions
error: error loading config file "/etc/rancher/k3s/k3s.yaml": open /etc/rancher/k3s/k3s.yaml: permission denied
/etc/rancher/k3s/k3s.yaml
というファイルのパーミッションの問題で、 kubectl
コマンドが設定ファイルをopen
できないようです。
$ ls -l /etc/rancher/k3s/k3s.yaml
-rw------- 1 root root 2961 8月 15 15:46 /etc/rancher/k3s/k3s.yaml
というわけで、読み取り権限をつけた後でもう一度実行してみると、今度はうまくいきました。
$ sudo chmod 644 /etc/rancher/k3s/k3s.yaml
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 14m
/etc/rancher/k3s/k3s.yaml is world readable · Issue #389 · k3s-io/k3sというイシューで、その辺りの話がされているのでご参照ください。k3sのインストール時に
$ curl -sfL https://get.k3s.io | sh -s - --write-kubeconfig-mode 644
としておけば良かったようです。
というわけで、以下の通り各種のコンポネントが元気に動いているのが確認できました。
$ kubectl get services --all-namespaces
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
default kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 17m
kube-system kube-dns ClusterIP 10.43.0.10 <none> 53/UDP,53/TCP,9153/TCP 17m
kube-system metrics-server ClusterIP 10.43.12.89 <none> 443/TCP 17m
kube-system traefik LoadBalancer 10.43.79.63 172.16.0.129 80:32621/TCP,443:30582/TCP 15m
$ kubectl get nodes --all-namespaces
NAME STATUS ROLES AGE VERSION
raspberrypi Ready control-plane,master 17m v1.21.3+k3s1
$ kubectl get pods --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-7448499f4d-5zkxr 1/1 Running 0 17m
kube-system metrics-server-86cbb8457f-82tmx 1/1 Running 0 17m
kube-system local-path-provisioner-5ff76fc89d-slrvh 1/1 Running 0 17m
kube-system helm-install-traefik-crd-75ccs 0/1 Completed 0 17m
kube-system helm-install-traefik-r8mkh 0/1 Completed 1 17m
kube-system svclb-traefik-b6df4 2/2 Running 0 15m
kube-system traefik-97b44b794-zfwwp 1/1 Running 0 15m
Livebookをデプロイする
dockerイメージの作成
Docker Hubで提供されているLivebookおよびElixirのイメージには、Raspberry Pi 4のアーキテクチャであるarm32のイメージがないため、自前でイメージを作成する必要があります。
その作業のためにkentaro/livebook-on-k3sに必要なものを用意したので、そちらを用います。
$ git clone https://github.com/kentaro/livebook-on-k3s.git
$ cd livebook-on-k3s/
Raspberry Pi 4にDockerを入れてなかったら、ここでインストールしておきます。
$ curl -sSL https://get.docker.com | sh
dockerをインストールできたら、docker build
でイメージを作成します。
$ sudo docker build -t livebook .
この時に用いたDockerfileは、先に述べた事情により、以下を混ぜ合わせたものとして作成しました(すごく汚くなってしまいましたが、kentaro/livebook-on-k3sに置いてあります)。
また、Alpineの3.13以上(?)だと「alpine:3.13 armv7をRaspberry Piでrunしたらネットワークにつながらなかった」にある通りの問題があるため、現時点で3.12系の最新バージョンである3.12.7を指定しています。
というわけでイメージができました。
$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
livebook latest d049cbb4d2d3 32 seconds ago 144MB
Podにデプロイする前に、Dockerコンテナとして起動を確認してみます。
$ sudo docker run --rm -it -p 8080:8080 --name livebook livebook
11:57:03.729 [info] :alarm_handler: {:set, {:system_memory_high_watermark, []}}
[Livebook] Application running at http://localhost:8080/?token=t5jftyvxmnwva6becxfxvdkz3cxydlnc
コンソールに表示されたURLにアクセスすると、Livebookが立ち上がっています(localhost
となっている箇所は、Raspberry Pi 4のIPアドレスに変更してください)。
上記で作ったイメージを、後でPodにデプロイする時に使えるよう、ローカルにレジストリを立ててそちらに登録しておきます。まずは、Deploy a registry server | Docker Documentationにある通り、レジストリとなるサーバを起動します。
$ sudo docker run -d -p 5000:5000 --restart=always --name registry registry:2
そして、上記で作ったイメージにtagを付与した上で、ローカルのレジストリへpushします。
$ sudo docker tag livebook localhost:5000/livebook
$ sudo docker push localhost:5000/livebook
Podにデプロイする
このイメージを用いて、LivebookをPodにデプロイします。先ほどのkentaro/livebook-on-k3sに含まれる設定ファイルを用いて、以下の通りkubectl apply
コマンドで適用します。
$ kubectl apply -f livebook.yml
service/livebook-service created
deployment.apps/livebook-deployment created
ちなみに、 livebook.yml
の中身はこんな感じ。
apiVersion: v1
kind: Service
metadata:
name: livebook-service
labels:
app: livebook
spec:
selector:
app: livebook
type: NodePort
ports:
- protocol: TCP
port: 8080
targetPort: 8080
nodePort: 30080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: livebook-deployment
spec:
replicas: 1
selector:
matchLabels:
app: livebook
template:
metadata:
labels:
app: livebook
spec:
containers:
- name: livebook-container
image: localhost:5000/livebook
ports:
- containerPort: 8080
ポイントとしては、上記で作成したローカルのDockerイメージを使いたいので、イメージの指定をimage: localhost:5000/livebook
としています。
さて、LivebookにアクセスするためのURLはログに吐かれているはず。どうやってみたらいいのでしょうか。以下のように kubectl get pods
でPodの名前を取得したのち、 kubectl logs
でみると良いでしょう。
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
livebook-deployment-5864f9c6d9-srp2s 1/1 Running 0 3m1s
$ kubectl logs livebook-deployment-5864f9c6d9-srp2s
13:53:13.100 [info] :alarm_handler: {:set, {:system_memory_high_watermark, []}}
[Livebook] Application running at http://localhost:8080/?token=x6vb5bf2asnzlrrqzfiynyud3bx6jwib
上記の通り、Livebookへの接続先のURLがトークン付きで表示されています。しかし、これはRaspberry Pi 4上のURLなので、手元のマシンからアクセスするには、もうひと捻り必要です。 localhost
となっているところは、Raspberry Pi 4のIPアドレスに変更します。また、livebook.yml
でNodeのポートを30080に設定してあったので、ポート番号は30080番を使います。
めでたくブラウザでアクセスできました。長かった〜。
動作確認もしてみます。ちゃんと動いているようですね。
次のステップ
現状では、Podが再起動したらディスクに書き出したデータが消えてしまうはず。StatefulSetを使えば良いでしょう。実際ちょっとやってみたのですが、うまくいかないまま時間切れになってしまったので、また次の課題にしたいと思います。
おわりに
ていうか、k3sやDockerを使ってLivebookを起動しようとしたから色々苦労しましたが、普通にRaspberry Pi 4上でLivebookを直接実行したらもっとすんなりできましたね。
Discussion