H13(连接关闭,无响应)Heroku规模缩小错误

n53p2ov0  于 2022-11-13  发布在  其他
关注(0)|答案(1)|浏览(257)

我正在Heroku上用uWSGI、管理员和nginx运行Docker映像中的Django应用程序。
当应用程序缩小时,我经常收到H13(连接关闭,无响应)错误:

此问题会生成以下日志事件:

2022-10-12T09:35:13.231318+00:00 heroku web.3 - - State changed from up to down
2022-10-12T09:35:13.774228+00:00 heroku web.3 - - Stopping all processes with SIGTERM
2022-10-12T09:35:14.028602+00:00 heroku router - - at=error code=H13 desc="Connection closed without response" method=GET path="/comments/api/assets-uuidasset/xxxx-xxxx-xxxx-xxxx-xxxxx/count/?_=1665564563"

我认为问题在于套接字没有在SIGTERM信号上关闭,或者nginx使用SIGTERM信号不正常地关闭(它应该接收SIGQUIT以正常关闭),或者类似的问题。
第一个案例在本文中描述,是关于Puma和Ruby的:https://www.schneems.com/2019/07/12/puma-4-hammering-out-h13sa-debugging-story/
第二种情况描述如下:https://canonical.com/blog/avoiding-dropped-connections-in-nginx-containers-with-stopsignal-sigquit

dldeef67

dldeef671#

经过三个星期的工作,我终于能够解决这个问题。

简短答案:

尽量避免使用Heroku运行Docker映像。

Heroku将SIGTERM发送到dyno中的所有进程,这是非常难以处理的事情。您将需要修补Docker容器中的几乎每个进程,以便使用SIGTERM计数并顺利终止。
终止Docker容器的标准方法是使用docker stop命令,该命令仅将SIGTERM发送到根进程(入口点),在那里可以处理它。
Heroku有一个非常任意的过程来终止与现有应用程序以及现有Docker映像部署不兼容的示例。根据我与Heroku的沟通,他们在未来无法改变这一点。

长答案:

不是一个问题,而是5个不同的问题。要成功终止示例,需要满足以下条件:

  • Nginx必须首先被终止(因此Heroku路由器停止发送请求,这类似于Puma),并且它必须是优雅的,这通常是通过SIGQUIT信号完成的。
  • 其他应用程序需要在Nginx之后延迟一段时间(例如,在Heroku 30秒宽限期之前,需要20-25秒才能关闭)正常终止。
  • 终止应用程序的顺序可能很重要-例如,PGBouncer必须在Gunicorn之后终止,以避免中断正在运行的SQL查询。
  • docker-entrypoint.sh需要捕捉SIGTERM信号。当我在本地测试时,这并没有出现。

为了实现这一点,我不得不单独处理每个应用程序:
Nginx:
我不得不修补Nginx来切换SIGTERMSIGQUIT信号,所以我在我的Dockerfile中运行以下命令:

# Compile nginx and patch it to switch SIGTERM and SIGQUIT signals
RUN curl -L http://nginx.org/download/nginx-1.22.0.tar.gz -o nginx.tar.gz \
  && tar -xvzf nginx.tar.gz \
  && cd nginx-1.22.0 \
  && sed -i "s/ QUIT$/TIUQ/g" src/core/ngx_config.h \
  && sed -i "s/ TERM$/QUIT/g" src/core/ngx_config.h \
  && sed -i "s/ TIUQ$/TERM/g" src/core/ngx_config.h \
  && ./configure --without-http_rewrite_module \
  && make \
  && make install \
  && cd .. \
  && rm nginx-1.22.0 -rf \
  && rm nginx.tar.gz

Issue I created

uWSGI/独角兽:

我放弃了uWSGI并切换到Gunicorn(它在SIGTERM上优雅地终止),但最后我不得不修补它,因为它需要比Nginx晚终止。我禁用了SIGTERM信号并将其功能Map到SIGUSR1上我的修补版本在这里:https://github.com/PetrDlouhy/gunicorn/commit/1414112358f445ce714c5d4f572d78172b993b79
我安装了:

RUN poetry run pip install -e git+https://github.com/PetrDlouhy/gunicorn@no_sigterm#egg=gunicorn[gthread] \
   && cd `poetry env info -p`/src/gunicorn/ \
   && git config core.repositoryformatversion 0  # Needed for Dockerfile.test only untill next version of Dulwich is released \
   && cd /project

Issue I created

PG弹跳机:

我还部署了PGBouncer,我必须修改它,使其在SIGTERM上不起作用:

# Compile pgbouncer and patch it to switch SIGTERM and SIGQUIT signals
RUN curl -L https://github.com/pgbouncer/pgbouncer/releases/download/pgbouncer_1_17_0/pgbouncer-1.17.0.tar.gz -o pgbouncer.tar.gz \
  && tar -xvzf pgbouncer.tar.gz \
  && cd pgbouncer-1.17.0 \
  && sed -i "s/got SIGTERM, fast exit/PGBouncer got SIGTERM, do nothing/" src/main.c \
  && sed -i "s/ exit(1);$//g" src/main.c \
  && ./configure \
  && make \
  && make install \
  && cd .. \
  && rm pgbouncer-1.17.0 -rf \
  && rm pgbouncer.tar.gz

它仍然可以用SIGINT优雅地关闭。
Issue I created
第1001章:我的docker-entrypoint.sh
我不得不在我的docker-entrypoint.sh中捕获SIGTERM,其中包含:

_term() {
  echo "Caught SIGTERM signal. Do nothing here, because Heroku already sent signal everywhere."
}

trap _term SIGTERM

主管

为了不接收R12错误,所有进程都需要在30秒Heroku宽限期之前终止。我通过以下supervisord.conf实现了这一点:

[supervisord]
nodaemon=true

[program:gunicorn]
command=poetry run newrelic-admin run-program gunicorn wsgi:application -c /etc/gunicorn/gunicorn.conf.py
stopwaitsecs=20
stopsignal=USR1
...

[program:nginx]
command=/usr/local/nginx/sbin/nginx -c /etc/nginx/nginx.conf
...

[program:pgbouncer]
command=/usr/local/bin/pgbouncer /project/pgbouncer/pgbouncer.ini
stopwaitsecs=25
stopsignal=INT
...

测试溶液:

为了测试正在发生的事情,我必须开发一些测试技术,这些技术在不同但相似的情况下可能会很方便。
我创建了一个视图,它等待10秒后才回答,并将其绑定到/slow_view url上。
然后,我在Docker示例中启动服务器,使用curl -I "http://localhost:8080/slow_view"查询慢视图,并与Docker示例建立第二个连接,使用pkill -SIGTERM .pkill -SIGTERM gunicorn执行kill命令。
我还可以在测试Heroku dyno时运行kill命令,其中我连接了heroku ps:exec --dyno web.1 --app my_app

相关问题