LoginSignup
26
2

More than 1 year has passed since last update.

Krustletを使ってKubernetesでWebAssemblyアプリケーションを実行する

Last updated at Posted at 2021-12-09

krustlet.jpg

はじめに

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プロジェクトへ加わりました。

krustlet card.PNG

CNCF Landscapeより引用

Krustletのゴールとしては以下の2点が掲げられています。

  1. WebAssemblyワークロードのKubernetesへのデプロイを簡単にする
  2. 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として規定されています。

krustlet_cluster.jpg

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の設定画面から確認できます。

Docker確認事項

2.2 Krustletのインストール

Krustletのインストールは非常に簡単です。
公式サイトにも記載がありますが、githubからKrustletの実行ファイルをダウンロードして解凍するだけです。

WSL2のプロンプトを開き、下記のコマンドでKrustletの実行ファイルを取得します。
以降の手順では、ダウンロードした実行ファイルkrustlet-wasiがあるディレクトリで作業を行うことを想定し、パスは通しません。

作業ディレクトリの作成とKrustletのインストール
[~]$ 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であることが分かります。

WSL2のIPアドレスを確認
[~/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項目のみ指定します。

  1. --node-ip:先ほど取得したWSL2のIPアドレスを指定します
  2. --node-name:ノード名になるので、任意の名前を指定します
  3. --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のどちらで実行しても構いません。

Krustletノード追加リクエストの承認
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に最小限の機能しか実装されていないことを意味しています。

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を使わずにサンプルプログラムが動作することを確認します。
適当な作業ディレクトリを作成してサンプルプログラムを記述します。

サンプルプログラム(hello.c)
#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をダウンロードしてパスを通しています。)

WASMをコンテナレジストリへPush
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としてサンプルプログラムを動かします。

pod.yaml
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をコンテナのように扱っていることも分かりました。

サンプルアプリの実行を通して単一のアプリケーションの実行方法は理解できましたが、他のアプリケーションをやコンテナとどのように連携させるのかなどはより深堀して検証する必要がありそうです。
マイクロサービスアーキテクチャとの相性も気になるところです。

開発のしやすさ、デバッグのしやすさ、トレーサビリティなど、実システムへ適用する際に無視できない観点を犠牲にしていると使えない技術になってしまうので、そのような点も含めて技術検証が必要だと感じます。

参考資料

26
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
2