Conexão · Interrompida

Algo não carregou

Parte desta página não chegou até você. Recarregue para tentar novamente — se persistir, verifique sua conexão.

Pular para o conteúdo principal
Engineering12 min de leitura

O que o APISIX no Anel Trial Realmente Oferece: Notas sobre seu Plano de Controle Baseado em etcd

O Volume 34 do Thoughtworks Technology Radar moveu o Apache APISIX para o anel Trial. Passei uma semana investigando a documentação, o código-fonte e alguns bug reports para me convencer de que a alegação de roteamento dinâmico baseado em etcd era real — e para pesar o custo operacional que ela esconde. Estas são minhas notas sobre o mecanismo de watch, o precipício de escala de conexões nos 263 long polls, e quando eu recorreria ou não ao APISIX em 2026.

Todos os Posts
2/4

O Volume 34 do Thoughtworks Technology Radar, publicado em abril de 2026, moveu o Apache APISIX de Assess para o anel Trial do quadrante de Plataformas. A entrada do radar o posiciona como um sério candidato para substituir controllers de ingress legados baseados em Nginx, credita sua "arquitetura totalmente dinâmica e plugável" e aponta o uso de etcd como o mecanismo que remove o custo de latência dos reloads de configuração. Passei uma semana investigando essa alegação a partir das minhas próprias anotações, principalmente para me convencer se a história do roteamento dinâmico é real, e se a dependência de etcd é do tipo que eu rodaria com tranquilidade.

As duas respostas são sim, com ressalvas. Este post é o que eu gostaria de ter lido antes de começar.

A conclusão central que estou mantendo fixada acima da minha mesa: um plano de controle baseado em etcd compra para o APISIX propagação de configuração em milissegundos, sem swap de workers, mas o custo é um cluster etcd que você precisa rodar, monitorar e observar como um banco de dados primário, porque uma única regressão em conexão watch pode transformar uma chamada admin de 0,16 segundo em uma de 7 segundos.

O que "sem nginx -s reload" realmente significa dentro de um worker

A parte interessante do APISIX não é que ele é construído sobre Nginx e ngx_lua via OpenResty. É o que eles fizeram com o pipeline de configuração. O controller de ingress clássico do Nginx renderiza um nginx.conf a partir de recursos do Kubernetes e, em seguida, dispara nginx -s reload. Um reload cria novos processos worker, deixa os antigos drenarem e os substitui. Em um cluster saudável isso é invisível. Em um caminho quente com conexões TLS de longa duração, mudanças frequentes de rota ou milhares de upstreams, é um brownout que você precisa agendar.

O APISIX nunca faz reload. O mecanismo que ele usa em vez disso é pequeno o suficiente para ser descrito em uma frase e vale a pena entender antes da adoção.

Cada processo worker do Nginx, na inicialização, agenda uma função Lua via ngx.timer.at. Essa função abre um watch HTTP de longa duração contra um prefixo de chave do etcd — /apisix/routes/, /apisix/services/, /apisix/upstreams/ e alguns outros. Quando o etcd reporta uma mudança, o worker muta sua própria tabela Lua em processo e um cache LRU que o caminho da requisição lê em cada match. Não há estado compartilhado entre workers, sem reload, sem fork. A nova rota está ativa no momento em que a escrita no cache retorna.

A história da propagação é direta de verificar. O mesmo long poll que inscreve o APISIX em um diretório do etcd é exposto pela API v3 do etcd como Watch. Escrevi um pequeno programa em Go usando go.etcd.io/etcd/client/v3 que abre o mesmo tipo de watch contra o mesmo prefixo que o APISIX usa, e então escreve uma rota falsa via API KV do etcd. A mudança aparece no canal do watch em milissegundos de um único dígito em um cluster local. Apontar o mesmo programa para uma instância real do APISIX e escrever pela sua API admin dá o mesmo resultado.

Aqui está o programa, arquivo único, executável como está escrito:

go
// main.go — observa o mesmo prefixo etcd que o Apache APISIX assina,
// então escreve uma rota falsa e mede a latência de propagação.
//
// Suba um etcd local primeiro, ex.:
//   docker run -d -p 2379:2379 quay.io/coreos/etcd:v3.5.13 \
//     etcd --advertise-client-urls=http://0.0.0.0:2379 \
//          --listen-client-urls=http://0.0.0.0:2379
//
// Depois: go run main.go
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	clientv3 "go.etcd.io/etcd/client/v3"
)

const prefix = "/apisix/routes/"

func main() {
	cli, err := clientv3.New(clientv3.Config{
		Endpoints:   []string{"localhost:2379"},
		DialTimeout: 2 * time.Second,
	})
	if err != nil {
		log.Fatalf("dial etcd: %v", err)
	}
	defer cli.Close()

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	watch := cli.Watch(ctx, prefix, clientv3.WithPrefix(), clientv3.WithPrevKV())

	go func() {
		time.Sleep(200 * time.Millisecond)
		started := time.Now()
		key := prefix + "demo-route-1"
		val := `{"uri":"/hello","upstream":{"nodes":{"127.0.0.1:8080":1}}}`
		if _, err := cli.Put(ctx, key, val); err != nil {
			log.Fatalf("put: %v", err)
		}
		fmt.Printf("put took %v\n", time.Since(started))
	}()

	for resp := range watch {
		for _, ev := range resp.Events {
			fmt.Printf("event=%s key=%s value_len=%d at=%s\n",
				ev.Type, string(ev.Kv.Key), len(ev.Kv.Value),
				time.Now().Format(time.RFC3339Nano))
		}
	}
}

Execute com go run main.go contra um etcd local na porta 2379. No meu laptop o put retorna em cerca de 3 ms e o evento de watch chega em cerca de 5 ms, ambos ponta a ponta incluindo o round-trip. Essa é a mesma primitiva sobre a qual o gateway está apoiado. Uma vez que tive essa imagem na cabeça, o enquadramento do radar — milissegundos para o APISIX, segundos para o Kong, que faz polling em um banco de dados a cada poucos segundos — deixou de soar como marketing e começou a soar como arquitetura.

As linhas não-óbvias são as opções WithPrefix e WithPrevKV. WithPrefix amplia o watch para toda chave sob /apisix/routes/, que é como o APISIX fica sabendo de uma nova rota criada com qualquer nome. WithPrevKV faz com que o evento inclua o valor anterior, o que permite ao worker decidir entre um insert, um update e um delete sem uma leitura extra. Pule qualquer um deles e você reproduz uma classe de bugs que aparecem como "a rota foi atualizada, mas o gateway esqueceu os upstreams antigos".

A próxima imagem que vale carregar é como isso se parece no nível de cluster do gateway, porque o custo do design vive ali.

Quero destacar um diagrama de sequência neste ponto, porque o fan-out é difícil de manter na cabeça em prosa. O que procurar na figura quando ela estiver no lugar: cada processo worker dentro de cada nó de gateway mantém sua própria conexão HTTP/1.1 de longa duração com o etcd. O número de streams de watch escala como nós de gateway vezes processos worker vezes prefixos observados, não como nós de gateway sozinhos.

Onde o design começa a morder: escala de conexões watch

Esta foi a parte da minha leitura que mais me surpreendeu.

O APISIX conversa com o etcd através do lua-resty-etcd, uma biblioteca cliente do OpenResty. Até 2023, o gateway abria uma conexão HTTP/1.1 long-poll separada por tipo de recurso observado por worker. Com quatro workers por nó, seis prefixos observados e um punhado de nós, você já está em dezenas de conexões de longa duração por peer do etcd. Com algumas dezenas de nós, você está nos milhares baixos.

O modo de falha que o projeto documentou no FAQ é nítido. Em um nó etcd com 263 conexões watch ativas, uma requisição HTTP que deveria ter levado 0,159 segundos em um peer tranquilo levou 7 segundos. Isso é uma desaceleração de aproximadamente quarenta vezes para o caminho admin que os operadores acessam primeiro quando algo está errado. Há também um limite documentado do servidor HTTP/2 do Go de 250 streams concorrentes por conexão que os peers do etcd herdam, o que significa que um cluster APISIX dimensionado para tráfego pode silenciosamente cruzar um teto de plano de controle que não tem nada a ver com tráfego.

O PR #9456, mergeado em 2023, move o design para uma única conexão long-poll por worker que multiplexa eventos para todos os prefixos observados via streaming chunked. Um follow-up, o PR #9837, levou o caminho de watch baseado em HTTP a aproximadamente a mesma eficiência do gRPC ao consolidar todos os watches de recursos em uma única conexão. Essa é a forma certa. Também permanece um caminho opt-in use_grpc: true em alguns branches, o que vale a pena verificar em qualquer versão que você realmente fizer deploy. Na minha própria leitura, o fan-out por worker ainda é o modo de deployment dominante que vejo na prática em meados de 2026, então planeje a capacidade em torno dele em vez do caminho otimizado.

Dois hábitos operacionais que agora considero inegociáveis em uma instalação do APISIX:

  • Trate o etcd como um banco de dados primário. Rode-o em nós dedicados com seu próprio SLO, discos amigáveis a fsync, observabilidade separada e uma estratégia de backup que seja exercitada. A entrada do radar silenciosamente assume isso e a maioria dos posts de blog sobre adoção pula essa parte.
  • Alerte sobre contagens de conexões watch por peer do etcd, não apenas sobre taxas de erro de RPC. Um peer que silenciosamente acumula conexões parece saudável em um dashboard de CPU bem até as chamadas admin começarem a dar timeout. O trip wire que escolhi é duas vezes a contagem em estado estacionário.

Há dentes mais afiados também. Existe um bug real, GitHub issue #12580 (aberta em setembro de 2025), no qual o APISIX 3.13.0, 3.12.0 e 3.9.1 trava indefinidamente durante a fase init_etcd, nunca completando a sincronização de rotas. Essa é uma falha dura que se disfarça como uma inicialização lenta. A mitigação é deselegante: fixe um release conhecido como bom do APISIX, rode um smoke test que bloqueia no primeiro fetch de rota bem-sucedido e segure o roll-forward até que esse smoke test passe em staging.

Há também uma cauda longa de timeouts da API admin que aparecem apenas após várias horas de uptime, rastreados de volta a vazamentos de conexão do lado do cliente contra o etcd. A assinatura é um nó APISIX cujo data path permanece saudável enquanto cada chamada admin eventualmente excede seu deadline. Se o caminho admin também é onde seu pipeline de CI aplica mudanças de rota, o sintoma se parece com deployments travando.

Por que a separação data plane / control plane importa mais do que parece

A outra alegação que quero pesar a partir da entrada do radar é a arquitetural: o APISIX separa o data plane do control plane, enquanto o controller de ingress canônico do Nginx no Kubernetes empacota ambos no mesmo Pod.

Esse empacotamento é a causa raiz por trás do pior padrão de incidente do Nginx-ingress sobre o qual já li: o controller crasha dentro de um Pod, leva o data plane do Nginx junto, e o restart do Pod conta contra o mesmo readiness gate que decide se o tráfego flui. A separação do APISIX coloca os processos do gateway em seu próprio deployment com seu próprio ciclo de vida. Se o controller estiver infeliz, os gateways continuam servindo a última configuração conhecida-boa de seu cache em processo. Não há atualização automática durante a indisponibilidade, mas também não há perda de tráfego.

Essa propriedade vale muito. É também exatamente a propriedade que torna a dependência de etcd cara: a única forma do gateway poder servir a partir de uma "última configuração conhecida-boa" indefinidamente é tratando o etcd como uma dependência soft no momento da requisição, o que significa que todo o caminho quente da requisição nunca pode bloquear em um fetch de plano de controle. O APISIX acerta isso por design — cada match lê do LRU em processo do worker — mas é também a razão pela qual um budget de retry mal configurado ou uma chamada etcd:get() em tempo de debug dentro de um plugin customizado é genuinamente perigoso. Um único plugin Lua que chama etcd sincronamente em cada requisição colapsa toda a propriedade que a entrada do radar está elogiando.

Quando eu escolheria APISIX, e quando não

Estou convencido o suficiente para usar o APISIX em duas formas específicas:

  • Um gateway multi-tenant na frente de uma frota de serviços onde mudanças de rota acontecem dezenas de vezes por hora durante o expediente, schemas mudam constantemente, e um nginx -s reload por mudança é um custo real. A propriedade de hot-reload paga pelo cluster etcd no primeiro dia.
  • Uma borda voltada para o sul para workloads de IA onde preciso de extensibilidade de plugins, roteamento ponderado para pools de modelos e rate limiting por rota que varia por tenant. O modelo de plugin e a tabela de rotas dinâmica encaixam melhor nessa forma do que renderizar config do Nginx.

Ainda recorreria ao controller de ingress padrão do Nginx em duas outras formas:

  • Um serviço cuja tabela de rotas muda uma vez por semana, onde o custo do reload é irrelevante e a simplicidade operacional de um único binário sem cluster extra vence.
  • Uma organização que ainda não tem alguém disposto a ser dono do etcd como sistema com estado. O anel Trial do radar significa um projeto que vale a pena pilotar em algo real, não algo para jogar em um time que não vai reservar espaço operacional para a dependência que ele introduz.

A decisão é realmente sobre quem está comprando o quê. O APISIX troca um arquivo de config estático mais um signal de reload por um key-value store observado e fortemente consistente. Essa troca compra coisas reais. Também move a área de superfície operacional de "templates de config do Nginx" para "saúde de cluster etcd", e o segundo é mais difícil.

Conclusões

  • A alegação de roteamento dinâmico é real. Os processos worker do APISIX se inscrevem em prefixos do etcd via watches long-poll e atualizam um LRU em processo a cada evento, sem nginx -s reload e sem swap de worker. A propagação chega em milissegundos de um único dígito em um cluster saudável.
  • A dependência de etcd é real também. Planeje um cluster etcd em infraestrutura dedicada com seu próprio SLO, observabilidade e disciplina de backup, antes de contabilizar qualquer ganho do APISIX.
  • O fan-out de watch escala como nós vezes workers vezes prefixos, não como nós sozinhos. Alerte sobre contagens de conexões watch por peer do etcd, com um trip wire em aproximadamente duas vezes o estado estacionário. O precipício de 0,159 segundo para 7 segundos com 263 conexões é o número mais concreto que estou guardando na cabeça.
  • Fixe um release conhecido como bom do APISIX e faça seu smoke test de inicialização sair com código de erro no primeiro fetch de rota bem-sucedido. O modo de falha de hang no init_etcd é real em várias versões recentes.
  • A separação data plane / control plane é a propriedade que faz valer a pena rodar o APISIX. Não desfaça isso com um plugin customizado que chama o etcd sincronamente no caminho da requisição.

Recorra ao APISIX quando mudanças de rota acontecem por hora e o imposto de reload se acumula, ou quando a extensibilidade de plugins no data plane é o requisito que escolheu o gateway em primeiro lugar. Fique no chato Nginx ingress quando mudanças de rota acontecem por semana e ninguém se voluntariou para ser dono de um serviço de coordenação com estado.

Continue lendo

Curtindo? Talvez goste disso aqui.

Nada parecido — quer tentar outro ângulo?

Isso foi útil?

Deixe uma avaliação ou uma nota rápida — me ajuda a melhorar.