专栏首页k8s技术圈Envoy 基于 API 的动态配置

Envoy 基于 API 的动态配置

当在 Envoy 配置中定义了上游集群后,Envoy 需要知道如何解析集群成员,这就是服务发现。端点发现服务(EDS)是 Envoy 基于 gRPC 或者用来获取集群成员的 REST-JSON API 服务的 xDS 管理服务。在本节我们将学习如何使用 REST-JSOn API 来配置端点的自动发现。

1. 介绍

在前面的章节中,我们使用文件来定义了静态和动态配置,在这里我们将介绍另外一种方式来进行动态配置:API 动态配置。

端点发现服务(EDS)是 Envoy 基于 gRPC 或者用来获取集群成员的 REST-JSON API 服务的 xDS 管理服务,集群成员在 Envoy 术语中成为端点,对于每个集群,Envoy 都从发现服务中获取端点。其中 EDS 就是最常用的服务发现机制,因为下面几个原因:

  • Envoy 对每个上游主机都有一定的了解(相对于通过 DNS 解析的负载均衡器进行路由),可以做出更加智能的负载均衡策略。
  • 发现 API 返回的每个主机的一些属性会将主机的负载均衡权重、金丝雀状态、区域等等告知 Envoy,这个额外的属性在负载均衡、统计数据收集等会被 Envoy 网格在全局中使用到
  • Envoy 项目在 Java 和 Golang 中都提供了 EDS 和其他服务发现的 gRPC 实现参考

接下来我们将更改配置来使用 EDS,从而允许基于来自 REST-JSON API 服务的数据进行动态添加节点。

2. EDS 配置

下面是提供的一个 Envoy 配置的初始配置 envoy.yaml,文件内容如下所示:

admin:
  access_log_path: /dev/null
  address:
    socket_address:
      address: 127.0.0.1
      port_value: 9000

node:
  cluster: mycluster
  id: test-id

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address: { address: 0.0.0.0, port_value: 10000 }
    filter_chains:
    - filters:
      - name: envoy.http_connection_manager
        config:
          stat_prefix: ingress_http
          codec_type: AUTO
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match: { prefix: "/" }
                route: { cluster: targetCluster }
          http_filters:
          - name: envoy.router

接下来需要添加一个 EDS 类型的集群配置,并在 eds_config 中配置使用 REST API:

clusters:
- name: targetCluster
  type: EDS
  connect_timeout: 0.25s
  eds_cluster_config:
    service_name: myservice
    eds_config:
      api_config_source:
        api_type: REST
        cluster_names: [eds_cluster]
        refresh_delay: 5s

然后需要定义 eds_cluster 的解析方式,这里我们可以使用静态配置:

- name: eds_cluster
  type: STATIC
  connect_timeout: 0.25s
  hosts: [{ socket_address: { address: 172.17.0.4, port_value: 8080 }}]

然后同样启动一个 Envoy 代理实例来进行测试:

$ docker run --name=api-eds -d \
    -p 9901:9901 \
    -p 80:10000 \
    -v $(pwd)/manifests:/etc/envoy \
    envoyproxy/envoy:latest

然后启动一个如下所示的上游端点服务:

$ docker run -p 8081:8081 -d -e EDS_SERVER_PORT='8081' cnych/docker-http-server:v4

启动完成后我们可以使用如下命令来测试上游的端点服务:

$ curl http://localhost:8081 -i
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 36
Server: Werkzeug/0.15.4 Python/2.7.16
Date: Tue, 14 Apr 2020 06:32:56 GMT

355d92db-9295-4a22-8b2c-fc0e5956ecf6

现在我们启动了 Envoy 代理和上游的服务集群,但是由于我们这里启动的服务并不是 eds_cluster 中配置的服务,所以还没有连接它们。这个时候我们去查看 Envoy 代理的日志,可以看到如下所示的一些错误:

$ docker logs -f api-eds
[2020-04-14 06:50:07.334][1][warning][config] [source/common/config/http_subscription_impl.cc:110] REST update for /v2/discovery:endpoints failed
......

3. 启动 EDS

为了让 Envoy 获取端点服务,我们需要启动 eds_cluster,我们这里将使用 python 实现的一个示例 eds_server。

使用如下所示的命令来启动 eds_server 服务:

$ docker run -p 8080:8080 -d cnych/eds_server

服务启动后,可以在服务日志中查看到如下所示的日志信息,表明一个 Envoy 发现请求成功:

* Serving Flask app "main" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Running on http://0.0.0.0:8080/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 185-412-562
172.17.0.2 - - [14/Apr/2020 07:12:00] "POST /v2/discovery:endpoints HTTP/1.1" 200 -
 Inbound v2 request for discovery.  POST payload: {u'node': {u'user_agent_name': u'envoy', u'cluster': u'mycluster', u'extensions': [{......}], u'user_agent_build_version': {u'version': {u'minor_number': 14, u'major_number': 1, u'patch': 1}, u'metadata': {u'ssl.version': u'BoringSSL', u'build.type': u'RELEASE', u'revision.status': u'Clean', u'revision.sha': u'3504d40f752eb5c20bc2883053547717bcb92fd8'}}, u'build_version': u'3504d40f752eb5c20bc2883053547717bcb92fd8/1.14.1/Clean/RELEASE/BoringSSL', u'id': u'test-id'}, u'type_url': u'type.googleapis.com/envoy.api.v2.ClusterLoadAssignment', u'resource_names': [u'myservice'], u'version_info': u'v1'}
172.17.0.2 - - [14/Apr/2020 07:12:08] "POST /v2/discovery:endpoints HTTP/1.1" 200 -

现在我们就可以将上游的服务配置添加到 EDS 服务中去了,这样可以让 Envoy 来自动发现上游服务。

我们在 Envoy 配置中将服务定义为了 myservice,所以我们需要针对该服务注册一个端点:

$ curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d '{
  "hosts": [
    {
      "ip_address": "172.17.0.3",
      "port": 8081,
      "tags": {
        "az": "cn-beijing-a",
        "canary": false,
        "load_balancing_weight": 50
      }
    }
  ]
}' http://localhost:8080/edsservice/myservice

由于我们已经启动了上面注册的上游服务,所以现在我们可以通过 Envoy 代理访问到它了:

$ curl -i http://localhost
HTTP/1.1 200 OK
content-type: text/html; charset=utf-8
content-length: 36
server: envoy
date: Tue, 14 Apr 2020 07:33:04 GMT
x-envoy-upstream-service-time: 4

355d92db-9295-4a22-8b2c-fc0e5956ecf6

接下来我们在上游集群中运行更多的节点,并调用 API 来进行动态注册,使用如下所示的命令来向上游集群再添加4个节点:

for i in 8082 8083 8084 8085
  do
    docker run -d -e EDS_SERVER_PORT=$i cnych/docker-http-server:v4;
    sleep .5
done

然后将上面的4个节点注册到 EDS 服务上面去,同样使用如下所示的 API 接口调用:

$ curl -X PUT --header 'Content-Type: application/json' --header 'Accept: application/json' -d '{
    "hosts": [
        {
        "ip_address": "172.17.0.3",
        "port": 8081,
        "tags": {
            "az": "cn-beijing-a",
            "canary": false,
            "load_balancing_weight": 50
        }
        },
        {
        "ip_address": "172.17.0.5",
        "port": 8082,
        "tags": {
            "az": "cn-beijing-a",
            "canary": false,
            "load_balancing_weight": 50
        }
        },
        {
        "ip_address": "172.17.0.6",
        "port": 8083,
        "tags": {
            "az": "cn-beijing-a",
            "canary": false,
            "load_balancing_weight": 50
        }
        },
        {
        "ip_address": "172.17.0.7",
        "port": 8084,
        "tags": {
            "az": "cn-beijing-a",
            "canary": false,
            "load_balancing_weight": 50
        }
        },
        {
        "ip_address": "172.17.0.8",
        "port": 8085,
        "tags": {
            "az": "cn-beijing-a",
            "canary": false,
            "load_balancing_weight": 50
        }
        }
    ]
    }' http://localhost:8080/edsservice/myservice

注册成功后,我们可以通过如下所示的命令来验证网络请求是否与注册的节点之间是均衡的:

$ while true; do curl http://localhost; sleep .5; printf '\n'; done
d671262d-39b5-4150-9e25-94fb4f733959
dd1519ef-e03a-4708-bcd1-71890d38e40c
b0c218f0-99f4-43e4-87fc-8989d49fccec
355d92db-9295-4a22-8b2c-fc0e5956ecf6
d671262d-39b5-4150-9e25-94fb4f733959
34690963-0887-4d36-8776-c35cf37fa901
......

根据上面的输出结果可以看到每次请求的服务是不同的响应,我们一共注册了5个端点服务。

现在我们来通过 API 删除 EDS 服务上面注册的主机来测试下,执行如下所示的命令清空 hosts

$ curl -X PUT --header 'Content-Type: application/json' --header 'Accept: application/json' -d '{
  "hosts": []
}' http://localhost:8080/edsservice/myservice

现在如果我们尝试向 Envoy 发送请求,我们将会看到如下所示的不健康的日志信息:

$ curl -v http://localhost
* Rebuilt URL to: http://localhost/
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 80 (#0)
> GET / HTTP/1.1
> Host: localhost
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 503 Service Unavailable
< content-length: 19
< content-type: text/plain
< date: Tue, 14 Apr 2020 07:50:06 GMT
< server: envoy
<
* Connection #0 to host localhost left intact
no healthy upstream

这是因为我们将端点服务的节点清空了,所以没有服务来接收 Envoy 的代理请求了。

接下来我们再来测试下 Envoy 和 EDS 服务器的连接断掉了会是一种什么样的情况。首先还是将前面的上游服务节点恢复:

$ curl -X PUT --header 'Content-Type: application/json' --header 'Accept: application/json' -d '{
    "hosts": [
        {
        "ip_address": "172.17.0.3",
        "port": 8081,
        "tags": {
            "az": "cn-beijing-a",
            "canary": false,
            "load_balancing_weight": 50
        }
        },
        {
        "ip_address": "172.17.0.5",
        "port": 8082,
        "tags": {
            "az": "cn-beijing-a",
            "canary": false,
            "load_balancing_weight": 50
        }
        },
        {
        "ip_address": "172.17.0.6",
        "port": 8083,
        "tags": {
            "az": "cn-beijing-a",
            "canary": false,
            "load_balancing_weight": 50
        }
        },
        {
        "ip_address": "172.17.0.7",
        "port": 8084,
        "tags": {
            "az": "cn-beijing-a",
            "canary": false,
            "load_balancing_weight": 50
        }
        },
        {
        "ip_address": "172.17.0.8",
        "port": 8085,
        "tags": {
            "az": "cn-beijing-a",
            "canary": false,
            "load_balancing_weight": 50
        }
        }
    ]
    }' http://localhost:8080/edsservice/myservice

然后同样用如下命令来验证节点是否正确响应:

$ while true; do curl http://localhost; sleep .5; printf '\n'; done

然后我们使用如下所示的命令来停止并删除 EDS 服务的容器:

$ docker ps -a | awk '{ print $1,$2 }' | grep cnych/eds_server  | awk '{print $1 }' | xargs -I {} docker stop {}
$ docker ps -a | awk '{ print $1,$2 }' | grep cnych/eds_server  | awk '{print $1 }' | xargs -I {} docker rm {}

这个时候我们可以看到上面的验证命令还是正常的收到相应,这就证明即使 Envoy 和 EDS 服务器断开了链接,也不会影响已经发现的集群节点。


本文分享自微信公众号 - k8s技术圈(kube100),作者:阳明

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

原始发表时间:2020-06-23

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

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 使用 GitLab CI 与 Argo CD 进行 GitOps 实践

    在现在的云原生世界里面 GitOps 不断的被提及,这种持续交付的模式越来越受到了大家的青睐,在网上也可以找到很多关于它的资源,但是关于 GitOps 相关的工...

    我是阳明
  • 使用 Elastic Stack 构建 Kubernetes 全栈监控(2/4)

    在前文中我们已经安装配置了 ElasticSearch 的集群,本文我们将来使用 Metricbeat 对 Kubernetes 集群进行监控。Metricbe...

    我是阳明
  • 通过 Descheduler 实现 Kubernetes 集群均衡

    在介绍 Kubernetes 集群均衡器之前我们还是非常有必要再来回顾下 kube-scheduler 组件的概念。我们知道基本上所有的分布式系统都需要一个流程...

    我是阳明
  • 最近大火的XXE漏洞是什么

    XXE全称是——XML External Entity,也就是XML外部实体注入攻击。漏洞是在对不安全的外部实体数据进行处理时引发的安全问题。

    HACK学习
  • JSON和XML:不可同日而语

    很多人都在心里纠结,如果 JSON 和 XML 相比,谁更好谁更快?在接下来的新项目中到底选择哪一个?别傻了!完全没有可比性。就像自行车和 AMG S65 ,你...

    九州暮云
  • 从面试题中学安全

    根据 Github 上的面经总结的一些安全岗面试的基础知识,这些基础知识不仅要牢记,而且要熟练操作,分享给大家,共勉。

    信安之路
  • http、https、http2一些概念

    非对称加密,现在用的几乎都是非对称加密,自己有一个密钥对公钥和私钥,公钥可以给任何人知道,别人通过公钥加密发数据给自己,自己通过密钥解密。

    wade
  • LeetCode 860. 柠檬水找零(贪心)

    每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。

    Michael阿明
  • XML基本语法

     导入一个XML文件可分为如下几部分内容:文档声明 、元素、属性、注释 、CDATA区 ,特殊字符 、处理指令

    MonroeCode
  • C语言(跳转语句中的流氓)

    从上面的代码看到goto的语法很简单,就是直接跳转到指定的标签处,所谓的标签(如例子中的label)指的是后面带一个冒号的标识符。

    用户2617681

扫码关注云+社区

领取腾讯云代金券