此场景旨在支持您从 NGINX 迁移到 Envoy 代理。它将帮助您将以前对 NGINX 的任何经验和理解应用到 Envoy。
你将学到如何:
Envoy
代理服务器配置和设置。Envoy
代理以将流量代理到外部服务。在场景结束时,您将了解核心 Envoy 代理功能,以及如何将现有 NGINX 脚本迁移到平台。
这个场景使用了一个基于NGINX Wiki的完整示例精心制作的nginx.conf
。您可以通过打开在编辑器中查看配置nginx.conf
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 配置通常具有三个关键要素:
Gzip
功能。这是在所有实例中全局定义的。one.example.com
主机的请求。并非所有配置都适用于 Envoy 代理,您不需要配置某些方面。Envoy Proxy 有四种关键类型,支持 NGINX 提供的核心基础设施。核心是:
Listeners
: 它们定义了 Envoy 代理如何接受传入的请求。目前,Envoy Proxy
仅支持基于 TCP的侦听器。建立连接后,它会传递给一组过滤器进行处理。Filters
:它们是可以处理入站和出站数据的管道架构的一部分。此功能启用过滤器,例如 Gzip,它在将数据发送到客户端之前压缩数据。Routers
:这些将流量转发到所需的目的地,定义为集群。Clusters
:它们定义流量的目标端点和配置设置。我们将使用这四个组件来创建 Envoy 代理配置以匹配定义的 NGINX 配置。Envoy 一直专注于 API 和动态配置。在这种情况下,配置将使用 NGINX 定义的静态硬编码资源。
第一部分nginx.conf
定义了一些应该配置的 NGINX 内部结构。
下面的配置侧重于定义工作进程和连接的数量。这表明 NGINX 将如何扩展以处理需求。
worker_processes 2;
events {
worker_connections 2000;
}
Envoy Proxy 以不同的方式管理工作进程和连接。
Envoy 为系统中的每个硬件线程生成一个工作线程。每个工作线程运行一个非阻塞事件循环,负责
连接的所有进一步处理都完全在工作线程内处理,包括任何转发行为。
Envoy 中的所有连接池(pool)都是每个工作线程。尽管 HTTP/2 连接池一次只与每个上游主机建立一个连接,但如果有四个工作人员,则每个上游主机在稳定状态下将有四个 HTTP/2 连接。通过将所有内容保留在单个工作线程中,几乎所有代码都可以在没有锁的情况下编写,就像是单线程一样。拥有过多的工作人员会浪费内存,创建更多的空闲连接,并导致连接池命中率较低。
有关更多信息,请访问Envoy 代理博客。
NGINX 配置的下一个块定义了 HTTP 设置,例如:
mime
类型您可以通过 Envoy Proxy 中的过滤器配置这些方面
在 HTTP 配置块中,NGINX 配置指定侦听端口 8080 并响应域one.example.com
和www.one.example.com
的传入请求。
server {
listen 80;
server_name one.example.com www.one.example.com;
在 Envoy 中,这由 Listeners
管理。
从 Envoy Proxy 开始,最重要的方面是定义列表器 (listers)。您需要创建一个配置文件来描述您希望如何运行 Envoy 实例。
下面的代码片段将创建一个新的侦听器并将其绑定到端口 8080。该配置向 Envoy 代理指示它应该为传入请求绑定到哪些端口。
Envoy Proxy 使用 YAML 表示法进行配置。如果您不熟悉此表示法,可以查看此链接。
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 8080 }
无需定义server_name
,因为 Envoy 代理过滤器将处理此问题。
当请求进入 NGINX 时,位置块定义了如何处理以及将流量转发到哪里。在以下代码段中,该站点的所有流量都被代理到名为 的上游集群targetCluster
。上游集群定义了应该处理请求的节点。我们将在下一步中讨论这个问题。
location / {
proxy_pass http://targetCluster/;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
在 Envoy 中,这由过滤器管理。
对于静态配置,过滤器(filters)定义了如何处理传入请求。在这种情况下,我们正在设置与上一步中的server_names
匹配的过滤器。当收到与定义的域和路由匹配的传入请求时,流量将转发到集群。这相当于上游 NGINX 配置。
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
名称envoy.http_connection_manager
是 Envoy Proxy 中的内置过滤器。其他过滤器包括Redis
、Mongo
、TCP
。您可以在文档中找到完整列表。
有关其他负载平衡策略的更多信息,请访问Envoy 文档。
在 NGINX 中,上游配置定义了一组将处理流量的目标服务器。在这种情况下,已经分配了两个集群。
upstream targetCluster {
172.18.0.3:80;
172.18.0.4:80;
}
在 Envoy 中,这是由集群管理的。
上游的等价物被定义为集群。在这种情况下,已经定义了将为流量提供服务的主机。访问主机的方式(例如超时)被定义为集群配置。这允许对超时和负载平衡等方面进行更精细的控制。
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 }}
]
当使用STRICT_DNS
服务发现时,Envoy 将持续异步地解析指定的 DNS 目标。DNS 结果中返回的每个 IP 地址都将被视为上游集群中的显式主机。这意味着如果查询返回两个 IP 地址,Envoy 将假设集群有两个主机,并且都应该进行负载均衡。如果主机从结果中删除,Envoy 会假设它不再存在,并将从任何现有连接池中排出流量。
有关更多信息,请参阅Envoy 代理文档。
最后的配置是日志记录。Envoy Proxy 没有将错误日志通过管道传输到磁盘,而是遵循云原生方法。所有应用程序日志都输出到stdout
和stderr
。
当用户提出请求时,访问日志是可选的,默认情况下是禁用的。要启用 HTTP 请求的访问日志,包括access_log
为HTTP 连接管理器的配置。路径可以是设备(例如stdout)或磁盘上的文件,具体取决于您的要求。
以下配置会将所有访问日志通过管道传输到stdout. 将代码片段复制到连接管理器的配置部分:
access_log:
- name: envoy.file_access_log
config:
path: "/dev/stdout"
默认情况下,Envoy 有一个格式字符串,其中包含 HTTP 请求的详细信息:
- 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:
此格式字符串的结果是:
[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"
可以通过设置格式字段来自定义输出的内容。例如:
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%"\n"
通过设置json_format字段,日志行也可以输出为 JSON 。例如:
access_log:
- name: envoy.file_access_log
config:
path: "/dev/stdout"
json_format: {"protocol": "%PROTOCOL%", "duration": "%DURATION%", "request_method": "%REQ(:METHOD)%"}
有关 Envoy 日志记录方法的更多信息,请访问https://www.envoyproxy.io/docs/envoy/latest/configuration/access_log#config-access-log-format-dictionaries
日志记录并不是使用 Envoy Proxy 获得生产可见性的唯一方法。它内置了高级跟踪和度量功能。您可以在跟踪文档中或通过交互式跟踪方案找到更多信息。
您现在已将配置从 NGINX 转换为 Envoy 代理。最后一步是启动 Envoy Proxy 实例来测试它。
在 NGINX 配置的顶部,该行user www www;
指示以低权限用户身份运行 NGINX 以提高安全性。
Envoy Proxy 采用云原生方法来管理流程所有者。当我们通过容器启动 Envoy 代理时,我们可以指定一个低权限用户。
下面的命令将通过主机上的 Docker 容器启动 Envoy 代理。该命令公开 Envoy 以侦听端口 80 上的传入请求。但是,正如侦听器配置中指定的那样,Envoy 代理正在侦听端口 8080 上的传入流量。这允许进程以低特权用户身份运行。
$ docker run --name proxy1 -p 80:8080 --user 1000:1000 -v /root/envoy.yaml:/etc/envoy/envoy.yaml envoyproxy/envoy
[2021-11-15 08:27:31.097][000011][info][main] [source/server/server.cc:202] initializing epoch 0 (hot restart version=10.200.16384.127.options=capacity=16384, num_slots=8209 hash=228984379728933363 size=2654312)
[2021-11-15 08:27:31.116][000011][info][main] [source/server/server.cc:204] statically linked extensions:
[2021-11-15 08:27:31.116][000011][info][main] [source/server/server.cc:206] access_loggers: envoy.file_access_log,envoy.http_grpc_access_log
[2021-11-15 08:27:31.116][000011][info][main] [source/server/server.cc:209] filters.http: envoy.buffer,envoy.cors,envoy.ext_authz,envoy.fault,envoy.filters.http.header_to_metadata,envoy.filters.http.jwt_authn,envoy.filters.http.rbac,envoy.grpc_http1_bridge,envoy.grpc_json_transcoder,envoy.grpc_web,envoy.gzip,envoy.health_check,envoy.http_dynamo_filter,envoy.ip_tagging,envoy.lua,envoy.rate_limit,envoy.router,envoy.squash
[2021-11-15 08:27:31.116][000011][info][main] [source/server/server.cc:212] filters.listener: envoy.listener.original_dst,envoy.listener.proxy_protocol,envoy.listener.tls_inspector
[2021-11-15 08:27:31.116][000011][info][main] [source/server/server.cc:215] filters.network: envoy.client_ssl_auth,envoy.echo,envoy.ext_authz,envoy.filters.network.rbac,envoy.filters.network.sni_cluster,envoy.filters.network.thrift_proxy,envoy.http_connection_manager,envoy.mongo_proxy,envoy.ratelimit,envoy.redis_proxy,envoy.tcp_proxy
[2021-11-15 08:27:31.116][000011][info][main] [source/server/server.cc:217] stat_sinks: envoy.dog_statsd,envoy.metrics_service,envoy.stat_sinks.hystrix,envoy.statsd
[2021-11-15 08:27:31.116][000011][info][main] [source/server/server.cc:219] tracers: envoy.dynamic.ot,envoy.lightstep,envoy.zipkin
[2021-11-15 08:27:31.117][000011][info][main] [source/server/server.cc:222] transport_sockets.downstream: envoy.transport_sockets.alts,envoy.transport_sockets.capture,raw_buffer,tls
[2021-11-15 08:27:31.117][000011][info][main] [source/server/server.cc:225] transport_sockets.upstream: envoy.transport_sockets.alts,envoy.transport_sockets.capture,raw_buffer,tls
[2021-11-15 08:27:31.121][000011][critical][main] [source/server/server.cc:80] error initializing configuration '/etc/envoy/envoy.yaml': Unable to parse JSON as proto (INVALID_ARGUMENT:filter_chains: Cannot find field.): {"filter_chains":[{"filters":[{"config":{"http_filters":[{"name":"envoy.router"}],"route_config":{"virtual_hosts":[{"routes":[{"route":{"cluster":"targetCluster"},"match":{"prefix":"/"}}],"domains":["one.example.com","www.one.example.com"],"name":"backend"}],"name":"local_route"},"stat_prefix":"ingress_http","codec_type":"auto"},"name":"envoy.http_connection_manager"}]}]}
启动代理后,现在可以进行和处理测试。以下 cURL 命令使用代理配置中定义的主机标头发出请求。
$ curl -H "Host: one.example.com" localhost -i
HTTP/1.1 503 Service Unavailable
content-length: 57
content-type: text/plain
date: Mon, 15 Nov 2021 08:34:48 GMT
server: envoy
HTTP 请求将导致503错误。这是因为上游连接未运行且不可用。因此,Envoy 代理没有可用于请求的目标目的地。以下命令将启动一系列与为 Envoy 定义的配置相匹配的 HTTP 服务。
$ docker run -d katacoda/docker-http-server; docker run -d katacoda/docker-http-server;
Unable to find image 'katacoda/docker-http-server:latest' locally
latest: Pulling from katacoda/docker-http-server
f139eb4721ae: Pull complete
Digest: sha256:76dc8a47fd019f80f2a3163aba789faf55b41b2fb06397653610c754cb12d3ee
Status: Downloaded newer image for katacoda/docker-http-server:latest
8a94122a8b00f3ba11112f8e454d0d82eea73269ca1a32766c8ef13452032c85
f7b79df5ff888e921fd6bbf1353a3a62cdd559e77d335a867620d783a29af94b
有了可用的服务,Envoy 就可以成功地将流量代理到目标目的地
$ curl -H "Host: one.example.com" localhost -i
HTTP/1.1 200 OK
date: Mon, 15 Nov 2021 08:35:17 GMT
content-length: 58
content-type: text/html; charset=utf-8
x-envoy-upstream-service-time: 0
server: envoy
<h1>This request was processed by host: 0cf13596f52e</h1>
您应该会看到一个响应,指示哪个 docker 容器处理了请求。在 Envoy Proxy 日志中,您还应该看到输出的访问行。
在有效请求的响应标头中,您将看到额外的 HTTP 标头。标头显示上游主机处理请求所花费的时间。它以毫秒表示。如果客户端想要确定与网络延迟相比的服务时间,这将非常有用。
x-envoy-upstream-service-time: 0
server: envoy