Hoje, temos o orgulho de abrir o código do Pingora, a estrutura Rust que usamos para criar serviços que alimentam uma parte significativa do tráfego na Cloudflare. O Pingora é lançado sob a licença Apache versão 2.0.
Conforme mencionado em nosso post do blog anterior, o Pingora é uma estrutura desenvolvida em Rust multithread assíncrona que nos ajuda a construir serviços de proxy HTTP. Desde o nosso último post no blog, o Pingora atendeu quase um quatrilhão de solicitações de internet em toda a nossa rede global.
Estamos abrindo o código do Pingora para ajudar a construir uma internet melhor e mais segura além de nossa própria infraestrutura. Queremos fornecer ferramentas, ideias e inspiração para nossos clientes, usuários e outros para que criem sua própria infraestrutura de internet usando uma estrutura segura para a memória. Ter essa estrutura é especialmente essencial dada a crescente conscientização sobre a importância da segurança da memória em todo o setor e no governo dos EUA. Sob esse objetivo comum, estamos colaborando com o projeto Prossimo do Internet Security Research Group (ISRG) para ajudar a promover a adoção do Pingora na infraestrutura mais crítica da internet.
Em nosso post do blog anterior, discutimos por que e como criamos o Pingora. Neste, falaremos sobre por que e como você pode usar o Pingora.
O Pingora fornece elementos básicos não apenas para proxy, mas também para clientes e servidores. Juntamente com esses componentes, também fornecemos algumas bibliotecas de utilitários que implementam uma lógica comum, como contagem de eventos, tratamento de erros e armazenamento em cache.
O que vem na caixa
O Pingora fornece bibliotecas e API para criar serviços sobre HTTP/1 e HTTP/2, TLS ou apenas TCP/UDP. Como proxy, ele é compatível com HTTP/1 e HTTP/2 de ponta a ponta, gRPC e faz proxy com websocket. (A compatibilidade com HTTP/3 está no roteiro). Ele também vem com balanceamento de carga e estratégias de failover personalizáveis. Para fins de conformidade e segurança, ele é compatível com as bibliotecas OpenSSL e BoringSSL, usadas comumente, que vêm com conformidade com FIPS e criptografia pós-quântica.
Além de fornecer esses recursos, o Pingora oferece filtros e callbacks para permitir que seus usuários personalizem totalmente como o serviço deve processar, transformar e encaminhar as solicitações. Essas API serão especialmente familiares para os usuários de OpenResty e NGINX, já que muitos mapeiam intuitivamente para os callbacks "*_by_lua" do OpenResty.
Operacionalmente, o Pingora oferece reinicializações aprimoradas sem tempo de inatividade para se atualizar sem deixar de atender uma única solicitação recebida. O Syslog, o Prometheus, o Sentry, o OpenTelemetria e outras ferramentas essenciais de observabilidade também são facilmente integrados ao Pingora.
Quem pode se beneficiar do Pingora
Você deve considerar o Pingora se:
Segurança for a sua principal prioridade: o Pingora é uma alternativa mais segura para a memória dos serviços escritos em C/C++. Embora alguns possam argumentar sobre a segurança de memória entre linguagens de programação, pela nossa experiência prática, somos muito menos propensos a cometer erros de codificação que levam a problemas de segurança da memória. Além disso, à medida que gastamos menos tempo lutando com esses problemas, ficamos mais produtivos na implementação de novos recursos.
Seu serviço depende do desempenho: o Pingora é rápido e eficiente. Conforme explicado em nosso post do blog anterior, economizamos muitos recursos de CPU e memória graças à arquitetura multithread do Pingora. A economia de tempo e recursos pode ser atraente para cargas de trabalho sensíveis ao custo e/ou velocidade do sistema.
Seu serviço requer ampla personalização: as APIs fornecidas pela estrutura proxy do Pingora é altamente programável. Para os usuários que desejam criar um gateway ou balanceador de carga personalizado e avançado, o Pingora oferece maneiras poderosas e simples de implementá-lo. Fornecemos exemplos na próxima seção.
Vamos criar um balanceador de carga
Vamos explorar a API programável do Pingora criando um balanceador de carga simples. O balanceador de carga selecionará entre https://1.1.1.1/ e https://1.0.0.1/ para ser o upstream em um estilo round-robin.
Primeiro, vamos criar um proxy HTTP em branco.
Qualquer objeto que implemente a característica ProxyHttp
(semelhante ao conceito de uma interface em C++ ou Java) é um proxy HTTP. O único método necessário é upstream_peer()
, que é chamado para todas as solicitações. Essa função deve retornar um HttpPeer
que contém o IP de origem ao qual se conectar e como se conectar a ele.
pub struct LB();
#[async_trait]
impl ProxyHttp for LB {
async fn upstream_peer(...) -> Result<Box<HttpPeer>> {
todo!()
}
}
A seguir, vamos implementar a seleção round-robin. A estrutura do Pingora já fornece o LoadBalancer
com algoritmos de seleção comuns, como round robin e hashing, então vamos usá-los. Se o caso de uso exigir uma lógica de seleção de servidor mais sofisticada ou personalizada, os usuários podem simplesmente implementá-la nessa função.
Como estamos nos conectando a um servidor HTTPS, o SNI também precisa ser configurado. Certificados, tempos limite e outras opções de conexão também podem ser definidos aqui no objeto HttpPeer, se necessário.
pub struct LB(Arc<LoadBalancer<RoundRobin>>);
#[async_trait]
impl ProxyHttp for LB {
async fn upstream_peer(...) -> Result<Box<HttpPeer>> {
let upstream = self.0
.select(b"", 256) // hash doesn't matter for round robin
.unwrap();
// Set SNI to one.one.one.one
let peer = Box::new(HttpPeer::new(upstream, true, "one.one.one.one".to_string()));
Ok(peer)
}
}
Para terminar, vamos colocar o serviço em ação. Neste exemplo, codificamos os IPs do servidor de origem. Em cargas de trabalho reais, os IPs do servidor de origem também podem ser descobertos dinamicamente quando o upstream_peer()
é chamado ou está em segundo plano. Depois que o serviço é criado, apenas instruímos o serviço LB para escutar 127.0.0.1:6188. No final, criamos um servidor Pingora e o servidor será o processo que executa o serviço de balanceamento de carga.
Vamos experimentar:
fn main() {
let mut upstreams = LoadBalancer::try_from_iter(["1.1.1.1:443", "1.0.0.1:443"]).unwrap();
let mut lb = pingora_proxy::http_proxy_service(&my_server.configuration, LB(upstreams));
lb.add_tcp("127.0.0.1:6188");
let mut my_server = Server::new(None).unwrap();
my_server.add_service(lb);
my_server.run_forever();
}
Podemos ver que o proxy está funcionando, mas o servidor de origem nos rejeita com um 403. Isso ocorre porque nosso serviço simplesmente faz proxy do cabeçalho host, 127.0.0.1:6188, definido pelo curl, o que perturba o servidor de origem. Como fazemos para que o proxy corrija isso? Isso pode ser feito simplesmente adicionando outro filtro chamado upstream_request_filter
. Esse filtro é executado em todas as solicitações após o servidor de origem ser conectado e antes de qualquer solicitação HTTP ser enviada. Podemos adicionar, remover ou alterar os cabeçalhos de solicitação http nesse filtro.
curl 127.0.0.1:6188 -svo /dev/null
> GET / HTTP/1.1
> Host: 127.0.0.1:6188
> User-Agent: curl/7.88.1
> Accept: */*
>
< HTTP/1.1 403 Forbidden
Vamos tentar de novo:
async fn upstream_request_filter(…, upstream_request: &mut RequestHeader, …) -> Result<()> {
upstream_request.insert_header("Host", "one.one.one.one")
}
Desta vez funciona. O exemplo completo pode ser encontrado aqui.
curl 127.0.0.1:6188 -svo /dev/null
< HTTP/1.1 200 OK
Abaixo está um diagrama muito simples de como essa solicitação flui através do callback e do filtro que usamos neste exemplo. A estrutura de proxy do Pingora atualmente fornece mais filtros e callbacks em diferentes estágios de uma solicitação para permitir que os usuários modifiquem, rejeitem, encaminhem e/ou registrem a solicitação (e a resposta).
Nos bastidores, a estrutura de proxy do Pingora cuida do pool de conexões, handshakes TLS , leitura, gravação, análise de solicitações e quaisquer outras tarefas comuns de proxy para que os usuários possam se concentrar na lógica que é importante para eles.
Código aberto, presente e futuro
O Pingora é uma biblioteca e um conjunto de ferramentas, não um binário executável. Em outras palavras, o Pingora é o motor que move um carro, não o carro em si. Embora o Pingora esteja pronto para uso em produção no setor, entendemos que muitas pessoas querem um serviço web completo, pronto para uso, com opções de configuração com pouco ou nenhum código. A criação desse aplicativo sobre o Pingora será o foco de nossa colaboração com o ISRG para expandir o alcance do Pingora. Não perca os anúncios futuros sobre esse projeto.
Outros pontos a serem lembrados:
Hoje, a estabilidade das APIs não é garantida. Embora tentemos minimizar a frequência com que fazemos alterações significativas, ainda nos reservamos o direito de adicionar, remover ou alterar componentes como filtros de solicitação e resposta à medida que a biblioteca evolui, especialmente durante o período pré-1.0.
O suporte para sistemas operacionais não baseados em Unix não está no roteiro no momento. Não temos um plano imediato para dar suporte a esses sistemas, embora isso possa mudar no futuro.
Como contribuir
Sinta-se à vontade para relatar erros, problemas de documentação ou solicitações de recursos em nosso rastreador de problemas do GitHub. Antes de abrir uma solicitação pull, sugerimos que você dê uma olhada no nosso guia de contribuição.
Conclusão
Neste blog, anunciamos o código aberto da nossa estrutura Pingora. Mostramos que as entidades e a infraestrutura da internet podem se beneficiar da segurança, do desempenho e da capacidade de personalização do Pingora. Também demonstramos como é fácil usar o Pingora e como ele é personalizável.
Não importa se você está criando serviços da web de produção ou experimentando tecnologias de rede, esperamos que encontre valor no Pingora. Foi uma longa jornada, mas compartilhar esse projeto com a comunidade de código aberto foi uma meta desde o início. Gostaríamos de agradecer à comunidade Rust, pois o Pingora é construído com muitas caixas Rust de código aberto excelentes. Mudar para uma internet segura para a memória pode parecer uma jornada impossível, mas esperamos que você se junte a nós nessa jornada.