Python Docker SIGTERM 不会在容器停止时触发。

huangapple go评论179阅读模式
英文:

Python Docker SIGTERM not being fired on container stop

问题

Here's the translation of your provided content:

  • 我有一个使用asyncio和aiohttp运行的Python脚本,它在每次运行后都会睡眠一分钟。
  • 我试图在Docker容器中使用poetry 1.5.1运行它,当我在本地按下Ctrl + C时,它通过处理SIGINT和SIGTERM信号正常工作。
  • 当我在Docker文件中运行它并调用docker container stop <container-name>时,它不会正常终止。

Dockerfile

  1. FROM python:3.10.11-slim as base
  2. ENV PYTHONFAULTHANDLER=1 \
  3. PYTHONHASHSEED=random \
  4. PYTHONUNBUFFERED=1
  5. RUN apt-get update \
  6. && apt-get install --no-install-recommends -y gcc libffi-dev g++ \
  7. && apt-get clean \
  8. && rm -rf /var/lib/apt/lists/*
  9. FROM base as builder
  10. ENV PIP_DEFAULT_TIMEOUT=100 \
  11. PIP_DISABLE_PIP_VERSION_CHECK=1 \
  12. PIP_NO_CACHE_DIR=1 \
  13. POETRY_VERSION=1.5.1
  14. RUN pip install "poetry==$POETRY_VERSION"
  15. RUN python -m venv /venv
  16. WORKDIR /home/ch_news
  17. COPY --chown=10000:10000 pyproject.toml poetry.lock ./
  18. RUN . /venv/bin/activate && poetry install --no-interaction --no-dev --no-ansi
  19. COPY --chown=10000:10000 . .
  20. RUN . /venv/bin/activate && poetry build
  21. FROM base as final
  22. WORKDIR /home/ch_news
  23. RUN groupadd --gid 10000 ch_news \
  24. && useradd --uid 10000 --gid ch_news --shell /bin/bash --create-home ch_news
  25. COPY --from=builder --chown=10000:10000 /venv /venv
  26. COPY --from=builder --chown=10000:10000 /home/ch_news/dist .
  27. COPY --chown=10000:10000 ./docker/production/python_server/docker-entrypoint.sh ./
  28. RUN chmod +x docker-entrypoint.sh
  29. RUN . /venv/bin/activate && pip install *.whl
  30. USER ch_news
  31. CMD ["./docker-entrypoint.sh"]

docker-entrypoint.sh

  1. #!/bin/sh
  2. set -e
  3. . /venv/bin/activate
  4. python -m news

我正在使用Docker Compose运行所有内容。

  1. version: '3.9'
  2. name: ch_news_prod
  3. services:
  4. ch_news_pro_python:
  5. build:
  6. context: ../../
  7. dockerfile: ./docker/production/python_server/Dockerfile
  8. container_name: ch_news_pro_python
  9. depends_on:
  10. ch_news_pro_postgres:
  11. condition: service_healthy
  12. env_file:
  13. - .env
  14. image: ch_news_pro_python_image
  15. networks:
  16. - network
  17. restart: 'always'
  18. ch_news_pro_postgres:
  19. build:
  20. context: ../..
  21. dockerfile: ./docker/production/postgres_server/Dockerfile
  22. container_name: ch_news_pro_postgres
  23. env_file:
  24. - .env
  25. healthcheck:
  26. test:
  27. - 'CMD-SHELL'
  28. - "pg_isready -d 'host=ch_news_pro_postgres user=ch_api_user port=47289 dbname=ch_api_db_pro'"
  29. interval: 5s
  30. timeout: 5s
  31. retries: 3
  32. start_period: 10s
  33. image: ch_news_pro_postgres_image
  34. networks:
  35. - network
  36. ports:
  37. - '47289:47289'
  38. restart: 'always'
  39. volumes:
  40. - postgres_data:/var/lib/postgresql/data
  41. networks:
  42. network:
  43. driver: bridge
  44. volumes:
  45. postgres_data:
  46. driver: local

这是我的脚本大致内容:

  1. import asyncio
  2. import signal
  3. def handle_sigterm(signum: int, frame: Any) -> None:
  4. raise KeyboardInterrupt()
  5. def app() -> None:
  6. try:
  7. asyncio.run(periodic_task())
  8. except KeyboardInterrupt:
  9. logger.info("因为手动退出而关闭...")
  10. signal.signal(signal.SIGTERM, handle_sigterm)
  11. signal.signal(signal.SIGINT, handle_sigterm)

有人能告诉我我哪里错了以及如何使SIGTERM处理程序在通过docker-compose运行时触发Keyboard Interrupt吗?

英文:
  • I have a Python script that uses asyncio and aiohttp to run infinitely while sleeping for a minute after each run
  • I am trying to run this inside Docker with poetry 1.5.1 and when I hit Ctrl + C locally, it works by handling the SIGINT, SIGTERM signal

When I run it inside docker file and call

  1. docker container stop &lt;container-name&gt;

It does not terminate gracefully

Dockerfile

  1. FROM python:3.10.11-slim as base
  2. ENV PYTHONFAULTHANDLER=1 \
  3. PYTHONHASHSEED=random \
  4. # Turns off buffering for easier container logging
  5. PYTHONUNBUFFERED=1
  6. RUN apt-get update \
  7. &amp;&amp; apt-get install --no-install-recommends -y gcc libffi-dev g++ \
  8. &amp;&amp; apt-get clean \
  9. &amp;&amp; rm -rf /var/lib/apt/lists/*
  10. FROM base as builder
  11. ENV PIP_DEFAULT_TIMEOUT=100 \
  12. PIP_DISABLE_PIP_VERSION_CHECK=1 \
  13. PIP_NO_CACHE_DIR=1 \
  14. POETRY_VERSION=1.5.1
  15. RUN pip install &quot;poetry==$POETRY_VERSION&quot;
  16. RUN python -m venv /venv
  17. WORKDIR /home/ch_news
  18. COPY --chown=10000:10000 pyproject.toml poetry.lock ./
  19. # --no-interaction not to ask any interactive questions
  20. # --no-ansi flag to make your output more log friendly
  21. RUN . /venv/bin/activate &amp;&amp; poetry install --no-interaction --no-dev --no-ansi
  22. COPY --chown=10000:10000 . .
  23. RUN . /venv/bin/activate &amp;&amp; poetry build
  24. FROM base as final
  25. WORKDIR /home/ch_news
  26. RUN groupadd --gid 10000 ch_news \
  27. &amp;&amp; useradd --uid 10000 --gid ch_news --shell /bin/bash --create-home ch_news
  28. COPY --from=builder --chown=10000:10000 /venv /venv
  29. COPY --from=builder --chown=10000:10000 /home/ch_news/dist .
  30. COPY --chown=10000:10000 ./docker/production/python_server/docker-entrypoint.sh ./
  31. RUN chmod +x docker-entrypoint.sh
  32. RUN . /venv/bin/activate &amp;&amp; pip install *.whl
  33. USER ch_news
  34. CMD [&quot;./docker-entrypoint.sh&quot;]

docker-entrypoint.sh

  1. #!/bin/sh
  2. set -e
  3. . /venv/bin/activate
  4. python -m news

I am running everything inside docker-compose if that helps

  1. version: &#39;3.9&#39; # optional since v1.27.0
  2. name: ch_news_prod
  3. services:
  4. ch_news_pro_python:
  5. build:
  6. context: ../../
  7. dockerfile: ./docker/production/python_server/Dockerfile
  8. container_name: ch_news_pro_python
  9. depends_on:
  10. ch_news_pro_postgres:
  11. condition: service_healthy
  12. env_file:
  13. - .env
  14. image: ch_news_pro_python_image
  15. networks:
  16. - network
  17. restart: &#39;always&#39;
  18. ch_news_pro_postgres:
  19. build:
  20. context: ../..
  21. dockerfile: ./docker/production/postgres_server/Dockerfile
  22. container_name: ch_news_pro_postgres
  23. env_file:
  24. - .env
  25. healthcheck:
  26. test:
  27. [
  28. &#39;CMD-SHELL&#39;,
  29. &quot;pg_isready -d &#39;host=ch_news_pro_postgres user=ch_api_user port=47289 dbname=ch_api_db_pro&#39;&quot;,
  30. ]
  31. interval: 5s
  32. timeout: 5s
  33. retries: 3
  34. start_period: 10s
  35. image: ch_news_pro_postgres_image
  36. networks:
  37. - network
  38. ports:
  39. - &#39;47289:47289&#39;
  40. restart: &#39;always&#39;
  41. volumes:
  42. - postgres_data:/var/lib/postgresql/data
  43. networks:
  44. network:
  45. driver: bridge
  46. volumes:
  47. postgres_data:
  48. driver: local

This is what my script roughly looks like

  1. import asyncio
  2. import signal
  3. def handle_sigterm(signum: int, frame: Any) -&gt; None:
  4. raise KeyboardInterrupt()
  5. def app() -&gt; None:
  6. try:
  7. asyncio.run(periodic_task())
  8. except KeyboardInterrupt:
  9. logger.info(&quot;Shutting down because you exited manually...&quot;)
  10. signal.signal(signal.SIGTERM, handle_sigterm)
  11. signal.signal(signal.SIGINT, handle_sigterm)

Can someone tell me where i am going wrong and how to make the SIGTERM handler fire the Keyboard Interrupt when running via docker-compose?

答案1

得分: 3

Your docker-entrypoint.sh 脚本将主应用程序作为普通子进程运行。这意味着 Shell 脚本将在 Python 应用程序运行时保持运行。如果你运行 docker-compose exec ch_news_pro_python ps,我预期你将看到进程 1 是 docker-entrypoint.sh 脚本而不是 Python 应用程序。

docker stop 只向容器中的进程 1 发送信号。这意味着 Shell 脚本会接收到 SIGTERM 信号,但不会将其传递给应用程序。

最简单的解决方法是将脚本的最后一行更改为使用 exec 启动 Python 应用程序。这会导致 Shell 脚本用 Python 解释器替换自身;在 exec 命令之后无法在脚本中运行任何内容,但相应地,现在 python 将成为进程 1 并接收 Docker 信号。

  1. exec python -m news
  2. #^^^

或者,通常可以直接在虚拟环境中运行命令,而无需先显式激活它。最简单的 Docker 设置可能是使用 Poetry 的 scripts 设置,在 /venv/bin/news 中创建一个脚本来运行你的应用程序;通过 poetry install 将整个应用程序安装到虚拟环境中;将 /venv/bin 目录放入 $PATH;然后将镜像的 CMD 设置为运行包装脚本。

  1. FROM base as final
  2. COPY --from=builder /venv /venv
  3. ENV PATH /venv/bin:$PATH
  4. CMD ["news"]
英文:

Your docker-entrypoint.sh script runs the main application as an ordinary subprocess. This means the shell script will stay running while your Python application runs. If you docker-compose exec ch_news_pro_python ps, I expect you will see that process 1 is the docker-entrypoint.sh script and not the Python application.

docker stop sends its signal only to process 1 in the container. That means the shell script receives SIGTERM, and it doesn't forward it on to the application.

The easiest way to work around this is to change the last line of the script to exec the Python application. This causes the shell script to replace itself with the Python interpreter; you can't run anything in the script after the exec command, but conversely, now python will be process 1 and will receive Docker signals.

  1. exec python -m news
  2. #^^^

Alternatively, you can usually run commands directly out of a virtual environment without specifically activating it first. The simplest Docker setup might be to use the Poetry scripts setting to create a script in /venv/bin/news that runs your application; have poetry install install the entire application into the virtual environment; put the /venv/bin directory in the $PATH; and then set the image CMD to run the wrapper script.

  1. FROM base as final
  2. COPY --from=builder /venv /venv
  3. ENV PATH /venv/bin:$PATH
  4. CMD [&quot;news&quot;]

huangapple
  • 本文由 发表于 2023年6月13日 16:23:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/76462990.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定