APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

WebAssembly on Azure Kubernetes Service入門編

はじめに

こんにちは、ACS事業部の吉川です。

本記事はQiitaのAzure Advent Calendar 2022の12日目が空いていたので書きました。

qiita.com

先日、以下のアップデートが発表されたのをご存知でしょうか。

azure.microsoft.com

Azure Kubernetes Service(AKS)で、コンテナではなく WebAssembly(WASM)が動く というものです。
最近社内でもWebAssemblyが話題に挙がることが多いので、これを機に入門してみることにします。

AKSでWASMを動かすしくみ

私のぼんやりした認識では「WASMはブラウザ上で動くもの」と理解していたのですが、WASI(WebAssembly System Interface)というインターフェースが整備されておりOS上でもWASMが動くようになっているそうです。この仕組みを使ってAKS上でサーバーサイドのアプリケーションとして稼働させるわけですね。
冒頭紹介したアップデート情報では、Kubernetes上でのWASMの実行に Krustlet を利用するとの記載があったのですが、最新のドキュメントを見ると Containerd Wasm Shimsを利用するように変更したようです。

learn.microsoft.com

昨年の10月に発表された機能ですが、実行環境がガラッと変更になるなどまだまだ安定してなさそうです。この記事は2022年12月21日時点の状態で確認していますが、今後同じ手順では動かない可能性もあるのでご注意ください。 (まだプレビューの機能ですので…)

Containerd Wasm Shimsの制約として、実行するWASMアプリケーションは

のどちらかのフレームワークを使って作成する必要があります。今回は Spin を利用したアプリケーションを例として説明します。

Azure上の環境作成

チュートリアルとしてAzure上に環境を作成していきます。
作成するリソースは AKSクラスターとAzure Container Registry(ACR) の2つです。

# リソース名/リージョンの定義
RG_NAME=rg-sample
AKS_NAME=aks-sample
ACR_NAME=acrwasmsample
LOCATION=japaneast

# リソースグループ作成
az group create --location $LOCATION --name $RG_NAME

# AKSクラスター作成
az aks create -g $RG_NAME -n $AKS_NAME -l $LOCATION \
  --node-count 1

# ACR作成
az acr create -g $RG_NAME -n $ACR_NAME -l $LOCATION --sku Basic 

# kubectlのインストール
sudo az aks install-cli

# AKSの認証情報取得
az aks get-credentials -g $RG_NAME -n $AKS_NAME

作成したAKSクラスターにWASM用のノードプールを追加します。

# サブスクリプションでプレビュー機能を有効化する
az feature register --namespace "Microsoft.ContainerService" --name "WasmNodePoolPreview"

# 数分待ってから以下を実行し、StateがRegisteredになったことを確認する
az feature list -o table --query "[?contains(name, 'Microsoft.ContainerService/WasmNodePoolPreview')].{Name:name,State:properties.state}"

# リソースプロバイダーの更新を行う
az provider register --namespace Microsoft.ContainerService

# Azure CLIの拡張機能を追加する
az extension add --name aks-preview

# 過去に拡張機能を追加済みの場合は以下コマンドで更新する
az extension update --name aks-preview

# AKSクラスターにWASM用ノードプールを追加する
az aks nodepool add -g $RG_NAME --cluster-name $AKS_NAME \
  --node-count 1 --name mywasipool \
  --workload-runtime WasmWasi

WASMアプリケーションの作成

AKS上で動かすWASMアプリケーションを準備していきます。

まずはSpinをインストールします。インストールスクリプトが公開されているのでそれを利用します。

# インストールスクリプトの実行
curl -fsSL https://developer.fermyon.com/downloads/install.sh | bash

# カレントディレクトリに置かれた spin バイナリを /usr/local/bin 配下に移動
sudo mv ./spin /usr/local/bin/spin

Spinではアプリケーション作成のためのテンプレートが準備されています。これを利用しましょう。

# テンプレートのインストール
spin templates install --git https://github.com/fermyon/spin

Spinはイベントドリブンなアプリケーションを作るためのフレームワークです。 プログラム言語はRustとGo、イベントトリガーとしてHTTPとRedisに対応しています。
今回はGo言語/HTTPトリガーのテンプレートを使用します。

# hello-world ディレクトリにhttp-go テンプレートを使って新規作成する
spin new http-go hello-world --accept-defaults

cd hello-world

Goのビルド環境を準備してテンプレートをそのままビルドしてみます。

# GoとTinyGoのインストール
curl -sL https://go.dev/dl/go1.17.9.linux-amd64.tar.gz | sudo tar -xzf - -C /usr/local
curl -sL https://github.com/tinygo-org/tinygo/releases/download/v0.22.0/tinygo_0.22.0_amd64.deb -o tinygo_amd64.deb \
  && sudo dpkg -i tinygo_amd64.deb && rm tinygo_amd64.deb

# ビルド
tinygo build -wasm-abi=generic -target=wasi -no-debug -o main.wasm main.go

ビルドが完了したら以下のコマンドで起動してみましょう。

spin up

3000番ポートでListenする形で起動します。リクエストを投げてみると以下のように Hello Fermyon! というレスポンスが返ってきます。

curl -v http://127.0.0.1:3000
*   Trying 127.0.0.1:3000...
* Connected to 127.0.0.1 (127.0.0.1) port 3000 (#0)
> GET / HTTP/1.1
> Host: 127.0.0.1:3000
> User-Agent: curl/7.74.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< content-type: text/plain
< content-length: 15
< date: Wed, 21 Dec 2022 08:15:45 GMT
< 
Hello Fermyon!
* Connection #0 to host 127.0.0.1 left intact

OCIイメージの作成

AKS上で起動するためには、WASMをイメージ化する必要があります。
以下のような Dockerfile を準備します。

FROM --platform=${BUILDPLATFORM} tinygo/tinygo:0.22.0 AS build
WORKDIR /opt/build
COPY . .
RUN tinygo build -wasm-abi=generic -target=wasi -no-debug -o main.wasm main.go

FROM scratch
COPY --from=build /opt/build/main.wasm .
COPY --from=build /opt/build/spin.toml .

通常のDockerはWASMイメージのビルドには対応していません。
そこで今回は img というツールを使用してビルドしました。

# img に必要なパッケージのインストール
sudo apt install uidmap libseccomp-dev

# imgのインストール
export IMG_SHA256="cc9bf08794353ef57b400d32cd1065765253166b0a09fba360d927cfbd158088"
sudo curl -fSL "https://github.com/genuinetools/img/releases/download/v0.5.11/img-linux-amd64" -o "/usr/local/bin/img" \
  && echo "${IMG_SHA256}  /usr/local/bin/img" | sha256sum -c - \
  && sudo chmod a+x "/usr/local/bin/img"

# ビルドしたwasmファイルを削除
rm main.wasm

# イメージのビルド
img build --platform=wasi/wasm -t ${ACR_NAME}.azurecr.io/spin-hello-world:0.0.1 .

# ACRにPush
az acr login -n $ACR_NAME --expose-token --output tsv --query accessToken |  \
  img login ${ACR_NAME}.azurecr.io --username 00000000-0000-0000-0000-000000000000 --password-stdin
img push ${ACR_NAME}.azurecr.io/spin-hello-world:0.0.1

ビルドしたイメージはwasmファイルとspinの設定を記したtomlファイルしか含んでいないため、わずか100KBちょっとでとっても軽量です。

img ls
NAME                                            SIZE    CREATED AT      UPDATED AT      DIGEST
acrwasmsample.azurecr.io/spin-hello-world:0.0.1 109KiB  16 minutes ago  16 minutes ago  sha256:…

AKS上でWASMを起動する

ビルドしたイメージをAKS上で起動してみましょう。
WASMを利用する場合、RuntimeClass というCRDの定義が必要です。Spinの場合マニフェストは以下のようになります。

apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: "wasmtime-spin-v1"
handler: "spin"
scheduling:
  nodeSelector:
    "kubernetes.azure.com/wasmtime-spin-v1": "true"
kubectl apply -f runtimeclass.yaml

Deployment/Serviceのマニフェストは以下のとおりです。
ビルドしたWASMをローカルで稼働させた際のポート番号は3000番でしたが、Containerd Wasm ShimsのSpin部分のソースを見ると80番ポート決め打ちでListenするようです。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: spin-hello-world
spec:
  replicas: 1
  selector:
    matchLabels:
      app: spin-hello-world
  template:
    metadata:
      labels:
        app: spin-hello-world
    spec:
      runtimeClassName: wasmtime-spin-v1
      containers:
        - name: spin-hello-world
          image: acrwasmsample.azurecr.io/spin-hello-world:0.0.1
          command: ["/"]
---
apiVersion: v1
kind: Service
metadata:
  name: spin-hello-world
spec:
  type: LoadBalancer
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  selector:
    app: spin-hello-world

これをAKSクラスターにデプロイすると…

kubectl apply -f spin-hello-world.yaml

WASM用ノードの上でPodが起動します。

kubectl get pod -o wide
NAME                               READY   STATUS    RESTARTS   AGE   IP           NODE
NOMINATED NODE   READINESS GATES
spin-hello-world-974cf8789-blz4n   1/1     Running   0          72s   10.244.1.5   aks-mywasipool-33908649-vmss000000   <none>           <none>

ServiceのIPアドレスを確認し、

kubectl get svc spin-hello-world
NAME               TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)        AGE
spin-hello-world   LoadBalancer   10.0.139.244   203.0.113.0   80:30514/TCP   2m28s

リクエストを投げると、ビルドしたWASMからレスポンスが返ってくることが確認できます。

curl http://203.0.113.0
Hello Fermyon!

おわりに

AKS上でWASMのサンプルアプリケーションを起動させてみました。
開発環境のエコシステムを考えると業務アプリケーションがすぐすぐWASMに変わることはないと思いますが、サイドカーのような形でAKS上で従来のコンテナとWASMを一緒に動かすという未来は近いかもしれませんね。

私達ACS事業部はAzure・AKSを活用した内製化のご支援をしております。ご相談等ありましたらぜひご連絡ください。

www.ap-com.co.jp

また、一緒に働いていただける仲間も募集中です!
切磋琢磨しながらスキルを向上できる、エンジニアには良い環境だと思います。ご興味を持っていただけたら嬉しく思います。

www.ap-com.co.jp

本記事の投稿者: 吉川 俊甫
AKS/ACAをメインにインフラ系のご支援を担当しています。 Shunsuke Yoshikawa - Credly