In my previous post, Understanding Envoy’s Extension and Integration Mechanisms: From Built-in Filters to Dynamic Modules, I systematically reviewed the various extension and integration mechanisms of Envoy and specifically recommended the emerging Dynamic Module mechanism. This post focuses on the principles and full workflow of the Dynamic Module, helping you efficiently master native extension development for Envoy.
What is a Dynamic Module?
Dynamic Module is an experimental feature introduced in Envoy Proxy v1.34, allowing developers to load custom extensions as shared libraries (.so) dynamically. It does not depend on a virtual machine or out-of-process integration—extensions are connected directly to the main process via C ABI, achieving extremely high performance and modern SDK support (such as the official Rust SDK).
Technical Principles
A Dynamic Module is essentially a shared library that implements specific ABI entry points, interacting with Envoy through interfaces defined in a C header file. The official SDK primarily supports Rust (for type safety and better developer experience), but Go, C++, and other languages that can compile to a C ABI-compatible dynamic library are also viable. The community already offers an experimental Go implementation.
- Set the search path for dynamic modules via the
ENVOY_DYNAMIC_MODULES_SEARCH_PATH
environment variable. .so
files must be named according to thelib{name}.so
convention, where{name}
matches thedynamic_module_config.name
in your Envoy config.- Once loaded, the module is directly inserted into the HTTP filter chain and enjoys equal status to native filters.
Note: Dynamic Modules run within the main process and share memory and privileges—there is no isolation or sandboxing. Always load only trusted modules.
Advantages and Limitations
As summarized in my previous article, Dynamic Modules offer several advantages:
- Ultra-high performance: No intermediate VM, no cross-process or language serialization, suitable for high-throughput scenarios.
- Modern development: The Rust SDK is type-safe, well-documented, and offers a much better developer experience than Wasm.
- Highly flexible: You can implement virtually any custom logic at the HTTP layer.
- Multi-language potential: While officially Rust is supported, Go, C++, etc., are all theoretically possible.
Limitations:
- No security sandbox—only suitable for controlled environments.
- Tight coupling with Envoy version; any upgrade requires module recompilation.
Hands-on: Complete Dynamic Module Demo with Official Examples
Next, let’s walk through a one-click experience of Envoy Dynamic Modules (Rust/Go versions) from source to container, based on the dynamic-modules-examples official repo.
This lab includes several typical modules to help you understand the extension capabilities of the Dynamic Module:
- access_logger
- Structured logging for all request/response headers; outputs JSON for observability and debugging.
- header_mutation
- Supports adding/removing headers and policy injection—useful for canary/A-B testing and policy rollout.
- passthrough (Rust/Go implementations)
- Demonstrates multi-language compatibility and filter chain loading (no logic).
1. Clone the source repo
git clone https://github.com/envoyproxy/dynamic-modules-examples.git
cd dynamic-modules-examples
git checkout 74e9dbf80c0ea056e0ee5fc1b8f1c35d3a4c9a98
2. Build all dynamic modules and the Envoy image with Docker
docker build -t envoy-with-dynamic-modules:latest .
- This command will:
- Compile the Rust/Go dynamic modules, producing
.so
shared libraries. - Build a custom Envoy binary.
- Package everything into a runnable Docker image.
- Compile the Rust/Go dynamic modules, producing
- By default, only the host architecture is supported (e.g., x86_64 or ARM64). For cross-platform, you must adapt the Dockerfile manually.
- Dynamic modules are compiled as
.so
shared libraries, dynamically loaded into the Envoy process via C ABI. - Ensure your host and container architectures match; otherwise, the module or Envoy binary may not be compatible.
3. Envoy configuration and mounting
The included envoy.yaml
is already configured for dynamic module loading. Here’s a typical snippet:
static_resources:
listeners:
- name: main
address:
socket_address:
address: 0.0.0.0
port_value: 1062 # Listening port
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
route_config: ...
http_filters:
- name: envoy.filters.http.dynamic_module
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.dynamic_modules.v3.DynamicModuleFilter
dynamic_module_config:
name: access_logger # Must match the .so file name
filter_name: access_logger
- name: envoy.filters.http.router
clusters:
- name: echo_service
connect_timeout: 0.25s
type: STATIC
load_assignment: ...
- The
dynamic_module_config.name
field tells Envoy to look forlibaccess_logger.so
in the directory specified by theENVOY_DYNAMIC_MODULES_SEARCH_PATH
environment variable. - For example,
name: access_logger
requires alibaccess_logger.so
file. filter_name
is used for filter chain identification; it’s recommended (but not required) to match the module name.- In your
docker-compose.yaml
, you’ll see environment variables and volume mounts to ensure modules and config files are loaded into the container:
services:
envoy:
image: envoy-with-dynamic-modules:latest
volumes:
- ./integration/envoy.yaml:/etc/envoy/envoy.yaml
- ./target/x86_64-unknown-linux-gnu/release:/usr/local/lib
- ./integration/access_logs:/access_logs
ports:
- 1062:1062
environment:
- ENVOY_DYNAMIC_MODULES_SEARCH_PATH=/usr/local/lib
echo:
image: hashicorp/http-echo
command: ["-text=Hello from echo server"]
ports:
- 1234:1234
- All dynamic modules are stored in
/usr/local/lib
; mount additional.so
files as needed (e.g.,header_mutation
,passthrough
, etc.) - Multi-port demonstration: test different modules using different listeners/routes/filters.
- Access logs are output via mounted directories for local analysis and debugging.
4. Start the lab environment (Envoy + Echo)
docker compose up -d
- This launches two services:
- Envoy: Loads all integrated dynamic modules automatically.
- Echo Server: Simple HTTP backend for verifying traffic and filter effects.
- Envoy listens on ports 1062, 1063, 1064, each configured with different modules (see
envoy.yaml
).
5. Send a request to test dynamic module effects
For the access_logger
module (listening on 1062):
curl localhost:1062/uuid
You should receive:
Hello from echo server
Likewise, try ports 1063/1064 for other modules (header_mutation
, passthrough
, etc.).
6. Check structured logs and header changes
Access logs help you analyze module behavior (such as access_logger
). For header_mutation
, check the response headers for injected values.
cat integration/access_logs/access_log_0.log
access_logger
outputs structured headers for every request and response.header_mutation
injects custom fields likefoo: bar
,foo2: bar2
into response headers.- Compare the logs and responses to verify the full effect of your filter chain.
7. Tear down and clean up
docker compose down
- This stops and cleans up all containers and mounts.
- To retest, modify the source, rebuild the module, and launch again.
Important Notes
- Ensure that all
.so
files are compiled for the exact Envoy version you are running (same Git SHA/Release Tag), or ABI incompatibilities will arise. Every Envoy upgrade requires recompilation of all dynamic modules. - The
dynamic_module_config.name
field in the config must match the module filename. - Rust is officially supported; Go is experimental; C/C++ support depends on ABI compatibility.
- Only load modules that are fully controlled and trusted by your organization.
Summary
Dynamic Modules bridge the gap between C++/Wasm/Lua/out-of-process extension methods, offering both high performance and developer friendliness. For enterprise, self-managed scenarios, Dynamic Modules are highly recommended. If you wish to discuss implementation or filter chain details, feel free to leave a comment.