Hello, Habr! I present to you a translation of the post: .
Envoy is a high-performance distributed proxy server (written in C++) designed for individual services and applications; it also serves as a messaging bus and a "universal data plane" developed for large microservices architectures known as a service mesh. In its creation, solutions to problems encountered while developing servers like NGINX, HAProxy, hardware load balancers, and cloud load balancers were considered. Envoy works alongside every application and abstracts the network, providing common functions regardless of the platform. When all service traffic in the infrastructure flows through the Envoy mesh, it becomes easy to visualize problem areas with consistent observability, tune overall performance, and add core features in a specific location.
Capabilities
- Out-of-process architecture: Envoy is a standalone, high-performance server that occupies a small amount of memory. It works seamlessly with any application language or framework.
- Support for http/2 and grpc: Envoy has first-class support for http/2 and grpc for both incoming and outgoing connections. It is a transparent proxy from http/1.1 to http/2.
- Advanced load balancing: Envoy supports advanced load balancing features, including automatic retries, circuit breaking, global rate limiting, request shadowing, local zone load balancing, etc.
- Configuration management API: Envoy provides a robust API for dynamically managing its configuration.
- Observability: Deep observability of L7 traffic, built-in support for distributed tracing, and observability for MongoDB, DynamoDB, and many other applications.
Step 1 — Example of an NGINX config
This scenario uses a specially created file nginx.conf, based on a complete example from . You can view the configuration in the editor by opening nginx.conf
The original nginx config
user www www;
pid /var/run/nginx.pid;
worker_processes 2;
events {
worker_connections 2000;
}
http {
gzip on;
gzip_min_length 1100;
gzip_buffers 4 8k;
gzip_types text/plain;
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $bytes_sent '
'"$http_referer" "$http_user_agent" '
'"$gzip_ratio"';
log_format download '$remote_addr - $remote_user [$time_local] '
'"$request" $status $bytes_sent '
'"$http_referer" "$http_user_agent" '
'"$http_range" "$sent_http_content_range"';
upstream targetCluster {
172.18.0.3:80;
172.18.0.4:80;
}
server {
listen 8080;
server_name one.example.com www.one.example.com;
access_log /var/log/nginx.access_log main;
error_log /var/log/nginx.error_log info;
location / {
proxy_pass http://targetCluster/;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}NGINX configurations typically consist of three key elements:
- The NGINX server configuration, log structure, and Gzip functionality. This is defined globally in all cases.
- Configuring NGINX to accept requests on the host one.example.com on port 8080.
- Configuring the target location, how to handle traffic for different parts of the URL.
Not all configurations will be applied to Envoy Proxy, and you do not need to configure some parameters. Envoy Proxy has four key types, which support the basic infrastructure provided by NGINX. The core includes:
- Listeners: They define how Envoy Proxy accepts incoming requests. Currently, Envoy Proxy supports only TCP-based listeners. Once a connection is established, it is passed to a set of filters for processing.
- Filters: They are part of a pipeline architecture that can process incoming and outgoing data. This functionality includes filters like Gzip, which compresses data before sending it to the client.
- Routers: They redirect traffic to the required destination defined as a cluster.
- Clusters: They define the endpoint for the traffic and configuration parameters.
We will use these four components to create the Envoy Proxy configuration to match a specific NGINX configuration. The goal of Envoy is to work with APIs and dynamic configuration. In this case, the basic configuration will utilize static, hard-coded parameters from NGINX.
Step 2 — NGINX Configuration
The first part nginx.conf defines some internal components of NGINX that need to be configured.
Worker Connections
The configuration below defines the number of worker processes and connections. This indicates how NGINX will scale to meet demand.
worker_processes 2;
events {
worker_connections 2000;
}Envoy Proxy manages worker processes and connections differently.
Envoy creates a worker thread for each hardware thread in the system. Each worker thread runs a non-blocking event loop that is responsible for
- Listening to each listener
- Accepting new connections
- Creating a set of filters for the connection
- Handling all input/output operations for the duration of the connection.
All further processing of the connection is fully handled in the worker thread, including any redirect behavior.
For each worker thread in Envoy, there is a connection in the pool. Thus, HTTP/2 connection pools establish only one connection for each external host at a time; with four worker threads, there will be four HTTP/2 connections for each external host at steady state. Keeping everything in one worker thread allows nearly all code to be written without locks, as if it were single-threaded. If more worker threads are allocated than necessary, it can lead to inefficient memory usage, creating many idle connections, and reducing the number of connections returned to the pool.
For more information, visit .
HTTP Configuration
The following NGINX configuration block defines HTTP settings such as:
- Which mime types are supported
- Default timeouts
- Gzip configuration
You can configure these aspects using filters in Envoy Proxy, which we will discuss later.
Step 3 — Server Configuration
In the HTTP configuration block, NGINX specifies to listen on port 8080 and respond to incoming requests for the domains one.example.com and www.one.example.com.
server {
listen 8080;
server_name one.example.com www.one.example.com;Inside Envoy, this is managed by Listeners.
Envoy Listeners
The most important aspect of getting started with Envoy Proxy is defining listeners. You need to create a configuration file that describes how you want to run the Envoy instance.
The snippet below will create a new listener and bind it to port 8080. The configuration indicates to Envoy Proxy which ports it should be bound to for incoming requests.
Envoy Proxy uses YAML notation for its configuration. For an introduction to this notation, check here .
Copy to Editorstatic_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 8080 }There is no need to specify server_name, as Envoy Proxy filters will handle it.
Step 4 — Location Configuration
When a request comes into NGINX, the location block determines how to handle and where to direct the traffic. In the following snippet, all traffic to the site is routed to an upstream cluster named targetCluster. The upstream cluster defines the nodes that should handle the request. We will discuss this in the next step.
location / {
proxy_pass http://targetCluster/;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}In Envoy, this is handled by Filters.
Envoy Filters
For static configuration, filters determine how to handle incoming requests. In this case, we set up filters that correspond to server_names from the previous step. When incoming requests match certain domains and routes, the traffic is directed to the cluster. This is equivalent to the upstream configuration in NGINX.
Copy to Editor filter_chains:
- filters:
- name: envoy.http_connection_manager
config:
codec_type: auto
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: backend
domains:
- "one.example.com"
- "www.one.example.com"
routes:
- match:
prefix: "\/"
route:
cluster: targetCluster
http_filters:
- name: envoy.routerName envoy.http_connection_manager is a built-in filter in Envoy Proxy. Other filters include Redis, Mongo, TCP. You can find the complete list in .
For more information on other load balancing policies, visit .
Step 5 — Proxy and Upstream Configuration
In NGINX, the upstream configuration defines a set of target servers that will handle the traffic. In this case, two clusters have been designated.
upstream targetCluster {
172.18.0.3:80;
172.18.0.4:80;
}In Envoy, this is managed by clusters.
Envoy Clusters
The upstream equivalent is defined as clusters. In this case, hosts have been identified to handle the traffic. The method of accessing the hosts, such as timeout, is defined as the cluster configuration. This allows for finer control over aspects such as timeout and load balancing.
Copy to Editor clusters:
- name: targetCluster
connect_timeout: 0.25s
type: STRICT_DNS
dns_lookup_family: V4_ONLY
lb_policy: ROUND_ROBIN
hosts: [
{ socket_address: { address: 172.18.0.3, port_value: 80 }},
{ socket_address: { address: 172.18.0.4, port_value: 80 }}
]When using service discovery STRICT_DNS Envoy will continuously and asynchronously resolve the specified DNS targets. Each returned IP address from the DNS will be treated as an explicit host in the upstream cluster. This means that if the query returns two IP addresses, Envoy will assume there are two hosts in the cluster, and both should be load balanced. If a host is removed from the results, Envoy assumes it no longer exists and will select traffic from any existing connection pools.
For more information see .
Step 6 — Access log and errors
Final configuration — logging. Instead of sending error logs to disk, Envoy Proxy uses a cloud approach. All application logs are output to stdout and stderr.
When users make a request, access logs are optional and are disabled by default. To enable access logs for HTTP requests, turn on the configuration access_log for the HTTP connection manager. The path can either be a device like stdout, or a file on disk, depending on your requirements.
The following configuration will redirect all access logs to stdout (translator’s note — stdout is necessary for using Envoy inside Docker. If using outside of Docker, replace /dev/stdout with a path to a regular log file). Copy the snippet into the connection manager configuration section:
Copy to Clipboardaccess_log:
- name: envoy.file_access_log
config:
path: "/dev/stdout"The results should look like this:
- name: envoy.http_connection_manager
config:
codec_type: auto
stat_prefix: ingress_http
access_log:
- name: envoy.file_access_log
config:
path: "/dev/stdout"
route_config:By default, Envoy has a format string that includes details of the HTTP request:
[%START_TIME%] "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%" %RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% "%REQ(X-FORWARDED-FOR)%" "%REQ(USER-AGENT)%" "%REQ(X-REQUEST-ID)%" "%REQ(:AUTHORITY)%" "%UPSTREAM_HOST%"The result of this format string:
[2018-11-23T04:51:00.281Z] "GET / HTTP/1.1" 200 - 0 58 4 1 "-" "curl/7.47.0" "f21ebd42-6770-4aa5-88d4-e56118165a7d" "one.example.com" "172.18.0.4:80"The output content can be configured by setting the format field. For example:
access_log:
- name: envoy.file_access_log
config:
path: "/dev/stdout"
format: "[%START_TIME%] "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%" %RESPONSE_CODE% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% "%REQ(X-REQUEST-ID)%" "%REQ(:AUTHORITY)%" "%UPSTREAM_HOST%"
"The log line can also be output in JSON format by setting the json_format. For example:
access_log:
- name: envoy.file_access_log
config:
path: "/dev/stdout"
json_format: {"protocol": "%PROTOCOL%", "duration": "%DURATION%", "request_method": "%REQ(:METHOD)%"}For more information on the logging methodology of the envoy, visit
Logging is not the only way to gain insights into the workings of Envoy Proxy. It has built-in tracing and metrics capabilities. You can learn more in the or through .
Step 7 — Starting
You have now translated the configuration from NGINX to Envoy Proxy. The final step is to run an instance of Envoy Proxy for testing.
Running as a user
At the top of the NGINX configuration, the line user www www; indicates that NGINX runs as a low-privilege user for enhanced security.
Envoy Proxy adopts a cloud-native approach to managing who owns the process. When we run Envoy Proxy through a container, we can specify a low-privilege user.
Starting Envoy Proxy
The command below will run Envoy Proxy through a Docker container on the host. This command allows Envoy to listen for incoming requests on port 80. However, as specified in the listener configuration, Envoy Proxy listens for incoming traffic on port 8080. This allows the process to run as a low-privilege user.
docker run --name proxy1 -p 80:8080 --user 1000:1000 -v /root/envoy.yaml:/etc/envoy/envoy.yaml envoyproxy/envoyTesting
With the proxy running, tests can now be performed and processed. The following cURL command sends a request with the host header set in the proxy configuration.
curl -H "Host: one.example.com" localhost -iThe HTTP request will result in an error 503This is because upstream connections are not working and are unavailable. Thus, Envoy Proxy has no available targets for the request. The following command will launch a series of HTTP services that match the configuration defined for Envoy.
docker run -d katacoda/docker-http-server; docker run -d katacoda/docker-http-server;With available services, Envoy can successfully proxy traffic to the destination.
curl -H "Host: one.example.com" localhost -iYou should see a response indicating which Docker container handled the request. In the Envoy Proxy logs, you should also see the access log output.
Additional HTTP Response Headers
In the headers of a valid request response, you will see additional HTTP headers. The header shows the time the upstream host spent processing the request, expressed in milliseconds. This is useful if the client wants to determine the service time compared to network latency.
x-envoy-upstream-service-time: 0
server: envoyFinal Config
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 8080 }
filter_chains:
- filters:
- name: envoy.http_connection_manager
config:
codec_type: auto
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: backend
domains:
- "one.example.com"
- "www.one.example.com"
routes:
- match:
prefix: "\/"
route:
cluster: targetCluster
http_filters:
- name: envoy.router
clusters:
- name: targetCluster
connect_timeout: 0.25s
type: STRICT_DNS
dns_lookup_family: V4_ONLY
lb_policy: ROUND_ROBIN
hosts: [
{ socket_address: { address: 172.18.0.3, port_value: 80 }},
{ socket_address: { address: 172.18.0.4, port_value: 80 }}
]
admin:
access_log_path: \/tmp\/admin_access.log
address:
socket_address: { address: 0.0.0.0, port_value: 9090 }Additional information from the translator
You can find installation instructions for Envoy Proxy on the website
By default, the rpm lacks a systemd service config.
Add the systemd service config to /etc/systemd/system/envoy.service:
[Unit]
Description=Envoy Proxy
Documentation=https://www.envoyproxy.io/
After=network-online.target
Requires=envoy-auth-server.service
Wants=nginx.service
[Service]
User=root
Restart=on-failure
ExecStart=\/usr\/bin\/envoy --config-path \/etc\/envoy\/config.yaml
[Install]
WantedBy=multi-user.targetYou need to create the directory /etc/envoy/ and place the config.yaml file there.
There is a Telegram chat about envoy proxy:
Envoy Proxy does not support serving static content. So, please vote for this feature:
Only registered users can participate in the survey. , please.
Did this post encourage you to install and test envoy proxy?
yes
none
75 users voted. 18 users abstained.
Source: habr.com
