3.3.4 使用Envoy

Envoy是基于C++11实现的,性能优异,目前不提供单独的预先编译好的二进制文件,但提供了Docker镜像。使用Docker运行Envoy容器是开始使用Envoy的最佳方式。如果你希望在Docker容器外使用Envoy,则需要构建它。

下面的示例使用v2 Envoy API,但仅使用API的静态配置功能,这对于简单的需求非常有用。更复杂的需求是由动态配置来支持的。

首先,拉取Envoy的Docker镜像,执行如下命令:


docker pull envoyproxy/envoy:latest

另外,拉取本示例中使用到的另外两个Docker镜像httpbin与curl,如下所示:


docker pull citizenstig/httpbin
docker pull tutum/curl

我们先创建一个简单的httpbin服务。它实现了一个简单的服务,可以返回用于调用它的请求头、延迟HTTP请求,甚至能根据你调用的端点发出错误返回等。

一旦启动httpbin服务,我们将启动Envoy代理,并将其配置为代理httpbin服务的所有流量。然后启动一个客户端应用程序并调用代理来测试验证。

运行以下命令,在Docker中运行httpbin服务:


docker run -d --name httpbin citizenstig/httpbin
d1030455e1773d3409d1958c8649e1863f1e631d2a5277ccb26aab420d9163f8

通过curl命令查询httpbin服务的/headers端点,验证上述启动的新服务httpbin是否正确部署,如下所示:


docker run -it --rm --link httpbin tutum/curl \
curl -X GET http://httpbin:8000/headers
{
  "headers": {
    "Accept": "*/*",
    "Host": "httpbin:8000",
    "User-Agent": "curl/7.35.0"
  }
}

成功执行之后,应该可以看到类似于上面的响应返回结果。

现在让我们运行Envoy代理并传递--help给命令,以了解它的一些命令行参数和使用方式,如下所示:


docker run -it --rm envoyproxy/envoy:latest envoy --help

USAGE:

   envoy  [--enable-mutex-tracing] [--disable-hot-restart]
          [--max-obj-name-len <uint64_t>] [--max-stats <uint64_t>] [--mode
          <string>] [--parent-shutdown-time-s <uint32_t>] [--drain-time-s
          <uint32_t>] [--file-flush-interval-msec <uint32_t>]
          [--service-zone <string>] [--service-node <string>]
          [--service-cluster <string>] [--hot-restart-version]
          [--restart-epoch <uint32_t>] [--log-path <string>] [--log-format
          <string>] [--component-log-level <string>] [-l <string>]
          [--local-address-ip-version <string>] [--admin-address-path
          <string>] [--allow-unknown-fields] [--config-yaml <string>] [-c
          <string>] [--concurrency <uint32_t>] [--base-id <uint32_t>] [--]
          [--version] [-h]

其中,主要参数如下:

·service-zone用于指定Envoy代理部署的可用区域。

·service-node定义Envoy运行的本地服务节点名称。

·service-cluster定义Envoy运行的本地服务集群名称。

·log-level用于控制日志记录的详细程度。

其他参数介绍如下:

·restart-epoch表示热重启周期,第一次启动默认为0,每次热重启后它都应该被增加。

·drain-time-s表示热重启期间Envoy将耗尽连接的时间(秒),默认为600秒(10分钟)。通常耗尽时间应小于通过--parent-shutdown-time-s选项设置的父进程关闭时间。

·parent-shutdown-time-s表示Envoy在热重启时关闭父进程之前等待的时间(秒)。

·max-obj-name-len描述的是集群cluster、路由配置route_conf ig以及监听器listener中名称字段的最大长度,以字节为单位。此选项通常用于自动生成集群名称的场景,通常会超过60个字符的内部限制。默认为60。

通过执行如下命令,让我们试着运行Envoy:


docker run -it --rm envoyproxy/envoy:latest envoy
[2019-03-24 11:52:09.140][1][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)
[2019-03-24 11:52:09.140][1][info][main] [source/server/server.cc:204] statically 
linked extensions:
[2019-03-24 11:52:09.140][1][info][main] [source/server/server.cc:206]   access_loggers:
 envoy.file_access_log,envoy.http_grpc_access_log
[2019-03-24 11:52:09.140][1][info][main] [source/server/server.cc:209]   filters.http: 
envoy.buffer,envoy.cors,envoy.ext_authz,envoy.fault,envoy.filters.http.grpc_http1_
reverse_bridge,envoy.filters.http.header_to_metadata,envoy.filters.http.jwt_authn,
envoy.filters.http.rbac,envoy.filters.http.tap,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
[2019-03-24 11:52:09.140][1][info][main] [source/server/server.cc:212]   filters.
listener: envoy.listener.original_dst,envoy.listener.original_src,envoy.listener.proxy_
protocol,envoy.listener.tls_inspector
[2019-03-24 11:52:09.140][1][info][main] [source/server/server.cc:215]   filters.
network: envoy.client_ssl_auth,envoy.echo,envoy.ext_authz,envoy.filters.network.dubbo_
proxy,envoy.filters.network.mysql_proxy,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
[2019-03-24 11:52:09.140][1][info][main] [source/server/server.cc:217]   stat_sinks: envoy.dog_statsd,envoy.metrics_service,envoy.stat_sinks.hystrix,envoy.statsd
[2019-03-24 11:52:09.140][1][info][main] [source/server/server.cc:219]   tracers: 
envoy.dynamic.ot,envoy.lightstep,envoy.tracers.datadog,envoy.zipkin
[2019-03-24 11:52:09.140][1][info][main] [source/server/server.cc:222]   transport_
sockets.downstream: envoy.transport_sockets.alts,envoy.transport_sockets.tap,raw_buffer,tls
[2019-03-24 11:52:09.140][1][info][main] [source/server/server.cc:225]   transport_
sockets.upstream: envoy.transport_sockets.alts,envoy.transport_sockets.tap,raw_buffer,tls
[2019-03-24 11:52:09.140][1][critical][main] [source/server/server.cc:87] error 
initializing configuration '': At least one of --config-path and --config-yaml should 
be non-empty
[2019-03-24 11:52:09.140][1][info][main] [source/server/server.cc:516] exiting
At least one of --config-path and --config-yaml should be non-empty

可以看到,因为没有传入有效的配置文件,Envoy代理无法正常启动。解决这个问题的方法就是传入一个简单的配置文件来启动Envoy。基于我们之前看到的示例配置,将传入的配置文件按照如下结构定义:


admin:
  access_log_path: /tmp/admin_access.log
  address:
    socket_address: { address: 0.0.0.0, port_value: 15000 }

static_resources:
  listeners:
  - name: httpbin-demo
    address:
      socket_address: { address: 0.0.0.0, port_value: 15001 }
    filter_chains:
    - filters:
      - name: envoy.http_connection_manager
        config:
          stat_prefix: egress_http
          route_config:
            name: httpbin_local_route
            virtual_hosts:
            - name: httpbin_local_service
              domains: ["*"]
              routes:
              - match: { prefix: "/" }
                route:
                  auto_host_rewrite: true
                  cluster: httpbin_service
          http_filters:
          - name: envoy.router
  clusters:
    - name: httpbin_service
      connect_timeout: 5s
      type: LOGICAL_DNS
      # Comment out the following line to test on v6 networks
      dns_lookup_family: V4_ONLY
      lb_policy: ROUND_ROBIN
      hosts: [{ socket_address: { address: httpbin, port_value: 8000 }}]

如上配置所示,我们在端口15001上暴露了一个监听器,然后将所有流量路由到httpbin集群。通过执行如下命令,用这个配置文件启动Envoy:


docker run -d --name proxy --link httpbin \
-v /Users/wangxn/Documents/GitHub/istio-book/envoyproxy/simple.yaml:/etc/envoy/
simple.yaml \
envoyproxy/envoy:latest envoy -c /etc/envoy/simple.yaml
be91ca93925893ca3b51d90cc70995871aea2669d4a99bc652ce6ae3cff74a79

查看运行中的Proxy容器日志,可以看到类似的结果:


docker logs proxy
[2019-12-14 11:02:14.285][1][info][main] [source/server/server.cc:249] initializing epoch 0 (hot restart version=11.104)
[2019-12-14 11:02:14.285][1][info][main] [source/server/server.cc:251] statically linked extensions:
[2019-12-14 11:02:14.285][1][info][main] [source/server/server.cc:253]
……..
……..
[2019-12-14 11:02:14.303][1][info][main] [source/server/server.cc:344] admin address: 0.0.0.0:15000
[2019-12-14 11:02:14.305][1][info][main] [source/server/server.cc:458] runtime: layers:
  - name: base
    static_layer:
      {}
  - name: admin
    admin_layer:
      {}
[2019-12-14 11:02:14.305][1][info][config] [source/server/configuration_impl.cc:62] loading 0 static secret(s)
[2019-12-14 11:02:14.305][1][info][config] [source/server/configuration_impl.cc:68] loading 1 cluster(s)
[2019-12-14 11:02:14.306][1][info][upstream] [source/common/upstream/cluster_manager_impl.cc:161] cm init: all clusters initialized
[2019-12-14 11:02:14.306][1][info][config] [source/server/configuration_impl.cc:72] loading 1 listener(s)
[2019-12-14 11:02:14.307][1][info][config] [source/server/configuration_impl.cc:97] loading tracing configuration
[2019-12-14 11:02:14.308][1][info][config] [source/server/configuration_impl.cc:117] loading stats sink configuration
[2019-12-14 11:02:14.308][1][info][main] [source/server/server.cc:528] all clusters initialized. initializing init manager
[2019-12-14 11:02:14.308][1][info][config] [source/server/listener_manager_impl.cc:578] all dependencies initialized. starting workers
[2019-12-14 11:02:14.309][1][info][main] [source/server/server.cc:549] starting main dispatch loop

根据上述日志,应该看到Envoy代理已经成功启动并正在监听端口。下面使用一个简单的命令行客户端curl来调用代理,如下所示:


docker run -it --rm --link proxy tutum/curl \
  curl  -X GET http://proxy:15001/headers
{
  "headers": {
    "Accept": "*/*",
    "Content-Length": "0",
    "Host": "httpbin",
    "User-Agent": "curl/7.35.0",
    "X-Envoy-Expected-Rq-Timeout-Ms": "15000",
    "X-Request-Id": "f9d38fcb-9e9b-4ba4-8f14-d95cc139d754"
  }
}

可以看到,虽然没有直接调用httpbin服务,通过调用Envoy代理,流量已经可以正确地发送到目标服务。此外,还可看到用于调用httpbin服务的请求头略有变化,增加了如下两个字段:

·X-Request-Id:用于关联集群中的请求以及唯一标识请求并执行稳定的访问日志记录和跟踪,Envoy将为所有外部来源请求生成一个x-request-id头。

·X-Envoy-Expected-Rq-Timeout-Ms:是对上游服务的提示,请求预计在此之后超时15000ms。以毫秒为单位,路由器要求请求在这一时长内完成。Envoy把这个信息写入HTTP请求头,这样被代理的主机收到这一请求之后,就可以根据这一节的内容来判断是否超时,也就是快速失败。

让我们稍微改变一下这个配置,将预期的请求超时设置为1秒。在配置文件中更新路由规则,如下所示:


- match: { prefix: "/" }
  route:
    auto_host_rewrite: true
    cluster: httpbin_service
    timeout: 1s

首先停止前面步骤中运行的Envoy代理,如下所示:


docker rm -f proxy

然后,使用这个新配置文件重新启动它,如下所示:


docker run -d --name proxy --link httpbin \
-v /Users/wangxn/Documents/GitHub/istio-book/envoyproxy/simple_change_timeout.yaml:/etc/envoy/simple_change_timeout.yaml \
envoyproxy/envoy:latest envoy -c /etc/envoy/simple_change_timeout.yaml
b2fb1ef46857ed19f48f1c0e59c58347ae4cfd89d78dadafb83056fca2452a9b

现在,让我们再次调用代理,如下所示:


docker run -it --rm --link proxy tutum/curl \
curl  -X GET http://proxy:15001/headers

{
  "headers": {
    "Accept": "*/*",
    "Content-Length": "0",
    "Host": "httpbin",
    "User-Agent": "curl/7.35.0",
    "X-Envoy-Expected-Rq-Timeout-Ms": "1000",
    "X-Request-Id": "b42e334d-fc28-4d5e-b687-c57d663bb2a9"
  }
}

从返回的结果中可以看到预期的请求超时值已更改为1000ms,这说明Envoy的配置变更已经生效。