🌝

Raspberry Pi 4にk3sを導入してElixirアプリ(Livebook)を動かす

2021/08/15に公開

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を見てみます。長いログが表示されるのですが、その中にこんな行がありました。

815 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

 815 15:46:59 raspberrypi k3s[907]: time="2021-08-15T15:46:59.959320143+09:00" level=info msg="Creating CRD helmcharts.helm.cattle.io"
 815 15:47:00 raspberrypi k3s[907]: time="2021-08-15T15:47:00.016548162+09:00" level=info msg="Creating CRD helmchartconfigs.helm.cattle.io"
 815 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"
 815 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"
 815 15:47:00 raspberrypi k3s[907]: I0815 15:47:00.531727     907 shared_informer.go:240] Waiting for caches to sync for tokens
 815 15:47:00 raspberrypi k3s[907]: I0815 15:47:00.599097     907 controller.go:611] quota admission added evaluator for: serviceaccounts
 815 15:47:00 raspberrypi k3s[907]: I0815 15:47:00.609180     907 controllermanager.go:574] Started "endpointslice"
 815 15:47:00 raspberrypi k3s[907]: I0815 15:47:00.609264     907 endpointslice_controller.go:256] Starting endpoint slice controller
 815 15:47:00 raspberrypi k3s[907]: I0815 15:47:00.609458     907 shared_informer.go:240] Waiting for caches to sync for endpoint_slice
 815 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  815 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