Pontos Principais
- Desenvolvido pela Datawire, o Ambassador é um API gateway open source projetado especificamente para ser usado com o framework de orquestração de container Kubernetes.
- No núcleo, o Ambassador é um plano de controle sobre medida para ponta e configuração de API para gerenciamento do "plano de dados" do Envoy Proxy.
- O Envoy em si é um proxy de Camada 7 e barramento de comunicação nativo de nuvem usado para entrada na "ponta" e comunicação de rede entre serviços.
- Este artigo fornece um insight na criação do Ambassador, discute os desafios técnicos, lições aprendidas a partir da construção do plano de controle focado no desenvolvedor para gerenciar o tráfego de entrada em aplicações baseadas em microservices que são desenvolvidas no cluster Kubernetes.
- A migração do Ambassador para a configuração do Envoy v2 e APIs de Aggregated Discovery Service (ADS) foi uma longa e difícil jornada que necessitou de muitas discussões sobre arquitetura e projeto, e muita codificação, porém o feedback inicial da comunidade foi positivo.
Desenvolvido pela Datawire, o Ambassador é um API Gateway open source projetado especificamente para ser usado com o framework de orquestração de container no Kubernetes. No núcleo, o Ambassador é plano de controle costurado para configuração da API de gestão do "plano de dados" do Envoy Proxy. O Envoy em si é um proxy e barramento de comunicação de camada 7 usado para manipular a "porta de entrada" e comunicação de rede serviço a serviço. Embora originado da Lyft, o Envoy está rapidamente tornando de fato o proxy para redes modernas e pode ser visto sendo oferecido em praticamente todos fornecedores de nuvem pública, também como bem recomendado por grandes organizações de usuários finais como eBay, Pinterest e Groupon.
Este artigo faz uma introspecção na criação do Ambassador e discute os desafios técnicos e lições aprendidas na construção e um plano de controle para gerenciamento de tráfego de entrada em aplicações baseadas em microservices que são disponibilizadas em um cluster Kubernetes.
O Surgimento da Fábrica "Cloud Native": Kubernetes e Envoy
Embora a expressão "cloud native" está se tornando termos sem muito sentido, assim como "DevOps" e "microservices", está ganhando visualização em toda indústria de TI. De acordo com o Gartner, a receita prevista de serviços da nuvem pública no mundo todo em 2018 foi na casa dos $176 bilhões de dólares e isto pode crescer mais 15% no próximo ano. Apesar do mercado de nuvem pública está dominado por poucas empresas chaves que oferecem na maioria das vezes tecnologia proprietária (e aumenta, de forma controversa, o open source como serviço), a Cloud Native Computing Foudation (CNCF) foi fundada em 2015 pela Linux Foudation para fornecer um lugar para discussão e hospedagem de "componentes open source para estrutura completa do ambiente cloud native".
Possivelmente aprendendo a partir da jornada anterior empreendida pela comunidade OpenStack, o projetos iniciais apoiados pela CNCF foram menos ambiciosos no escopo, fornecendo abstrações mais claras (direcionadas) mais experimentadas nas necessidades do mundo real (ou inspiradas por necessidades do mundo real no caso do Kubernetes). Dois componentes de plataforma chaves que surgiram do CNCF são o framework de orquestração de container Kubernetes, sendo o Google o mantenedor original, e o proxy Envoy para porta de entrada e comunicação de rede entre serviços, sendo originalmente disponibilizado pela Lyft. Mesmo quando combinados, as duas tecnologias específicas não fornecem um serviço completo de Plataforma como Serviço (PaaS) que muitos desenvolvedores gostariam. Porém, o Kubernetes e Envoy estão começando a serem incluídos dentro de muitos serviços categorizados como PaaS.
Muitos fornecedores de PaaS e também muitos times de engenheiros de usuários finais, estão testando estas tecnologias como o "plano de dados" para sistemas de nuvem: Por exemplo, a parte "pesada" do sistema, como orquestração de containers e roteamento de tráfego baseado nos metadados da camada 7 (tais como cabeçalhos e URIs HTTP, metadados do protocolo MongoDB). Consequentemente, muita inovação e oportunidades de negócio estão focadas em criar um efetivo "plano de controle" que é onde o usuário final interage com a tecnologia, define a configuração que deve ser seguida pelo plano de dados, além de acompanhar métricas e logging.
O plano de controle do Kubernetes é muito focado em torno de uma série de APIs REST bem definidas (conhecida simplesmente como "a API do Kubernetes") e o associado 'kubectl'. A ferramenta CLI fornece uma abstração amigável para humanos sobre estas APIs. O plano de controle do Envoy v1 foi inicialmente criado em torno de configurações JSON combinadas em arquivos, com várias APIs pouco definidas que permitiam uma atualização seletiva. Estas APIs tem atualmente evoluído com a API v2 do Envoy, que fornece uma série de APIs baseadas em gRPC que são fortemente tipadas por meio de Protocol Buffers. Porém, inicialmente não havia uma ferramenta do Envoy similar ao kubectl do Kubernetes, e isso levou aos desafios na adoção por alguns times. Onde há desafios, entretanto, há também oportunidade na implementação de um plano de controle amigável para humanos.
"Malha de serviços de todas as coisas" … Talvez?
Se nós focamos no plano de controle de rede, pode ser difícil de deixar escapar o aparecimento do conceito de "malha de serviço". Tecnologias como Istio, Linkerd e Consul Connect são direcionadas para gerenciar o tráfego de cruzamento da ponta entre serviços (também conhecida como, "leste-oeste") dentro de sistemas de microservices. De fato, o Istio em si é efetivamente um plano de controle, que permite um usuário gerenciar o Envoy Proxy como o plano de dados básico para gerenciamento de tráfego de rede na camada 7 através da malha. O Linkerd oferece o próprio (agora baseado em Rust) proxy como o plano de dados de dados e o Consul Connect oferece juntamente um proxy sob medida, e mais recentemente, suportado pelo Envoy.
Arquitetura do Istio, mostrando o plano de dados do Envoy Proxy na parte de cima do diagrama e o plano de controle abaixo (imagem de cortesia da documentação do Istio)
O ponto importante para se ter com uma malha de serviço é a ideia fundamental de que nós exercemos tipicamente um alto nível de domínio e controle sobre ambas as partes que estão comunicando sobre a malha. Por exemplo, dois serviços podem ser construídos por departamento de engenharia separados, mas vão trabalhar para a mesma organização ou, um serviço pode ser uma aplicação de um terceiro, mas está implantado dentro dos limites confiáveis de rede (que podem alcançar múltiplos centros de dados ou Nuvens Virtuais Privadas, VPC). Aqui o tipo de operações irá basicamente concordar com padrões sensíveis de comunicação, e o time de serviços irá de forma independente configurar o roteamento entre dos serviços. Nestes cenários podemos não confiar totalmente em cada serviço e muito provavelmente iremos querer implementar medidas de proteção como rate limit e circuit breaking. Porém isso não acontece no gerenciamento de pontas ou tráfego de entrada ("norte-sul") que origina de fora dos limites de rede.
Tráfego de "entrada" no cluster geralmente provém de origens fora do controle direto
Qualquer comunicação que vem de fora da rede confiável pode ser uma ameaça, com motivações que são intencionais (por exemplo, crime cibernético) ou o contrário (por exemplo, uma biblioteca de um cliente que está quebrada dentro de um aplicativo móvel), e portanto devemos posicionar as defesas apropriadas. Aqui o time de operações vai especificar sistemas sensíveis por padrão, e também adaptá-los em tempo real de acordo com eventos externos. Além do rate limiting, provavelmente também queremos a possibilidade de configurar o vazamento tanto global quanto de uma API específica, por exemplo, se os serviços de backend ou armazenamento de dados ficou sobrecarregado, e também implementar uma proteção contra DDoS (que pode também ser definida por tempo ou região geográfica). O time de desenvolvimento de serviços também pode querer acessar a ponta para configurar o roteamento para uma nova API, testar ou lançar um novo serviço via ocultamento de tráfego ou canary releasing, ou outras tarefas.
Abrindo um rápido parênteses, para uma discussão adicional sobre a função dos API Gateways que às vezes causa algumas confusões, Christian Posta publicou recentemente um interessante post no blog, "API Gateways Are Going Through an Identity Crisis". Existem alguns outros artigos sobre a função de uma API Gateway durante a migração ou transformação digital do cloud/container, e como API gateways podem ser integrados com modernos padrões de entrega contínua.
Embora a primeira impressão destes casos de uso de malha de serviços e API Gateway e pontas possa parecer semelhantes, nós acreditamos que há algumas diferenças sutis, e outras não tão sutis assim, e isso impacta o projeto da associação entre serviços e planos de controle de pontas.
Projetando um Plano de Controle de Pontas
A escolha do plano de controle é extremamente influenciada pelo escopo do controle requerido e o papel(eis) das pessoas próximas que utilizam-o. Rafael Schloming falou sobre isso anteriormente na QCon San Francisco, onde comentou como os requisitos para centralizar e descentralizar o controle e também o estágio do ciclo de vida do desenvolvimento e operação em que um serviço está atualmente (protótipo, missão crítica, etc) impacta a implementação do plano de controle.
Como mencionado acima, pegando um proxy de ponta de plano de controle como exemplo, uma operação centralizada ou time de SRE pode querer especificar de maneira global e sensível padrões e proteções para todo tráfego de entrada. Porém, os (múltiplos) times de desenvolvimento do produto descentralizado trabalhando na linha de frente e lançando funcionalidades vão querer um controle mais granular do isolamento dos serviços e potencialmente (se abraçarem o modelo "liberdade e responsabilidade") a capacidade de passar por cima localmente das proteções globais.
Uma escolha consciente que foi feita pela comunidade do Ambassador foi que o papel primário como responsável do plano de controle do Ambassador é o desenvolvedor ou engenheiro de aplicação, e portanto o foco sobre o plano de controle foi sobre a configuração descentralizada. O Ambassador foi construído para ser específico ao Kubernetes, e dessa forma a escolha lógica para a definição da configuração de pontas foi direcionada às especificações do Kubernetes Service que foram incluídas dentro de arquivos YAML e carregadas no Kubernetes via kubectl.
As opções para especificar as configurações do Ambassador incluem o uso do objeto Kubernetes Ingress, uso das anotações do Kubernetes personalizadas ou definição de Definições de Recursos Personalizados (CRDs). Ultimamente o uso de anotações foi escolhido, como sendo simples e apresentaram um mínima curva de aprendizagem para o usuário final. Usar o Ingress pode ter sido a alternativa mais óbvia, mas infelizmente a especificação para o Ingress está presa em um beta perpétuo e devido a isso, os custos não pagam os benefícios.
Abaixo temos um exemplo de uma anotação do Ambassador que demonstra um simples roteamento para o endpoint de um serviço no Kubernetes Service:
kind: Service
apiVersion: v1
metadata:
name: my-service
annotations:
getambassador.io/config: |
---
apiVersion: ambassador/v0
kind: Mapping
name: my_service_mapping
prefix: /my-service/
service: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
A configuração dentro do getambassador.io/config deve ser autoexplicativa para qualquer pessoa que já tenha configurado um proxy de ponta, proxy reverso ou API Gateway. O tráfego enviado ao endpoint com prefixo definido será "mapeado" ou roteado para o "my-service" do serviço do Kubernetes. Como este artigo é focado no projeto e implementação do Ambassador, nós não vamos abranger todas as funcionalidades que podem ser configuradas, tais como roteamento, canarying (com integração com Prometheus para monitoramento) e rate limiting. Apesar do Ambassador ser focado para o desenvolvedor, há também um grande suporte para operadores e a centralização de configuração pode ser especificada para autenticação TLS/SNI, rastreamento e integração com malha de serviço.
Vamos agora voltar nossa atenção para evolução do Ambassador nos últimos dois anos.
Ambassador < v0.40: APIs Envoy v1, Templating e Reinicializações
O Ambassador em si é implantado dentro de um container como um serviço Kubernetes e usa anotações adicionais do Kubernetes Services como núcleo do modelo de configuração. Esta abordagem permite aos desenvolvedores de aplicação gerenciar o roteamento como parte do fluxo de trabalho no processo de definição de serviço do Kubernetes (possivelmente como parte de uma abordagem "GitOps"). Traduzindo a simples anotação de configuração do Ambassador em uma configuração válida do Envoy v1 que não é um tarefa trivial. Por definição, as configurações do Ambassador não são baseadas no mesmo modelo conceitual das configurações do Envoy, nós deliberadamente queríamos agregar e simplificar as operações e configurações, portanto, uma quantidade justa de lógica dentro do Ambassador traduz entre um conjunto de conceitos para a outra ponta.
De forma específica, quando um usuário aplica um manifesto do Kubernetes contendo anotações do Ambassador, ocorrem os seguintes passos:
- O Ambassador é notificado de forma assíncrona pela API do Kubernetes sobre uma mudança.
- O Ambassador traduz a configuração em uma representação intermediária abstrata (IR).
- Um arquivo de configuração do Envoy é gerado a partir do IR.
- O arquivo de configuração do Envoy é validado pelo Ambassador, usando o Envoy no modo de validação.
- Assumindo que o arquivo de configuração é válido, o Ambassador usa o mecanismo de reinicialização do Envoy para implantar a nova configuração drenando as conexões.
- O tráfego flui pelo processo reinicializado do Envoy.
Havia muitos benefícios com esta implementação inicial: o mecanismo envolvido era basicamente simples, a transformação da configuração do Ambassador na configuração do Envoy era confiável e a integração baseada em arquivo para reinicialização da integração com o Envoy também era segura.
Porém, havia também desafios notáveis com esta versão do Ambassador. Primeiro, embora a reinicialização fosse efetiva para a maioria dos casos de uso, não era rápida e alguns usuários (particularmente aqueles com grande implantação de aplicações) viram que isso estava limitando a frequência que poderiam mudar as configurações. A reinicialização pode também cortar conexões de forma imprópria, especialmente aquelas de longa vida como WebSockets ou gRPC streams.
O ponto crucial é que a primeira implementação da representação intermediária do Ambassador ao Envoy permitiu prototipação rápida, mas foi primitiva o suficiente para se provar muito difícil fazer mudanças substanciais. Enquanto isso era um sofrimento desde o início, tornou-se uma questão crítica quando o Envoy passou para Envoy v2 API. Estava claro que a API v2 poderia oferecer ao Ambassador muitos benefícios, como Matt Klein retratou em uma postagem intitulada, "The universal data plane API", incluindo acesso a novas funcionalidades e uma solução ao problema do corte de conexão observado acima, mas também ficou claro que a implementação existente da IR não era capaz de dar este salto.
Ambassador Atualmente: Envoy v2 APIs (com ADS), Representações Intermediárias e Teste com KAT
Em conversa com a comunidade do Ambassador, o time da Datawire (liderado por Flynn, engenheiro líder do Ambassador) aceitou fazer um redesenho interno do Ambassador em 2018. Isso aconteceu devido a dois pontos chaves. Primeiro, nós queríamos integrar o formato de configuração do Envoy v2, que poderia permitir o suporte de features como Indicação de Servidor de Nome (SNI), rate limit baseado em categoria e a autenticação melhorada. Segundo, nós também queríamos fazer uma validação semântica de configuração do Envoy muito mais robusta, devido ao aumento de complexidade (que aconteceu particularmente quando configura o Envoy para uso com deploy de aplicações de larga escala).
Nós iniciamos a reestruturação interna do Ambassador bem mais além das linhas de um compilador de múltiplos passos. A hierarquia de classes foi feita refletindo mais rigorosamente a separação de interesses entre os recursos de configuração do Ambassador, o IR e os recursos de configuração do Envoy. Partes do núcleo do Ambassador foram redesenhadas para facilitar contribuições da comunidade fora da Datawire. Nós decidimos por adotar esta abordagem por diversas razões. Primeiro, o Envoy Proxy é um projeto que muda rapidamente, e nós percebemos que era necessário uma abordagem onde uma pequena mudança aparente na configuração do Enovy não resulte em dias de reengenharia no Ambassador. Além disso, nós queríamos ser capazes de prover uma verificação semântica de configuração.
Como nós começamos trabalhando aproximando muito com o Envoy v2, um desafio de teste foi identificado imediatamente. Como muitas e muitas funcionalidades estavam sendo suportadas no Ambassador, mais e mais bugs apareceram, tratando as combinações de funcionalidades menos comuns, mas completamente válidas. Isso levou a criação de um novo requisito de teste que mostrou a necessidade do conjunto de teste do Ambassador ser retrabalhado para gerenciar automaticamente muitas combinações de funcionalidades, ao invés de confiar nos humanos para escrever cada teste individualmente. Além disso, nós queríamos que o conjunto de teste fosse rápido para maximizar a produtividade na construção.
Isso significou que, como parte da redefinição de arquitetura do Ambassador, nós também criamos o framework Kubernetes Acceptance Test (KAT). O KAT é um framework extensível de teste que:
- Disponibiliza um grupo de serviços (juntamente com o Ambassador) para um cluster de Kubernetes.
- Executa uma série de verificações em torno das APIs.
- Realiza um grupo de declarações sobre o resultados dessas verificações.
O KAT foi projetado para desempenho, carregando uma configuração inicial que posteriormente executa assincronamente todas as verificações em 3 passos com um cliente HTTP de alto desempenho. O direcionador de tráfego no KAT executa localmente usando uma das outras ferramentas opensource, Telepresence, que torna fácil a tarefa de depuração.
Com o framework de teste KAT no lugar, nós rapidamente avançamos em algumas questões como a configuração do Envoy v2 e a reinicialização rápida, que apresentou a oportunidade para mudar e usar as APIs de Aggregated Discovery Service (ADS) do Envoy. Isso eliminou por completo a obrigação de um processo de reinicialização uma vez que a configuração mudou, onde anteriormente tínhamos descoberto que poderia levar ao corte de conexões sobre altas cargas ou conexões de vida longa. Nós decidimos usar o plano de controle Envoy Go para fazer as interfaces do ADS. Isso introduziu, entretanto, uma dependência baseada no Go a base de código do Ambassador que antes era predominantemente baseada em Python.
Com um novo framework de teste, nova IR gerando configuração válida do Envoy v2, e o ADS, completamos as maiores mudanças arquiteturais no Ambassador 0.50. Agora, quando um usuário aplica um manifesto Kubernetes contendo anotações do Ambassador, ocorrem as seguintes etapas:
Logo antes de lançarmos nós acertamos mais uma questão. No Azure Kubernetes Services, as anotações de mudanças do Ambassador não demoravam muito para serem detectadas. Trabalhando com o time de engenharia altamente responsivo do AKS, nós fomos capazes de identificar a questão, isto é, o servidor de API do Kubernetes no AKS é exposto por um cadeia de proxies que estavam cortando algumas requisições. A solução apropriada para isso foi suportar chamada ao FQDN do servidor de API, que é fornecido por um webhook de mudança no AKS. Infelizmente, o suporte para essa funcionalidade não estava disponível no cliente Python oficial do Kubernetes. Portanto elegemos mudar para o cliente Go do Kubernetes, introduzindo mais outra dependência baseada em Go.
Principais Tópicos da Construção de um Plano de Controle do Envoy (De novo!)
Como Matt Klein mencionou na inauguração da EnvoyCon, com a atual popularidade do Envoy Proxy no domínio das tecnologias nativas para nuvem, geralmente é mais fácil perguntar quem não está usando Envoy. Nós sabemos que o Istio do Google tem ajudado a elevar o perfil do Envoy com os usuários do Kubernetes, e todos os outros maiores fornecedores de nuvem também estão investindo nesta ferramenta, por exemplo, no AWS App Mesh e Azure Service Fabric Mesh. Na EnvoyCon também ouvimos como alguns grandes atuantes como eBay, Pinterest e Groupon estão migrando para Envoy como proxy de ponta primário. Há também alguns outros planos de controle de proxy opensource baseados no Envoy que estão emergindo, como Istio Gateway, Solo.io Gloo e Heptio Contour. Poderíamos argumentar que o Envoy está realmente se tornando o plano de dados universal das comunicações nativas de nuvem, mas há muito trabalho ainda a ser feito no domínio do plano de controle.
Neste artigo abordamos como o time Datawire e a comunidade opensource do Ambassador migraram com sucesso o plano de controle de ponto do Ambassador para usar a configuração do Envoy v2 e APIs ADS. Aprendemos muito no processo da construção do Ambassador 0.50 e estamos prontos para destacar nossos principais tópicos a seguir:
- Kubernetes e Envoy são frameworks muito poderosos, mas também estão mudando os alvos de forma extremamente rápida, algumas vezes nada substitui a leitura do código fonte e conversa com os mantenedores (que felizmente são muito acessíveis!)
- As melhores bibliotecas suportadas no ecossistema do Kubernetes/Envoy estão escritas em Go. Embora nós amamos Python, porém tivemos que adotar esta linguagem para não sermos forçados a mantermos diversos componentes.
- Redesenhar uma suíte de teste é necessário para mudar o andamento do software. Frequentemente o custo real no redesenho está geralmente em portar os velhos testes para a nova implementação da suíte de teste.
- Desenhar e implementar um plano de controle efetivo para o caso de uso do proxy de ponta foi desafiante, e o feedback da comunidade opensource em torno do Kubernetes, Envoy e Ambassador foi extremamente útil.
A migração da configuração do Ambassador para o Envoy v2 e APIs ADS foi uma longa e difícil jornada que necessitou de muitas discussões sobre arquitetura e projeto, além de codificação, porém o feedback inicial dos resultados foi positivo. O Ambassador 0.50 está disponível agora, podemos pegá-lo para testar e compartilhar o feedback com a comunidade no canal do Slack ou Twitter.
Sobre o Autor
Daniel Bryant está liderando mudanças em organizações e tecnologia, e atualmente trabalha como um consultor freelance, de quem a Datawire é uma cliente. Seu trabalho atual inclui tornar possível a agilidade dentro de organizações através da introdução de melhores práticas encontrando e modelando técnicas, focando na importância da arquitetura no desenvolvimento ágil, e facilitando a integração/entrega contínua. A atual especialização técnica de Daniel está focada no ferramental ‘DevOps’, plataformas cloud/container e implementação de microservices. Também é um líder na London Java Community (LJC), e contribui para vários projetos open source, escreve para websites técnicos bem conhecidos como InfoQ, DZone e Voxxed, e apresenta regularmente em conferências internacionais como QCon, JavaOne e Devoxx.