Введение

Полгода назад Docker объявил о поддержке WebAssembly на базе WasmEdge.

В этой статье расскажем, что такое WebAssembly, почему эта технология актуальна для экосистемы Docker, а также приведем несколько практических примеров. Предполагается, что вы знакомы с инструментарием Docker. Чтобы продемонстрировать, как создать интерпретатор PHP, упаковать его как часть OCI-образа и запустить с помощью Docker, воспользуемся нашим WebAssembly-портом PHP.

Обратите внимание: эта статья скорее направлена на получение практического опыта, нежели на обсуждение технических тонкостей. Вы можете воспроизвести приведенные ниже примеры сами или довериться нам — результат выполнения всех команд мы публикуем.

WebAssembly — что это и зачем?

В этом разделе приводятся базовые сведения. Его можно пропустить, если вы уже знакомы с технологией.

Что такое WebAssembly?

Открытый стандарт WebAssembly (или Wasm) определяет формат двоичных инструкций, который позволяет создавать портируемые исполняемые файлы из исходников, написанных на различных языках программирования.

Wasm есть во всех браузерах
Wasm есть во всех браузерах

Эти исполняемые файлы могут работать в различных окружениях. Формат поддерживают все основные браузеры.

Как Wasm работает в браузерах

В движки браузеров интегрирована виртуальная машина Wasm (обычно называемая «средой исполнения Wasm»), которая и отвечает за выполнение Wasm-инструкций. Специализированные компиляторы (например, Emscripten) могут компилировать исходный код в Wasm. Технология позволяет переносить legacy-приложения в браузер и обеспечивает их прямое взаимодействие с JS-кодом в веб-приложениях на стороне клиента.

Wasm в браузере
Wasm в браузере

Таким образом, традиционные настольные приложения теперь успешно работают в браузере, то есть их можно запускать на любом устройстве, на котором есть браузер с поддержкой Wasm. Среди примечательных примеров — Google Earth и библиотека компьютерного зрения OpenCV.

Как Wasm работает на серверах

Существуют рантаймы Wasm, которые могут работать вне браузера, в том числе в классических операционных системах, таких как Linux, Windows и macOS. Поскольку в данном случае на движок Javascript рассчитывать не приходится, коммуникация с внешним миром идет через различные интерфейсы — например, WASI (WebAssembly System Interface). Такие рантаймы позволяют Wasm-приложениям взаимодействовать с хост-системой по аналогии с POSIX (хотя есть и отличия). Проекты вроде WASI SDK и wasi-libc помогают компилировать существующие POSIX-совместимые приложения в WebAssembly.

Wasm на сервере
Wasm на сервере

При этом достаточно скомпилировать приложение в модуль Wasm только один раз, и его можно будет запускать где угодно.

Чем так хорош Wasm?

Некоторые из особенностей, благодаря которым Wasm отлично работает в браузерах, делают его привлекательным и для server-side-разработки:

???? Открытость. Стандарт широко распространен в отрасли. Браузерные войны остались в прошлом: крупные игроки активно сотрудничают в области стандартизации WASI и WebAssembly-приложений.

???? Быстрота. Почти нативная скорость благодаря JIT/AOT-возможностям большинства рантаймов. Никаких холодных стартов — в отличие от виртуальных машин или контейнеров.

???? Безопасность. Рантайм Wasm по умолчанию работает в режиме песочницы и следит за безопасным доступом к памяти. Модель, основанная на возможностях (capabilities-based), гарантирует, что Wasm-приложение имеет доступ только к тому, к чему ему явно разрешено. Повышается безопасность цепи поставок за счет виртуализации внешних зависимостей способом, с которым не нужно постоянно обновлять уязвимые компоненты приложения.

???? Портируемость. Популярные рантаймы поддерживают большинство процессоров (x86, ARM, RISC-V) и большинство ОС, включая Linux, Windows, macOS, Android, ESXi и даже non-Posix-системы.

???? Эффективность. Можно сделать так, чтобы Wasm-приложение работало с минимальным объемом памяти и минимальными требованиями к процессору.

???? Полиглотизм. В Wasm можно скомпилировать исходники на 40+ языках. Инструменты для этого активно разрабатываются и совершенствуются.

Следующий шаг в эволюции серверных платформ

Возможно, вы видели этот твит Соломона Хайкса (одного из соучредителей корпорации Docker и автора Docker Open Source Initiative):

Если бы WASM+WASI существовали в 2008 году, нам бы не пришлось создавать Docker. Вот насколько они важны. WebAssembly на сервере — будущее компьютерных технологий.

В самом деле, WASM+WASI выглядят как следующий шаг в эволюции программной server-side-инфраструктуры.

  • Раньше нам приходилось работать с реальным оборудованием, кропотливо устанавливать ОС и приложения на каждое устройство и по очереди их обслуживать.

  • Все упростилось с внедрением виртуальных машин (здесь первопроходцем стала VMware). Их можно было копировать, клонировать и перемещать между устройствами. Однако все равно приходилось устанавливать ОС и приложения в виртуальные машины.

  • Затем появились контейнеры, ставшие популярными благодаря Docker. С их помощью стало возможно запускать конфигурации приложений в минималистском оберточном контексте, при этом не влияя на другие приложения на ОС хоста. Но контейнеры все равно приходилось дополнять средой исполнения и необходимыми библиотеками, а за границами безопасности следило ядро Linux.

  • И вот, наконец, появилась технология WebAssembly. Ее технические характеристики и портируемость позволяют распространять приложения, не «нагружая» их зависимостями системного уровня. Плюс они работают в строгих границах безопасности.

Учитывая все это, WebAssembly обычно рассматривается как «преемник» контейнеров и следующий логический шаг в развертывании инфраструктуры.

Wasm — следующий шаг в эволюции серверных платформ
Wasm — следующий шаг в эволюции серверных платформ

Но есть и другой взгляд на WebAssembly — как на альтернативный «бэкэнд» для Docker-инструментария. Можно применять те же инструменты командной строки и рабочие процессы, но вместо Linux-контейнеров запускать их WebAssembly-эквиваленты. В остальной части статьи рассматривается именно эта концепция — то, что мы называем «Docker без контейнеров».

Как Wasm работает с Docker'ом?

Docker Desktop теперь работает с WebAssembly. Поддержка реализована с помощью containerd shim'а, способного запускать Wasm-приложения в среде исполнения WasmEdge. То есть вместо обычных Windows- или Linux-контейнеров, которые запускают отдельный процесс из исполняемого файла в образе, под видом контейнера можно запустить Wasm-приложение в рантайме WasmEdge.

Такому «контейнеру» не нужны ОС или контекст рантайма — достаточно исполняемого Wasm-файла.

Подробнее об этом рассказывается в статье Introducing the Docker+Wasm Technical Preview (примечание редакции: это статья на английском языке).

Что такое WasmEdge

WasmEdge — это высокопроизводительный рантайм для WebAssembly, который:

  • является проектом с открытым исходным кодом и входит в состав CNCF;

  • поддерживает все основные архитектуры процессоров (x86, ARM, RISC-V);

  • поддерживает все основные операционные системы (Linux, Windows, macOS) и некоторые другие, такие как seL4 RTOS, Android;

  • оптимизирован для cloud-native и Edge-приложений;

  • расширяется и поддерживает связанные с ИИ технологии, такие как Tensorflow, OpenVINO, PyTorch;

  • поддерживает асинхронную работу с сетью с помощью Tokio. А также микросервисы, клиенты баз данных, очереди сообщений;

  • позволяет настроить интеграцию с экосистемой контейнеров Docker и Kubernetes (как показано в этой статье).

Что насчет интерпретируемых языков?

До сих пор мы говорили только о превращении в WebAssembly-программы исходников, написанных на компилируемых языках, таких как C и Rust. В случае интерпретируемых языков, таких как Python, Ruby и PHP, подход иной: их интерпретаторы написаны на C и могут быть скомпилированы в WebAssembly. После этого Wasm-интерпретатор cможет работать с исходниками в соответствующем формате (.py, .rb, .php и т. п.) После компиляции в Wasm любая платформа с Wasm-рантаймом сможет запускать программы, написанные на интерпретируемых языках, даже если интерпретатор изначально не предназначался для нее.

Wasm на сервере — интерпретируемые языки
Wasm на сервере интерпретируемые языки

Практические примеры

Давайте перейдем к практике. В примерах будем использовать интерпретатор PHP, скомпилированный в Wasm. Мы:

  • соберем Wasm-контейнер;

  • сравним Wasm и нативные исполняемые файлы;

  • сравним классические контейнеры и контейнеры Wasm;

  • продемонстрируем портируемость Wasm.

Требования

Чтобы воспроизвести примеры локально, необходимо подготовить окружение. Для этого понадобятся следующие компоненты:

  • WASI SDK для сборки WebAssembly-приложений из исходников на C;

  • PHP для запуска нативного PHP-файла для сравнения;

  • рантайм WasmEdge для запуска WebAssembly-приложений;

  • Docker Desktop + Wasm (на момент написания этой статьи доступен как стабильная бета в версии 4.15) для запуска Wasm-контейнеров.

В репозитории Wasm Language Runtimes содержится все необходимое для сборки интерпретатора PHP в виде WebAssembly-приложения.

Начать можно с клонирования демо-ветки:

git clone --depth=1 -b php-wasmedge-demo \
   https://github.com/vmware-labs/webassembly-language-runtimes.git wlr-demo
cd wlr-demo

Собираем Wasm-контейнер

В качестве первого примера рассмотрим, как собрать приложение на языке C — скажем, интерпретатор PHP.

Для этого нам понадобится набор инструментов WASI-SDK. Он включает в себя компилятор clang, способный собирать wasm32-wasi-модули, а также библиотеку wasi-libc, отвечающую за реализацию базовых интерфейсов системных вызовов POSIX поверх WASI. WASI SDK позволяет из кодовой базы PHP собрать модуль Wasm, написанный на C. После этого достаточно создать простой Docker-файл для сборки OCI-образа, который будет запускаться с помощью Docker+Wasm.

От кода на Си до Wasm-контейнера
От кода на Си до Wasm-контейнера

Собираем исполняемый Wasm-файл

Находясь в директории wlr-demo, которую вы клонировали ранее, выполните следующие действия для сборки исполняемого Wasm-файла:

export WASI_SDK_ROOT=/opt/wasi-sdk/
export WASMLABS_RUNTIME=wasmedge

./wl-make.sh php/php-7.4.32/ && tree build-output/php/php-7.4.32/bin/

... (через несколько минут и сотни строк логов)

build-output/php/php-7.4.32/bin/
├── php-cgi-wasmedge
└── php-wasmedge

PHP собирается с autoconf и make. Изучив скрипт scripts/wl-build.sh, вы увидите, что были установлены все нужные компилятору WASI_SDK переменные (CC, LD, CXX и т.д.).

export WASI_SYSROOT="${WASI_SDK_ROOT}/share/wasi-sysroot"
export CC=${WASI_SDK_ROOT}/bin/clang
export LD=${WASI_SDK_ROOT}/bin/wasm-ld
export CXX=${WASI_SDK_ROOT}/bin/clang++
export NM=${WASI_SDK_ROOT}/bin/llvm-nm
export AR=${WASI_SDK_ROOT}/bin/llvm-ar
export RANLIB=${WASI_SDK_ROOT}/bin/llvm-ranlib

Углубившись в php/php-7.4.32/wl-build.sh, можно увидеть, что используется обычный процесс сборки с autoconf.

./configure --host=wasm32-wasi host_alias=wasm32-musl-wasi \
   --target=wasm32-wasi target_alias=wasm32-musl-wasi \
   ${PHP_CONFIGURE} || exit 1
...
make -j ${MAKE_TARGETS} || exit 1

Работа над WASI еще не завершена, и многие POSIX-вызовы пока не поддерживаются. Поэтому для сборки PHP пришлось наложить несколько патчей на исходную кодовую базу.

Выше мы видели, что готовые исполняемые файлы сохраняются в build-output/php/php-7.4.32. В последующих примерах будем использовать бинарник php-wasmedge, специально собранный для WasmEdge. Его особенность — поддержка сокетов на стороне сервера (она пока не является частью WASI).

Оптимизируем исполняемый файл

Wasm — виртуальный набор инструкций, поэтому по умолчанию любой рантайм будет интерпретировать их «на лету». В некоторых случаях это грозит замедлением работы. WasmEdge позволяет создать AOT-оптимизированный (ahead-of-time) исполняемый файл, который нативно работает на текущей машине и может интерпретироваться на других.

Для этого нужно выполнить следующую команду:

wasmedgec --enable-all --optimize 3 \
   build-output/php/php-7.4.32/bin/php-wasmedge \
   build-output/php/php-7.4.32/bin/php-wasmedge-aot

В примерах ниже мы будем использовать именно этот бинарник (build-output/php/php-7.4.32/bin/php-wasmedge-aot). Здесь можно получить дополнительную информацию об исполняемых файлах, оптимизированных для WasmEdge AOT.

Собираем OCI-образ

Теперь исполняемый файл можно обернуть в OCI-образ.

Давайте посмотрим на images/php/Dockerfile.cli. Нужно лишь скопировать исполняемый Wasm-файл и установить его в качестве ENTRYPOINT.

FROM scratch
ARG PHP_TAG=php-7.4.32
ARG PHP_BINARY=php
COPY build-output/php/${PHP_TAG}/bin/${PHP_BINARY} /php.wasm

ENTRYPOINT [ "php.wasm" ]

Также в образ можно положить некоторые вспомогательные данные, которые потребуются Wasm-бинарнику, когда его запустит Docker. Например, в images/php/Dockerfile.server добавляется некоторое содержимое docroot, которое php.wasm будет передавать при запуске контейнера.

FROM scratch
ARG PHP_TAG=php-7.4.32
ARG PHP_BINARY=php
COPY build-output/php/${PHP_TAG}/bin/${PHP_BINARY} /php.wasm
COPY images/php/docroot /docroot

ENTRYPOINT [ "php.wasm" , "-S", "0.0.0.0:8080", "-t", "/docroot"]

Используя вышеуказанные файлы, можно легко собирать образы php-wasm локально.

docker build --build-arg PHP_BINARY=php-wasmedge-aot -t ghcr.io/vmware-labs/php-wasm:7.4.32-cli-aot -f images/php/Dockerfile.cli .
docker build --build-arg PHP_BINARY=php-wasmedge-aot -t ghcr.io/vmware-labs/php-wasm:7.4.32-server-aot -f images/php/Dockerfile.server .

Нативный vs Wasm

Теперь давайте сравним исполняемый PHP-файл с Wasm-файлом как локально, так и в Docker-контейнере. Для этого возьмем один и тот же index.php и изучим результаты его запуска с помощью:

  • php;

  • php-wasmedge-aot;

  • php в классическом контейнере;

  • php-wasmedge-aot в Wasm-контейнере.

Схемы запуска index.php
Схемы запуска index.php

Во всех примерах ниже используется один и тот же файл images/php/docroot/index.php. Вот его содержимое:

<html>
<body>
<h1>Hello from PHP <?php echo phpversion() ?> running on "<?php echo php_uname()?>"</h1>

<h2>List env variable names</h2>
<?php
$php_env_vars_count = count(getenv());
echo "Running with $php_env_vars_count environment variables:\n";
foreach (getenv() as $key => $value) {
    echo  $key . " ";
}
echo "\n";
?>

<h2>Hello</h2>
<?php
$date = getdate();

$message = "Today, " . $date['weekday'] . ", " . $date['year'] . "-" . $date['mon'] . "-" . $date['mday'];
$message .= ", at " . $date['hours'] . ":" . $date['minutes'] . ":" . $date['seconds'];
$message .= " we greet you with this message!\n";
echo $message;
?>

<h2>Contents of '/'</h2>
<?php
foreach (array_diff(scandir('/'), array('.', '..')) as $key => $value) {
    echo  $value . " ";
}
echo "\n";
?>

</body>
</html>

Этот скрипт:

  • с помощью phpversion и php_uname выводит версию интерпретатора и платформы, на которой запущен;

  • выводит имена всех переменных окружения, к которым у скрипта есть доступ;

  • выводит приветствие с указанием текущего времени и даты;

  • выводит список содержимого корневой папки /.

Нативный PHP

При запуске нативного PHP-файла видим:

  • платформу под управлением Linux, на которой он выполняется;

  • список из 58 переменных окружения, к которым скрипт при необходимости может получить доступ;

  • список всех файлов и поддиректорий в /, к которым опять же скрипт при необходимости может получить доступ.

$ php -f images/php/docroot/index.php

<html>
<body>
<h1>Hello from PHP 7.4.3 running on "Linux alexandrov-z01 5.15.79.1-microsoft-standard-WSL2 #1 SMP Wed Nov 23 01:01:46 UTC 2022 x86_64"</h1>

<h2>List env variable names</h2>
Running with 58 environment variables:
SHELL NVM_INC WSL2_GUI_APPS_ENABLED rvm_prefix WSL_DISTRO_NAME TMUX rvm_stored_umask TMUX_PLUGIN_MANAGER_PATH MY_RUBY_HOME NAME RUBY_VERSION PWD NIX_PROFILES LOGNAME rvm_version rvm_user_install_flag MOTD_SHOWN HOME LANG WSL_INTEROP LS_COLORS WASMTIME_HOME WAYLAND_DISPLAY NIX_SSL_CERT_FILE PROMPT_COMMAND NVM_DIR rvm_bin_path GEM_PATH GEM_HOME LESSCLOSE TERM CPLUS_INCLUDE_PATH LESSOPEN USER TMUX_PANE LIBRARY_PATH rvm_loaded_flag DISPLAY SHLVL NVM_CD_FLAGS LD_LIBRARY_PATH XDG_RUNTIME_DIR PS1 WSLENV XDG_DATA_DIRS PATH DBUS_SESSION_BUS_ADDRESS C_INCLUDE_PATH NVM_BIN HOSTTYPE WASMER_CACHE_DIR IRBRC PULSE_SERVER rvm_path WASMER_DIR OLDPWD BASH_FUNC_cr-open%% _

<h2>Hello</h2>
Today, Wednesday, 2022-12-14, at 12:0:36 we greet you with this message!

<h2>Contents of '/'</h2>
apps bin boot dev docroot etc home init lib lib32 lib64 libx32 lost+found media mnt nix opt path proc root run sbin snap srv sys tmp usr var wsl.localhost

</body>
</html>

php-aot-wasm

При запуске с помощью php-aot-wasm и Wasmedge видим:

  • платформу wasi/wasm32, на которой выполняется скрипт;

  • пустой список переменных окружения, поскольку Wasm-приложению не был явно предоставлен доступ ни к одной из них;

  • попытка вывести содержимое / закончилась ошибкой, поскольку приложению Wasm не был предоставлен явный доступ к корневой директории.

Чтобы у php-wasmedge-aot был доступ на чтение файла index.php, нужно явно указать WasmEdge, что директория images/php/docroot должна быть открыта для доступа как /docroot в контексте Wasm-приложения.

Этот момент отлично демонстрирует одно из главных преимуществ Wasm (помимо портируемости): более высокий уровень безопасности, доступ по умолчанию закрыт, если явно не указано иное.

$ wasmedge --dir /docroot:$(pwd)/images/php/docroot \
   build-output/php/php-7.4.32/bin/php-wasmedge-aot -f /docroot/index.php


<html>
<body>
<h1>Hello from PHP 7.4.32 running on "wasi (none) 0.0.0 0.0.0 wasm32"</h1>

<h2>List env variable names</h2>
Running with 0 environment variables:


<h2>Hello</h2>
Today, Wednesday, 2022-12-14, at 10:8:46 we greet you with this message!

<h2>Contents of '/'</h2>

Warning: scandir(/): failed to open dir: Capabilities insufficient in /docroot/index.php on line 27

Warning: scandir(): (errno 76): Capabilities insufficient in /docroot/index.php on line 27

Warning: array_diff(): Expected parameter 1 to be an array, bool given in /docroot/index.php on line 27

Warning: Invalid argument supplied for foreach() in /docroot/index.php on line 27


</body>
</html>

PHP в контейнере

При запуске в классическом контейнере видим:

  • платформу под управлением Linux, на которой выполняется скрипт;

  • список из 14 переменных окружения, к которым у скрипта есть доступ;

  • приветствие с указанием текущего времени и даты;

  • список содержимого корневой папки /.

Уже заметна разница в лучшую сторону по сравнению с запуском на хост-машине: переменные окружения и содержимое / являются «виртуальными» и существуют только внутри контейнера.

docker run --rm \
   -v $(pwd)/images/php/docroot:/docroot \
   php:7.4.32-cli \
   php -f /docroot/index.php


<html>
<body>
<h1>Hello from PHP 7.4.32 running on "Linux 227b2bc2f611 5.15.79.1-microsoft-standard-WSL2 #1 SMP Wed Nov 23 01:01:46 UTC 2022 x86_64"</h1>

<h2>List env variable names</h2>
Running with 14 environment variables:
HOSTNAME PHP_INI_DIR HOME PHP_LDFLAGS PHP_CFLAGS PHP_VERSION GPG_KEYS PHP_CPPFLAGS PHP_ASC_URL PHP_URL PATH PHPIZE_DEPS PWD PHP_SHA256

<h2>Hello</h2>
Today, Wednesday, 2022-12-14, at 10:15:35 we greet you with this message!

<h2>Contents of '/'</h2>
bin boot dev docroot etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var

</body>
</html>

php-aot-wasm в контейнере

При запуске с помощью php-aot-wasm и WasmEdge видим:

  • платформу wasi/wasm32, на которой выполняется скрипт;

  • всего 2 инфраструктурные переменные окружения, предварительно настроенные с помощью WasmEdge shim, который работает внутри containerd;

  • список всех файлов и директорий в / внутри контейнера, который явно предварительно открыт для доступа приложением Wasm (часть логики в WasmEdge shim).

Примечание: если внимательно посмотреть, можно увидеть, что для запуска контейнера из этого образа необходимо:

  • явно указать рантайм с помощью аргумента командной строки --runtime=io.containerd.wasmedge.v1pass, передав его напрямую php.wasm, не включая сам исполняемый файл. Вернитесь выше: можно было бы явно написать полную команду с обычным контейнером PHP и включить в нее исполняемый файл php (но это не является необходимостью).

И последнее замечание: даже при запуске в Docker-контейнере Wasm ужесточил границы безопасности, сократив число параметров, к которым у index.php есть доступ:

docker run --rm \
   --runtime=io.containerd.wasmedge.v1 \
   -v $(pwd)/images/php/docroot:/docroot \
   ghcr.io/vmware-labs/php-wasm:7.4.32-cli-aot \
   -f /docroot/index.php


<html>
<body>
<h1>Hello from PHP 7.4.32 running on "wasi (none) 0.0.0 0.0.0 wasm32"</h1>

<h2>List env variable names</h2>
Running with 2 environment variables:
PATH HOSTNAME

<h2>Hello</h2>
Today, Wednesday, 2022-12-14, at 11:33:10 we greet you with this message!

<h2>Contents of '/'</h2>
docroot etc php.wasm

</body>
</html>

Обычные контейнеры vs их Wasm-аналоги

Мы не только собрали/запустили исполняемый Wasm-файл, но и протестировали его работу в виде контейнера. Заметна разница в выводе Wasm и традиционного контейнера, а также расширенный «песочный» режим, который привносит Wasm. Давайте посмотрим, какие еще различия существуют между двумя типами контейнеров.

Сначала мы запустим два контейнера-демона и посмотрим, как можно интерпретировать их статистику. Затем мы рассмотрим различия в образах контейнеров.

Архитектура контейнеров
Архитектура контейнеров

Статистика по контейнерам

Запустим два контейнера-демона — один из обычного образа php, другой — из php-wasm.

docker run --rm -d \
   -p 8083:8080 -v $(pwd)/images/php/docroot:/docroot \
   php:7.4.32-cli \
   -S 0.0.0.0:8080 -t /docroot
docker run --rm -d \
   --runtime=io.containerd.wasmedge.v1 \
   -p 8082:8080 -v $(pwd)/images/php/docroot:/docroot \
   ghcr.io/vmware-labs/php-wasm:7.4.32-cli-aot
   -S 0.0.0.0:8080 -t /docroot

docker stats пока выдает статистику только для обычного контейнера. Это может измениться со временем, поскольку Docker+Wasm находятся в стадии beta. Чтобы понять, что происходит, понаблюдаем за контрольными группами. Каждый классический контейнер получает собственную контрольную группу (например, docker/ee44). С другой стороны, Wasm-контейнеры включены как часть контрольной группы podruntime/docker, и можно косвенно наблюдать за потреблением ими ресурсов CPU или памяти.

$ systemd-cgtop -kP --depth=10

Control Group           Tasks    %CPU     Memory
podruntime              145      0.1      636.3M
podruntime/docker       145      0.1      636.3M
docker                  2        0.0      39.7M
docker/ee444b...        1        0.0      6.7M

Размер образа

Видно, что образы Wasm-контейнеров намного меньше обычных. Даже alpine-версия контейнера php больше, чем Wasm.

$ docker images


REPOSITORY                     TAG                 IMAGE ID       CREATED          SIZE
php                            7.4.32-cli          680c4ba36f1b   2 hours ago      166MB
php                            7.4.32-cli-alpine   a785f7973660   2 minutes ago    30.1MB
ghcr.io/vmware-labs/php-wasm   7.4.32-cli-aot      63460740f6d5   44 minutes ago   5.35MB

Это ожидаемо, поскольку в Wasm достаточно положить исполняемый файл внутрь контейнера. А в обычные контейнеры приходится добавлять некоторые базовые библиотеки и файлы из ОС, под которой этот исполняемый файл будет работать.

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

Портируемость Wasm

Портируемость — одно из главных преимуществ Wasm. Благодаря Docker'у, как только речь заходит о портируемости, сразу вспоминаются классические контейнеры. Помимо большого размера образа, такие контейнеры также привязаны к архитектуре платформы, на которой они запускаются. Многие из нас сталкивались с необходимостью собирать версии программного обеспечения под разные архитектуры и упаковывать их в отдельные образы для каждой архитектуры.

WebAssembly привносит истинную портируемость. Исполняемый файл достаточно собрать один раз, после чего его можно будет запускать везде. Чтобы наглядно это продемонстрировать, мы подготовили несколько примеров запуска WordPress'а в интерпретаторе PHP, который собрали под WebAssembly.

WordPress будем запускать как отдельное Wasm-приложение и как контейнер Docker+Wasm. Кроме того, он сможет работать в любом приложении, в которое встроен Wasm-рантайм. В нашем примере — это apache httpd с модулем mod_wasm, который позволяет использовать Wasm-приложения как обработчики контента. Наконец, PHP.wasm с таким же успехом можно запускать прямо в браузере.

Сравнение контейнеров
Сравнение контейнеров

WordPress через WasmEdge

Для демонстрации есть компактный пример — связка WordPress+Sqlite. Поскольку она является частью образа контейнера ghcr.io/vmware-labs/php-wasm:7.4.32-server-wordpress, давайте сначала загрузим его локально.

Команда ниже просто создаст временный контейнер (и запросит образ), скопирует файлы WordPress в /tmp/wp/docroot, а затем удалит контейнер.

container_id=$(docker create ghcr.io/vmware-labs/php-wasm:7.4.32-server-wordpress) && \
   mkdir /tmp/wp && \
   docker cp $container_id:/docroot /tmp/wp/ && \
   docker rm $container_id

Теперь у нас есть WordPress. Давайте его запустим:

wasmedge --dir /docroot:/tmp/wp/docroot \
   build-output/php/php-7.4.32/bin/php-wasmedge-aot \
   -S 0.0.0.0:8085 -t /docroot

Можно перейти на http://localhost:8085 и проверить работу WordPress'а под интерпретатором PHP Wasm.

WordPress через Docker+Wasm

С Docker'ом все намного проще, что ожидаемо:

docker run --rm --runtime=io.containerd.wasmedge.v1 \
   -p 8086:8080 -v /tmp/wp/docroot/:/docroot/ \
   ghcr.io/vmware-labs/php-wasm:7.4.32-cli-aot
   -S 0.0.0.0:8080 -t /docroot

Можно перейти на http://localhost:8086 и убедиться, что WordPress работает под интерпретатором PHP Wasm прямо из Docker-контейнера.

WordPress через mod_wasm в Apache HTTPD

Apache HTTPD — один из наиболее широко используемых HTTP-серверов. А теперь с помощью mod_wasm можно запускать и приложения WebAssembly. Чтобы не устанавливать и не настраивать его локально, мы подготовили контейнер, в котором находятся Apache HTTPD, mod_wasm и WordPress.

docker run -p 8087:8080 projects.registry.vmware.com/wasmlabs/containers/php-mod-wasm:wordpress

Можно перейти на http://localhost:8087 и посмотреть, как WordPress работает с интерпретатором PHP Wasm и модулем mod_wasm в Apache HTTPD.

Запуск WordPress непосредственно в браузере

Для примера зайдите на сайт https://wordpress.wasmlabs.dev. Увидите фрейм, в котором интерпретатор PHP Wasm рендерит WordPress в реальном времени.

Заключение

Спасибо, что дочитали до конца. Информации было много. Надеемся, она помогла оценить возможности WebAssembly и разобраться, как эта технология работает с существующими кодовыми базами и инструментами, в том числе с Docker'ом. 

А какой у вас опыт работы с Wasm? Делитесь в комментариях!

P.S.

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

Комментарии (35)


  1. denaspireone
    15.05.2023 06:18
    +1

    мда, а бенчмарки есть, где меряют производительность php via WASM и чистый php 8.2 ?

    PS: https://github.com/WordPress/sqlite-database-integration для тех, кто увидел wordpress и решил попробовать странного


    1. NastyaDani
      15.05.2023 06:18
      +2

      Спасибо за вопрос. Вот тут есть хороший бенч. Можно сравнить, например, время обхода Merkle-дерева. Еще можно посмотреть академическое исследование Understanding the Performance of WebAssembly Applications и вот это:

      The raw execution of an algorithm in WASM is almost always faster than in JavaScript. However, the cost of writing data into the WASM module’s memory can be so high that it removes the benefit of using WASM in the first place.


  1. ednersky
    15.05.2023 06:18
    +3

    а почему только WASM?

    тогда уж надо просто дать возможность запуска любого системного бинарника как контейнера (а wasm будет одним из вариантов).

    брюки превращаются... превращаются брюки... в элегантный apache версии 1!


    1. domix32
      15.05.2023 06:18

      Проблема в изоляции - те бинарники могут знать о файловой системе, то чего им знать особо и не надо и сисколить то, чего сисколить по хорошему не положено. UML может спасать в некоторых случаях. но если это не Linux, а какая-нибудь *BSD или Windows, где искать тот юзермод? Вот тут и приходит wasm и WASI - настроил вирутальную файлуху, указал доступы к реальной файлухе и сисколам - вот тебе и изоляция.

      Это почти как джава - write once, run everywhere.


      1. ednersky
        15.05.2023 06:18

        сисколить через WASM и будет можно и… придётся.
        иначе не сделать ни TCP сервер ни TCP клиент, ни файл сохранить/удалить итп


        1. domix32
          15.05.2023 06:18

          Для этого и есть WASI. И уже конкретные имплементации этого WASI имплементят права доступа на всё.


  1. alfss
    15.05.2023 06:18

    Звучит интересно, но есть вопрос с отладкой, как отлаживать такие приложения, возможно ли это стандартными методами языка?


    1. gibson_dev
      15.05.2023 06:18

      К сожалению нет. В отладкой постепенно улучщается но пока все сыро.


      1. LordDarklight
        15.05.2023 06:18

        По идеи каких-то серьёзных проблем с отладкой WASM приложений быть не должно - это же виртуальная машина - тут всё дело просто в разработке инструмента. Но, вот, считается же что отладка нативных приложений для Linux или мобильных платформ, до сих пор сильно проигрывает отладке под Windows.

        WASM сейчас очень сильно завязывается на среду выполнения (в первую очередь - это браузеры) - и отладку должны в первую очередь обеспечивать именно они.

        Первым должна тут подтянуться Mozila - ибо WebAsasembly это изначально их проект.

        Во вторую очередь - Google - расширенные средства отладки веб разработки у них в приоритете

        Затем, вероятно подтянется Microsoft - ну те любят переизобретать велосипед в рамках своей экосистемы.

        А вот Apple может ещё долго тормозить (не любят они сторонние приблуды в рамках своей экосистемы).

        А вот с не браузерными средами исполнения всё будет куда печальнее!


    1. domix32
      15.05.2023 06:18

      Можно абстрагировать wasm часть и сделать нативный таргет для отладки. Напрямую wasm бинари пока разве, что в браузере отлаживать, если вы конечно понимаете ассемблерные языки.


  1. novoselov
    15.05.2023 06:18

    Замена Docker для server-side и всего-всего ...

    Круто! И как запустить в Wasm "контейнере" Spring, Kafka и Oracle?


  1. LordDarklight
    15.05.2023 06:18
    +1

    Сначала были Lisp, Forth и другие интерпретируемые ЯП, позволяющие писать крос-платформенный код - оставим из покое (тогда это было не так актуально).

    Потом пришёл Java, за ним Python, JavaScipt, C#... позволяющие создавать кроссплатформенный код в рамках своей виртуальной управляемой среды выполнения (но ещё до конца не изолированной).

    Затем пришёл LLVM - задавший новый стандарт кроссплатформенности - уже без виртуальной среды (не смотря на Virtual Machine в названии)! Но впихнуть его в браузер не удалость.

    И вот (после отдельной тупиковой но важной вехой в лице Asm.js) этот LLVM в итоге переродился в WebAssembly, который становится очередным стандартом кроссплатформенности, на этот раз оттолкнувшись именно от браузеров (хотя тяжеловесное проксирование вызовов с JavaScript до сих пор имеется - что сильно снижает производительность этих вызовов, да и работа с DOM идёт через JS), но технология, решила-таки, окончательно вытеснить LLVM из кроссплатформенных разработок без виртуальной среды (всё-таки создав её для изоляции).

    Основная беда LLVM была видимо в том, что это регистровая виртуальная машина, и были сложности со встраиванием её в именно в браузеры (зато она хорошо компилировалась в машинный код, который почти всегда регистровый на этапе исполнения; ну и над изолированностью тогда не подумали). Да и инструкции не оптимизировали для работы в браузере. Вот автор и переделал её в WASM - где уже стековая машина (заодно это дало возможность компилировать в WASM и исходные коды с ЯП, предназначенные для стековых машин - как Java, C# и изначально интерпретируемые - их на стекловавшую машину переводить проще, как проще было перевести ЯП, изначально созданные для регистровых процессоров, тоже не стековое исполнение).

    Так что, у WebAsembly есть много шансов занять своё место и в серверном кода - тут пока ещё тоже востребована кроссплатформенность.

    Я правильно понял, что компиляцию WASM кода под конкретную платформу (бакэнд-компиляция а-ля LLVM) уже тоже завезли? А что там с JIT компиляцией - та же техника как у JavaScript?


    1. SabMakc
      15.05.2023 06:18
      +1

      Если не ошибаюсь, в большинстве случаев код в WASM как раз через LLVM и компилируется. Как и LLVM умеет компилировать WASM-файлы в нативные бинарники. Некоторые интерпретаторы WASM тоже через LLVM работают вроде.

      Так что, у WebAsembly есть много шансов занять своё место и в серверном кода - тут пока ещё тоже востребована кроссплатформенность.

      С одной стороны согласен, шансы есть. А с другой - очень сильно сомневаюсь, что получится. Просто потому, что WASM имеет очень мощную песочницу (которая и идет главной фишкой WASM) и весь софт надо серьезно адаптировать под WASM.


      1. LordDarklight
        15.05.2023 06:18

        Из LLVM в WASM делают компиляцию. Но это только для тех ЯП, что умеют компилироваться в LLVM - и, скорее всего, это переходное решение (чтобы не переделывать фронт-компилятор). Например C# не умеет в LLVM - но умеет в WASM.

        и весь софт надо серьезно адаптировать под WASM.

        Да - тут проблем много, как и много софта у которого не так много взаимодействия за пределы песочницы. Но в итоге - скорее новый софт будут разрабатывать уже с оглядкой на WASM - для перехода на эту стадию уйдёт лет 10


        1. SabMakc
          15.05.2023 06:18

          Когда я щупал C#/Java в WASM - там все было очень сыро, на уровне очень ранней беты. Скорее просто демонстрация возможности.

          На сколько понимаю, основной затык в GC - свой сборщик и рантайм тащить не хотят, а хотят на уровне WASM согласовать сборку мусора.

          Но в итоге - скорее новый софт будут разрабатывать уже с оглядкой на WASM - для перехода на эту стадию уйдёт лет 10

          WASM когда появился? В 2015? А с 2017 пошла интеграция в браузеры?
          Технология уже достаточно взрослая. И за ближайшие 10 лет не думаю, что-то в WASM принципиально изменится. Да, досогласуются расширения, улучшится поддержка в браузере и расширится экосистема в целом...
          Но WASM сейчас - технология крайне нишевая.
          Фронт-разработчикам он особо не нужен, им хватает JS/TS. А бекендерам нет особо дел до браузера. Тем более, что для запуска WASM все равно нужна серьезная обвязка из JS.
          Можно сказать, что своей полноценной ниши WASM пока не нашел. И не думаю, что в ближайшие лет 10 это значительно изменится.


          1. LordDarklight
            15.05.2023 06:18

            На сколько понимаю, основной затык в GC

            Всё верно - все ждут появления сборщика в WebAssembly 2.0

            А пока на костылях тянут свой

            Технология уже достаточно взрослая. И за ближайшие 10 лет не думаю, что-то в WASM

            Ну взрослой технология обычно становится после 3-тьей версии, а пока только ожидается 2.0

            Да и срок, реально не большой - из беты они вышли всего пару лет назад!

            Пока WASM очень нишевой продукт (хотя бы потому что у него много детских недостатков, да хоть сильное снижение производительности при JS вызовах в т.ч. к DOM).

            Но сейчас WASM интересен тем, что позволяют относительно легко портировать готовые приложения под браузер (особенно если они написаны под Native; с виртуальными машинами всё пока сложнее), и это можно делать, условно не прибегая к помощи профессиональных веб-разработчиков.

            Так же можно вести разработку новых приложений с прицелом не на веб-клиент, но с оглядкой на него - параллельно создавая и WEB-клиент (или разрабатывать сразу подо всё).

            В будущем WASM скорее будет интересен для разработки нового софта, на традиционных ЯП, без углубления в WEB разработку. Но даже сейчас - есть такие платформы как UNO Platform (и готовят аналогично в Avalonia), где можно вести и WEB разработку полностью на C# - что новым разработчикам может быть интереснее, чем изучение мира JS.

            Но пока, для проф JS-фронтэндщиков WASM не интересен из-за падения производительности при JS вызовах - а там полно библиотек на JS. Вот обрастёт WASM своими библиотеками (и уменьшит задержки в JS вызовах) вот тогда и во фронтэде может быть интересным - учитывая что в WASM уже и TypeScript и Kotlin умеют компилироваться. Тогда можно будет вообще собирать ПО написанное на разных ЯП.

            Ну а для развития темы кроссплатформенности можно прикручивать WASM и на серверной стороне. Но тут скорее как внутренний движок скрипто-исполнителя (как это п прикручено к Docker) - единую системы внутреннего исполнения алгоритмов. Хотя, на C# вот уже есть .NET Scripting и это уже не так актуально. Но для JVM пока ещё актуально! Ну это если серверную часть не стали пилить на Node.js (вместе с JS на клиенте) - там есть eval.


            1. SabMakc
              15.05.2023 06:18

              Да, перспективы у WASM интересные. Но не скажу, что это будет простой и понятный путь...

              Правда в копилку WASM можно назвать блокчейн и смарт-контракты - вот где изоляция и детерминированное выполнение нужны как никогда. Многие новые платформы так или иначе контракты на WASM развивают сейчас.


  1. SabMakc
    15.05.2023 06:18
    +4

    Сравнивать docker и WASM крайне некорректно. Они решают разные задачи и решают их по разному. Единственное что у них общее - возможность что-то запустить.

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

    WASM - виртуальная машина со своей системой команд (своим низкоуровневым языком). Да, за счет виртальности дает хорошую изоляцию от хоста (особенно если эту изоляцию подчеркивать). Но если бы не продвижение WASM как возможности запуска ресурсоемких вычислений в браузере - WASM был бы практически никому не интересен.

    Зачем docker лезет в WASM - понятно. WASM - перспективная технология с неустоявшейся экосистемой. И популярный инструмент, который будет запускать WASM, может стать инструментом работы с WASM "по умолчанию", что в свою очередь может принести очень много денег.

    Но называть WASM заменой docker - это очень и очень некорректно.


    1. LordDarklight
      15.05.2023 06:18
      +2

      Но называть WASM заменой docker - это очень и очень некорректно.

      Разве так называют?

      Посыл был такой, что будет WASM представлен раньше (в виде WASI) - то Docker (по крайней мере в нынешнем виде) вряд ли появился бы (появилось бы нечто другое)


      1. hssergey
        15.05.2023 06:18
        +1

        Ну вообще достаточно странный посыл. Потому что это технологии для решения совершенно разных задач. Docker позволяет запаковывать в контейнеры, изолировать и деплоить одинаковым образом имеющийся зоопарк софта без оглядки на то, как на одной машине могут сосуществовать разные версии php и питона например. А WASM - это фактически переизобретение джавы и сам по себе он проблему деплоя уже имеющегося зоопарка софта со своими либами и версиями никак не решает. Разве что пересобирать весь софт под WASM...


        1. LordDarklight
          15.05.2023 06:18

          Docker использовали для изолированного выполнения кроссплатформенного кода на серверной стороне. Теперь для этого есть WASM. Ранее альтернативой были только .NET, JVM, Node.js - но все они видимо чем-то не подходили - видимо отсутствием изолированности. А WASM это решает - и можно писать код на широком спектре ЯП


          1. mayorovp
            15.05.2023 06:18

            Docker никогда не использовали для выполнения кроссплатформенного кода, платформа контейнера обязана совпадать с платформой хоста.


            Вы не можете ни запустить amd64 контейнер на arm, ни запустить windows контейнер под linux.


            1. SabMakc
              15.05.2023 06:18

              Не совсем так - linux контейнеры работают на всех платформах (пускай местами и через виртуалку).
              Также как и контейнеры amd64 запускается и на MacBook M1 и старше (не знаю, правда, как дела на других arm-платформах).


              1. mayorovp
                15.05.2023 06:18

                Но докер тут ни при чём, это фичи платформы.


                1. SabMakc
                  15.05.2023 06:18

                  Это фича Docker Desktop - а он всегда на возможности платформы так или иначе полагается.
                  Если не ошибаюсь, практически любой ARM может тот же x86 на QEMU запустить.
                  И на тех же же arm-маках Docker Desktop через QEMU и работает, на сколько мне известно. Считать ли QEMU фичей платформы - вопрос отдельный )


                  1. mayorovp
                    15.05.2023 06:18

                    Нет, в случае винды это точно фича винды, называется WSL2. Docker Desktop просто удачно её использует для запуска linux контейнеров.


                    Причём он даже не делает попыток как-то различать контейнеры, просто запускает dockerd из-под wsl2 и передаёт все контейнеры нему. Ах да, и прозрачно использовать windows-контейнеры и linux-контейнеры совместно невозможно, пользователю надо явно переключать контексты. Совсем не то поведение, которое ожидается от слов "изолированного выполнения кроссплатформенного кода".


                    1. SabMakc
                      15.05.2023 06:18

                      Docker на windows появился задолго до появления WSL2 (и WSL1). И тогда он просто запускал виртуалку с ubuntu, если не ошибаюсь.
                      Режим виртуалки есть и для linux - если Docker Desktop ставить, а не Docker Engine.


          1. hssergey
            15.05.2023 06:18
            +1

            Да, но докер - это не среда для написания кода. А среда для интеграции уже имеющегося софта. Например, у вас есть какой-то проприетарный бинарник, который требует либы определенных версий, и докер позволяет его упаковать в контейнер, а дальше деплоить на разные серверы, не заботясь о совместимости либ и изоляции. Как эту же задачу решить средствами WASM? Исходников нет.

            Так что WASM - это просто еще один рантайм, под который надо компилировать исходники, да, обладающий гибкими настройками изоляции, поддерживающий множество языков программирования и т.п. Но проблему интеграции софта он вообще никак не решает.

            Так что мне например непонятна фраза, что если бы был уже WASM, то и докер создавать не нужно было...


      1. SabMakc
        15.05.2023 06:18

        Я с таким посылом и не согласен. Они решают разные задачи и по разному - их области применения пересекаются в очень узком спектре, чтобы WASM оказал серьезное влияние на docker.

        Даже в этой статье для запуска PHP в WASM наложили патчи на интерпретатор PHP и в WASM добавили расширение для обхода песочницы. И даже с развитием WASM эти моменты не уйдут - просто потому что изоляция в WASM идет как главная фишка. И без адаптации софта под эту изоляцию никуда не деться.

        А фишка docker - как раз в запуске стандартных linux-приложений.


  1. acmnu
    15.05.2023 06:18
    +1

    Странное сравнение. Это скорее похоже на Application Server из мира Java, чем на Docker.


  1. vvzvlad
    15.05.2023 06:18
    +1

    WebAssembly привносит истинную портируемость. Исполняемый файл достаточно собрать один раз, после чего его можно будет запускать везде.

    А питоновские либы, которым нужны свои бинарники, например?


  1. Kenya-West
    15.05.2023 06:18
    +2

    Видно, что образы Wasm-контейнеров намного меньше обычных. Даже alpine-версия контейнера php больше, чем Wasm.

    Каким образом это достигается? Ведь php даже внутри Wasm всё равно требуются все зависимости и окружение для работы платформы, которая интерпретирует php-код? Или каким-то образом оно научилось всё окружение тоже комплизировать и запихивать внутрь Wasm-виртуалки?

    Вопрос даже не столь по php, сколь по обвязкам, зависимостям и окружениям любых других ЯП и их рантаймов.


    1. kksudo
      15.05.2023 06:18

      Это кстати важный момент и отличный вопрос.
      Подожду тут ответа.


    1. mayorovp
      15.05.2023 06:18

      Предположу, что часть окружения находится непосредственно в реализации WASI, а оставшаяся часть недоступна.


      Ну там, к примеру, функция system в таком порезанном окружении просто упадёт с ошибкой.


  1. medvedevia
    15.05.2023 06:18
    +2

    WasmEdge позволяет создать AOT-оптимизированный (ahead-of-time) исполняемый файл, который нативно работает на текущей машине и может интерпретироваться на других.

    И в чем прикол? Опять возвращаемся к тому, что надо компилировать под каждую платформу?

    Уже сейчас можно rust компилировать с musl и контейнер собирать из scratch, в итоге получается очень маленький контейнер. Выходит, что wasm уже имеет не так много плюсов.