Как запускать WebAssembly-приложения в Kubernetes с помощью Deckhouse

Всем привет! На связи Егор Лазарев, DevOps-инженер компании «Флант». В последнее время WebAssembly (Wasm) набирает популярность благодаря своей высокой производительности и безопасности. Мне стало интересно, что это вообще такое и как работает на практике. Я решил поработать с Wasm в Kubernetes, так можно воспользоваться всеми плюсами кубов: шеринг ресурсов, отказоустойчивость, масштабируемость и прочее.

42b20e40706efd686fea71b7b026fe34.png

Но запускать Wasm-приложения в ванильном Kubernetes затруднительно, так как есть неудобства в настройке сред выполнения на рабочих узлах. Штатных средств недостаточно, чтобы легко конфигурировать узлы. Конечно, можно сконфигурировать один узел руками. Но если нужно обкатать различные рантаймы или большое количество приложений, то хочется максимально просто масштабировать кластер и управлять узлами декларативно. Поэтому я решил запустить Wasm-приложение в Deckhouse Kubernetes Platform (DKP). Эта платформа упрощает развёртывание и управление кластерами Kubernetes.

В этой статье я покажу, как запускать Wasm-приложения в Kubernetes с использованием DKP. Мы настроим окружение, установим необходимые компоненты и запустим простой WebAssembly-модуль. 

Настройка NodeGroup

Думаю, будет правильно разделить «обычную» нагрузку и Wasm-нагрузку, чтобы был отдельный рабочий узел для экспериментов. Для этого создадим NodeGroup, с помощью которой платформа будет управлять отдельными узлами. При настройке нужно сразу добавить в NodeGroup лейблы, чтобы далее с помощью NodeSelector посадить нагрузку на нужные узлы:

kubectl create -f -<

После создания NodeGroup DKP закажет в облаке одну виртуальную машину в зоне ru-central1-a, соответствующую YandexInstanceClass=worker, а также добавит на неё лейбл node.deckhouse.io/group=wasm.

Установка WasmEdge runtime

В Kubernetes для запуска Wasm-приложений нам потребуется специальный runtime — WASI (WebAssembly System Interface). В этой статье установим WasmEdge. А также нам нужно дополнить конфигурацию containerd настройками, связанными с новыми рантаймами. Для установки WasmEdge и дополнительного конфигурирования будем использовать ресурс NodeGroupConfiguration, который позволяет выполнять bash-скрипты на узлах.

Проверяем наличие bin-файла WASI и скачиваем по необходимости. Также с помощью bashbooster получаем мерж основного конфига containerd с конфигом из /etc/containerd/conf.d/*.toml. При изменении /etc/containerd/config.toml также будет перезапущен containerd:

kubectl create -f -<

Определение новых RuntimeClass

После установки WasmEdge необходимо определить новый RuntimeClass, чтобы мы могли указать, как запускать ту или иную нагрузку: использовать дефолтный рантайм или какой-то другой, если явно в подах будем указывать spec.runtimeClassName:

kubectl apply -f -<

Запуск тестового Wasm-приложения

Предварительно, проверяем, что bashible закончил настройку узла и дополнил конфигурацию containerd:

root@test-wasm-75934c42-5956c-l5m7f:~# grep wasm /etc/containerd/config.toml
        [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.wasmedge]
          runtime_type = "io.containerd.wasmedge.v1"
          [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.wasmedge.options]
            BinaryName = "/bin/containerd-shim-wasmedge-v1"

Теперь можно запустить тестовое Wasm-приложение. Для этого создадим Job с простым WebAssembly-модулем. В джобе укажем NodeSelector и новосозданный RuntimeClass wasmedge:

kubectl apply -f -<

Проверим статус и логи пода, чтобы убедиться, что всё работает корректно:

root@test-master-0:~# kubectl get pods
NAME              READY   STATUS      RESTARTS   AGE
wasm-test-2g5jl   0/1     Completed   0          18s

root@test-master-0:~# kubectl logs wasm-test-2g5jl
Random number: -700610054
Random bytes: [163, 184, 229, 154, 4, 145, 145, 96, 181, 77, 64, 159, 123, 45, 5, 134, 93, 193, 207, 74, 129, 113, 204, 174, 188, 152, 172, 151, 125, 78, 199, 177, 127, 112, 116, 255, 188, 180, 47, 110, 22, 241, 63, 87, 78, 168, 36, 202, 168, 90, 248, 79, 38, 59, 204, 128, 141, 92, 209, 205, 129, 51, 71, 214, 91, 237, 115, 145, 77, 136, 166, 115, 221, 66, 123, 186, 19, 39, 122, 204, 103, 221, 89, 97, 148, 57, 250, 255, 165, 53, 14, 241, 97, 138, 147, 201, 204, 29, 76, 219, 128, 48, 143, 165, 138, 231, 62, 235, 190, 94, 142, 63, 197, 37, 57, 241, 33, 99, 240, 215, 216, 33, 68, 141, 82, 21, 152, 93]
Printed from wasi: This is from a main function
This is from a main function
The env vars are as follows.
KUBERNETES_SERVICE_PORT_HTTPS: 443
KUBERNETES_PORT_443_TCP: tcp://10.222.0.1:443
KUBERNETES_PORT_443_TCP_ADDR: 10.222.0.1
KUBERNETES_PORT_443_TCP_PROTO: tcp
KUBERNETES_SERVICE_PORT: 443
HOSTNAME: wasm-test-2g5jl
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
KUBERNETES_SERVICE_HOST: 10.222.0.1
KUBERNETES_PORT: tcp://10.222.0.1:443
KUBERNETES_PORT_443_TCP_PORT: 443
The args are as follows.
/wasi_example_main.wasm
File content is This is in a file

Под в статусе Completed, то есть задание выполнилось и под завершил свою работу без ошибок.

В логах видим, что приложение сгенерировало случайное число и множество случайных байтов, выполнилась основная функция приложения. Также видим, что у приложения есть доступ к окружению и файловой системе. 

Запуск тестового Wasm-приложения с init-контейнером

Теперь немного усложним задачу. Довольно часто в подах нам нужны init- или sidecar-контейнеры, которые должны запускаться из «обычного» container image, а не как Wasm. Для этого нам нужно определить для каждого контейнера свой рантайм запуска. Но проблема в том, что runtimeClassName определяется на уровне пода, а не контейнеров. 

Containerd поддерживает переключение среды выполнения контейнера, соответственно, нам нужен инструмент, который может определить, какая среда для контейнера нужна. Стандартный runc, который используется у нас в кластере, это не поддерживает. Но в бета-версии такое есть у crun. Поэтому я попробую реализовать задачу с его помощью. 

Для начала нам нужно собрать crun, так как при установке пакетным менеджером из официальных репозиториев он не поддерживает WasmEdge. Используем NodeGroupConfiguration:

kubectl apply -f -<

Здесь мы устанавливаем непосредственно WasmEdge (ранее мы устанавливали WasmEdge runtime), необходимые зависимости и собираем crun. Также добавляем в конфигурацию /etc/containerd/config.toml новый контейнер рантайм, как мы это делали ранее. 

Нужно обратить внимание на pod_annotations. Это список аннотаций, который передается как в среду выполнения, так и в OCI аннотации контейнера. Зачем это нужно, рассмотрим чуть ниже. 

Далее создаем новый RuntimeClass:

kubectl apply -f -<

Теперь попробуем запустить нашу нагрузку:

kubectl apply -f -<

Здесь мы определяем runtimeClassName: crun, чтобы за запуск контейнеров отвечал crun, а не дефолтный runc. Также добавляем аннотацию module.wasm.image/variant: compat-smart, благодаря которой crun понимает, в каком режиме работать.

Чтобы это работало, WASM-образ должен быть собран с OCI аннотацией:

...
"annotations": {
 "run.oci.handler": "wasm"
},
...

Если у нас есть pod_annotations в конфигурации containerd и аннотация compat-smart на кубовом объекте, то в таком случае crun понимает, какую нагрузку запустить самому, а какую передать для запуска в Wasm runtime.

Смотрим состояние пода и логи. В логах увидим то же, что и ранее:

root@test-master-0:~# kubectl get pods
NAME              READY   STATUS      RESTARTS   AGE
wasm-test-pn4gv   0/1     Completed   0          32s

root@test-master-0:~# kubectl logs wasm-test-pn4gv
Defaulted container "wasm-test" out of: wasm-test, hello (init)
Random number: -158793507
Random bytes: [210, 246, 181, 132, 184, 214, 110, 71, 198, 68, 154, 182, 253, 103, 116, 207, 5, 205, 185, 81, 19, 28, 61, 61, 85, 26, 222, 111, 239, 110, 21, 68, 119, 245, 153, 190, 105, 175, 191, 163, 48, 198, 41, 207, 155, 30, 122, 166, 23, 56, 59, 168, 91, 57, 103, 213, 145, 10, 130, 224, 28, 5, 73, 176, 206, 111, 37, 241, 38, 57, 98, 158, 150, 115, 249, 233, 194, 156, 13, 109, 85, 130, 232, 91, 253, 16, 8, 233, 92, 162, 237, 197, 151, 112, 52, 140, 83, 179, 31, 48, 233, 56, 54, 75, 43, 239, 233, 169, 169, 81, 36, 52, 59, 66, 102, 40, 52, 202, 34, 56, 167, 229, 197, 25, 72, 136, 147, 254]
Printed from wasi: This is from a main function
This is from a main function
The env vars are as follows.
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME: wasm-test-pn4gv
KUBERNETES_PORT: tcp://10.222.0.1:443
KUBERNETES_PORT_443_TCP: tcp://10.222.0.1:443
KUBERNETES_PORT_443_TCP_PROTO: tcp
KUBERNETES_PORT_443_TCP_PORT: 443
KUBERNETES_PORT_443_TCP_ADDR: 10.222.0.1
KUBERNETES_SERVICE_HOST: 10.222.0.1
KUBERNETES_SERVICE_PORT: 443
KUBERNETES_SERVICE_PORT_HTTPS: 443
HOME: /
The args are as follows.
/wasi_example_main.wasm
File content is This is in a file

И логи init-контейнера:

root@test-master-0:~# kubectl logs wasm-test-pn4gv -c hello
Hello, Habr!

Заключение

Запуск WebAssembly-приложений в Kubernetes может показаться не совсем удобным, но с помощью Deckhouse это становится достаточно простым процессом. В этой статье я показал, как настроить окружение, установить необходимые компоненты и запустить тестовое Wasm-приложение. Надеюсь, что эта информация будет полезна и поможет в работе. 

Deckhouse Kubernetes Platform предоставляет множество возможностей для управления Kubernetes-кластером, и мы обязательно будем делиться новыми практиками и советами в будущих статьях.

P. S.

Читайте также в нашем блоге:

© Habrahabr.ru