Chinaunix首页 | 论坛 | 博客
  • 博客访问: 8139418
  • 博文数量: 594
  • 博客积分: 13065
  • 博客等级: 上将
  • 技术积分: 10324
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-26 16:44
个人简介

推荐: blog.csdn.net/aquester https://github.com/eyjian https://www.cnblogs.com/aquester http://blog.chinaunix.net/uid/20682147.html

文章分类

全部博文(594)

分类: 架构设计与优化

2020-01-07 10:31:06

目录


1. 前言

Envoy入门并不简单,可以说有些陡峭,本文尽可能帮助降低入门门槛。本文内容主要基于版本,官方链接:

1.1. Envoy是什么

Lyft开源的一个C++实现的代理(Proxy),和NginxHAProxy类似,可代理L3/L4层和L7层。代理是它最核心和基础的功能,它也是服务网格框架Sidecar

 

1.2. 如何入门Envoy

从研究Envoy的配置文件开始,Envoy支持多种格式的配置文件:YAMLJSONPB等,其中YAML使用最多,官方示例基本都是YAML格式的。

配置文件中涉及多个概念,所以最好先将概念了解清楚,然后使用最简单的配置走一遍流程,如果会用Docker则这一步会比较简单。在了解概念之前,最好先了解Envoy的基本架构,以弄明白各概念间的协作关系。

1.3. Envoy的源码在哪

Envoy的源码托管在Github上:

2. 缩略语

缩写

全写

说明

lb

load balance

负载均衡

lb_policy

load balance policy

负载均衡策略

SNI

Server Name Indication

TLS的扩展,用来解决一个服务器拥有多个域名或证书。

工作原理:在连接到服务器建立SSL链接前,先发送要访问的域名,服务器根据这个域名返回一个合适的证书。

TLS

Transport Layer Security

传输层安全性协议

L3

Layer 3

网络层(IP)

L4

Layer 4

传输层(PORT)

L7

Layer 7

应用层(HTTP)

L2

Layer 2

数据链路层(MAC)

YAML

YAML Ain't a Markup Language

以数据做为中心的标记语(Yet Another Markup Language)

JSON

JavaScript Object Notation

JS 对象简谱,一种轻量级的数据交换格式

REST

Representational State Transfer

表述性状态传递,一种软件架构风格

gRPC

Google RPC

谷歌开源的RPC框架

pb

Protocol buffers

谷歌开发的一种数据描述语言,常被简称为protobuf

3. Envoy架构

3.1. 外部架构

下图展示了Envoy的外部架构,从图很容易看到服务间、应用和服务间都是通过Envoy串联起来的,Envoy是它们间的高速公路,Envoy的作用就是在各部分间转发读写请求(也可叫读写操作),所以Envoy是名副其实的代理(Proxy)。

 

3.2. 内部架构

外部架构展示了Envoy的作用,但无法窥见它是如何实现的,Envoy的内部结构展示出了它的实现原理。

 


其中过滤器(Filter)是Envoy的核心中的核心,多Filter形成了过滤器链(Chain),和iptablesChain类似,请求经过过滤器链后到达目的服务(Service)。

 


如果将Envoy看成黑盒,则它所处位置可定义成如下图所示:

 

4. Envoy配置文件

Envoy架构有初步了解后,再通过对Enovy配置文件的了解,将对掌握Enovy十分有帮助。Envoy的配置文件定义了代理转发规则,规则也可通过gRPCREST动态拉取。

Envoy配置文件支持四种书写格式:jsonyamlpbpb_text,官方文档和示例基本使用yaml格式。可将Envoy配置文件分解成三大部分:

admin

定义Envoy进程的管理端口

static_resources

静态配置,定义静态资源

dynamic_resources

态配置,定义动态资源,static_resources中一些配置可通过服务调用(接口调用)动态拉取。

4.1. admin

管理配置,比较简单,内容一般如下:

admin:

  access_log_path: /tmp/admin_access.log

  address:

    socket_address: { address: 192.168.0.1, port_value: 9901 }


通过admin可以查询到大量的配置、监控和运维等数据:

 

4.2. static_resources

static_resources又可分解为两大部分:

listeners

定义监听器,服务下游(downstream)

clusters

定义上游(upstream)的微服务集群

4.2.1. listeners

listeners中,可以定义多个listener,每个listener由三部分组成:

name

定义listener名称

address

定义listener的监听地址和端口

filter_chains

定义过滤器(Filter)链,这是最核心和最复杂的部分

4.2.2. clusters

定义上游集群,Envoy最基础的功能即是将来自下游的请求转发给上游。clusters的内容包括五大部分,其中load_assignment部分是核心:

name

下游集群名,可定义一或多个

connect_timeout

连接上游的超时时长,可带单位,如“0.25s”表示250毫秒

type

集群类型,如STATIC、STRICT_DNS、LOGICAL_DNS和EDS等

lb_policy

负载均衡策略,如ROUND_ROBIN表示轮询

load_assignment

type为STATIC、STRICT_DNS和LOGICAL_DNS时,如果type为EDS则使用eds_cluster_config


lb_policy可取值:

ROUND_ROBIN

轮询

LEAST_REQUEST

请求最少

RING_HASH

环形哈希

RANDOM

随机

MAGLEV

一致性哈希算法

CLUSTER_PROVIDED

定制


load_assignment示例:

load_assignment:

  cluster_name: some_service

  endpoints:

  - lb_endpoints:

    - endpoint:

      address:

        socket_address:

          address: 127.0.0.1

          port_value: 1234


clusters示例:

clusters:

- name: some_service

  connect_timeout: 0.25s

  type: STATIC

  lb_policy: ROUND_ROBIN

  load_assignment:

    cluster_name: some_service

    endpoints:

    - lb_endpoints:

      - endpoint:

          address:

            socket_address:

              address: 127.0.0.1

              port_value: 1234

4.3. dynamic_resources

static_resources中的四种动态资源可通过动态服务发现(xDS)来动态配置,共有六种动态服务发现:

缩写

全写

说明

LDS

Listener Discovery Service

监听器(资源)发现服务,解决有哪些监听器问题

CDS

Cluster Discovery Service

集群(资源)发现服务,解决有哪些集群问题

RDS

Route Discovery Service

路由(资源)发现服务,解决有哪些路由规则问题

EDS

Endpoint Discovery Service

端点(资源)发现服务,解决集群内有哪些端点问题

ADS

Aggregated Discovery Service

这并不是一个独立的发现服务,而是对其它发现服务的聚合


动态配置在启动Envoy进程时,需要指定idcluster,否则报错“node 'id' and 'cluster' are required.”。

4.3.1. 怎么理解

怎么理解dynamic_resources?在static_resouces基础上,动态拉取动态资源,即有动态资源配置不是直接写在配置中,而是需要通过服务调用动态取得,Envoy支持gRPC/HTTP2REST两种方式动态拉取。

4.3.2. 全动态配置

可以部分动态配置,也可全动态配置。下列为一个官方的全动态配置示例:

admin:

  access_log_path: /tmp/admin_access.log

  address:

    socket_address: { address: 127.0.0.1, port_value: 9901 }

 

dynamic_resources:

  lds_config:

    api_config_source:

      api_type: GRPC

      grpc_services:

        envoy_grpc:

          cluster_name: xds_cluster

  cds_config:

    api_config_source:

      api_type: GRPC

      grpc_services:

        envoy_grpc:

          cluster_name: xds_cluster

 

static_resources:

  clusters:

  - name: xds_cluster

    connect_timeout: 0.25s

    type: STATIC

    lb_policy: ROUND_ROBIN

    http2_protocol_options: {}

    upstream_connection_options:

      # configure a TCP keep-alive to detect and reconnect to the admin

      # server in the event of a TCP socket half open connection

      tcp_keepalive: {}

    load_assignment:

      cluster_name: xds_cluster

      endpoints:

      - lb_endpoints:

        - endpoint:

            address:

              socket_address:

                address: 127.0.0.1

                port_value: 5678


上例为gRPC/HTTP2方式动态拉取配置,提供配置的服务名为xds_cluster,服务端口为“127.0.0.1:5678”。

4.3.3. gRPC接口

1) CDS

POST /envoy.api.v2.ClusterDiscoveryService/StreamClusters


gRPC服务定义在文件,链接地址:


2) EDS

POST /envoy.api.v2.EndpointDiscoveryService/StreamEndpoints


gRPC服务定义在文件,链接地址:


3) LDS

POST /envoy.api.v2.ListenerDiscoveryService/StreamListeners


gRPC服务定义在文件,链接地址:


4) RDS

POST /envoy.api.v2.RouteDiscoveryService/StreamRoutes


gRPC服务定义在文件,链接地址:

4.3.4. REST接口

1) CDS

POST /v2/discovery:clusters



2) EDS

POST /v2/discovery:endpoints


3) LDS

POST /v2/discovery:listeners


4) RDS

POST /v2/discovery:routes

5. 试跑体验

试跑Enovy要求有Docker基础(如无基础可参考《Docker入门之安装Docker》和《Docker入门之创建镜像初步》),从源代码构建会有些复杂,所以本文直接使用官方提供的Docker景象作为试跑对象。试跑前提要求Docker环境已准备好,并且试跑机要能访问外网。

5.1. 体验目标

代理https://,当访问本机的8080端口时,实际为访问被代理的https://

5.2. 下载Envoy镜像

执行下列命令拉取EnvoyDocker镜像:

docker pull envoyproxy/envoy

5.3. 准备Envoy配置文件

在本地主备配置文件/tmp/bd.yaml”,文件内容如下:

$ cat /tmp/bd.yaml

admin:

  access_log_path: /tmp/admin_access.log

  address:

    socket_address:

      protocol: TCP

      address: 0.0.0.0 # 管理地址

      port_value: 8081 # 管理端口

 

static_resources:

  listeners: # 监听器数组

  - name: listener_0 # 监听器

    address:

      socket_address:

        protocol: TCP

        address: 0.0.0.0 # 监听地址

        port_value: 8080 # 监听端口

    filter_chains: # 过滤器链

    - filters: # 过滤器数组

      - name: envoy.http_connection_manager # 过滤器名

        typed_config:

          "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager

          stat_prefix: ingress_http

          route_config: # 路由配置

            name: local_route # 路由配置名

            virtual_hosts: # 虚拟主机数组

            - name: local_service

              domains: ["*"] # 需代理的域名数组

              routes: # 定义路由

              - match:

                  prefix: "/" # 匹配规则

                route:

                  host_rewrite:  # 将HOST重写为

                  cluster: bd_service # 下游集群名,通过它找到下游集群的配置

          http_filters:

          - name: envoy.router

  clusters: # 下游集群数组

  - name: bd_service # 下游集群名

    connect_timeout: 0.25s # 连接下游的超时时长

    type: LOGICAL_DNS

    # Comment out the following line to test on v6 networks

    dns_lookup_family: V4_ONLY # 域名查找范围,这里表示只查找IPV4地址

    lb_policy: ROUND_ROBIN # 负载均衡策略

    load_assignment:

      cluster_name: bd_service

      endpoints:

      - lb_endpoints:

        - endpoint:

            address:

              socket_address:

                address:

                port_value: 443

    transport_socket:

      name: envoy.transport_sockets.tls

      typed_config:

        "@type": type.googleapis.com/envoy.api.v2.auth.UpstreamTlsContext

        sni:

 


由于代理的是域名,所以clusterstype值需为LOGICAL_DNSstrict_dnstype还有如下几个取值(不区分大小写):

STATIC

缺省值,在集群中列出所有可代理的主机(Endpoints)

LOGICAL_DNS

Envoy使用DNS添加主机,但如果DNS不再返回时,也不会丢弃

STRICT_DNS

Envoy将监控DNS,而每个匹配的A记录都将被认为是有效的

OriginalDst

 

EDS

Envoy调用一个外部的gRPC或REST服务查找被代理的主机(Endpoints)

自定义值

 


访问https://,一定要配置transport_socket,否则将报错upstream connect error or disconnect/reset before headers. reset reason: connection failure”。如果是访问http://,则不用配置transport_socketsni不是必须的。

5.4. 启动Envoy容器

在宿主机上,执行下列命令启动Envoy容器:

docker run -it --rm -v=/tmp:/tmp -p 8080:8080 -p 8081:8081 envoyproxy/envoy bash


可在宿主机上执行命令docker ps|grep envoy”,检查Envoy容器是否起来了。

5.5. 启动Envoy进程

Envoy容器中,执行下列命令拉起Envoy进程:

envoy -c /tmp/bd.yaml


启动成功可看到如下日志:

[info][config] [source/server/configuration_impl.cc:62] loading 0 static secret(s)

[info][config] [source/server/configuration_impl.cc:68] loading 1 cluster(s)

[info][config] [source/server/configuration_impl.cc:72] loading 1 listener(s)

[info][config] [source/server/configuration_impl.cc:97] loading tracing configuration

[info][config] [source/server/configuration_impl.cc:117] loading stats sink configuration

[info][main] [source/server/server.cc:549] starting main dispatch loop

[info][upstream] [source/common/upstream/cluster_manager_impl.cc:161] cm init: all clusters initialized

[info][main] [source/server/server.cc:528] all clusters initialized. initializing init manager

[info][config] [source/server/listener_manager_impl.cc:578] all dependencies initialized. starting workers

5.6. 体验效果

假设Envoy容器所在机器IP192.168.1.21,则访问等同于访问https://

6. 动态配置

6.1. 动态配置EDS

Envoy定时访问EDS服务取EDS配置。

6.1.1. Envoy配置文件

$ cat /tmp/bd.yaml

admin:

  access_log_path: /tmp/admin_access.log

  address:

    socket_address:

      protocol: TCP

      address: 0.0.0.0 # 管理地址

      port_value: 8081 # 管理端口

 

static_resources:

  listeners: # 监听器数组

  - name: listener_0 # 监听器

    address:

      socket_address:

        protocol: TCP

        address: 0.0.0.0 # 监听地址

        port_value: 8080 # 监听端口

    filter_chains: # 过滤器链

    - filters: # 过滤器数组

      - name: envoy.http_connection_manager # 过滤器名

        typed_config:

          "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager

          stat_prefix: ingress_http

          route_config: # 路由配置

            name: local_route # 路由配置名

            virtual_hosts: # 虚拟主机数组

            - name: local_service

              domains: ["*"] # 需代理的域名数组

              routes: # 定义路由

              - match:

                  prefix: "/" # 匹配规则

                route:

                  host_rewrite: # 将HOST重写为

                  cluster: bd_service # 下游集群名,通过它找到下游集群的配置

          http_filters:

          - name: envoy.router

  clusters: # 下游集群数组

  - name: bd_service # 下游集群名

    connect_timeout: 0.25s # 连接下游的超时时长

    type: eds

    lb_policy: ROUND_ROBIN # 负载均衡策略

    eds_cluster_config:

      eds_config:

        api_config_source:

          api_type: rest

          refresh_delay: "10s" # 动态一定要有这个配置

          cluster_names: [xds_cluster] # 这里并不提供静态的endpoints,需访问EDS服务得到

    transport_socket:

      name: envoy.transport_sockets.tls

      typed_config:

        "@type": type.googleapis.com/envoy.api.v2.auth.UpstreamTlsContext

        sni:

  - name: xds_cluster

    connect_timeout: 0.25s

    type: static

    lb_policy: ROUND_ROBIN

    load_assignment:

      cluster_name: xds_cluster

      endpoints:

      - lb_endpoints:

        - endpoint:

            address:

              socket_address:

                address: 127.0.0.1 # EDS的服务地址

                port_value: 2020 # EDS的服务端口

6.1.2. EDS服务源码

// 演示Envoy的动态EDS(Endpoint Discovery Service)

// 执行命令“go build eds.go ”,即生成可执行程序eds

package main

 

import (

  "encoding/json"

  "fmt"

  "net/http"

  "time"

)

 

// {

//   "version_info": "0",

//   "resources": [

//     {

//       "@type": "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment",

//       "cluster_name": "some_service",

//       "endpoints": [

//         {

//           "lb_endpoints": [

//             {

//               "endpoint": {

//                 "address": {

//                   "socket_address": {

//                     "address": "127.0.0.2",

//                     "port_value": 1234

//                   }

//                 }

//               }

//             }

//           ]

//         }

//       ]

//     }

//   ]

// }

 

type SocketAddress struct {

  Address string `json:"address"`

  PortValue int `json:"port_value"`

}

 

type Address struct {

  SocketAddress SocketAddress `json:"socket_address"`

}

 

type Endpoint struct {

  Address Address `json:"address"`

}

 

type LbEndpoint struct {

  Endpoint Endpoint `json:"endpoint"`

}

 

type LbEndpoints struct {

  LbEndpoints []LbEndpoint `json:"lb_endpoints"`

}

 

type Resource struct {

  Type string `json:"@type"`

  ClusterName string `json:"cluster_name"`

  Endpoints []LbEndpoints `json:"endpoints"`

}

 

type EDS struct {

  VersionInfo string `json:"version_info"`

  Resources []Resource `json:"resources"`

}

 

func DiscoveryEndpointsHandler(w http.ResponseWriter, r *http.Request) {

  // LbEndpoint

  var lb_endpoint1 LbEndpoint

  lb_endpoint1.Endpoint.Address.SocketAddress.Address = "180.101.49.12"

  lb_endpoint1.Endpoint.Address.SocketAddress.PortValue = 443

 

  // LbEndpoint

  var lb_endpoint2 LbEndpoint

  lb_endpoint2.Endpoint.Address.SocketAddress.Address = "180.101.49.11"

  lb_endpoint2.Endpoint.Address.SocketAddress.PortValue = 443

 

  // LbEndpoint

  var lb_endpoint3 LbEndpoint

  lb_endpoint3.Endpoint.Address.SocketAddress.Address = "14.215.177.38"

  lb_endpoint3.Endpoint.Address.SocketAddress.PortValue = 443

 

  // LbEndpoints

  var lb_endpoints LbEndpoints

  lb_endpoints.LbEndpoints = append(lb_endpoints.LbEndpoints, lb_endpoint1)

  lb_endpoints.LbEndpoints = append(lb_endpoints.LbEndpoints, lb_endpoint2)

  lb_endpoints.LbEndpoints = append(lb_endpoints.LbEndpoints, lb_endpoint3)

 

  // Resource

  var resource Resource

  resource.Type = "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment"

  resource.ClusterName = "bd_service"

  resource.Endpoints = append(resource.Endpoints, lb_endpoints)

 

  // EDS

  var eds EDS

  eds.VersionInfo = "0"

  eds.Resources = append(eds.Resources, resource)

 

  // struct to json

  jsonBytes, err := json.Marshal(eds)

  if err != nil {

    fmt.Println(err)

  }

 

  // json to string

  str := string(jsonBytes)

 

  // output json string

  now := time.Now()

  // 注意只能是“2006-01-02 15:04:05

  fmt.Printf("[%s] %s\n", now.Format("2006-01-02 15:04:05"), string(jsonBytes))

  fmt.Println(str)

  fmt.Fprintln(w, str)

}

 

func main() {

  http.HandleFunc("/v2/discovery:endpoints", DiscoveryEndpointsHandler)

  http.ListenAndServe("0.0.0.0:2020", nil)

}

6.1.3. 启动EDS进程

先将程序xds复制到容器中(以容器ID0779d56f4f65为例):

docker cp eds 0779d56f4f65:/tmp


进入容器:

docker container exec -it 0779d56f4f65 /bin/bash


然后,在Envoy容器中启动EDS进程:

/tmp/eds

6.1.4. 启动Envoy进程

Envoy容器中启动Envoy进程:

envoy -c /tmp/bd.yaml --service-cluster xds_cluster --service-node 1


阅读(2750) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~