💨

krustletを使ってkubernetes上でWasmのワークロードを実行する

2022/10/26に公開

はじめに

krustletを使えば、kubernetesでWasmのワークロードを実行できるということを知ったので、試してみました。
本記事では、以下を試しました。

  • kubernetesの用意(kindを使用)
  • krustletのインストール
  • チュートリアル
    • サンプルアプリをWasmモジュールにビルド
    • Wasmモジュールをレジストリに公開
    • k8s上でサンプルアプリを実行

実行環境

PC上でVirtulaBoxで実行しているVM

  • ホストOS:Windows 10 21H2
  • ゲストOS:Ubuntu 22.04.1

使用ソフトウェア

  • Docker v20.10.20
  • kind v0.16.0
  • kubernetes v1.25.2
  • krustlet(v1.0.0-alpha.1 → カナリアビルドのバイナリを使用)
  • Wasmer 2.3.0
  • Azure CLI 2.41.0
  • wasm-to-oci v0.1.2

作業ログ

k8sの用意

今回はkindを使用します。

Dockerインストール

以下を参考に実施。
https://docs.docker.com/engine/install/ubuntu/

nakkoh@work:~$ sudo apt-get update

... snip ...

nakkoh@work:~$ sudo apt-get install \
    ca-certificates \
    curl \
    gnupg \
    lsb-release

... snip ...

nakkoh@work:~$ sudo mkdir -p /etc/apt/keyrings
nakkoh@work:~$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
nakkoh@work:~$ echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
nakkoh@work:~$ sudo apt-get update

... snip ...

nakkoh@work:~$ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin

... snip ...

nakkoh@work:~$ docker version
Client: Docker Engine - Community
 Version:           20.10.20
 API version:       1.41
 Go version:        go1.18.7
 Git commit:        9fdeb9c
 Built:             Tue Oct 18 18:20:18 2022
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/version": dial unix /var/run/docker.sock: connect: permission denied

Dockerを一般ユーザーが実行できるようにする。

nakkoh@work:~$ sudo usermod -aG docker nakkoh
nakkoh@work:~$ groups nakkoh 
nakkoh : nakkoh adm cdrom sudo dip plugdev lpadmin lxd sambashare docker

一度、sshで入り直すと、一般ユーザーでDockerを実行できるようになる。

kindインストール

以下を参考に実施。
https://kind.sigs.k8s.io/docs/user/quick-start/#installing-from-release-binaries

nakkoh@work:~$ curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.16.0/kind-linux-amd64

... snip ...

nakkoh@work:~$ chmod +x ./kind
nakkoh@work:~$ sudo mv ./kind /usr/local/bin/kind
nakkoh@work:~$ kind version
kind v0.16.0 go1.19.1 linux/amd64

k8sクラスタのデプロイ

control plane×1、worker×1のクラスタをデプロイする。

以下を参考に、k8sクラスタをデプロイする。
https://kind.sigs.k8s.io/docs/user/quick-start/#configuring-your-kind-cluster

まずは、以下のサンプルを基にconfigを作成する。
https://raw.githubusercontent.com/kubernetes-sigs/kind/main/site/content/docs/user/kind-example-config.yaml

以下定義を使用して、クラスタをデプロイする。

apiVersion: kind.x-k8s.io/v1alpha4
kubeadmConfigPatches:
- |
  apiVersion: kubelet.config.k8s.io/v1beta1
  kind: KubeletConfiguration
  evictionHard:
    nodefs.available: "0%"
kubeadmConfigPatchesJSON6902:
- group: kubeadm.k8s.io
  version: v1beta2
  kind: ClusterConfiguration
  patch: |
    - op: add
      path: /apiServer/certSANs/-
      value: my-hostname
nodes:
- role: control-plane
- role: worker
nakkoh@work:~$ kind create cluster --config kind-try-krustlet.yaml -n try-krustlet
Creating cluster "try-krustlet" ...

... snip ...

kubectl cluster-info --context kind-try-krustlet

Not sure what to do next? 😅  Check out https://kind.sigs.k8s.io/docs/user/quick-start/
nakkoh@work:~$ kind get clusters
try-krustlet

以下を参考にkubectlをインスールする。
https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/#install-kubectl-binary-with-curl-on-linux

nakkoh@work:~$ curl -LO "https://dl.k8s.io/release/v1.25.3/bin/linux/amd64/kubectl"

... snip ...

nakkoh@work:~$ chmod +x kubectl 
nakkoh@work:~$ sudo mv kubectl /usr/local/bin/
nakkoh@work:~$ kubectl version
WARNING: This version information is deprecated and will be replaced with the output from kubectl version --short.  Use --output=yaml|json to get the full version.
Client Version: version.Info{Major:"1", Minor:"25", GitVersion:"v1.25.3", GitCommit:"434bfd82814af038ad94d62ebe59b133fcb50506", GitTreeState:"clean", BuildDate:"2022-10-12T10:57:26Z", GoVersion:"go1.19.2", Compiler:"gc", Platform:"linux/amd64"}
Kustomize Version: v4.5.7
Server Version: version.Info{Major:"1", Minor:"25", GitVersion:"v1.25.2", GitCommit:"5835544ca568b757a8ecae5c153f317e5736700e", GitTreeState:"clean", BuildDate:"2022-09-22T05:25:21Z", GoVersion:"go1.19.1", Compiler:"gc", Platform:"linux/amd64"}
nakkoh@work:~$ kubectl get node
NAME                         STATUS   ROLES           AGE   VERSION
try-krustlet-control-plane   Ready    control-plane   26m   v1.25.2
try-krustlet-worker          Ready    <none>          26m   v1.25.2

補完、エイリアスの設定

nakkoh@work:~$ echo 'source <(kubectl completion bash)' >>~/.bashrc
nakkoh@work:~$ echo 'alias k=kubectl' >>~/.bashrc
nakkoh@work:~$ echo 'complete -o default -F __start_kubectl k' >>~/.bashrc
nakkoh@work:~$ source ~/.bashrc

podが作成できるか確認

ubuntu@kind:~$ k run nginx --image nginx
pod/nginx created
nakkoh@work:~$ k get pod
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          58s

krustletインストール

kindを実行しているホストでkrustlet起動する。

以下を参考に実施します。
https://docs.krustlet.dev/intro/install/

krustletのproviderをインストールします。

v1.0.0-alpha.1のバイナリをダウンロード
https://github.com/krustlet/krustlet/releases/tag/v1.0.0-alpha.1

nakkoh@work:~$ curl -O https://krustlet.blob.core.windows.net/releases/krustlet-v1.0.0-alpha.1-linux-amd64.tar.gz

... snip ...

nakkoh@work:~$ tar -xzf krustlet-v1.0.0-alpha.1-linux-amd64.tar.gz 
nakkoh@work:~$ sudo mv krustlet-wasi /usr/local/bin/
nakkoh@work:~$ krustlet-wasi 
krustlet-wasi: error while loading shared libraries: libssl.so.1.1: cannot open shared object file: No such file or directory

ライブラリが足りないようである。
ググったところ、ubuntu 22.04にインストールされているOpen SSLパッケージに問題があるようである。
https://stackoverflow.com/questions/72133316/ubuntu-22-04-libssl-so-1-1-cannot-open-shared-object-file-no-such-file-or-di

libssl.so.1.1のパッケージをインストールすれば解消されるとのこと。

nakkoh@work:~$ wget http://nz2.archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2.16_amd64.deb

... snip ...

nakkoh@work:~$ sudo dpkg -i libssl1.1_1.1.1f-1ubuntu2.16_amd64.deb 

... snip ...

nakkoh@work:~$ krustlet-wasi -V
krustlet 1.0.0-alpha.1

以下を参考にトークン、kubeconfigを作成します。
https://docs.krustlet.dev/howto/bootstrapping/#generating-a-token-and-kubeconfig

nakkoh@work:~$ bash <(curl https://raw.githubusercontent.com/krustlet/krustlet/main/scripts/bootstrap.sh)
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  2456  100  2456    0     0   6983      0 --:--:-- --:--:-- --:--:--  6997
secret/bootstrap-token-7vlc6m created
Switched to context "kind-try-krustlet".
Context "kind-try-krustlet" 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.
ubuntu@kind:~$ tree ~/.krustlet/
/home/ubuntu/.krustlet/
└── config
    └── bootstrap.conf

1 directory, 1 file

krustletのインストール

nakkoh@work:~$ KUBECONFIG=~/.krustlet/config/kubeconfig \
  krustlet-wasi \
  --node-ip 172.17.0.1 \
  --node-name=krustlet \
  --bootstrap-file=${HOME}/.krustlet/config/bootstrap.conf
Error: ApiError: the server could not find the requested resource: NotFound (ErrorResponse { status: "Failure", message: "the server could not find the requested resource", reason: "NotFound", code: 404 })

Caused by:
    the server could not find the requested resource: NotFound

k8sへのAPIリクエストがresourceがnot foundでエラーとなったようである。

issueで報告されていた。
https://github.com/krustlet/krustlet/issues/699

CSRのAPIバージョンがv1betav1に昇格したことが原因とのこと。
修正mainリポジトリには既に取り込まれているもののまだリリースされていないので、カナリアビルドされたバイナリを使えば問題を回避できるようである。

カナリアビルドされたバイナリをダウロード。

nakkoh@work:~$ curl -O https://krustlet.blob.core.windows.net/releases/krustlet-canary-linux-amd64.tar.gz

... snip ...

nakkoh@work:~$ tar -xzf krustlet-canary-linux-amd64.tar.gz
nakkoh@work:~$ sudo mv krustlet-wasi /usr/local/bin/

再度、krustletを起動する。

nakkoh@work:~$ bash <(curl https://raw.githubusercontent.com/krustlet/krustlet/main/scripts/bootstrap.sh)

... snip ...

secret/bootstrap-token-8x79b1 created
Switched to context "kind-try-krustlet".
Context "kind-try-krustlet" 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.
nakkoh@work:~$ KUBECONFIG=~/.krustlet/config/kubeconfig \
  krustlet-wasi \
  --node-ip 172.17.0.1 \
  --node-name=krustlet \
  --bootstrap-file=${HOME}/.krustlet/config/bootstrap.conf
BOOTSTRAP: TLS certificate requires manual approval. Run kubectl certificate approve work-tls

CSRを承認するように言われるので、別セッションで実施する。

nakkoh@work:~$ kubectl certificate approve work-tls
certificatesigningrequest.certificates.k8s.io/work-tls approved

CSRの承認後、krustletが起動するが以下のイメージプルのエラーが発生する。

BOOTSTRAP: received TLS certificate approval: continuing
Oct 24 14:33:08.646 ERROR kubelet::state::common::image_pull: error=unsupported media type: application/vnd.docker.distribution.manifest.list.v2+json
Oct 24 14:33:19.383 ERROR kubelet::state::common::image_pull: error=unsupported media type: application/vnd.docker.distribution.manifest.list.v2+json

ノード一覧を見ると、krustletノードが追加されていて、Readyステータスとなっている。

NAME                         STATUS   ROLES           AGE     VERSION
krustlet                     Ready    <none>          11m     1.0.0-alpha.1
try-krustlet-control-plane   Ready    control-plane   2d22h   v1.25.2
try-krustlet-worker          Ready    <none>          2d22h   v1.25.2

issueを見ると、kindnetのdaemonsetがkrustletのノードにもコンテナのpodをスケジューリングするためとのこと。
tolerationを設定して、krustletのノードにコンテナのpodがスケジューリングされないようにする回避方法が提示されている。
https://github.com/krustlet/krustlet/issues/700#issuecomment-968138990

kindnetのdaemonsetに以下を定義し、krustletのノードにスケジューリングされないようにする。

spec:
  template:
    spec:
      tolerations:
      - key: beta.kubernetes.io/arch
        operator: Exists
        effect: NoSchedule

上記を実施することで、errorが発生しなくなった。

krustlet-wasiコマンドを実行するとkrustletが起動するが、デーモン化されていないためセッションを終了するとkrustletが停止してしまう。
本来であれば、systemdとかでデーモン化すべきだと思うが、今回はscreenでセッションを保存する。

セッションの作成

nakkoh@work:~$ screen -S krustlet

新規セッションに移るので、そこでkrustletを起動

nakkoh@work:~$ KUBECONFIG=~/.krustlet/config/kubeconfig \
>   krustlet-wasi \
>   --node-ip 172.17.0.1 \
>   --node-name=krustlet \
>   --bootstrap-file=${HOME}/.krustlet/config/bootstrap.conf

チュートリアル

サンプルアプリのビルド

以下を実施します。
https://docs.krustlet.dev/intro/tutorial01/

C言語、もしくはRustでサンプルアプリのコードを作成する。
以下の別記事でRustからwasmモジュールへのビルドは試したので、今回はC言語を使用する。
https://zenn.dev/nakkoh/articles/aceb946c314059

C言語からコンパイルする場合は、WASI SDKが必要なためインストールする
https://github.com/WebAssembly/wasi-sdk

nakkoh@work:~$ export WASI_VERSION=16
nakkoh@work:~$ export WASI_VERSION_FULL=${WASI_VERSION}.0
nakkoh@work:~$ wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_VERSION}/wasi-sdk-${WASI_VERSION_FULL}-linux.tar.gz

... snip ...

nakkoh@work:~$ tar xvf wasi-sdk-${WASI_VERSION_FULL}-linux.tar.gz
nakkoh@work:~$ export PATH=$PATH:/home/nakkoh/wasi-sdk-16.0/bin

アプリ用のディレクトリを作成し、そこにコードを作成する。

nakkoh@work:~$ mkdir demo
nakkoh@work:~$ cd demo/
nakkoh@work:~/demo$ vim main.c

作成したコードは以下の通り。

#include <stdio.h>
#include <unistd.h>

int main() {
    while(1) {
        printf("Hello, World!\n");
        sleep(5);
    }
    return 0;
}

wasmモジュールにコンパイル

nakkoh@work:~/demo$ clang main.c -o demo.wasm
nakkoh@work:~/demo$ ls
demo.wasm  main.c
nakkoh@work:~/demo$ file demo.wasm 
demo.wasm: WebAssembly (wasm) binary module version 0x1 (MVP)

この環境には既にWasmerがインストールされているので、コンパイルしたwasmモジュールを実行してみる。

nakkoh@work:~/demo$ wasmer -V
wasmer 2.3.0
nakkoh@work:~/demo$ wasmer demo.wasm
Hello, World!
Hello, World!
^C

Wasmモジュールをレジストリで公開

以下を実施します。
https://docs.krustlet.dev/intro/tutorial02/

ドキュメントの例の通り、Azure Container Registryを使用する。

まずはAzure CLIをインストールする。
https://learn.microsoft.com/ja-jp/cli/azure/install-azure-cli-linux?pivots=apt

nakkoh@work:~/demo$ sudo apt-get update

--- snip ---

nakkoh@work:~/demo$ sudo apt-get install ca-certificates curl apt-transport-https lsb-release gnupg

--- snip ---

nakkoh@work:~/demo$ curl -sL https://packages.microsoft.com/keys/microsoft.asc |
    gpg --dearmor |
    sudo tee /etc/apt/trusted.gpg.d/microsoft.gpg > /dev/null
nakkoh@work:~/demo$ AZ_REPO=$(lsb_release -cs)
echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ $AZ_REPO main" |
    sudo tee /etc/apt/sources.list.d/azure-cli.list
deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ jammy main
nakkoh@work:~/demo$ sudo apt-get update

... snip ...

nakkoh@work:~/demo$ sudo apt-get install azure-cli

... snip ...

nakkoh@work:~/demo$ az -v
azure-cli                         2.41.0

core                              2.41.0
telemetry                          1.0.8

Dependencies:
msal                            1.20.0b1
azure-mgmt-resource             21.1.0b1

Python location '/opt/az/bin/python3'
Extensions directory '/home/nakkoh/.azure/cliextensions'

Python (Linux) 3.10.5 (main, Oct 10 2022, 03:02:37) [GCC 11.2.0]

Legal docs and information: aka.ms/AzureCliLegal


Your CLI is up-to-date.

リソースグループを作成

nakkoh@work:~/demo$ az login

... snip ...

nakkoh@work:~/demo$ az group create --name try-wasm --location japaneast

... snip ...

コンテナレジストリの作成

nakkoh@work:~/demo$ az acr create --sku Basic --resource-group try-wasm --name trywasm20221025

... snip ...

作成したレジストリにログイン

nakkoh@work:~/demo$ az acr login --name trywasm20221025.azurecr.io
The login server endpoint suffix '.azurecr.io' is automatically omitted.
Login Succeeded

次に、wasm-to-ociを使って、wasmモジュールをOCIレジストリにプッシュする。

wasm-to-ociのインストール
https://github.com/engineerd/wasm-to-oci

nakkoh@work:~/demo$ wget https://github.com/engineerd/wasm-to-oci/releases/download/v0.1.2/linux-amd64-wasm-to-oci

... snip ...

nakkoh@work:~/demo$ mv linux-amd64-wasm-to-oci wasm-to-oci
nakkoh@work:~/demo$ chmod +x wasm-to-oci
nakkoh@work:~/demo$ sudo mv wasm-to-oci /usr/local/bin
nakkoh@work:~/demo$ wasm-to-oci 
Usage:
  wasm-to-oci [command]

... snip ...

wasmモジュールをAzure Container Registryにプッシュする。

nakkoh@work:~/demo$ wasm-to-oci push demo.wasm trywasm20221025.azurecr.io/krustlet-tutorial:v1.0.0
INFO[0001] Pushed: trywasm20221025.azurecr.io/krustlet-tutorial:v1.0.0 
INFO[0001] Size: 29873                                  
INFO[0001] Digest: sha256:34ab01c02f0b5f68b6fe396022858b94ad27127c6945823aacc8919b208fc0fa

上記のレジストリはプライベートなため、wasmモジュールをプルするためにはkubernets pull secretを作成する必要があります。

pull secretを作成するには、まずservice principalを作成する必要がある。

krustletのドキュメントのbashにバグがあったため、参照先であるazureのドキュメントを参照してservice principalを作成する。
https://learn.microsoft.com/en-us/azure/container-registry/container-registry-auth-kubernetes#create-a-service-principal

nakkoh@work:~$ ACR_NAME=trywasm20221025
nakkoh@work:~$ SERVICE_PRINCIPAL_NAME=acr-service-principal
nakkoh@work:~$ ACR_REGISTRY_ID=$(az acr show --name $ACR_NAME --query id --output tsv)
nakkoh@work:~$ PASSWORD=$(az ad sp create-for-rbac --name $SERVICE_PRINCIPAL_NAME --scopes $ACR_REGISTRY_ID --role acrpull --query "password" --output tsv)

... snip ...

nakkoh@work:~$ USER_NAME=$(az ad sp list --display-name $SERVICE_PRINCIPAL_NAME --query "[].appId" --output tsv)

pull secretの作成

nakkoh@work:~$ kubectl create secret docker-registry wasm-registry-login \
>     --docker-server=trywasm20221025.azurecr.io \
>     --docker-username=$USER_NAME \
>     --docker-password=$PASSWORD
secret/wasm-registry-login created
nakkoh@work:~$ k get secrets 
NAME                  TYPE                             DATA   AGE
wasm-registry-login   kubernetes.io/dockerconfigjson   1      6s

サンプルアプリをk8s上で実行

以下のマニフェストを適用し、podを作成

apiVersion: v1
kind: Pod
metadata:
  name: krustlet-tutorial
spec:
  containers:
    - name: krustlet-tutorial
      image: trywasm20221025.azurecr.io/krustlet-tutorial:v1.0.0
  imagePullSecrets:
    - name: wasm-registry-login
  tolerations:
    - key: "kubernetes.io/arch"
      operator: "Equal"
      value: "wasm32-wasi"
      effect: "NoExecute"
    - key: "kubernetes.io/arch"
      operator: "Equal"
      value: "wasm32-wasi"
      effect: "NoSchedule"
nakkoh@work:~$ k apply -f krustlet-tutorial.yaml 
pod/krustlet-tutorial created
nakkoh@work:~$ k get pod
NAME                READY   STATUS    RESTARTS   AGE
krustlet-tutorial   1/1     Running   0          53m
nginx               1/1     Running   0          19m
nakkoh@work:~$ kubectl logs krustlet-tutorial
Hello, World!

サンプルアプリがk8s上で動いていることが確認できた。

1点気になったのが、wasmのpodにはIPアドレスが割り振られていなかった。

nakkoh@work:~$ k get pod -o wide
NAME                READY   STATUS    RESTARTS   AGE   IP           NODE                  NOMINATED NODE   READINESS GATES
krustlet-tutorial   1/1     Running   0          54m   <none>       krustlet              <none>           <none>
nginx               1/1     Running   0          19m   10.244.1.3   try-krustlet-worker   <none>           <none>

krustletのブログに、v1.0ではネットワーキングの機能は提供されないことが言及されていた。
https://deislabs.io/posts/towards-krustlet-v1-cncf/

所感

まだv1に達していないこともあり、ネットワーキングの機能がなかったり、krustletがデーモン化されていなかったりと、実用段階に達するまでにはまだまだ時間がかかりそうな印象を受けました。
何より気になったのが、v1.0.0-alpha.1がリリースされたのが2021/07/28で、それ以降リリースされておらず、githubを見てもあまり開発が活発ではなさそうな点です。
https://github.com/krustlet/krustlet/releases/tag/v1.0.0-alpha.1
今後どうなっていくのかわかりませんが、動向を追っていきたいと思います。

Discussion