tcp: broken pipe. E agora?

Victor Freire
sbf-tech
Published in
3 min readApr 25, 2022
Photo by pisauikan on Unsplash

tl;dr: Kong Ingress Controller era o culpado. Suas configurações padrões de timeout estavam fechando a conexão antes que o arquivo pudesse ser totalmente enviado. Se você está enfrentando esse problema em uma longa requisição, cheque as configurações do seu proxy reverso, pois ele pode conter uma configuração diferente de sua aplicação. ;-)

Nós, do time de alocação do Grupo SBF, temos um serviço HTTP escrito em Go que executa uma consulta no BigQuery e gera um grande csv como resposta. Entretanto, após algum tempo começamos a receber o seguinte erro ao invés de nosso arquivo:

write tcp 10.0.0.1:8080->10.0.0.2:38302: write: broken pipe

Isso é uma grande surpresa, pois jamais havíamos visto esse erro… Afinal, o que isso quer dizer? Uma pesquisa rápida nos trouxe a seguinte definição:

A condition in programming (also known in POSIX as EPIPE error code and SIGPIPE signal), when a process requests an output to pipe or socket, which was closed by peer

Hmm, isso definitivamente nos dá uma luz sobre o problema. Considerando que o servidor HTTP é provido pelo poderoso pacote net/http da biblioteca padrão de Go, nós temos algumas pistas por onde começar.

A Cloudflare possui um ótimo artigo sobre as configurações padrões do servidor HTTP de Go e como evitar alguns tiros no pé. Pulamos diretamente para a parte que diz respeito aos timeouts e conferimos se não esquecemos de nada.

srv := &http.Server{
ReadTimeout: 10 * time.Minute, // 10 minutes
WriteTimeout: 10 * time.Minute,
Addr: ":8080",
Handler: r,
}

Para se ter uma ideia, nossa aplicação leva em média 2 minutos para completar a requisição. Isso não deveria ocorrer pois temos 10 minutos até que um erro 504 seja retornado.

Curiosamente, não recebemos um erro ao enviar a requisição para um servidor local. Melhor! Comparando nosso ambiente local com o ambiente de produção, percebemos que nossa conexão era fechada em exatamente 1 minuto de execução. Portanto, tem que ser algo entre nosso cliente e nosso servidor!

Sabendo que fazemos deploy para um cluster Kubernetes com o Kong Ingress Controller (controlando 😜) tomando conta de nosso proxy reverso, checamos sua documentação e… Bingo! Essa é a raíz de nosso problema! De acordo com a documentação do Kong Ingress Controller, o timeout padrão é de 60.000 milissegundos — em outras palavras, 1 minuto!

Replicando o comportamento

Antes de fazermos qualquer coisa em nossos servidores, por quê não replicamos este comportamento de forma local? Para isto, nós podemos utilizar uma imagem Docker do nginx e um simples servidor HTTP escrito em Go com uma funcionalidade parecida com a de nosso serviço.

A ideia por trás do teste é configurar um endpoint que leve bastante tempo escrevendo em um buffer, enquanto nosso proxy reverso possuirá um timeout de 2 segundos.

Servidor Go e Dockerfile

Servidor Go e Dockerfile

Configuração do nginx e Dockerfile

Docker Compose

Por último, utilizaremos o Docker Compose para nos auxiliar com a orquestração desses containers.

Rodando e testando

Depois de configurar nosso ambiente, podemos testá-lo com os comandos abaixo:

  • docker-compose up --build para rodar nossos containers
  • curl localhost para fazer uma requisição em nosso servidor

Voilà! O erro aparece, confirmando nossa teoria!

goservice_1  | 2022/04/07 01:12:14 error writing: write tcp 172.18.0.2:8080->172.18.0.3:56768: write: broken pipe

Conclusão

Caramba, investigar esse problema foi extremamente divertido! Como notamos nos nossos testes, a configuração de nosso cluster realmente era o problema. Sobrescrever as configurações de timeout com o código abaixo resolveu nosso problema instantaneamente.

Você pode ver o código-fonte na íntegra no GitHub.

--

--