CADDi Tech Blog

モノづくり産業のポテンシャルを開放するCADDiのTech Blogです。

whasm!("Rust Christmas: WebAssemblyをKubernetes上で動かす")

1. Rust Christmas, I gave you my heart

はじめに

折角クリスマスなので、楽しい近未来感のある技術を検証してみようと思います。お仕事で使っているRust、Kubernetes、そして個人的に興味のあるWebAssemblyをガッチャンコ出来ないか考えてみた。技術者としては常に勉強をする事が重要だと思っているので勉強兼ねて 「highly experimental」 と注意書きのある技術の紹介をしたいと思います。

本日は会社のアドベントカレンダーということもあり仕事しているふりをする必要があるため、ポエムも混ぜながらKubernetes上でWebAssemblyのモジュールを実行するKrustletを使ってみたお話をします。

Krustletとは?

GitHubのページ曰く: Krustlet is a tool to run WebAssembly workloads natively on Kubernetes. Krustlet acts like a node in your Kubernetes cluster.

流行りの言葉を並べた感じになりますが、今年の4月にMicrosoft Azure部門で始まった Krustlet プロジェクト。WebAssemblyモジュールを直接Kubernetesクラスター上でPod同様に実行するKubernetesのNode同等のプログラム。

通常のKubernetesクラスターはマスターと、複数のノードによって構成され、各ノード内のコンテナをKubeletがマスターからの指示の元手配する。新しくコンテナをクラスター上で動かしたい場合マスターに依頼を送れば、後はマスターが勝手に適切なノード上で動かしてくれます。

環境構築と設定

まずは Kubernetesクラスターが必要です。今回はminikubeで立ち上げました。こちらの図で描かれているように、既存のコンテナ実行するNodeと、新たにKrustletを利用してWasm実行するNodeの2つを並行で利用します。

$ minikube start

立ち上がったら動作確認しましょう。minikubeは勝手にkubectlの設定もしてくれるので、そのまま試せるはずです:

$ minikube status
host: Running
kubelet: Running
apiserver: Running
kubectl: Correctly Configured: pointing to minikube-vm at 192.168.99.100

$ kubectl cluster-info
Kubernetes master is running at https://192.168.99.100:8443
KubeDNS is running at https://192.168.99.100:8443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

Kubernetes dashboardも立ち上げることが出来ます。kubectl で取得できる情報ばかりですが、見やすいので一応裏で立ち上げておきます。

$ minikube dashboard
🔌  Enabling dashboard ...
🤔  Verifying dashboard health ...
🚀  Launching proxy ...
🤔  Verifying proxy health ...
🎉  Opening http://127.0.0.1:36305/api/v1/namespaces/kube-system/services/http:kubernetes-dashboard:/proxy/ in your default browser...

ブラウザー開くとこんな感じのダッシュボード。

Kubernetes dashboard

手元でKubernetesクラスターが動いているので、続いてkrustletを手元で実行します。GitHubからバイナリーを直接ダウンロード出来ます。wasi版とwascc版がありますが、今回はwasiで行きます。違いをすごく雑に纏めると、Wasmのモジュールが外部環境とインタフェースする上で必要な接続プロトコールが2種類あり、それに適したバイナリーを利用する必要がある。

こちらのバイナリーは、kubelet同様KubernetesのNode上でPod実行出来る環境を用意するものです。普段だとNode上でコンテナが実行されますが、Wasmの場合はKrustletがWasmを実行する形になります。

Kubernetesのcontrol planeにアクセスして、「該当するPodは実行出来るので待っています」という登録をする必要があり、こちら少し分かりにくいんですがささっと進めていきます。

まずはBootstrapから始めます。詳細はこちらを参考にしてください。

$ bash <(curl https://raw.githubusercontent.com/deislabs/krustlet/master/docs/howto/assets/bootstrap.sh)

裏ではまずKrustletがKubernetesのマスターと通信出来るように秘密鍵等を整備している。こちらの情報が最終的には bootstrap.confに書き出され、これを参考にしてkrustletは立ち上がる。

$ export KUBECONFIG=~/.krustlet/config/kubeconfig
$ krustlet-wasi --node-ip 192.168.99.100 --cert-file=~/.krustlet/config/krustlet.crt --private-key-file=~/.krustlet/config/krustlet.key --bootstrap-file=~/.krustlet/config/bootstrap.conf
BOOTSTRAP: TLS certificate requires manual approval. Run kubectl certificate approve silicon-tls

最後に、KrustletがKubernetesマスターと通信した上で、CSR(certificate service request)を手動で承認する必要があります。別ターミナルで指示通りクラスターのCSR承認しましょう

$ kubectl certificate approve silicon-tls
certificatesigningrequest.certificates.k8s.io/silicon-tls approved

するとついにkrustletが稼動します!

BOOTSTRAP: TLS certificate requires manual approval. Run kubectl certificate approve silicon-tls
BOOTSTRAP: received TLS certificate approval: continuing
[2020-12-14T14:51:51Z ERROR wasi_provider::states::registered] Cannot run kube-proxy
[2020-12-14T14:51:56Z ERROR wasi_provider::states::registered] Cannot run kube-proxy
[2020-12-14T14:52:01Z ERROR wasi_provider::states::registered] Cannot run kube-proxy
[2020-12-14T14:52:06Z ERROR wasi_provider::states::registered] Cannot run kube-proxy

こちらエラー出ているのは一旦無視して大丈夫です。ちなみに、krustlet-wasi実行時にはほぼログメッセージが出ないので、気になる方はRustのロギング環境変数を設定して RUST_LOG=infoで実行してみて下さい。

最後確認のため、ノードとして登録されているか確認しましょう。

$ kubectl get nodes -o=wide
NAME       STATUS   ROLES    AGE   VERSION   INTERNAL-IP      EXTERNAL-IP   OS-IMAGE            KERNEL-VERSION   CONTAINER-RUNTIME
minikube   Ready    master   29h   v1.14.0   10.0.2.15        <none>        Buildroot 2018.05   4.15.0           docker://18.6.2
silicon    Ready    <none>   29h   0.5.0     192.168.99.100   <none>        <unknown>           <unknown>        mvp

いいですね、siliconという別ノードが立ち上がっています。少し詳細を見てみましょう。

$ kubectl describe node silicon
Name:               silicon
Roles:              <none>
Labels:             beta.kubernetes.io/arch=wasm32-wasi
                    beta.kubernetes.io/os=linux
                    kubernetes.io/arch=wasm32-wasi
                    kubernetes.io/hostname=silicon
                    kubernetes.io/os=linux
                    type=krustlet

アーキテクチャとしても wasm32-wasi対応されている事が分かります。

wasmをkubernetes上で実行

色々と整備できたので、待ちに待ったデプロイです。krustletのデモをそのまま使ってみました。実際に自分でもコンパイルしてみたんですが、OCIコンテナにパッケージする必要もあるため省略します。サンプルのYamlファイルはkrustletのレポジトリにあります: https://github.com/deislabs/krustlet/tree/master/demos/wasi/simpleserver

$ kubectl apply -f simpleserver.yaml

YAMLファイルの中にはTolerationsがありますが、こちらはある意味Kubernetesのハック。wasm32-wasiアノテーションがあるPodに関しては、NoSchedule, NoExecuteということで、普通のkubeletは実行しないようにする指示が入っています。このTolerantionsが入っていないとコンテナでも無いWasmをコンテナ扱いしてしまい、実行が失敗してしまいます。Kubernetesはコンテナのオーケストレーションするものなので、今回のようにwasmを無理やりコンテナに突っ込んだものを扱う仕様は恐らく想定されないはずですから、krustletの開発者はちょっとした工夫をしたんですね。

ここで、先程スケジュールしたPodのログを取りに行きましょう。

$ kubectl logs simpleserver
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!

という訳で、wasmモジュールの出力がkubernetesのログとして見れました!ただのHello Worldですが、これが実現出来ている背景にはKubernetesの素晴らしい設計と、多大なる努力があることを感じた。

waSCCでウェブサーバー動かそう

WASIではネットワーキング対応出来ていませんが、折角なのでwaSCCを試してみましょう。krustlet-wasccを同じオプションで実行して:

$ ~/Downloads/krustlet-v0.5.0/krustlet-wascc --node-ip 192.168.99.1  --cert-file=~/.krustlet/config/krustlet.crt --private-key-file=~/.krustlet/config/krustlet.key --bootstrap-file=~/.krustlet/config/bootstrap.conf

別ターミナルでサンプルのyamlファイルをデプロイ: 

$ kubectl apply -f uppercase-wascc.yaml 
pod/uppercase created
$ kubectl get pods
NAME        READY   STATUS      RESTARTS   AGE
uppercase   0/1     ImagePull   0          2s

こちらのステータスがImagePullからRunningになったらリクエストを送ってみましょう:

$ curl localhost:8080/?hello-world
{"original":"hello-world","uppercased":"HELLO-WORLD"}a

そしてwasmのログの方を確認すると:

$ kubectl logs uppercase
11:47:47 [ INFO] [MDFC3LZ2YAGPTI452SEKDZ3D5D6QJD62R5KDPPJVPDL5B6DFFQKM3B62] Query String: hello-world

サンプルのソースコードを見てみましょう。

fn uppercase(r: codec::http::Request) -> HandlerResult<codec::http::Response> {
    info!("Query String: {}", r.query_string);
    let upper = UppercaseResponse {
        original: r.query_string.to_string(),
        uppercased: r.query_string.to_ascii_uppercase(),
    };

    Ok(codec::http::Response::json(upper, 200, "OK"))
}

ウェブアプリケーションになってるじゃん!サンプルのソースコードは本当に簡単なんですが、これがWebAssemblyになってKubernetes上で動いているって、ワクワクしますよね。

2. But the very next day, you gave it away

Krustletの実用性

最初は楽しくKrustletとはお付き合いさせて頂きましたが、やはり highly experimental で実は動かすだけで数日かかりました。相当細かいKubernetes内部の仕様も勉強した上で挑むべきだったと反省はしていますが、セキュリティ周り含めて幅広い知見が無いと躓いた瞬間に時間が溶けます。

日々の業務で Kubernetes 使っているので、そちらで利用しようとした結果、色々と苦労して諦めてREADMEどおりにminikubeで立ち上げました(笑)

READMEには本番環境では使うなと記載もありますし、WASIの規格が決まっていない時点で本番運用は論外だと思いますが、サーバアプリケーションをWasmにコンパイルしてそれを既存のインフラ上で実行させる世界感が見えてきましたね。。。

WebAssembly と Runtime 達

WASI規格は未完成ですし現時点でもネットワーク通信出来ないし、実用性のあるアプリケーション作ることは出来ない。これからのAPIの安定やエコシステムの拡大に期待すべきでしょう。今回はネットワーキングサポートの有るwaSCCランタイムを利用して簡単なウェブアプリケーションを立ち上げたが、こちらもまだデータベースへアクセスも出来ない。

時間注ぎ込んだ結果すぐには実らないかもしれない。クリスマスは、おとなしくコード書くべきだったのかもしれない。

3. Maybe next year, I’ll give it to someone special

それでも勉強し続ける

アプリやウェブ上のプロダクトを作る時って、ユーザさんには「アプリが使いやすい」とか「サービスすごく便利」と言われますが、「インフラすごく格好いい?」なんて褒めてもらえないですよね。「今日もホームページ動いている、拍手!」なんて言われたら逆に期待値の低さを感じてしまう。インフラのユーザインパクトはどちらかというと減点方式であり、改善に向けた努力の結果がどうしてもユーザには理解してもらえない部分はある。

インフラの仕事はUI作るのと違い因果関係が見えにくく、長い時間軸で価値発揮する仕事だと思っている。ボタン押してすぐ結果が出ないので、ある程度自分の行動がユーザの為になっている事を自信持って周りとコミュニケーション出来ないと、周りの信頼を失いやすい。デプロイ速度を上げたり、セキュリティ強化したり一般人には通じないが非常に大切な縁の下の力持であることは覚えておきたい。

そんなインフラ開発者に似ているのが日本の製造業に関わっているメーカーや町工場さん。自作PC作りながら「このPCのケース、キズ無くていいね!」なんて思わないですよね。「何かネジが入らないんだけど!」という不具合が記憶に残りやすいが、このようなミスを極力下げるために日々プロセス改善を続け、加工技術を磨いてらっしゃる製造業を支えるために弊社ではアプリケーション開発をしております。