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 . 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). 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. Cená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.comConfiguraçã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 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: 6565Ao 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 .
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
