Golang Envoy Configuration and Optimization Techniques

Author

Reads 1.1K

IT professional working on a computer in a modern office setting, focused on coding and tasks.
Credit: pexels.com, IT professional working on a computer in a modern office setting, focused on coding and tasks.

Configuring and optimizing Envoy in a GoLang application can be a daunting task, but with the right techniques, you can unlock its full potential.

A well-configured Envoy can handle high traffic and complex routing scenarios with ease, making it an ideal choice for microservices-based architectures.

To start, it's essential to understand the different Envoy configuration options available, such as static and dynamic configurations.

Static configurations are ideal for simple use cases, while dynamic configurations provide more flexibility and scalability.

Envoy's built-in support for service discovery protocols like DNS and etcd makes it easy to manage and scale your services.

Creating a Go Plugin

To develop a Go plugin for Envoy, you must implement the StreamFilter API.

The Go plugin API is not yet stable, so it's recommended to use the same version of the Go plugin SDK and Envoy to avoid any compatibility issues.

If you're using a release version of Envoy, such as 1.26.x, you should use the corresponding version of the Go plugin SDK, which is github.com/envoyproxy/envoyv1.26.x in your go.mod file.

A fresh viewpoint: Golang Version Manager

Credit: youtube.com, Episode 07: Developing Envoy Wasm Extensions

For non-release versions of Envoy, like the latest main branch, you can use go get -u github.com/envoyproxy/envoy@SHA to get the same version of the Go plugin SDK, where the SHA is the latest commit SHA.

To build a Go plugin dynamic library, ensure that you're using a Go version consistent with Envoy's version of glibc.

One way to achieve this is by using the Go binary provided by Envoy's bazel setup, which ensures a compatible Go version.

Related reading: Gcloud Api Using Golang

Plugin Configuration

Plugin Configuration is a powerful feature of Envoy's Go filter. You can configure a filter with the type URL type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config.

A prebuilt Golang HTTP filter like my_plugin.so can be configured within an HttpConnectionManager's http_filters. This allows you to have multiple plugins, such as one for authentication and another for connection limiting.

Go plugins can also specify and use their own configuration. For example, a plugin might be configured in Envoy with its own configuration data. You can learn more about building and running a plugin in the step by step Envoy Go Sandbox.

Go plugins can even be configured on a per-route basis, giving you fine-grained control over plugin settings.

Readers also liked: Go vs Golang

Extensible Plugin Configuration

Credit: youtube.com, Designing for the Unknown - Writing Extensible Code - Friedrich Weinmann - PSConfEU 2024

Envoy's Go plugins can specify and use their own configuration, allowing for a high degree of flexibility and customization.

This is made possible through the use of the type URL type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config, which enables plugins to define their own configuration.

A simple example of how this can be done is shown in Example 2, where a plugin's configuration data can be accessed through the StreamFilter API.

This feature is particularly useful when building and running plugins for the Envoy Go filter, as it allows developers to tailor their plugins to specific use cases and requirements.

For more information on how to build and run a plugin for the Envoy Go filter, you can refer to the step-by-step guide in the Envoy Go Sandbox.

Recommended read: Golang Go

Xds Api Versioning

Xds Api Versioning is a well-defined scheme that Envoy's xDS APIs follow. This means you can expect a structured approach to versioning.

The xDS APIs have a clear versioning scheme, which is a good thing because it helps with understanding and working with the APIs. This structure is essential for developers who need to navigate and utilize the APIs effectively.

Credit: youtube.com, xDS transport and versioning evolution - Harvey Tuch, Mark D. Roth

Envoy's xDS APIs have a versioning scheme, and understanding it is crucial for plugin configuration. The versioning scheme ensures that updates and changes are managed in a predictable and organized way.

By following a well-defined versioning scheme, developers can work more efficiently with Envoy's xDS APIs and avoid potential issues that might arise from unclear or inconsistent versioning. This is especially important for plugin configuration, where versioning can impact the overall functionality and performance of the system.

Envoy API and Protobuf

To extend your Envoy server, you'll need to register it with the envoyproxy go-control-plane protobuf server. This involves defining a check function within a struct, which will be used to verify the request.

If your check function returns nil instead of &auth_pb.CheckResponse, Envoy will suspend the request with a 403 error code. This is also the case if the service is offline or unreachable.

The envoy-sync.yaml workflow is used to trigger synchronization.

Related reading: Golang Check Type

Caching and Optimization

Credit: youtube.com, CacheFilter: Flexible HTTP Caching in Envoy - Josiah Kiehl, Todd Greer

Envoy clients are ephemeral, which means they can come and go arbitrarily, and the server relies on a configuration cache to minimize the client load on the server.

The Simple cache is a snapshot-based cache that maintains a consistent view of the configuration for each group of proxies. It supports running as an ADS server or as regular dis-aggregated xDS servers.

In ADS mode, the Simple cache can hold responses until the complete set of referenced resources is requested, enabling an atomic update of xDS collections.

The Linear cache is an eventually consistent cache for a single type URL collection, maintaining a single linear version history and a version vector for the resources in the cache.

For each request, the Linear cache compares the request version against latest versions for the requested resources, and responds with any updated resources. This cache assumes the resources are entirely opaque.

The Mux cache is a simple cache combinator that allows mixing multiple caches for different type URLs, such as using a Simple cache for LDS/RDS/CDS and a Linear cache for EDS.

Credit: youtube.com, How One Tiny Change Made My Golang App 10x Faster! 🚀

Here's a quick summary of the available caches:

These caches help optimize the performance of Envoy clients by reducing the load on the server and improving response times.

Language Support

Language Support is a key feature of GoLang Envoy. It supports multiple languages, including English, Spanish, French, German, Chinese, and Japanese.

You can easily add or remove languages from the Envoy configuration. This flexibility is achieved through the use of language-specific configuration files.

The Envoy configuration file uses a simple key-value pair format, making it easy to read and modify. This simplicity is one of the reasons Envoy is a popular choice for building microservices.

For example, you can add a new language by creating a new configuration file and specifying the language code and translation data. This is a straightforward process that requires minimal code changes.

The language support feature is particularly useful for building multilingual applications. By supporting multiple languages, you can reach a broader audience and increase user engagement.

Credit: youtube.com, Building idiomatic Envoy SDKs for Rust and Go - Yaroslav Skopets, Takeshi Yoneda

In addition to language support, Envoy also provides a robust API for managing configuration data. This API allows you to easily update and manage your Envoy configuration.

You can use the Envoy API to add or remove languages from your configuration, as well as update translation data. This flexibility makes it easy to adapt to changing user needs and preferences.

Related reading: Golang Mod Update

Call Flow and Filters

In Envoy, the call flow starts with parsing the headers of an incoming request, which triggers the decodeHeaders() function of an HTTP filter. This function returns StopIteration, skipping other header filters in the chain, but the request body still moves forward to the data phase.

The decodeData() function is called for each body chunk, and it can return either StopIterationAndBuffer or StopIterationAndNoBuffer. If it returns StopIterationAndBuffer, the filter manager buffers the body chunk for later use.

A golang filter consists of two parts: the C++ filter and the golang filter instance. The decodeHeaders() function can create a goroutine to perform an asynchronous job and return StopIteration, causing the C++ filter to buffer the request body chunks for later use.

Broaden your view: Golang Func Type

Credit: youtube.com, A Practical Guide to Understanding and Configuring Envoy Filters - Peter Jausovec, Solo.io

There are 2 memory copies involved in this process:

  • Copying the envoy Buffer to a golang string

This copy is not trivial because the Buffer is not a flattened C string. The latest version of the golang filter uses unsafe.StringData() to get the golang string data pointer and length, which can be copied directly to the C++ side.

The golang filter buffers the entire request body by itself, instead of the filter manager. This is different from the C++ filter, which buffers the request body chunks for later use.

In a golang grpc filter, the Check method is called during a request and adds a custom header to all other requests. It also rejects requests with the path '/private'.

The golang grpc filter works by forwarding incoming requests to a golang grpc cluster. The Check method is called during a request and adds a custom header to all other requests, rejecting those with the path '/private'.

Here's a table showing the request flow through the golang grpc filter:

Benchmarking and Setup

Credit: youtube.com, Zero Allocations And Benchmarking In Golang

For benchmarking, the author uses k6 as the benchmark tool, specifically focusing on the latency metric, which is http_req_duration. This is a crucial aspect to consider when evaluating performance.

The benchmark is designed to test the time it takes to transfer data, with the biggest time difference between lua-resty-ffi and the golang filter being approximately 20% when transferring a 10MB body. This demonstrates the impact of memory copies on performance.

Benchmark

In this benchmark, I used k6 as the benchmark tool to compare the performance of lua-resty-ffi with golang filter.

The test focused solely on latency, specifically the http_req_duration metric. This is because the benchmark aimed to highlight the differences in performance between the two approaches.

Lua-resty-ffi took more time than the golang filter, with the biggest time difference being approximately 20% when transferring a 10MB body. This time difference is proportional to the size of the subject being transferred.

However, it's worth noting that in most use cases, the entire body doesn't need to be transferred to and from the lua-resty-ffi runtime, as data is often arbitrary and on demand. This demo represents an extreme situation and is for reference only.

Set Up Your Setup

IT professional working on a computer in a modern office setting, focused on coding and tasks.
Credit: pexels.com, IT professional working on a computer in a modern office setting, focused on coding and tasks.

Setting up your system is a crucial step in the benchmarking process. It's essential to have a solid foundation to build upon.

Start by setting up your Envoy, which involves adding a filter to your envoy config. This filter is specifically the envoy.filters.http.ext_authz.

Point the filter to the Go service cluster for it to function properly.

Authentication and Authorization

Authentication and Authorization is a crucial aspect of any microservices architecture, and Golang Envoy is no exception.

The example repo demonstrates Envoy External Authorization with a Golang GRPC service, which is a great starting point for understanding how authentication and authorization work in this context.

The envoy configuration file is activated for all incoming routes at port 8080, which is a common entry point for many services.

This configuration also includes an external auth filter, which is a key component of authentication and authorization in Envoy.

Two clusters are configured in the envoy, which is a typical setup for a microservices architecture.

In this setup, the external auth filter is used to authenticate incoming requests and authorize access to specific routes.

Request and Response Handling

Credit: youtube.com, Handling web request in golang

The Envoy filter chain relies on HttpFilter instances, which are created once per module instance via the NewHttpFilter function. These instances represent a single HTTP filter in the chain.

Request and response handling in Envoy involves interacting with opaque objects that represent the underlying HTTP request and response buffers. The RequestBodyBuffer and ResponseBodyBuffer objects provide a zero-copy view of the HTTP request and response body buffers, respectively.

These buffers consist of multiple slices of data, not a single contiguous buffer, making them efficient for handling large HTTP requests and responses.

Expand your knowledge: Simple Http Server Golang Github

Request Body Buffer

The Request Body Buffer is a crucial component of Envoy's request handling mechanism.

It's an opaque object that represents the underlying Envoy Http request body buffer.

The buffer consists of multiple slices of data, not a single contiguous buffer.

This provides a zero-copy view of the HTTP request body buffer.

This means you can interact with the buffer without having to create a copy of the data, which can be memory-intensive and slow.

The Request Body Buffer implements the io.ReaderAt interface, making it easy to use in your module code.

Type RequestHeaders

Credit: youtube.com, Learn in 5 Minutes: HTTP Headers (General/Request/Response/Entity)

RequestHeaders is an opaque object that represents the underlying Envoy Http request headers map.

This object is used to interact with it from the module code, allowing for efficient and direct access to the request headers.

RequestHeaders provides a convenient way to access and manipulate the request headers without having to worry about the underlying implementation details.

In Envoy, the RequestHeaders object is an essential part of the request handling process, enabling developers to easily access and modify the request headers as needed.

Type and Source Information

GoLang Envoy is an open-source proxy and communication bus that provides a high-performance, scalable, and extensible way to manage network communication. It's written in Go.

Envoy is developed by Lyft and is now maintained by the Cloud Native Computing Foundation (CNCF). Envoy's source code is hosted on GitHub.

The Envoy proxy is a critical component of many modern microservices architectures, providing features like traffic management, security, and observability.

You might like: Golang Source

Type Instance

Empty conference room featuring rows of chairs, screen, and whiteboard.
Credit: pexels.com, Empty conference room featuring rows of chairs, screen, and whiteboard.

Type Instance is a crucial concept in Envoy, and understanding it can help you navigate its inner workings.

EnvoyFilterInstance is an opaque object that represents the underlying Envoy Http filter instance.

Each EnvoyFilterInstance is created once per module instance via the Envoy filter chain.

HttpFilterInstance is an interface that represents each Http request.

It is created for each new Http request and is destroyed when the request is completed.

HttpFilterInstance is used to interact with the Envoy Http filter instance from the module code.

This interface is essential for creating instances that correspond to each Http request.

Additional reading: Check Type of Interface Golang

Source Files

In most programming languages, source files are plain text files that contain the code written by a programmer.

These files typically have a specific extension, such as .py for Python or .java for Java.

The code in a source file is usually organized into functions, classes, or modules, which can be reused throughout the program.

A single source file can contain multiple functions or classes, but it's common to keep related code in separate files for better organization.

Source files are often stored in a project's root directory or in a specific directory structure, making it easy to locate and manage them.

Francis McKenzie

Writer

Francis McKenzie is a skilled writer with a passion for crafting informative and engaging content. With a focus on technology and software development, Francis has established herself as a knowledgeable and authoritative voice in the field of Next.js development.

Love What You Read? Stay Updated!

Join our community for insights, tips, and more.