↑↓ 选择↵ 打开⌫ 切换范围完整搜索

PG.CENTER 连接 PostgreSQL 文档、百科与生态知识。由 Pigsty 维护。

etcd 3.7

gRPC 代理

etcd 3.7 开发、运维、升级、API 与内部原理中文指南

gRPC 代理是运行在 gRPC 层(L7)的无状态 etcd 反向代理。该代理旨在降低核心 etcd 集群的总体处理负载。为实现横向扩展,代理会合并监听和租约 API 请求。为防止恶意客户端对集群造成影响,代理会缓存键范围请求。

gRPC 代理支持多个 etcd 服务器端点。代理启动时,会随机选择一个 etcd 服务器端点使用。该端点将处理所有请求,直至代理检测到端点故障。若 gRPC 代理检测到端点故障,且存在其他可用端点,则会切换至其他端点,以向客户端隐藏故障。未来可能支持其他重试策略,例如加权轮询。

可扩展的监听 API

gRPC 代理将同一键或范围上的多个客户端监听器(c-watchers)合并为一个连接到 etcd 服务器的监听器(s-watcher)。代理将 s-watcher 的所有事件广播给其 c-watchers。

假设有 N 个客户端监听同一个键,一个 gRPC 代理可将 etcd 服务器的监听负载从 N 降低至 1。用户可部署多个 gRPC 代理以进一步分摊服务器负载。

在以下示例中,三个客户端监听键 A。gRPC 代理将这三个监听器合并为一个监听器,该监听器连接到 etcd 服务器。

            +-------------+
            | etcd server |
            +------+------+
                   ^ watch key A (s-watcher)
                   |
           +-------+-----+
           | gRPC proxy  | <-------+
           |             |         |
           ++-----+------+         |watch key A (c-watcher)
watch key A ^     ^ watch key A    |
(c-watcher) |     | (c-watcher)    |
    +-------+-+  ++--------+  +----+----+
    |  client |  |  client |  |  client |
    |         |  |         |  |         |
    +---------+  +---------+  +---------+

限制

为有效将多个客户端监听器合并为单一监听器,gRPC 代理在可能的情况下会将新的 c-watchers 合并至现有的 s-watcher。由于网络延迟或缓冲未送达的事件,该合并后的 s-watcher 可能与 etcd 服务器不同步。当监听修订版本未指定时,gRPC 代理不能保证 c-watcher 会从最新的存储修订版本开始监听。例如,若客户端从修订版本为 1000 的 etcd 服务器进行监听,该监听器将从修订版本 1000 开始。若客户端从 gRPC 代理进行监听,可能从修订版本 990 开始监听。

取消操作也存在类似的限制。当监听器被取消时,etcd 服务器的修订版本可能大于取消响应的修订版本。

上述两项限制通常不会对大多数使用场景造成影响。未来可能会增加额外选项,以强制监听器绕过 gRPC 代理,从而获得更精确的修订版本响应。

可扩展的租约 API

为保持租约有效,客户端必须至少建立一个 gRPC 流至 etcd 服务器,以发送周期性心跳。若 etcd 工作负载涉及大量租约操作且分布于多个客户端,这些流可能导致 CPU 利用率过高。为减少核心集群上的总流数,代理支持租约流合并。

假设有 N 个客户端在更新租约,单个 gRPC 代理可将 etcd 服务器的流负载从 N 降低至 1。部署中可增加额外的 gRPC 代理,以进一步将流分布到多个代理上。

在以下示例中,三个客户端分别更新三个独立的租约(L1、L2 和 L3)。gRPC 代理将这三个客户端的租约流(c-streams)合并为一个附加到 etcd 服务器的租约保活流(s-stream)。代理将客户端侧租约心跳从 c-流转发至 s-流,随后将响应返回至对应的 c-流。

          +-------------+
          | etcd server |
          +------+------+
                 ^
                 | heartbeat L1, L2, L3
                 | (s-stream)
                 v
         +-------+-----+
         | gRPC proxy  +<-----------+
         +---+------+--+            | heartbeat L3
             ^      ^               | (c-stream)
heartbeat L1 |      | heartbeat L2  |
(c-stream)   v      v (c-stream)    v
      +------+-+  +-+------+  +-----+--+
      | client |  | client |  | client |
      +--------+  +--------+  +--------+

客户端滥用防护

gRPC 代理在不违反一致性要求的前提下,会缓存请求的响应。这可以防止在紧密循环中运行的恶意客户端对 etcd 服务器造成过载。

启动 etcd gRPC 代理

考虑一个具有以下静态端点的 etcd 集群:

名称 地址 主机名
infra0 10.0.1.10 infra0.example.com
infra1 10.0.1.11 infra1.example.com
infra2 10.0.1.12 infra2.example.com

使用以下命令启动 etcd gRPC 代理,以通过这些静态端点进行访问:

$ etcd grpc-proxy start --endpoints=infra0.example.com,infra1.example.com,infra2.example.com --listen-addr=127.0.0.1:2379

etcd gRPC 代理启动并在端口 2379 上监听。它将客户端请求转发至上述三个端点中的一个。

通过代理发送请求:

$ ETCDCTL_API=3 etcdctl --endpoints=127.0.0.1:2379 put foo bar
OK
$ ETCDCTL_API=3 etcdctl --endpoints=127.0.0.1:2379 get foo
foo
bar

客户端端点同步与名称解析

代理支持将端点注册至用户定义的发现端点,以供发现。此举具有两个目的。首先,它允许客户端将其端点与一组代理端点同步,以实现高可用性。其次,它是 etcd gRPC 命名 的端点提供者。

通过提供用户自定义前缀来注册代理:

$ etcd grpc-proxy start --endpoints=localhost:2379 \
  --listen-addr=127.0.0.1:23790 \
  --advertise-client-url=127.0.0.1:23790 \
  --resolver-prefix="___grpc_proxy_endpoint" \
  --resolver-ttl=60

$ etcd grpc-proxy start --endpoints=localhost:2379 \
  --listen-addr=127.0.0.1:23791 \
  --advertise-client-url=127.0.0.1:23791 \
  --resolver-prefix="___grpc_proxy_endpoint" \
  --resolver-ttl=60

代理将列出其所有成员的成员列表:

ETCDCTL_API=3 etcdctl --endpoints=http://localhost:23790 member list --write-out table

+----+---------+--------------------------------+------------+-----------------+
| ID | STATUS  |              NAME              | PEER ADDRS |  CLIENT ADDRS   |
+----+---------+--------------------------------+------------+-----------------+
|  0 | started | Gyu-Hos-MBP.sfo.coreos.systems |            | 127.0.0.1:23791 |
|  0 | started | Gyu-Hos-MBP.sfo.coreos.systems |            | 127.0.0.1:23790 |
+----+---------+--------------------------------+------------+-----------------+

这使得客户端可通过 Sync 自动发现代理端点:

cli, err := clientv3.New(clientv3.Config{
    Endpoints: []string{"http://localhost:23790"},
})
if err != nil {
    log.Fatal(err)
}
defer cli.Close()

// fetch registered grpc-proxy endpoints
if err := cli.Sync(context.Background()); err != nil {
    log.Fatal(err)
}

请注意,如果配置代理时未指定解析器前缀,

$ etcd grpc-proxy start --endpoints=localhost:2379 \
  --listen-addr=127.0.0.1:23792 \
  --advertise-client-url=127.0.0.1:23792

成员列表 API 通过 gRPC 代理返回其自身的 advertise-client-url:

ETCDCTL_API=3 etcdctl --endpoints=http://localhost:23792 member list --write-out table

+----+---------+--------------------------------+------------+-----------------+
| ID | STATUS  |              NAME              | PEER ADDRS |  CLIENT ADDRS   |
+----+---------+--------------------------------+------------+-----------------+
|  0 | started | Gyu-Hos-MBP.sfo.coreos.systems |            | 127.0.0.1:23792 |
+----+---------+--------------------------------+------------+-----------------+

命名空间

假设某个应用需要完全控制整个键空间,但 etcd 集群与其他应用共享。为使所有应用能够互不干扰地运行,代理可对 etcd 键空间进行分区,使客户端看似拥有对完整键空间的访问权限。当代理收到标志 --namespace 时,所有进入代理的客户端请求都会被转换,使键带上用户自定义的前缀。对 etcd 集群的访问将基于该前缀,而代理返回的响应会移除前缀;对客户端而言,似乎根本不存在前缀。

要为代理命名空间,请使用 --namespace 启动它:

$ etcd grpc-proxy start --endpoints=localhost:2379 \
  --listen-addr=127.0.0.1:23790 \
  --namespace=my-prefix/

对代理的访问现在已透明地在 etcd 集群上添加前缀:

$ ETCDCTL_API=3 etcdctl --endpoints=localhost:23790 put my-key abc
# OK
$ ETCDCTL_API=3 etcdctl --endpoints=localhost:23790 get my-key
# my-key
# abc
$ ETCDCTL_API=3 etcdctl --endpoints=localhost:2379 get my-prefix/my-key
# my-prefix/my-key
# abc

TLS 终止

通过 gRPC 代理终止安全 etcd 集群的 TLS,通过提供一个未加密的本地端点。

尝试操作,请使用客户端 HTTPS 启动单成员 etcd 集群:

$ etcd --listen-client-urls https://localhost:2379 --advertise-client-urls https://localhost:2379 --cert-file=peer.crt --key-file=peer.key --trusted-ca-file=ca.crt --client-cert-auth

确认客户端端口正在提供 HTTPS 服务:

# fails
$ ETCDCTL_API=3 etcdctl --endpoints=http://localhost:2379 endpoint status
# works
$ ETCDCTL_API=3 etcdctl --endpoints=https://localhost:2379 --cert=client.crt --key=client.key --cacert=ca.crt endpoint status

接下来,在 localhost:12379 上启动一个 gRPC 代理,通过客户端证书连接到 etcd 端点 https://localhost:2379:

$ etcd grpc-proxy start --endpoints=https://localhost:2379 --listen-addr localhost:12379 --cert client.crt --key client.key --cacert=ca.crt --insecure-skip-tls-verify &

最后,通过使用 HTTP 向代理写入键来测试 TLS 终止:

$ ETCDCTL_API=3 etcdctl --endpoints=http://localhost:12379 put abc def
# OK

指标与健康状况

gRPC 代理为 --endpoints 定义的 etcd 成员暴露 /health 和 Prometheus /metrics 端点。可另定义一个额外的 URL,该 URL 将对 /metrics 和 /health 端点响应,并设置 --metrics-addr 标志。

$ etcd grpc-proxy start \
  --endpoints https://localhost:2379 \
  --metrics-addr https://0.0.0.0:4443 \
  --listen-addr 127.0.0.1:23790 \
  --key client.key \
  --key-file proxy-server.key \
  --cert client.crt \
  --cert-file proxy-server.crt \
  --cacert ca.pem \
  --trusted-ca-file proxy-ca.pem

已知问题

代理的主要接口同时支持 HTTP/2 和 HTTP/1.1。若如上例所示配置了 TLS,当使用 cURL 等客户端访问监听接口时,必须在请求中显式设置协议为 HTTP/1.1,才能返回 /metrics 或 /health。通过使用 --metrics-addr 标志,次要接口将不再具有此要求。

 $ curl --cacert proxy-ca.pem --key proxy-client.key --cert proxy-client.crt https://127.0.0.1:23790/metrics --http1.1
来源与许可

文档取自 pig.center · 上游文档

版本
3.7
许可
CC-BY-4.0
来源修订
dba8dc7e1afd3f6eea300ebf9ecb67c731145f1ee7db1ca0d3ebc49508812609
译文修订
dba8dc7e1afd3f6eea300ebf9ecb67c731145f1ee7db1ca0d3ebc49508812609
原始 Markdown