Envoy. 1. Introdução

Olá! Este é um pequeno artigo que responde perguntas como: "o que é o envoy?", "para que serve?" e "por onde começar?".

O que é

O Envoy é um balanceador L4-L7 escrito em C++, projetado para alto desempenho e disponibilidade. De certa forma, é um análogo ao nginx e haproxy, comparável a eles em termos de desempenho. Por outro lado, ele é mais voltado para arquiteturas de microsserviços e possui funcionalidades que não ficam atrás de balanceadores em java e go, como zuul ou traefik.

Tabela comparativa haproxy/nginx/envoy, que não reivindica verdade absoluta, mas oferece uma visão geral.

nginx
haproxy
envoy
traefik.

estrelas no github
11.2k/mirror
1.1k/mirror
12.4k
27.6k

escrito em
C
C
C++
go

API
não
socket only/push
dataplane/pull
pull

verificação de saúde ativa
não
sim
sim
sim

Open tracing
plugin externo
não
sim
sim

JWT
plugin externo
não
sim
não

Extensão
Lua/C
Lua/C
Lua/C++
não

Por que

Este é um projeto jovem, com muita coisa ainda inexistente, algo em fase inicial. Mas envoy, incluindo o fato de ser jovem, está se desenvolvendo rapidamente e já possui muitas funcionalidades interessantes: configuração dinâmica, muitos filtros prontos, uma interface simples para escrever seus próprios filtros.
Isso traz áreas de aplicação, mas para começar, dois anti-padrões:

  • Entrega de estáticos.

Acontece que neste momento em envoy não há suporte a cache. O pessoal do google está tentando resolver isso.. A ideia é implementar uma vez todas as nuances (zoológico de headers) de conformidade com o RFC, e para implementações específicas, criar uma interface. Mas por enquanto isso ainda não é nem uma versão alpha, a arquitetura está em discussão. envoy está aberto (enquanto eu escrevia este artigo, um PR foi mesclado, mas este ponto ainda é relevante). PR Enquanto isso, use nginx para estáticos.

Configuração estática.

  • Você pode usá-la, mas

não foi criado para isso. As capacidades na configuração estática não serão totalmente aproveitadas. Existem muitos pontos: envoy Ao editar a configuração em yaml, você cometerá erros, xingará os desenvolvedores pela verbosidade e pensará que as configurações do nginx/haproxy, embora menos estruturadas, são mais concisas. Essa é a essência. A configuração Nginx e Haproxy foi criada para ser editada manualmente, enquanto a

foi feita para geração a partir de código. Toda a configuração é descrita em envoy , gerando-a por arquivos proto é muito mais difícil cometer erros. protobufCenários de canary, deployment b/g e muitas outras funcionalidades são normalmente implementadas apenas em configuração dinâmica. Não estou dizendo que isso não pode ser feito em estático, nós fazemos tudo isso. Mas para isso, é preciso contornar as limitações, em qualquer um dos balanceadores, em

inclusive. envoy Tarefas nas quais o Envoy é indispensável:

Tarefas nas quais o Envoy é indispensável:

  • Balanceamento de tráfego em sistemas complexos e dinâmicos. Isso inclui service mesh, mas não é necessariamente apenas isso.
  • A necessidade de funcionalidade de rastreamento distribuído, autorização complexa ou outras que estejam disponíveis envoy de forma nativa ou que possam ser implementadas facilmente, enquanto no nginx/haproxy é necessário se cercar de lua e plugins duvidosos.

Ambos devem garantir alta performance quando necessário.

Como funciona

O Envoy é distribuído em binários apenas como uma imagem docker. A imagem já contém um exemplo de configuração estática. Mas nos interessa apenas para entender a estrutura.

envoy.yaml configuração estática

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address:
        protocol: TCP
        address: 0.0.0.0
        port_value: 10000
    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: www.google.com
                  cluster: service_google
          http_filters:
          - name: envoy.router
  clusters:
  - name: service_google
    connect_timeout: 0.25s
    type: LOGICAL_DNS
    # Comente a linha seguinte para testar em redes v6
    dns_lookup_family: V4_ONLY
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: service_google
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: www.google.com
                port_value: 443
    transport_socket:
      name: envoy.transport_sockets.tls
      typed_config:
        "@type": type.googleapis.com/envoy.api.v2.auth.UpstreamTlsContext
        sni: www.google.com

Configuração dinâmica

Qual problema estamos tentando resolver? Não podemos simplesmente reiniciar a configuração do balanceador sob carga, surgirão "pequenos" problemas:

  • Validação da configuração.

A configuração pode ser grande, pode ser muito grande, se a carregarmos toda de uma vez, as chances de haver erros aumentam.

  • Conexões de longa duração.

Ao inicializar um novo listener, é preciso cuidar das conexões que estão sendo mantidas no antigo; se as mudanças ocorrem frequentemente e há conexões de longa duração, será necessário buscar um compromisso. Olá, kubernetes ingress no nginx.

  • Health checks ativos.

Se tivermos health checks ativos, devemos verificar todos no novo config antes de enviar o tráfego. Se houver muitos upstreams, isso leva tempo. Olá, haproxy.

Como isso é resolvido em envoy, carregando a configuração dinamicamente, pelo modelo pool, ela pode ser dividida em partes separadas e evitar a reinicialização da parte que não foi alterada. Por exemplo, um listener, cuja reinicialização é cara, é alterado raramente.

Configuração envoy (do arquivo acima) tem as seguintes entidades:

  • listener — listener associado a um determinado ip/porta
  • virtual host — host virtual pelo nome do domínio
  • rota — regra de balanceamento
  • cluster — grupo de upstreams com parâmetros de balanceamento
  • endpoint — endereço da instância do upstream

Cada uma dessas entidades, além de algumas outras, pode ser preenchida dinamicamente. Para isso, na configuração, é especificado o endereço do serviço de onde a configuração será obtida. O serviço pode ser REST ou gRPC, sendo preferível usar gRPC.

Os serviços são denominados, respectivamente: LDS, VHDS, RDS, CDS e EDS. É possível combinar configuração estática e dinâmica, com a limitação de que um recurso dinâmico não pode ser especificado em uma configuração estática.

Para a maioria das tarefas, é suficiente implementar os últimos três serviços, que são chamados de ADS (Aggregated Discovery Service), para java e o Go tem uma implementação pronta do gRPC dataplane na qual é suficiente apenas preencher os objetos a partir de sua fonte.

A configuração adquire a seguinte forma:

envoy.yaml configuração dinâmica

dynamic_resources:
  ads_config:
    api_type: GRPC
    grpc_services:
      envoy_grpc:
        cluster_name: xds_clr
  cds_config:
    ads: {}
static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address:
        protocol: TCP
        address: 0.0.0.0
        port_value: 10000
    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
          rds:
            route_config_name: local_route
            config_source:
              ads: {}
          http_filters:
          - name: envoy.router
  clusters:
  - name: xds_clr
    connect_timeout: 0.25s
    type: LOGICAL_DNS
    dns_lookup_family: V4_ONLY
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: xds_clr
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: xds
                port_value: 6565

Ao iniciar envoy com essa configuração, ele se conectará ao control-plane e tentará solicitar a configuração RDS, CDS e EDS. Como o processo de interação ocorre está descrito aqui.

Se resumidamente, envoy ele envia uma solicitação, indicando o tipo de recurso solicitado, a versão e os parâmetros do nó. Em resposta, ele recebe o recurso e a versão; se a versão no control-plane não mudou, ele não responde.
Existem 4 opções de interação:

  • Uma transmissão gRPC para todos os tipos de recursos, enviando o estado completo do recurso.
  • Streams separados, estado completo.
  • Um stream, estado incremental.
  • Streams separados, estado incremental.

Incremental xDS permite reduzir o tráfego entre o control-plane e envoy, isso é relevante para configurações grandes. Mas complica a interação, na consulta é enviado uma lista de recursos para desinscrever e inscrever.

No nosso exemplo, usamos ADS — um stream para RDS, CDS, EDS e modo não incremental. Para ativar o modo incremental, é preciso especificar api_type: DELTA_GRPC

Dado que na consulta há parâmetros de nó, podemos enviar recursos diferentes para diferentes instâncias no control-plane envoy, isso é conveniente para construir uma service mesh.

Aquecimento

Sobre envoy ao iniciar ou ao receber uma nova configuração do control-plane, inicia-se o processo de aquecimento dos recursos. Ele é dividido em aquecimento de listener e aquecimento de cluster. O primeiro é ativado ao ocorrer alterações no RDS/LDS, o segundo no CDS/EDS. Isso significa que se apenas os upstreams forem alterados, o listener não será recriado.

Durante o aquecimento, espera-se recursos dependentes do control-plane dentro de um timeout. Se o timeout expirar, a inicialização não será bem-sucedida, o novo listener não começará a escutar a porta.
A ordem de inicialização: EDS, CDS, verificação de saúde ativa, RDS, LDS. Com verificações de saúde ativas ativadas, o tráfego só irá para o upstream após uma verificação de saúde bem-sucedida.

Se o listener for recriado, o antigo se tornará DRAIN e será removido após o fechamento de todas as conexões ou expiração do timeout. --drain-time-s, por padrão 10 minutos.

Continua.

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster