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クラスターが動いているので、続いて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のケース、キズ無くていいね!」なんて思わないですよね。「何かネジが入らないんだけど!」という不具合が記憶に残りやすいが、このようなミスを極力下げるために日々プロセス改善を続け、加工技術を磨いてらっしゃる製造業を支えるために弊社ではアプリケーション開発をしております。