krustletを使ってkubernetes上でWasmのワークロードを実行する
はじめに
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インストール
以下を参考に実施。
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インストール
以下を参考に実施。
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クラスタをデプロイする。
まずは、以下のサンプルを基にconfigを作成する。
以下定義を使用して、クラスタをデプロイする。
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をインスールする。
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起動する。
以下を参考に実施します。
krustletのproviderをインストールします。
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パッケージに問題があるようである。
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を作成します。
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で報告されていた。
CSRのAPIバージョンがv1beta
→ v1
に昇格したことが原因とのこと。
修正は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がスケジューリングされないようにする回避方法が提示されている。
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
チュートリアル
サンプルアプリのビルド
以下を実施します。
C言語、もしくはRustでサンプルアプリのコードを作成する。
以下の別記事でRustからwasmモジュールへのビルドは試したので、今回はC言語を使用する。
C言語からコンパイルする場合は、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モジュールをレジストリで公開
以下を実施します。
ドキュメントの例の通り、Azure Container Registryを使用する。
まずはAzure CLIをインストールする。
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のインストール
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を作成する。
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ではネットワーキングの機能は提供されないことが言及されていた。
所感
まだv1に達していないこともあり、ネットワーキングの機能がなかったり、krustletがデーモン化されていなかったりと、実用段階に達するまでにはまだまだ時間がかかりそうな印象を受けました。
何より気になったのが、v1.0.0-alpha.1
がリリースされたのが2021/07/28で、それ以降リリースされておらず、githubを見てもあまり開発が活発ではなさそうな点です。
今後どうなっていくのかわかりませんが、動向を追っていきたいと思います。
Discussion