This article is translated from the blog of Wasm Labs @ VMware OCTO: WebAssembly: Docker without container. This is a transcript of Wasm Labs’ talk on Docker+WebAssembly at Winter Docker Community All Hands 7 on December 15, 2022.
By Asen Alexandrov, Engineer, Wasm Labs. We in this article refer to the authors or Wasm Labs. This article will be more practical, taking PHP as an example to lead you to practice Docker + Wasm.
In the previous article, we learned why the server-side Wasm plays an important role, what is WasmEdge, and how to make programs written in interpreted languages run in Wasm. In this article, we will use hands-on examples to understand Wasm in the context of Docker + Wasm What are the benefits of a container and how to run a php.wasm image serving WordPress.
hands-on example
let’s start! In our hands-on example, we’ll use the PHP interpreter compiled to Wasm. we will:
- Build a Wasm container.
- Compare Wasm and native binaries.
- Compare traditional containers and Wasm containers.
- Demonstrate the portability of Wasm
Preparation
To reproduce these examples locally, you will need to prepare your environment with some or all of the following:
- WASI SDK – Build WebAssembly applications from C code
- PHP – run native PHP binaries for comparison
- WasmEdge Runtime – Runs WebAssembly applications
- Docker Desktop + Wasm (Available in Docker Desktop 4.15 as a stable beta at the time of writing) – able to run Wasm containers
We also make good use of the webassembly-language-runtimes repo, which provides a way to build the PHP interpreter as a WebAssembly application. The demo branch can be viewed like this:
git clone --depth=1 -b php-wasmedge-demo \
https://github.com/vmware-labs/webassembly-language-runtimes.git wlr-demo
cd wlr-demo
Build a Wasm container
As a first example, we’ll show how to build a C-based application, such as a PHP interpreter.
The build uses the WASI-SDK toolset. It includes a clang compiler that can build to the wasm32-wasi target, and wasi-libc that implements the basic POSIX system call interface on top of WASI. Using the WASI SDK, we can build a Wasm module written in C from the PHP codebase. After that, we need a very simple scratch-based Dockerfile to make an OCI image that can run with Docker+Wasm.
Build a WASM binary
Suppose you are now in wlr-demo
folder, which is part of the pre-preparation work, you can run the following command to build the Wasm binary.
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/
... ( a few minutes and hundreds of build log lines)几分钟和数百行构建日志
build-output/php/php-7.4.32/bin/
├── php-cgi-wasmedge
└── php-wasmedge
PHP is used autoconf with built by make. So if you take a look at the script scripts/wl-build.sh
you’ll notice that we set all relevant variables like CC
,LD
, CXX
etc. to use the compiler from WASI_SDK.
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
Then, drill down further to see php/php-7.4.32/wl-build.sh
you can see that we use the autoconf build process as usual.
./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 is a work in progress and many POSIX calls still cannot be implemented on top of it. Therefore, to build PHP, we had to apply multiple patches on top of the original codebase.
We saw above that the output binary goes to build-output/php/php-7.4.32
.In the examples below, we will use the built-in WasmEdge php-wasmedge
binary because it provides server-side socket support,Server socket support is not yet part of WASI.
optimized binary code
Wasm is a virtual instruction set, so the default behavior of any runtime is to interpret those instructions on the fly. Of course, this might slow things down in some cases. So, to get the best of both worlds with WasmEdge, you can create an AOT (compiled ahead of time) optimized binary that runs natively on the current machine but can still be interpreted on other machines.
To create optimized binaries, run the following command:
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
We use this in the example below build-output/php/php-7.4.32/bin/php-wasmedge-aot
binary code. To learn more about WasmEdge AOT optimized binaries, check out here.
Build OCI image
Now that we have a binary, we can wrap it in an OCI image.let’s look at this images/php/Dockerfile.cli。
All we need to do is copy the Wasm binary and set it to 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" ]
We can also add more content to the image that the Wasm binary can access when Docker runs it.For example, in images/php/Dockerfile.server
we also add some docroot content, which is generated by the php.wasm
Provide services.
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"]
Based on the above files, we can easily build our local php-wasm
mirror image.
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 .
Native vs Wasm
Now let’s compare native PHP binaries with Wasm binaries locally and in a Docker container.We will use the same index.php
file and compare the results you get when you run it with the following:
php
php-wasmedge-aot
- running in traditional containers
php
- running in a Wasm container
php-wasmedge-aot
In all the examples below we use the same images/php/docroot/index.php
file, let’s take a look. In short, the script will:
- use
phpversion
withphp_uname
Show the interpreter version and the platform it is running on - Print the names of all environment variables accessible to the script
- Print a greeting message containing the current time and date
- List the contents of the root folder
/
<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>
Native PHP runs index.js
We use local php
When looking at the binaries, see a Linux-based platform.
- A list of 58 environment variables that scripts can access when needed
/
A list of all files and folders in the , which can be accessed again by the script if needed
$ 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 run index.js
If we use in WasmEdge php-aot-wasm
We saw
- A wasi/wasm32 platform
- No environment variables, since they are not explicitly exposed to Wasm applications
- Wasm application does not get support for
/
explicit access to , so attempting to list its contents fails with the error
Naturally, in order to let php-wasmedge-aot
able to read index.php
file, we must explicitly declare to WasmEdge that we want to pre-open images/php/docroot
so that in the context of a Wasm application as /docroot
to visit. This clearly demonstrates one of Wasm’s biggest strengths besides portability. We get better security because nothing can be accessed unless explicitly stated.
$ 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 in the container runs index.js
When we use from a traditional container php
when we see
- Linux-based platforms
- List of 14 environment variables the script has access to
- Greeting message with current time and date
- A list containing the contents of the root folder
/
and on the host using php
Compared to running it, there is already a noticeable difference, and the performance is better.because /
The environment variables and contents are “virtual” and only exist inside the container.
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 runs index.js in a container
If we use in WasmEdge php-aot-wasm
We saw
- A wasi/wasm32 platform
- There are only 2 infrastructure environment variables, pre-set using the WasmEdge shim running in containerd
- in the container
/
A list of all files and folders inside, explicitly pre-opened for access by the Wasm application (part of the logic in the WasmEdge shim)
NOTE: If you look closely, to run a container from this image, we have to:
- pass
--runtime=io.containerd.wasmedge.v1
Passing command-line arguments directly to php.wasm explicitly declares the runtime, without including the binary itself. Pulling up above we can see that we can explicitly write the complete command, including the php binary (not required), using a traditional PHP container.
As a final note, even with Docker, Wasm tightens up the security of running index.php because much less is exposed to it.
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>
Traditional Containers vs Wasm Containers
We built and ran a Wasm binary and ran it as a container. We saw the difference in output between Wasm and traditional containers and the advanced “sandbox isolation” that Wasm brings. Other differences between the two containers that we can easily see.
First, we’ll run two daemon containers and see how we can interpret some statistics about them. Then we will check the difference of the container image.
container data
Let’s run two daemon containers – one from the traditional php
mirrored, the other is from php-wasm
mirror image.
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
but if we look at docker stats
, we only see data for legacy containers. This may change later, as Docker+Wasm is a beta feature now. So, if you really want to see what’s going on, you can monitor the control group instead.Each legacy container has its own control group, such as docker/ee44...
.On the other hand, Wasm containers serve as podruntime/docker
Part of the control group is included and their CPU or memory consumption can be observed indirectly.
$ 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
mirror size
First, exploring images, we see that Wasm container images are much smaller than traditional images.even if alpine
version of php
Containers are also larger than Wasm containers.
$ 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
This is expected because with Wasm we only need to add an executable binary inside the container, whereas with traditional containers we still need some basic libraries and files from the operating system the binary is running on. This size difference is very helpful for the speed of the first image pull and the space it takes up in the local repository.
Wasm portability
One of Wasm’s greatest strengths is its portability. Docker already offers traditional containers as an option when people want a portable application. However, in addition to being extremely large images, traditional containers are also bound to the platform architecture on which they run. As a programmer, many people have experienced this ups and downs: for different architectures, it is necessary to build supported software versions and package corresponding images for each architecture.
WebAssembly brings true portability. Build a binary once and run it anywhere. As a demonstration of this portability, we prepared several examples of running WordPress through the PHP interpreter we built for WebAssembly.
When PHP runs as a standalone Wasm application, it serves WordPress. It can also run in a Docker+Wasm container. Additionally, it can run in any application that embeds the Wasm runtime. In our case, this is apache httpd, which can use Wasm applications as content handlers via mod_wasm. Finally, PHP.wasm can also run in the browser.
Serving WordPress with WasmEdge
We have prepared a compact WordPress+Sqlite example for this demo.since it is ghcr.io/vmware-labs/php-wasm:7.4.32-server-wordpress
Part of the container image, we first download it locally.
This command will just create a temporary container (pull image), copy the WordPress files to /tmp/wp/docroot
and delete the container.
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
Now that we have WordPress, let’s add the server:
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
Access http://localhost:8085 to use WordPress served by the PHP Wasm interpreter.
Serving WordPress via Docker+Wasm
Naturally, it’s much easier with 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
You can visit http://localhost:8086 and use WordPress served by the PHP Wasm interpreter, this time running in a Docker container.
Serving WordPress via mod_wasm in Apache HTTPD
Apache HTTPD is one of the most widely used HTTP servers. Now with mod_wasm it can also run WebAssembly applications. To avoid having to install and configure it locally, we prepared a container containing Apache HTTPD, mod_wasm and WordPress.
docker run -p 8087:8080 projects.registry.vmware.com/wasmlabs/containers/php-mod-wasm:wordpress
You can visit http://localhost:8087 and use WordPress served by the PHP Wasm interpreter loaded by mod_wasm in Apache HTTPD.
Serve WordPress directly in your browser
Visit https://wordpress.wasmlabs.dev for examples. You’ll see a framework where the PHP Wasm interpreter renders WordPress live.
in conclusion
Thanks for reading this article. That’s a lot to digest, but we hope this article helps you understand what WebAssembly is capable of and how it works with your existing codebase and tooling, including Docker. Looking forward to seeing you program with Wasm!
If you think WasmEdge is good, don’t forget to give us a thumbs up!
https://github.com/WasmEdge/WasmEdge
#Running #WordPress #Docker #WasmEdge #WebAssembly #Docker #Containers #Part #WasmEdges #Personal #Space #News Fast Delivery