The custom code that you create for Service Extensions plugins must be packaged and published to Artifact Registry before other services can access it. This page describes how to create plugin code, package the code in a container image, and upload it to an Artifact Registry repository.
For information about Service Extensions, see Service Extensions overview.
Before you start, review the best practices for writing plugin code.
For more examples, see Code samples for plugins.
Before you begin
- Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
-
In the Google Cloud console, on the project selector page, select or create a Google Cloud project.
-
Make sure that billing is enabled for your Google Cloud project.
-
Enable the Network Services, Network Actions, Artifact Registry, Cloud Build, Cloud Logging, and Cloud Monitoring APIs.
- Install the Google Cloud CLI.
-
To initialize the gcloud CLI, run the following command:
gcloud init
-
In the Google Cloud console, on the project selector page, select or create a Google Cloud project.
-
Make sure that billing is enabled for your Google Cloud project.
-
Enable the Network Services, Network Actions, Artifact Registry, Cloud Build, Cloud Logging, and Cloud Monitoring APIs.
- Install the Google Cloud CLI.
-
To initialize the gcloud CLI, run the following command:
gcloud init
Set up the toolchain
C++
The Proxy-Wasm C++ SDK let developers use C++ to implement WebAssembly (Wasm) plugins for Service Extensions. The SDK uses the C++ WebAssembly toolchain Emscripten, as well as other libraries, such as protobuf and, optionally, Abseil.
Because building plugins written in C++ depends on specific versions of these tools and libraries, we recommend using the Docker image provided by the Proxy-Wasm C++ SDK. The instructions for C++ on this page use the Docker method. To build C++ plugins without using Docker, see the Proxy-Wasm C++ SDK documentation.
Install Docker if it's not already installed. Docker is included in Cloud Shell, the Google Cloud interactive shell environment.
Download a copy of the Proxy-Wasm C++ SDK. The simplest way to do this is to clone the Git repository:
git clone https://github.com/proxy-wasm/proxy-wasm-cpp-sdk.git
Build the Proxy-Wasm C++ SDK Docker image from the Dockerfile provided by the SDK:
cd proxy-wasm-cpp-sdk docker build -t wasmsdk:v3 -f Dockerfile-sdk .
When the command completes building SDK libraries and dependencies, the resulting Docker image is associated with the specified tag, which is
wasmsdk:v3
in this example.
Go
The Proxy-Wasm Go SDK provides a full-feature Go SDK. Go provides good performance and rich support for third-party libraries written in pure Go.
Rust
The customization capability of Service Extensions is provided through the use of WebAssembly and Proxy-Wasm. WebAssembly supports a number of programming languages. Google recommends Rust because it provides excellent WebAssembly support and Proxy-Wasm provides a full-featured Rust SDK. Rust also provides good performance and strong type safety.
-
At the end of the installation process, follow any instructions printed to the console to finish the configuration process.
Add Wasm support to the Rust toolchain:
rustup target add wasm32-wasi
Create the plugin package
C++
Create a new directory, separate from
proxy-wasm-cpp-sdk
:mkdir myproject
In the directory, create a Makefile with the following contents:
# Express any dependencies PROTOBUF= # full / lite / none WASM_DEPS= # absl_base re2 ... # Include the SDK Makefile PROXY_WASM_CPP_SDK=/sdk include ${PROXY_WASM_CPP_SDK}/Makefile
Add a C++ source file for the plugin in the same directory. The names of C++ source files must match the WASM files that the Makefile targets, with the
.wasm
suffix replaced by.cc
. In this example, the source file must be namedmyproject.cc
.Add your plugin code to the source file.
The following sample source code is a plugin that rewrites the request host, and emits a response header:
The
onRequestHeaders
method is a callback that Service Extensions invokes.
Go
Create a new directory for the plugin:
mkdir go-plugin
In the directory, create a
go.mod
file by using the Go tool:go mod init go-plugin
In the same directory, create a source file named
main.go
, and add your plugin code to the file.The following sample source code is a plugin that rewrites the request host, and emits a response header:
The
OnHttpRequestHeaders
method is a callback that Service Extensions invokes.To download and pin library dependencies, run
go mod tidy
:go mod tidy
Rust
Create a Rust package directory by using the
cargo new
command from Rust's package manager, Cargo:cargo new --lib my-wasm-plugin
The command creates a directory that contains a
cargo.toml
file that you can update to describe how to build the Rust package and ansrc
directory in which you store the plugin code.Update the
cargo.toml
file to specify the parameters required to build the package:[package] name = "my-wasm-plugin" version = "0.1.0" edition = "2021"
To register the Proxy-Wasm Rust SDK and logging support as dependencies, add the
dependencies
section. For example:[dependencies] proxy-wasm = "0.2" log = "0.4"
To build a dynamic library as required for plugins, add the
lib
section. For example:[lib] crate-type = ["cdylib"]
To reduce the size of the compiled plugin, add the
profile.release
section. For example:[profile.release] lto = true opt-level = 3 codegen-units = 1 panic = "abort" strip = "debuginfo"
Add your plugin code to the
lib.rs
file in thesrc
directory.The following sample source code is a plugin that rewrites the request host, and emits a response header:
The
on_http_request_headers
method is a callback that Service Extensions invokes.
Compile the plugin
C++
To compile the plugin, run the following command from within the directory in which the Makefile and C++ plugin source files are located:
docker run -v $PWD:/work -w /work wasmsdk:v3 /build_wasm.sh myproject.wasm
This command maps the current directory to the work
directory within the
Docker image, and then runs the build_wasm.sh
script provided by the
Docker image to build the plugin code. When the compile operation completes
successfully, a myproject.wasm
file, which contains the compiled Wasm
bytecode, is created in the current directory.
The first time that plugin code is compiled using the Docker image, Emscripten generates the standard libraries. To cache these in the Docker image so that they don't need to be regenerated each time, commit the image with the standard libraries after the first successful compilation:
docker commit `docker ps -l | grep wasmsdk:v3 | awk '{print $1}'` wasmsdk:v3
For more information about building C++ plugins, see the Proxy-Wasm C++ SDK documentation.
Go
To compile the plugin code, run the go build
command:
env GOOS=wasip1 GOARCH=wasm ~/goroot/bin/go build -buildmode=c-shared -o main.wasm main.go
A successful compilation creates a main.wasm
file in the current directory.
Rust
To compile the plugin code, run the cargo build
command:
cargo build --release --target wasm32-wasi
When the build completes successfully, a Finished release [optimized] target(s)
message appears. If you're familiar with Envoy,
you might want to load the plugin in Envoy and verify its behavior.
Publish the image to Artifact Registry
Publish the compiled plugin to Artifact Registry so that Google Cloud services can access it.
Store the plugin artifact in an image name with the following format:
LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE
Replace the following:
LOCATION
: the regional or multi-regional location of the repository. For increased reliability, specify a multi-regional location.PROJECT_ID
: your Google Cloud console project ID.REPOSITORY
: the name of the repository where you intend to store the image.IMAGE
: the name of the container image in the repository.
For example:
us-docker.pkg.dev/my-project/my-repo/my-wasm-plugin
Create a local
package/
directory and copy your publishable plugin artifact into it. The following sample copies the plugin artifact built by Rust:mkdir -p package && cp -f target/wasm32-wasi/release/my_wasm_plugin.wasm package/plugin.wasm
Create a
package/Dockerfile
file with the following contents:FROM scratch COPY plugin.wasm plugin.wasm
Package the plugin code by using either Cloud Build or Docker.
Cloud Build
Create a
package/cloudbuild.yaml
build config file with the following contents:steps: - name: 'gcr.io/cloud-builders/docker' args: [ 'build', '--no-cache', '--platform', 'wasm', '-t', 'LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE:prod', '.' ] images: [ 'LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE:prod' ]
Trigger the operation to build the plugin container and publish it to Artifact Registry:
gcloud builds submit --config package/cloudbuild.yaml package/
Docker
Install Docker if it's not already installed. Docker is included in Cloud Shell, the Google Cloud interactive shell environment.
Configure Docker to authenticate to Artifact Registry. For example:
gcloud auth login gcloud auth configure-docker LOCATION-docker.pkg.dev gcloud auth print-access-token | docker login -u oauth2accesstoken --password-stdin https://LOCATION-docker.pkg.dev
Build the container image:
docker build --no-cache --platform wasm -t my-wasm-plugin package/
To tag the local image with the repository image name, use image tags, such as
prod
in the following example:docker tag my-wasm-plugin LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE:prod
Publish your tagged container image to Artifact Registry.
docker push LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE:prod
To confirm that the image was successfully pushed to Artifact Registry, run the
gcloud artifacts docker images list
command.gcloud artifacts docker images list LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE --include-tags
Prepare and upload the configuration file
Plugins might optionally receive configuration data, which can affect plugin behavior at runtime. Configuration data can be text or binary and in any format accepted by the plugin. If the size of the configuration data is large, you might need to upload the configuration file to Artifact Registry.
Write plugin code to read configuration data
C++
Configuration data is passed to the onConfigure
method of the root context
object, which is instantiated once at plugin startup, and remains active for
the lifetime of the Wasm runtime that hosts the plugin. The root context
object is an instance of the RootContext
class or of a subclass of
RootContext
.
Plugin code can control what class is used for the root context through the
RegisterContextFactory
value. For example, the following plugin code
registers MyRootContext
and MyHttpContext
as the classes to use for root
and stream context instances. It then reads a secret value from plugin
configuration data, which stream context instances can access through
the root context object.
Go
Configuration data is read while running OnPluginStart
, a method receiver
that the runtime executes on the PluginContext
struct once at plugin
startup. The configuration data doesn't change during the lifetime of the
Wasm runtime that hosts the plugin. PluginContext
is useful for performing
initialization logic and maintaining state across many requests.
Plugin code can control what context is used for PluginContext
by
implementing the NewPluginContext
method on VMContext
, which
instantiates PluginContext
. PluginContext
then instantiates HttpContext
contexts.
For example, the following plugin code registers VMContext
, which
instantiates PluginContext
, the context that's responsible for reading
configuration data and instantiating HttpContext
contexts as required.
PluginContext
reads a secret value from plugin configuration data, stores
it inside the PluginContext
context, and passes it to HttpContext
in NewHttpContext
.
Rust
Configuration data is read in from a RootContext
trait, which is
instantiated once at plugin startup, and remains active for the lifetime of
the Wasm runtime that hosts the plugin. RootContext
traits are useful for
performing operations or maintaining state across many requests.
Plugin code can control what class is used for the root context through the
set_root_context
method, and the root context instantiates stream contexts.
For example, the following plugin code registers MyRootContext
, which
instantiates MyHttpContext
as required. The root context reads a secret
value from plugin configuration data, and passes it to stream contexts.
Upload the configuration file
If the size of the data to be delivered to your plugin in on_configure
exceeds 900 KiB, publish it to Artifact Registry by following the method
described in Publish the image to Artifact Registry.
In this case, save the configuration file by the name plugin.config
in the
container image.
The next step is to create a plugin.
While creating the plugin, you need to provide the URI to the uploaded container image.
Create a new version of plugin code
To create a new version of the plugin code, edit the plugin file. Then, as described in the preceding sections, compile the plugin code, repackage it, and upload it to Artifact Registry.
Callbacks
The code that you compile into Wasm can define arbitrary methods or functions, but some of them have a special significance. These are the methods that are defined in the Proxy-Wasm SDK for the language of your choice and map to the Proxy-Wasm Application Binary Interface (ABI) specifications. Service Extensions invokes these callbacks in response to user requests or in response to plugin lifecycle events.
These callbacks are listed in the following table in the order in which they're typically invoked:
Callback name and description | C++ method name | Go method name | Rust method name |
---|---|---|---|
START_PLUGIN : Invoked when a plugin is
started. |
RootContext::onStart
|
VMContext.OnVmStart
|
RootContext::on_vm_start
|
CONFIGURE_PLUGIN : Invoked after a plugin
is started, to provide configuration data
to the plugin. |
RootContext::onConfigure
|
PluginContext.OnPluginStart
|
RootContext::on_configure
|
CREATE_CONTEXT : Invoked when a new
stream context is created. Each stream
corresponds to a client HTTP request. |
Context::onCreate
|
PluginContext.NewHttpContext
|
RootContext:: create_http_context |
HTTP_REQUEST_HEADERS : Invoked to process
HTTP request headers. |
Context::onRequestHeaders
|
HttpContext.OnHttpRequestHeaders
|
HttpContext:: on_http_request_headers |
HTTP_REQUEST_BODY : Invoked repeatedly
to process HTTP request body chunks. |
Context::onRequestBody
|
HttpContext.OnHttpRequestBody
|
HttpContext:: on_http_request_body |
HTTP_RESPONSE_HEADERS : Invoked to
process HTTP response headers. |
Context::onResponseHeaders
|
HttpContext.OnHttpResponseHeaders
|
HttpContext:: on_http_response_headers |
HTTP_RESPONSE_BODY : Invoked repeatedly
to process HTTP response body chunks. |
Context::onResponseBody
|
HttpContext.OnHttpResponseBody
|
HttpContext:: on_http_response_body |
DONE : Invoked when the processing of a
plugin has completed. |
Context::onDone
|
HttpContext.OnHttpStreamDone
|
Context::on_done
|
DELETE : Invoked when the stream context
object corresponding to a client HTTP
request is deleted. |
Context::onDelete
|
(no callback) | (no callback) |