专栏首页aoho求索如何实现可伸缩的 etcd API?

如何实现可伸缩的 etcd API?

etcd 中如何实现可伸缩的 etcd API?使得 etcd 能够屏蔽内部集群的信息。本文将会介绍 etcd 中的 gRPC proxy 相关概念和使用分析。

gRPC proxy 是在 gRPC 层(L7)运行的无状态 etcd 反向代理,旨在「减少核心 etcd 集群上的总处理负载」。gRPC proxy 合并了监视和 Lease API 请求,实现了水平可伸缩性。同时,为了保护集群免受滥用客户端的侵害,gRPC proxy 实现了键值对的读请求缓存。

下面我们将围绕 gRPC proxy 基本应用、客户端端点同步、可伸缩的 API、命名空间的实现和其他扩展功能展开介绍。

gRPC proxy 基本应用

首先我们来配置 etcd 集群,集群中拥有如下的静态成员信息:

「Name」

「Address」

「HostName」

Infra0

192.168.10.7

infra0.blueskykong.com

Infra1

192.168.10.8

infra1.blueskykong.com

Infra2

192.168.10.9

infra2.blueskykong.com

使用etcd grpc-proxy start的命令开启 etcd 的 gRPC proxy 模式,包含上表中的静态成员:

$ etcd grpc-proxy start --endpoints=http://192.168.10.7:2379,http://192.168.10.8:2379,http://192.168.10.9:2379 --listen-addr=192.168.10.7:12379
{"level":"info","ts":"2020-12-13T01:41:57.561+0800","caller":"etcdmain/grpc_proxy.go:320","msg":"listening for gRPC proxy client requests","address":"192.168.10.7:12379"}
{"level":"info","ts":"2020-12-13T01:41:57.561+0800","caller":"etcdmain/grpc_proxy.go:218","msg":"started gRPC proxy","address":"192.168.10.7:12379"}

可以看到,etcd gRPC proxy 启动后在192.168.10.7:12379监听,并将客户端的请求转发到上述三个成员其中的一个。通过下述客户端读写命令,经过 proxy 发送请求:

$ ETCDCTL_API=3 etcdctl --endpoints=192.168.10.7:12379 put foo bar
OK
$ ETCDCTL_API=3 etcdctl --endpoints=192.168.10.7:12379 get foo
foo
bar

我们通过 grpc-proxy 提供的客户端地址进行访问,proxy 执行的结果符合预期,在使用方法上和普通的方式完全相同。

客户端端点同步

gRPC 代理是 gRPC 命名的提供者,支持「在启动时通过写入相同的前缀端点名称」进行注册。这样可以使客户端将其端点与具有一组相同前缀端点名的代理端点同步,进而实现高可用性。

下面我们来启动两个 gRPC 代理,在启动时指定自定义的前缀___grpc_proxy_endpoint来注册 gRPC 代理:

$ etcd grpc-proxy start --endpoints=localhost:12379   --listen-addr=127.0.0.1:23790   --advertise-client-url=127.0.0.1:23790   --resolver-prefix="___grpc_proxy_endpoint"   --resolver-ttl=60
{"level":"info","ts":"2020-12-13T01:46:04.885+0800","caller":"etcdmain/grpc_proxy.go:320","msg":"listening for gRPC proxy client requests","address":"127.0.0.1:23790"}
{"level":"info","ts":"2020-12-13T01:46:04.885+0800","caller":"etcdmain/grpc_proxy.go:218","msg":"started gRPC proxy","address":"127.0.0.1:23790"}
2020-12-13 01:46:04.892061 I | grpcproxy: registered "127.0.0.1:23790" with 60-second lease
$ etcd grpc-proxy start --endpoints=localhost:12379 \
>   --listen-addr=127.0.0.1:23791 \
>   --advertise-client-url=127.0.0.1:23791 \
>   --resolver-prefix="___grpc_proxy_endpoint" \
>   --resolver-ttl=60
{"level":"info","ts":"2020-12-13T01:46:43.616+0800","caller":"etcdmain/grpc_proxy.go:320","msg":"listening for gRPC proxy client requests","address":"127.0.0.1:23791"}
{"level":"info","ts":"2020-12-13T01:46:43.616+0800","caller":"etcdmain/grpc_proxy.go:218","msg":"started gRPC proxy","address":"127.0.0.1:23791"}
2020-12-13 01:46:43.622249 I | grpcproxy: registered "127.0.0.1:23791" with 60-second lease

在上面的启动命令中,将需要加入的自定义端点--resolver-prefix设置为___grpc_proxy_endpoint。启动成功之后,我们来验证下,gRPC 代理在查询成员时是否列出其所有成员作为成员列表,执行如下的命令:

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

通过下图,可以看到,通过相同的前缀端点名完成了自动发现所有成员列表的操作。

图片

同样地,客户端也可以通过 Sync 方法自动发现代理的端点,代码实现如下:

cli, err := clientv3.New(clientv3.Config{
    Endpoints: []string{"http://localhost:23790"},
})
if err != nil {
    log.Fatal(err)
}
defer cli.Close()
// 获取注册过的 grpc-proxy 端点
if err := cli.Sync(context.Background()); err != nil {
    log.Fatal(err)
}

相应地,如果配置的代理没有配置前缀,gRPC 代理启动命令如下:

$ ./etcd grpc-proxy start --endpoints=localhost:12379 \
>   --listen-addr=127.0.0.1:23792 \
>   --advertise-client-url=127.0.0.1:23792
# 输出结果
{"level":"info","ts":"2020-12-13T01:49:25.099+0800","caller":"etcdmain/grpc_proxy.go:320","msg":"listening for gRPC proxy client requests","address":"127.0.0.1:23792"}
{"level":"info","ts":"2020-12-13T01:49:25.100+0800","caller":"etcdmain/grpc_proxy.go:218","msg":"started gRPC proxy","address":"127.0.0.1:23792"}

我们来验证下 gRPC proxy 的成员列表 API 是不是只返回自己的advertise-client-url

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

通过下图,可以看到,结果如我们预期:当我们「没有配置代理的前缀端点名「「时」」,获取其成员列表只会显示当前节点的信息,也不会包含其他的端点」

图片

可伸缩的 watch API

如果客户端监视同一键或某一范围内的键,gRPC 代理可以将这些客户端监视程序(c-watcher)合并为连接到 etcd 服务器的单个监视程序(s-watcher)。当 watch 事件发生时,代理将所有事件从 s-watcher 广播到其 c-watcher。

假设 N 个客户端监视相同的 key,则 gRPC 代理可以将 etcd 服务器上的监视负载从 N 减少到 1。用户可以部署多个 gRPC 代理,进一步分配服务器负载。

如下图所示,三个客户端监视键 A。gRPC 代理将三个监视程序合并,从而创建一个附加到 etcd 服务器的监视程序。

图片

为了有效地将多个客户端监视程序合并为一个监视程序,gRPC 代理在可能的情况下将新的 c-watcher 合并为现有的 s-watcher。由于网络延迟或缓冲的未传递事件,合并的 s-watcher 可能与 etcd 服务器不同步。

「如果「「没有」」指定监视版本,gRPC 代理将不能保证 c-watcher 从最近的存储修订版本开始监视」。例如,如果客户端从修订版本为 1000 的 etcd 服务器监视,则该监视者将从修订版本 1000 开始。如果客户端从 gRPC 代理监视,则可能从修订版本 990 开始监视。

「类似的限制也适用于取消」。取消 watch 后,etcd 服务器的修订版可能大于取消响应修订版。

对于大多数情况,这两个限制一般不会引起问题,未来也可能会有其他选项强制观察者绕过 gRPC 代理以获得更准确的修订响应。

可伸缩的 lease API

为了保持客户端申请租约的有效性,客户端至少建立一个 gRPC 连接到 etcd 服务器,以定期发送心跳信号。如果 etcd 工作负载涉及很多的客户端租约活动,这些流可能会导致 CPU 使用率过高。「为了减少核心集群上的流总数,gRPC 代理支持将 lease 流合并」

假设有 N 个客户端正在更新租约,则单个 gRPC 代理将 etcd 服务器上的流负载从 N 减少到 1。在部署的过程中,可能还有其他 gRPC 代理,进一步在多个代理之间分配流。

在下图示例中,三个客户端更新了三个独立的租约(L1、L2 和 L3)。gRPC 代理将三个客户端租约流(c-stream)合并为连接到 etcd 服务器的单个租约(s-stream),以保持活动流。代理将客户端租约的心跳从 c-stream 转发到 s-stream,然后将响应返回到相应的 c-stream。

图片

除此之外,gRPC 代理在满足一致性时会缓存请求的响应。该功能可以保护 etcd 服务器免遭恶意 for 循环中滥用客户端的攻击。

命名空间的实现

上面我们讲到 gRPC proxy 的端点可以通过配置前缀,自动发现。而当应用程序期望对整个键空间有完全控制,etcd 集群与其他应用程序共享的情况下,为了使所有应用程序都不会相互干扰地运行,代理可以对「etcd 键空间进行分区」,以便客户端大概率访问完整的键空间。

当给代理提供标志--namespace时,所有进入代理的客户端请求都将转换为「在键上具有用户定义的前缀」。普通的请求对 etcd 集群的访问将会在我们指定的前缀(即指定的 --namespace 的值)下,而来自代理的响应将删除该前缀;而这个操作对于客户端来说是透明的,根本察觉不到前缀。

下面我们给 gRPC proxy 命名,只需要启动时指定--namespace标识:

$ ./etcd grpc-proxy start --endpoints=localhost:12379 \
>   --listen-addr=127.0.0.1:23790 \
>   --namespace=my-prefix/
{"level":"info","ts":"2020-12-13T01:53:16.875+0800","caller":"etcdmain/grpc_proxy.go:320","msg":"listening for gRPC proxy client requests","address":"127.0.0.1:23790"}
{"level":"info","ts":"2020-12-13T01:53:16.876+0800","caller":"etcdmain/grpc_proxy.go:218","msg":"started gRPC proxy","address":"127.0.0.1:23790"}

此时对代理的访问会在 etcd 群集上自动地加上前缀,对于客户端来说没有感知。我们通过 etcdctl 客户端进行尝试:

$ 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

上述三条命令,首先通过代理写入键值对,然后读取。为了验证结果,第三条命令通过 etcd 集群直接读取,不过需要加上代理的前缀,两种方式得到的结果完全一致。因此,「使用 proxy 的命名空间即可实现 etcd 键空间分区」,对于客户端来说非常便利。

其他扩展功能

gRPC 代理的功能非常强大,除了上述提到的客户端端点同步、可伸缩 API、命名空间功能,还提供了指标与健康检查接口和 TLS 加密中止的扩展功能。

指标与健康检查接口

gRPC 代理为--endpoints定义的 etcd 成员公开了/health和 Prometheus 的/metrics接口。我们通过浏览器访问这两个接口:

访问 metrics 接口的结果

访问 health 接口的结果

通过代理访问/metrics端点的结果如上图所示,其实和普通的etcd 集群实例没有什么区别,同样也会结合一些中间件进行统计和页面展示,如 Prometheus 和 Grafana 的组合。

除了使用默认的端点访问这两个接口,另一种方法是定义一个附加 URL,该 URL 将通过 --metrics-addr 标志来响应/metrics/health端点。命令如下所示:

$ ./etcd grpc-proxy start \
  --endpoints http://localhost:12379 \
  --metrics-addr http://0.0.0.0:6633 \
  --listen-addr 127.0.0.1:23790 \

在执行如上启动命令时,会有如下的命令行输出,提示我们指定的 metrics 监听地址为 http://0.0.0.0:6633。

{"level":"info","ts":"2021-01-30T18:03:45.231+0800","caller":"etcdmain/grpc_proxy.go:456","msg":"gRPC proxy listening for metrics","address":"http://0.0.0.0:6633"}

TLS 加密的代理

通过使用 gRPC 代理 etcd 集群的 TLS,可以给没有使用 HTTPS 加密方式的本地客户端提供服务,实现etcd 集群的TLS 加密中止,即未加密的客户端与gRPC 代理通过 HTTP 方式通信,gRPC 代理与 etcd 集群通过 TLS 加密通信。下面我们进行实践:

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

上述命令使用HTTPS启动了单个成员的 etcd 集群,然后确认 etcd 集群以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

显然第一种方式不能访问。

接下来通过使用客户端证书连接到 etcd 端点https://localhost:2379,并在 localhost:12379 上启动 gRPC 代理,命令如下:

$ 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

启动后,我们通过 gRPC 代理写入一个键值对测试:

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

可以看到,使用HTTP的方式设置成功。

回顾上述操作,我们通过 etcd 的 gRPC 代理实现了代理与实际的 etcd 集群之间的 TLS 加密,而本地的客户端通过 HTTP 的方式与gRPC 代理通信。因此这是一个简便的调试和开发手段,你在生产环境需要谨慎使用,以防安全风险。

小结

本文我们主要介绍了 etcd 中的 gRPC proxy。gRPC 代理用于支持多个 etcd 服务器端点,当代理启动时,它会随机选择一个 etcd 服务器端点来使用,该端点处理所有请求,直到代理检测到端点故障为止。如果 gRPC 代理检测到端点故障,它将切换到其他可用的端点,对客户端继续提供服务,并且隐藏了存在问题的 etcd 服务端点。

本文分享自微信公众号 - aoho求索(aohoBlog),作者:aoho

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-05-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 如何构建可伸缩的Web应用?

    想象一下,你的营销活动吸引了很多用户,在某个时候,应用必须同时为成千上万的用户提供服务,这么大的并发量,服务器的负载会很大,如果设计不当,系统将无法处理。

    dys
  • 什么是k8s

      首先,他是一个全新的基于容器技术的分布式架构领先方案。Kubernetes(k8s)是Google开源的容器集群管理系统(谷歌内部:Borg)。在Docke...

    步履不停凡
  • 利用EndpointSlices扩展Kubernetes网络,提供更强的可伸缩性和功能

    EndpointSlices是一个令人兴奋的新API,它提供了Endpoints API的可扩展和可扩张的替代方案。EndpointSlice跟踪Pod服务后面...

    灵雀云
  • K8s简介

    Kubernetes是Google 2014年创建管理的,是Google 10多年大规模容器管理技术Borg的开源版本。它是容器集群管理系统,是一个开源的平台,...

    分母为零
  • 容器服务9月月报:TKE Kubernetes 1.14 版本全量上线

    随着全球云计算产业高速发展,越来越多的企业选择业务上云,为了能够为广大用户提供更好、更全面的产品功能与使用体验,我们正在不断努力中。9月份,我们在容器服务的产品...

    腾讯云原生
  • 简介Kube-liveboard——一款可视化操作工具

    Kubernetes这个自动化容器操作的开源平台,这些操作包括部署,调度和节点集群间扩展。因其具有对容器集群的方便部署,自动伸缩扩容,以及便于维护等功能和特性,...

    CNCF
  • etcd 与 Zookeeper、Consul 等其它 kv 组件的对比

    本文的主角是 etcd。名称 “etcd” 源自两个想法,即 unix “/etc” 文件夹 和 “d” 分布式系统。“/etc” 文件夹是用于存储单个系统的配...

    aoho求索
  • Kubernetes 1.7:安全加固、有状态应用更新等

    今天我们公布了Kubernetes 1.7,这一里程碑版本引入了更为强大的安全性、存储以及扩展性因素,旨在满足Kubernetes在广泛企业环境下所面临的实际需...

    Debian中国
  • kubernetes基础概念知多少

    kubernetes(简称k8s)是一种用于在一组主机上运行和协同容器化应用程序的管理平台,皆在提供高可用、高扩展性和可预测性的方式来管理容器应用的生命周期。通...

    luoxn28
  • Kubernetes组件之kube-controller-manager

    Controller Manager作为集群内部的管理控制中心,负责集群内的Node、Pod副本、服务端点(Endpoint)、命名空间(Namespace)、...

    菲宇
  • 资深专家深度剖析Kubernetes API Server第2章(共3章)

    欢迎来到深入学习Kubernetes API Server的系列文章的第二部分。在上一部分中我们对APIserver总体,相关术语及request请求流进行...

    用户1108251
  • Kubernetes架构及相关服务详解

      API服务器是和etcd通信的唯一组件,其他组件不会直接和etcd进行通信。

    yaohong
  • 「走进k8s」Kubernetes基本概念和组件(13)

    k8s为每个pod分配了唯一的IP地址,一个pod里的多个容器共享pod IP。 pod其实有两种类型:普通的pod和静态pod,后者比较特殊,它并不存放在et...

    IT架构圈
  • Kubernetes+Prometheus+Grafana部署笔记

    Kubernetes(通常写成“k8s”)Kubernetes是Google开源的容器集群管理系统。其设计目标是在主机集群之间提供一个能够自动化部署、可拓展、应...

    KaliArch
  • (译)Zalando 是如何管理 140 多个 Kubernetes 集群的

    最近我接到一个问题:“你是如何管理这么多 Kubernetes 的?”。本文试图揭示 Zalando 在 AWS 管理 140 多个 Kubernetes 集群...

    崔秀龙
  • Kubernetes容器平台架构简析

    Docker自2013年发行以来,得到了飞速的发展,直至今日已经成为了基础架构中必不可缺的一份子,也是构建企业云平台的有效手段。而作为容器编排及管理的利器的ku...

    数据库架构之美
  • 大型网站的可伸缩性架构如何设计?

    将不同功能分离部署可以实现一定程度的伸缩性,但是随着网站的访问量逐步增加,即使分离到最小粒度的独立部署,单一的服务器也不能满足业务规模的要求。因此必须使用服务器...

    李红
  • 深入理解Kubernetes Operator

    多年来,Operator 一直是 Kubernetes 生态系统的重要组成部分。通过将管理界面移动到 Kubneretes API 中,带来了“单层玻璃”的体验...

    深度学习与Python
  • 在微服务领域Spring Boot自动伸缩如何实现

    自动伸缩是每个人都想要的,尤其是在微服务领域。让我们看看如何在基于Spring Boot的应用程序中实现。

    慕容千语

扫码关注云+社区

领取腾讯云代金券