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

Но запускать Wasm-приложения в ванильном Kubernetes затруднительно, так как есть неудобства в настройке сред выполнения на рабочих узлах. Штатных средств недостаточно, чтобы легко конфигурировать узлы. Конечно, можно сконфигурировать один узел руками. Но если нужно обкатать различные рантаймы или большое количество приложений, то хочется максимально просто масштабировать кластер и управлять узлами декларативно. Поэтому я решил запустить Wasm-приложение в Deckhouse Kubernetes Platform (DKP). Эта платформа упрощает развёртывание и управление кластерами Kubernetes.
В этой статье я покажу, как запускать Wasm-приложения в Kubernetes с использованием DKP. Мы настроим окружение, установим необходимые компоненты и запустим простой WebAssembly-модуль.
Настройка NodeGroup
Думаю, будет правильно разделить «обычную» нагрузку и Wasm-нагрузку, чтобы был отдельный рабочий узел для экспериментов. Для этого создадим NodeGroup, с помощью которой платформа будет управлять отдельными узлами. При настройке нужно сразу добавить в NodeGroup лейблы, чтобы далее с помощью NodeSelector посадить нагрузку на нужные узлы:
kubectl create -f -<<EOF apiVersion: deckhouse.io/v1 kind: NodeGroup metadata: name: wasm spec: cloudInstances: classReference: kind: YandexInstanceClass name: worker maxPerZone: 1 minPerZone: 1 zones: - ru-central1-a disruptions: approvalMode: Automatic kubelet: containerLogMaxFiles: 4 containerLogMaxSize: 50Mi resourceReservation: mode: Auto nodeTemplate: labels: node.deckhouse.io/group: wasm nodeType: CloudEphemeral EOF
После создания 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 -<<EOF apiVersion: deckhouse.io/v1alpha1 kind: NodeGroupConfiguration metadata: name: wasm-additional-shim.sh spec: bundles: - '*' content: | [ -f "/bin/containerd-shim-wasmedge-v1" ] || curl -L https://github.com/containerd/runwasi/releases/download/containerd-shim-wasmedge%2Fv0.3.0/containerd-shim-wasmedge-$(uname -m | sed s/arm64/aarch64/g | sed s/amd64/x86_64/g).tar.gz | tar -xzf - -C /bin mkdir -p /etc/containerd/conf.d bb-sync-file /etc/containerd/conf.d/additional_shim.toml - containerd-config-changed << "EOF" [plugins] [plugins."io.containerd.grpc.v1.cri"] [plugins."io.containerd.grpc.v1.cri".containerd] [plugins."io.containerd.grpc.v1.cri".containerd.runtimes] [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" EOF nodeGroups: - "wasm" weight: 30 EOF
Определение новых RuntimeClass
После установки WasmEdge необходимо определить новый RuntimeClass, чтобы мы могли указать, как запускать ту или иную нагрузку: использовать дефолтный рантайм или какой-то другой, если явно в подах будем указывать spec.runtimeClassName:
kubectl apply -f -<<EOF --- apiVersion: node.k8s.io/v1 kind: RuntimeClass metadata: name: wasmedge handler: wasmedge EOF
Запуск тестового 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 -<<EOF apiVersion: batch/v1 kind: Job metadata: name: wasm-test spec: template: spec: containers: - image: wasmedge/example-wasi:latest name: wasm-test resources: {} restartPolicy: Never runtimeClassName: wasmedge nodeSelector: node.deckhouse.io/group: wasm backoffLimit: 1 EOF
Проверим статус и логи пода, чтобы убедиться, что всё работает корректно:
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 -<<EOF apiVersion: deckhouse.io/v1alpha1 kind: NodeGroupConfiguration metadata: name: crun-install.sh spec: bundles: - '*' content: | if ! [ -x /usr/local/bin/crun ]; then apt-get update && apt-get install -y make git gcc build-essential pkgconf libtool libsystemd-dev libprotobuf-c-dev libcap-dev libseccomp-dev libyajl-dev go-md2man autoconf python3 automake cd /root [ -f "/root/.wasmedge/bin/wasmedge" ] || curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash git clone https://github.com/containers/crun && cd crun ./autogen.sh source /root/.wasmedge/env && ./configure --with-wasmedge make make install cd .. && rm -rf crun fi echo "crun has been installed" mkdir -p /etc/containerd/conf.d bb-sync-file /etc/containerd/conf.d/add_crun.toml - containerd-config-changed << "EOF" [plugins] [plugins."io.containerd.grpc.v1.cri"] [plugins."io.containerd.grpc.v1.cri".containerd] [plugins."io.containerd.grpc.v1.cri".containerd.runtimes] [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.crun] runtime_type = "io.containerd.runc.v2" pod_annotations = ["*.wasm.*", "wasm.*", "module.wasm.image/*", "*.module.wasm.image", "module.wasm.image/variant.*"] [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.crun.options] BinaryName = "/usr/local/bin/crun" EOF nodeGroups: - wasm weight: 30 EOF
Здесь мы устанавливаем непосредственно WasmEdge (ранее мы устанавливали WasmEdge runtime), необходимые зависимости и собираем crun. Также добавляем в конфигурацию /etc/containerd/config.toml новый контейнер рантайм, как мы это делали ранее.
Нужно обратить внимание на pod_annotations. Это список аннотаций, который передается как в среду выполнения, так и в OCI аннотации контейнера. Зачем это нужно, рассмотрим чуть ниже.
Далее создаем новый RuntimeClass:
kubectl apply -f -<<EOF --- apiVersion: node.k8s.io/v1 kind: RuntimeClass metadata: name: crun handler: crun EOF
Теперь попробуем запустить нашу нагрузку:
kubectl apply -f -<<EOF apiVersion: batch/v1 kind: Job metadata: name: wasm-test spec: template: metadata: annotations: module.wasm.image/variant: compat-smart spec: initContainers: - name: hello image: busybox:latest command: ['sh', '-c', 'echo "Hello, Habr!"'] containers: - image: wasmedge/example-wasi:latest name: wasm-test resources: {} restartPolicy: Never runtimeClassName: crun nodeSelector: node.deckhouse.io/group: wasm backoffLimit: 1 EOF
Здесь мы определяем 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.
Читайте также в нашем блоге:
ссылка на оригинал статьи https://habr.com/ru/articles/829946/
Добавить комментарий