はじめに
NRI OpenStandia Advent Calendar 2021 9日目はKrustletを取り上げます。
KrustletはAdvent Calendar 8日目で紹介したWebAsemblyのアプリケーションをKubernetes上で実行できるようにしたOSSです。
Krustletが登場したことにより、バックエンドのシステムでもコンテナではない別の選択肢(WebAssembly)が増えました。
もちろんKrustletはコンテナをWebAssemblyで置き換えることを目指したものではないため、適材適所で使用する技術の選択は必要になります。
本記事ではKrustletを用いてWebAssemblyのサンプルアプリの動かし、どのような仕組みでWebAssemblyが実行されるかを調べてみました。
1. Krustletの概要
1.1 Krustletとは
Rustで実装されたKubeletであることから、Krustletと名づけられました。KrustletにはKubelet APIが実装されています。
KrustletはコンテナとWebAssemblyを同じKubernetesクラスタ内で実行することを目指してMicrosoftのDeis Labsにより開発が進められてきました。2020年4月にKrustlet v0.1.0のリリースが発表され、2021年12月時点ではv1.0.0-alpha.1となっています。また、2021年7月にはCNCFのSandboxプロジェクトへ加わりました。
※ CNCF Landscapeより引用
Krustletのゴールとしては以下の2点が掲げられています。
- WebAssemblyワークロードのKubernetesへのデプロイを簡単にする
- KubernetesコンポーネントがGoではない別の言語(KrustletではRust)で実装できることをコミュニティに示す。
1.2 Krustletの全体像
Krustletを利用する際の全体像を示します。
KrustletはKubelet APIを実装しており、Kubernetesのワーカーノードのように機能させることができます。ただし、Kubeletではコンテナベースのランタイムを用いてコンテナを実行しますが、KrustletではWASI(WebAssembly System Interface)ベースのランタイムを用いてWebAssemblyを実行します。
なお、Krustletではwasmtimeをベースとしたランタイムが利用されているようです。
KrustletではWebAssemblyワークロードをコンテナレジストリから取得します。それでは、どうやってWebAssemblyをコンテナレジストリへPushするのでしょうか。答えは「コンテナの仕様に則ってWebAssemblyをラップする」です。
WebAssemblyをコンテナ形式にラップするにはwasm-to-ociなどのツールを使用します。OCIとはOpen Container Initiativeの略語であり、主にコンテナのランタイム仕様(runtime-spec)とイメージ仕様(image-spec)を規定しています。コンテナ形式にラップされたWebAssemblyの仕様はWasm Image specificationsとして規定されています。
2. Krustletの環境を構築する
2.1 環境
本記事ではKrustletを用いてKubernetes上でWebAssemblyのサンプルアプリを動かします。
動作確認は以下の環境で行いました。
# | ソフトウェアスタック | 備考 |
---|---|---|
1 | Windows 10 Pro 1909 | ホストマシンのOS |
2 | Ubuntu 20.04 LTS(WSL2) | Docker Desktopで利用 |
3 | Docker Desktop for Windows 3.2.2(61853) | |
4 | Docker Engine 20.10.5 | |
5 | Kubernetes v1.19.7 | Docker Desktopでインストールされるもの |
6 | Krustlet v1.0.0-alpha.1 |
Windows環境でKrustletを動かす場合、DockerはWSL2ベースで動いている必要があります。
これはDocker Desktopの設定画面から確認できます。
2.2 Krustletのインストール
Krustletのインストールは非常に簡単です。
公式サイトにも記載がありますが、githubからKrustletの実行ファイルをダウンロードして解凍するだけです。
WSL2のプロンプトを開き、下記のコマンドでKrustletの実行ファイルを取得します。
以降の手順では、ダウンロードした実行ファイルkrustlet-wasi
があるディレクトリで作業を行うことを想定し、パスは通しません。
[~]$ mkdir krustlet
[~]$ cd krustlet
[~/krustlet]$ curl -OL https://krustlet.blob.core.windows.net/releases/krustlet-v1.0.0-alpha.1-linux-amd64.tar.gz
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 14.7M 100 14.7M 0 0 537k 0 0:00:28 0:00:28 --:--:-- 565k
[~/krustlet]$
[~/krustlet]$ tar -zxvf krustlet-v1.0.0-alpha.1-linux-amd64.tar.gz
README.md
LICENSE
krustlet-wasi
2.3 KuberentesクラスタにKrustletノードを追加
KrustletノードをKubernetesクラスタへ追加します。
公式サイトの手順を参考に進めます。
はじめにWSL2のIPアドレスを確認しておきます。
下記の通り、ifconfig
コマンドで確認するとWSL2のIPアドレスが172.17.117.108
であることが分かります。
[~/krustlet]$ ifconfig eth0
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.117.108 netmask 255.255.255.240 broadcast 172.17.117.111
inet6 fe80::215:5dff:fe99:97b5 prefixlen 64 scopeid 0x20<link>
ether 00:15:5d:99:97:b5 txqueuelen 1000 (Ethernet)
RX packets 6073 bytes 15963285 (15.9 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 1985 bytes 132197 (132.1 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
次にKubernetesクラスタにKrustletのノードを追加します。
ノードを追加するにはブートストラップ用のトークンを用意する必要があります。
bootstrappingの手順を参考に進めます。
実行するコマンドは下記の通りです。
bootstrap.sh
という便利なシェルが提供されているので、こちらを実行しています。
[~/krustlet]$ curl -OL https://raw.githubusercontent.com/krustlet/krustlet/main/scripts/bootstrap.sh
[~/krustlet]$ chmod 777 ./bootstrap.sh
[~/krustlet]$ ./bootstrap.sh
secret/bootstrap-token-qd115j created
Switched to context "docker-desktop".
Context "docker-desktop" renamed to "tls-bootstrap-token-user@kubernetes".
User "tls-bootstrap-token-user" set.
Context "tls-bootstrap-token-user@kubernetes" modified.
Context "tls-bootstrap-token-user@kubernetes" modified.
続いてKubernetesクラスタへKrustletのノード追加リクエストを送ります。
この時に指定できるオプションはKrustlet Configurationに記載があります。ここでは次の3項目のみ指定します。
-
--node-ip
:先ほど取得したWSL2のIPアドレスを指定します -
--node-name
:ノード名になるので、任意の名前を指定します -
--hostname
:任意の名前を付けますが、のちに利用する証明書の名前が<ホスト名>-tls
のフォーマットになるので、分かりやすい名前を指定します
[~/krustlet]$ ./krustlet-wasi --node-ip 172.17.117.108 --node-name krustlet --hostname krustlet-host
BOOTSTRAP: TLS certificate requires manual approval. Run kubectl certificate approve krustlet-host-tls
なお、ノード追加のリクエストを送るコマンド打った後プロンプトは戻ってきません。
この段階ではKrustletからKubernetesクラスタに対してノード追加のリクエストが来ている状態です。
下記のコマンドによりリクエストが来ていることを確認します。このコマンドはWindowsとWSL2のどちらで実行しても構いません。
また、リクエスト名は<ホスト名>-tls
のフォーマットになります。
D:\wasm-work>kubectl get CertificateSigningRequest krustlet-host-tls
NAME AGE SIGNERNAME REQUESTOR CONDITION
krustlet-host-tls 2m56s kubernetes.io/kubelet-serving docker-for-desktop Pending
確かにkrustlet-host-tls
という名前でノード追加のリクエストが来ており、CONDITIONがPendingになっていることが確認できます。
ノード追加のリクエストはKubernetesクラスタ側で手動承認します。
承認コマンドは以下の通りです。承認コマンドはWindowsとWSL2のどちらで実行しても構いません。
D:\wasm-work>kubectl certificate approve krustlet-host-tls
certificatesigningrequest.certificates.k8s.io/krustlet-host-tls approved
承認コマンドを実行した際、ノード追加のリクエストを送った側のプロンプトにはreceived TLS certificate approval: continuing
というメッセージが表示されます。
[~/krustlet]$ ./krustlet-wasi --node-ip 172.17.117.108 --node-name krustlet --hostname krustlet-host
BOOTSTRAP: TLS certificate requires manual approval. Run kubectl certificate approve krustlet-host-tls
BOOTSTRAP: received TLS certificate approval: continuing
リクエストを承認したことはCertificateSigningRequest
の状態で確認可能です。
リクエストを削除する際には以下のコマンドを打ちます。
kubectl delete CertificateSigningRequest krustlet-host-tls
[~]$ kubectl get CertificateSigningRequest
NAME AGE SIGNERNAME REQUESTOR CONDITION
krustlet-host-tls 11m kubernetes.io/kubelet-serving docker-for-desktop Approved,Issued
最後にKrustletのノードがKubernetesクラスタに追加されていることを確認します。
ノード追加時に--node-name krustlet
のオプションを指定していたため、krustlet
という名前でノードが追加されています。また、VERSIONにはKrustletのバージョンである1.0.0-alpha.1
が表示されています。
CONTAINER-RUNTIMEにはmvp
と表示がありますが、これはMinimum Viable Productの略であり、まだKrustletに最小限の機能しか実装されていないことを意味しています。
D:\wasm-work>kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
docker-desktop Ready master 254d v1.19.7 192.168.65.4 <none> Docker Desktop 5.4.72-microsoft-standard-WSL2 docker://20.10.5
krustlet Ready <none> 3m38s 1.0.0-alpha.1 172.17.117.108 <none> <unknown> <unknown> mvp
ノードを削除する際には以下のコマンドを実行します。
D:\wasm-work>kubectl delete node krustlet
3. Krustletでサンプルアプリを実行する
3.1 WASI-SDKのインストール
今回はC言語で記述したプログラムをWebAssemblyにコンパイルします。
はじめにWindowsにWASI-SDKをインストールします。
インストールといっても実行ファイル(wasi-sdk-14.0-mingw.tar.gz)をダウンロードして適当なディレクトリに展開するだけです。また、実行ファイルのパス(wasi-sdk-14.0/bin)を通しておきます。
3.2 C言語のサンプルプログラムをWebAssemblyで動かす
まずはKrustletを使わずにサンプルプログラムが動作することを確認します。
適当な作業ディレクトリを作成してサンプルプログラムを記述します。
#include <stdio.h>
#include <unistd.h>
int main()
{
for(int i=0;i<5;i++){
printf("Hello, WebAssembly!\n");
}
return 0;
}
上記をコンパイルするために、WASI-SDKのclang
を使用します。
サンプルプログラムのあるディレクトリに移動し、clang
コマンドによりWebAssembly形式.wasm
へコンパイルします。
D:\wasm-work>clang hello.c -o hello.wasm
D:\wasm-work>dir
~略~
2021/12/09 06:37 150 hello.c
2021/12/09 06:39 19,218 hello.wasm
コンパイルされたhello.wasmを実際に動作させてみます。
wasmtimeを利用するとコマンドラインからWebAssemblyを実行できます。
以下は実行例です。(※wasmtimeをダウンロードしてパスを通しています。)
D:\wasm-work>wasmtime hello.wasm
Hello, WebAssembly!
Hello, WebAssembly!
Hello, WebAssembly!
Hello, WebAssembly!
Hello, WebAssembly!
3.3 WebAssemblyをコンテナ化する
WebAssemblyをKrustletで動かすには、WebAssemblyのアプリケーションをコンテナレジストリにPushする必要があります。
What is a registry, and what is wasm-to-oci?に記載されているようにWebAssemblyをコンテナレジストリへPushするにはwasm-to-ociを利用します。
wasm-to-ociの実行ファイルはgithubから入手できます。
DockerHubではWASM OCIイメージが拒否されるようなので、今回はGithubのコンテナレジストリであるghcr.io
へイメージをPushします。
なんでPushできないやと少し悩みましたが、これについてはwasm-to-ociが明記していました。
Note that trying to push a WebAssembly module to Docker Hub is not supported at the time of writing this document, as Docker Hub does not accept unknown artifact types.
https://github.com/engineerd/wasm-to-oci
以下は実行例です。(※wasm-to-ociをダウンロードしてパスを通しています。)
D:\wasm-work>windows-amd64-wasm-to-oci push hello.wasm ghcr.io/t-katsumura/hello-wasm:1.0
time="2021-12-09T06:46:48+09:00" level=info msg="Pushed: ghcr.io/t-katsumura/hello-wasm:1.0"
time="2021-12-09T06:46:48+09:00" level=info msg="Size: 19218"
time="2021-12-09T06:46:48+09:00" level=info msg="Digest: sha256:6cbdece2bba45fddb8f86d4b9dd8c00ff8d4c181aa74ad9ec79a26dd2a07fad8"
無事、WebAssemblyをコンテナレジストリへPushすることができました。
4. KrustletでWebAssemblyのサンプルアプリを動かす
4.1 Podの作成
先ほどコンテナレジストリへPushしたサンプルアプリをKubernetes上で動かすため、マニフェストを記載したyamlファイルを作成します。
マニフェストの内容は下記の通りであり、Podとしてサンプルプログラムを動かします。
apiVersion: v1
kind: Pod
metadata:
name: hello-wasm
spec:
nodeSelector:
kubernetes.io/arch: wasm32-wasi
containers:
- name: hello-wasm
image: ghcr.io/t-katsumura/hello-wasm:1.0
imagePullPolicy: Always
tolerations:
- effect: NoExecute
key: kubernetes.io/arch
operator: Equal
value: wasm32-wasi
- effect: NoSchedule
key: kubernetes.io/arch
operator: Equal
value: wasm32-wasi
上記のマニフェストにおいて、nodeSelector
およびtolerations
を指定することによりWebAssemblyがKrustletのノード上で実行されるように制限しています。
マニフェストを適用するとPodが作成され、WebAssemblyのサンプルプログラムが実行されます。
kubectl get pod
コマンドでSTATUSを確認するとExitCode:0
と表示されていますが、これはサンプルプログラムが正常終了したことを表しています。
D:\wasm-work>kubectl apply -f pod.yaml
pod/hello-wasm created
D:\wasm-work>kubectl get pod
NAME READY STATUS RESTARTS AGE
hello-wasm 0/1 ExitCode:0 0 8s
kubectl logs
コマンドを実行すると「Hello, WebAssembly!」と表示されるはずですが、なぜかエラーが出てしまいました。
これについては追加課題ということで、本記事はここまでとします。
D:\wasm-work>kubectl logs hello-wasm
Error from server: Get "https://172.17.117.108:3000/containerLogs/default/hello-wasm/hello-wasm": dial tcp 172.17.117.108:3000: connect: no route to host
まとめ
Krustletを用いるとWebAssemblyをPodとして実行できるできることが分かりました。
また、OCIの仕様に則ってラップすることでWebAssemblyをコンテナのように扱っていることも分かりました。
サンプルアプリの実行を通して単一のアプリケーションの実行方法は理解できましたが、他のアプリケーションをやコンテナとどのように連携させるのかなどはより深堀して検証する必要がありそうです。
マイクロサービスアーキテクチャとの相性も気になるところです。
開発のしやすさ、デバッグのしやすさ、トレーサビリティなど、実システムへ適用する際に無視できない観点を犠牲にしていると使えない技術になってしまうので、そのような点も含めて技術検証が必要だと感じます。