DEV Community

Cover image for WebAssembly, Docker container, Dapr, and Kubernetes better together - Part 4: Package and deploy to Kubernetes
Thang Chung
Thang Chung

Posted on

WebAssembly, Docker container, Dapr, and Kubernetes better together - Part 4: Package and deploy to Kubernetes

Banner image from freepik


Source code of this series can be found at https://github.com/thangchung/dapr-labs/tree/main/polyglot.


In this final part, we will install k3d, and containerd-wasm-shims which run runwasi inside. It allows us to run WASM/WASI workload with the annotation runtimeClassName: wasmtime-spin, and run a Docker container (containerd format). If you don't declare the previous annotation on the YAML deployment script.

Let's get started with the setup as the image below.

Image description

Setup Docker (buildx), k3d

# ref: https://github.com/docker/docker-install

> curl -fsSL https://get.docker.com -o get-docker.sh
> sh get-docker.sh --version 24.0
> docker --version
Docker version 24.0.5, build ced0996
Enter fullscreen mode Exit fullscreen mode
# install k3d

> wget -q -O - https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash
Enter fullscreen mode Exit fullscreen mode

Dockerized apps

# Product API
FROM --platform=${BUILDPLATFORM} rust:1.67 AS build
RUN rustup target add wasm32-wasi
COPY . /product
WORKDIR /product
RUN cargo build --target wasm32-wasi --release

FROM scratch
COPY --from=build /product/target/wasm32-wasi/release/product_api.wasm /target/wasm32-wasi/release/product_api.wasm
COPY ./spin.toml /spin.toml
Enter fullscreen mode Exit fullscreen mode
# Counter API, .NET 8
> dotnet publish ./counter-api/counter-api.csproj --os linux --arch x64 /t:PublishContainer -c Release
docker tag counter-api:latest ghcr.io/thangchung/dapr-labs/counter-api-polyglot:1.0.0
Enter fullscreen mode Exit fullscreen mode
# Barista API
FROM --platform=${BUILDPLATFORM} rust:1.67 AS build
RUN rustup target add wasm32-wasi
COPY . /barista
WORKDIR /barista
RUN cargo build --target wasm32-wasi --release

FROM scratch
COPY --from=build /barista/target/wasm32-wasi/release/barista_api.wasm /target/wasm32-wasi/release/barista_api.wasm
COPY ./spin.toml /spin.toml
Enter fullscreen mode Exit fullscreen mode
# Kitchen API
FROM --platform=${BUILDPLATFORM} rust:1.67 AS build
RUN rustup target add wasm32-wasi
COPY . /kitchen
WORKDIR /kitchen
RUN cargo build --target wasm32-wasi --release

FROM scratch
COPY --from=build /kitchen/target/wasm32-wasi/release/kitchen_api.wasm /target/wasm32-wasi/release/kitchen_api.wasm
COPY ./spin.toml /spin.toml
ENTRYPOINT ["/"]
Enter fullscreen mode Exit fullscreen mode

Build and push it into GitHub artifacts:

> docker login ghcr.io -u <your username>
Enter fullscreen mode Exit fullscreen mode

It asks you to provide the password (PAT), please go to your developer profile to generate it.

> docker buildx build -f Dockerfile --platform wasi/wasm,linux/amd64,linux/arm64 -t ghcr.io/thangchung/dapr-labs/product-api-spin:1.0.0 . --push

> docker push ghcr.io/thangchung/dapr-labs/counter-api-polyglot:1.0.0

> docker buildx build -f Dockerfile --platform wasi/wasm,linux/amd64,linux/arm64 -t ghcr.io/thangchung/dapr-labs/barista-api-spin:1.0.0 . --push

> docker buildx build -f Dockerfile --platform wasi/wasm,linux/amd64,linux/arm64 -t ghcr.io/thangchung/dapr-labs/kitchen-api-spin:1.0.0 . --push
Enter fullscreen mode Exit fullscreen mode

Ship them to Kubernetes

# Product API
apiVersion: apps/v1
kind: Deployment
metadata:
  name: product-api
spec:
  replicas: 1
  selector:
    matchLabels:
      app: product-api
  template:
    metadata:
      labels:
        app: product-api
      annotations:
        dapr.io/enabled: "true"
        dapr.io/app-id: "product-api"
        dapr.io/app-port: "80"
        dapr.io/enable-api-logging: "true"
    spec:
      runtimeClassName: wasmtime-spin
      containers:
        - name: product-api
          image: ghcr.io/thangchung/dapr-labs/product-api-spin:1.0.1
          command: ["/"]
          env:
            - name: RUST_BACKTRACE
              value: "1"
          resources: # limit the resources to 128Mi of memory and 100m of CPU
            limits:
              cpu: 100m
              memory: 128Mi
            requests:
              cpu: 100m
              memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
  name: product-api
spec:
  type: LoadBalancer
  ports:
    - protocol: TCP
      port: 5001
      targetPort: 80
  selector:
    app: product-api
Enter fullscreen mode Exit fullscreen mode
# Counter API
apiVersion: apps/v1
kind: Deployment
metadata:
  name: counter-api
spec:
  replicas: 1
  selector:
    matchLabels:
      app: counter-api
  template:
    metadata:
      labels:
        app: counter-api
      annotations:
        dapr.io/enabled: "true"
        dapr.io/app-id: "counter-api"
        dapr.io/app-port: "8080"
        dapr.io/enable-api-logging: "true"
    spec:
      containers:
        - name: counter-api
          image: ghcr.io/thangchung/dapr-labs/counter-api-polyglot:1.0.0
          env:
            - name: ProductCatalogAppDaprName
              value: "product-api"
          resources: # limit the resources to 128Mi of memory and 100m of CPU
            limits:
              cpu: 100m
              memory: 128Mi
            requests:
              cpu: 100m
              memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
  name: counter-api
spec:
  type: LoadBalancer
  ports:
    - protocol: TCP
      port: 5002
      targetPort: 8080
  selector:
    app: counter-api
Enter fullscreen mode Exit fullscreen mode
# Barista API
apiVersion: apps/v1
kind: Deployment
metadata:
  name: barista-api
spec:
  replicas: 1
  selector:
    matchLabels:
      app: barista-api
  template:
    metadata:
      labels:
        app: barista-api
      annotations:
        dapr.io/enabled: "true"
        dapr.io/app-id: "barista-api"
        dapr.io/app-port: "80"
        dapr.io/enable-api-logging: "true"
    spec:
      runtimeClassName: wasmtime-spin
      containers:
        - name: barista-api
          image: ghcr.io/thangchung/dapr-labs/barista-api-spin:1.0.0
          # imagePullPolicy: Always
          command: ["/"]
          env:
            - name: RUST_BACKTRACE
              value: "1"
            - name: DAPR_URL
              value: "http://localhost:3500"
          resources: # limit the resources to 128Mi of memory and 100m of CPU
            limits:
              cpu: 100m
              memory: 128Mi
            requests:
              cpu: 100m
              memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
  name: barista-api
spec:
  type: LoadBalancer
  ports:
    - protocol: TCP
      port: 5003
      targetPort: 80
  selector:
    app: barista-api
Enter fullscreen mode Exit fullscreen mode
# Kitchen API
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kitchen-api
spec:
  replicas: 1
  selector:
    matchLabels:
      app: kitchen-api
  template:
    metadata:
      labels:
        app: kitchen-api
      annotations:
        dapr.io/enabled: "true"
        dapr.io/app-id: "kitchen-api"
        dapr.io/app-port: "80"
        dapr.io/enable-api-logging: "true"
    spec:
      runtimeClassName: wasmtime-spin
      containers:
        - name: kitchen-api
          image: ghcr.io/thangchung/dapr-labs/kitchen-api-spin:1.0.0
          # imagePullPolicy: Always
          command: ["/"]
          env:
            - name: RUST_BACKTRACE
              value: "1"
            - name: DAPR_URL
              value: "http://localhost:3500"
          resources: # limit the resources to 128Mi of memory and 100m of CPU
            limits:
              cpu: 100m
              memory: 128Mi
            requests:
              cpu: 100m
              memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
  name: kitchen-api
spec:
  type: LoadBalancer
  ports:
    - protocol: TCP
      port: 5004
      targetPort: 80
  selector:
    app: kitchen-api
Enter fullscreen mode Exit fullscreen mode
# ingress.yaml
# Middleware
# Strip prefix /spin
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: strip-prefix
spec:
  stripPrefix:
    forceSlash: false
    prefixes:
      - /p
      - /c
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: polyglot-wasm-ingress
  annotations:
    ingress.kubernetes.io/ssl-redirect: "false"
    kubernetes.io/ingress.class: traefik
    traefik.ingress.kubernetes.io/router.middlewares: default-strip-prefix@kubernetescrd
spec:
  rules:
    - http:
        paths:
          - path: /p
            pathType: Prefix
            backend:
              service:
                name: product-api
                port:
                  number: 5001
          - path: /c
            pathType: Prefix
            backend:
              service:
                name: counter-api
                port:
                  number: 5002
Enter fullscreen mode Exit fullscreen mode
# create Kubernetes cluster for WASM/WASI
> k3d cluster create wasm-cluster --image ghcr.io/deislabs/containerd-wasm-shims/examples/k3d:v0.9.0 -p "8081:80@loadbalancer" --agents 2
Enter fullscreen mode Exit fullscreen mode
# add redis
> helm install my-redis oci://registry-1.docker.io/bitnamicharts/redis --set architecture=standalone --set global.redis.password=P@ssw0rd
Enter fullscreen mode Exit fullscreen mode
# for demo only, otherwise need use Dapr with Helm chart
> dapr init -k --runtime-version 1.11.2
Enter fullscreen mode Exit fullscreen mode
# remember to edit dapr component YAML, and put password for redis
> kubectl apply -f components-k8s

> kubectl apply -f iac/kind-spin
Enter fullscreen mode Exit fullscreen mode

Make sure you can query:

> kubectl get component
NAME            AGE
baristapubsub   2d1h
kitchenpubsub   2d1h
statestore      2d1h

> kubectl get subscription
NAME                           AGE
barista-ordered-subscription   2d1h
barista-updated-subscription   2d1h
kitchen-ordered-subscription   2d1h
kitchen-updated-subscription   2d1h

> kubectl get po
NAME                           READY   STATUS    RESTARTS       AGE
my-redis-master-0              1/1     Running   24 (55m ago)   10d
counter-api-8bdc488b7-h96pw    2/2     Running   16 (55m ago)   2d1h
product-api-8ccbc56b-ql5bt     2/2     Running   12 (54m ago)   2d1h
kitchen-api-54f7c588cb-ffnzl   2/2     Running   12 (55m ago)   2d1h
barista-api-6f5b4d6fb8-pxbqc   2/2     Running   12 (55m ago)   2d1h

> kubectl get svc
NAME                TYPE           CLUSTER-IP      EXTERNAL-IP                        PORT(S)
              AGE
kubernetes          ClusterIP      10.43.0.1       <none>                             443/TCP
              10d
my-redis-headless   ClusterIP      None            <none>                             6379/TCP
              10d
my-redis-master     ClusterIP      10.43.109.123   <none>                             6379/TCP
              10d
barista-api-dapr    ClusterIP      None            <none>                             80/TCP,50001/TCP,50002/TCP,9090/TCP   2d1h
counter-api-dapr    ClusterIP      None            <none>                             80/TCP,50001/TCP,50002/TCP,9090/TCP   2d1h
kitchen-api-dapr    ClusterIP      None            <none>                             80/TCP,50001/TCP,50002/TCP,9090/TCP   2d1h
product-api-dapr    ClusterIP      None            <none>                             80/TCP,50001/TCP,50002/TCP,9090/TCP   2d1h
product-api         LoadBalancer   10.43.58.9      172.19.0.2,172.19.0.4,172.19.0.5   5001:30896/TCP
              2d1h
kitchen-api         LoadBalancer   10.43.105.225   172.19.0.2,172.19.0.4,172.19.0.5   5004:32611/TCP
              2d1h
counter-api         LoadBalancer   10.43.19.86     172.19.0.2,172.19.0.4,172.19.0.5   5002:30630/TCP
              2d1h
barista-api         LoadBalancer   10.43.105.93    172.19.0.2,172.19.0.4,172.19.0.5   5003:31258/TCP
              2d1h

> kubectl get ing
NAME                    CLASS    HOSTS   ADDRESS                            PORTS   AGE
polyglot-wasm-ingress   <none>   *       172.19.0.2,172.19.0.4,172.19.0.5   80      2d1h
Enter fullscreen mode Exit fullscreen mode

If everything is okay, then we can play around with Rest Client at client.k3d.http.

Summary

So we have already walked through 4 parts of how can we build the polyglot apps with Docker, WebAssembly (Spin), Dapr, and Kubernetes (k3d). There are still obstacles, but the future is bright for WASM apps on Kubernetes due to the very active process from the community.

What's next? The WebAssembly/WASI has a clear roadmap. In WasmCon 2023 recently, they mentioned Component Model and I think that is a final abstraction in the computing unit. It helps a lot in building what Luke Wagner calls Modularity without microservices, and we will invest time on it as well. See you in the next posts to dive more into this kind of component model.

Stay stun!

Top comments (0)