Enviado. 1. Introdução

Saudações! Este é um pequeno artigo que responde às perguntas: “o que é enviado?”, “por que é necessário?” e "por onde começar?".

O que é isto

Envoy é um balanceador L4-L7 escrito em C++, focado em alto desempenho e disponibilidade. Por um lado, este é de alguma forma um análogo do nginx e do haproxy, comparável em desempenho a eles. Por outro lado, é mais orientado para a arquitetura de microsserviços e não possui funcionalidade pior que os balanceadores java e go, como zuul ou traefik.

Tabela de comparação de haproxy/nginx/envoy, não afirma ser a verdade absoluta, mas dá uma visão geral.

nginx
haproxy
enviado
Traefik

estrelas no github
11.2 mil/espelho
1.1 mil/espelho
12.4k
27.6k

escrito em
C
C
C + +
go

API
não
apenas soquete/empurrar
plano de dados/pull
puxar

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

Rastreamento aberto
plug-in externo
não
sim
sim

JWT
plug-in externo
não
sim
não

extensão
Lua/C
Lua/C
Lua/C++
não

Porquê

Este é um projeto jovem, faltam muitas coisas, algumas no início do alfa. Mas enviado, também por ser jovem, está se desenvolvendo rapidamente e já possui muitos recursos interessantes: configuração dinâmica, muitos filtros prontos, uma interface simples para escrever seus próprios filtros.
As áreas de aplicação decorrem disso, mas primeiro existem 2 antipadrões:

  • Recuo estático.

O fato é que neste momento enviado sem suporte de cache. Os caras do Google estão tentando isso consertar. A ideia será implementada uma vez em enviado todas as sutilezas (cabeçalhos zoológicos) da conformidade com RFC e, para implementações específicas, crie uma interface. Mas por enquanto nem é alfa, a arquitetura está em discussão, PR aberto (enquanto eu escrevia o artigo de RP, o RP congelou, mas este ponto ainda é relevante).

Por enquanto, use nginx para estática.

  • Configuração estática.

Você pode usá-lo, mas enviado Não foi para isso que foi criado. Os recursos em uma configuração estática não serão expostos. São muitos momentos:

Ao editar a configuração no yaml, você se enganará, repreenderá os desenvolvedores pela verbosidade e pensará que as configurações do nginx/haproxy, embora menos estruturadas, são mais concisas. Essa é a questão. A configuração do Nginx e Haproxy foi criada para edição manual e enviado para geração a partir do código. Toda a configuração está descrita em protobuf, gerá-lo a partir de arquivos proto é muito mais difícil de cometer erros.

Cenários de implantação Canary, b/g e muito mais são normalmente implementados apenas em uma configuração dinâmica. Não estou dizendo que isso não possa ser feito estaticamente, todos nós fazemos isso. Mas para isso é necessário usar muletas, em qualquer um dos balanceadores, em enviado incluindo

Tarefas para as quais o Envoy é indispensável:

  • Balanceamento de tráfego em sistemas complexos e dinâmicos. Isto inclui a malha de serviço, mas não é necessariamente a única.
  • A necessidade de funcionalidade de rastreamento distribuído, autorização complexa ou outra funcionalidade disponível em enviado pronto para uso ou convenientemente implementado, mas no nginx/haproxy você precisa estar cercado por lua e plug-ins duvidosos.

Ambos, se necessário, proporcionam alto desempenho.

Как это работает

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 estamos interessados ​​nisso apenas para compreender a estrutura.

configuração estática envoy.yaml

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
    # Comment out the following line to test on v6 networks
    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

Para qual problema estamos procurando uma solução? Você não pode simplesmente recarregar a configuração do balanceador de carga sob carga; surgirão “pequenos” problemas:

  • Validação de configuração.

A configuração pode ser grande, pode ser muito grande, se sobrecarregarmos tudo de uma vez, as chances de ocorrer um erro em algum lugar aumentam.

  • Conexões duradouras.

Ao inicializar um novo ouvinte, você precisa cuidar das conexões em execução no antigo; se as alterações ocorrerem com frequência e houver conexões de longa duração, você terá que procurar um compromisso. Olá, entrada do Kubernetes no nginx.

  • Verificações de saúde ativas.

Se tivermos verificações de integridade ativas, precisaremos verificar todas elas na nova configuração antes de enviar o tráfego. Se houver muitos upstreams, isso leva tempo. Olá haproxy.

Como isso é resolvido em enviadoAo carregar a configuração dinamicamente, de acordo com o modelo do pool, você pode dividi-la em partes separadas e não reinicializar a parte que não foi alterada. Por exemplo, um ouvinte cuja reinicialização é cara e raramente muda.

Configuração enviado (do arquivo acima) possui as seguintes entidades:

  • ouvinte — ouvinte pendurado em um ip/porta específico
  • host virtual - host virtual por nome de domínio
  • estrada - regra de equilíbrio
  • cacho — um grupo de upstreams com parâmetros de balanceamento
  • Ponto final — endereço da instância upstream

Cada uma dessas entidades e algumas outras podem ser preenchidas dinamicamente; para isso, a configuração especifica o endereço do serviço de onde a configuração será recebida. O serviço pode ser REST ou gRPC, gRPC é preferível.

Os serviços são nomeados respectivamente: LDS, VHDS, RDS, CDS e EDS. Você pode combinar configuração estática e dinâmica, com a limitação de que um recurso dinâmico não pode ser especificado em um recurso estático.

Para a maioria das tarefas basta implementar os três últimos serviços, eles são chamados de ADS (Aggregated Discovery Service), por Java e lá está uma implementação pronta do plano de dados gRPC na qual você só precisa preencher os objetos de sua fonte.

A configuração assume o seguinte formato:

configuração dinâmica envoy.yaml

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

Quando você executa enviado com esta configuração, ele se conectará ao plano de controle e tentará solicitar a configuração RDS, CDS e EDS. Como ocorre o processo de interação é descrito aqui.

Em resumo, enviado envia uma solicitação indicando o tipo de recurso solicitado, a versão e os parâmetros do nó. Em resposta, recebe um recurso e uma versão; se a versão no plano de controle não mudou, ele não responde.
Existem 4 opções de interação:

  • Um fluxo gRPC para todos os tipos de recursos, o status completo do recurso é enviado.
  • Fluxos separados, em perfeitas condições.
  • Um fluxo, estado incremental.
  • Fluxos separados, estado incremental.

O xDS incremental permite reduzir o tráfego entre o plano de controle e enviado, isso é relevante para configurações grandes. Mas isso complica a interação: a solicitação contém uma lista de recursos para cancelar e assinar.

Nosso exemplo usa ADS - um fluxo para RDS, CDS, EDS e modo não incremental. Para ativar o modo incremental, você precisa especificar api_type: DELTA_GRPC

Como a solicitação contém parâmetros de nó, podemos enviar diferentes recursos ao plano de controle para diferentes instâncias enviado, isso é conveniente para construir uma malha de serviço.

Aquecer

На enviado na inicialização ou ao receber uma nova configuração do plano de controle, o processo de aquecimento de recursos é iniciado. É dividido em aquecimento do ouvinte e aquecimento do cluster. O primeiro é lançado quando há alterações no RDS/LDS, o segundo quando há alterações no CDS/EDS. Isso significa que se apenas os upstreams mudarem, o ouvinte não será recriado.

Durante o processo de aquecimento, são esperados recursos dependentes do plano de controle durante o tempo limite. Se o tempo limite ocorrer, a inicialização não será bem-sucedida e o novo ouvinte não começará a escutar na porta.
Ordem de inicialização: EDS, CDS, verificação de saúde ativa, RDS, LDS. Com as verificações de integridade ativas habilitadas, o tráfego subirá somente após uma verificação de integridade bem-sucedida.

Se o ouvinte foi recriado, o antigo entra no estado DRAIN e será excluído após todas as conexões serem fechadas ou o tempo limite expirar --drain-time-s, padrão 10 minutos.

Para ser continuado.

Fonte: habr.com

Adicionar um comentário