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

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

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

FROM python:3.10.11-slim as base

ENV PYTHONFAULTHANDLER=1 \
    PYTHONHASHSEED=random \
    PYTHONUNBUFFERED=1

RUN apt-get update \
    && apt-get install --no-install-recommends -y gcc libffi-dev g++ \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

FROM base as builder

ENV PIP_DEFAULT_TIMEOUT=100 \
    PIP_DISABLE_PIP_VERSION_CHECK=1 \
    PIP_NO_CACHE_DIR=1 \
    POETRY_VERSION=1.5.1

RUN pip install "poetry==$POETRY_VERSION"
RUN python -m venv /venv

WORKDIR /home/ch_news

COPY --chown=10000:10000 pyproject.toml poetry.lock ./
RUN . /venv/bin/activate && poetry install --no-interaction --no-dev --no-ansi

COPY --chown=10000:10000 . .
RUN . /venv/bin/activate && poetry build

FROM base as final

WORKDIR /home/ch_news

RUN groupadd --gid 10000 ch_news \
    && useradd --uid 10000 --gid ch_news --shell /bin/bash --create-home ch_news

COPY --from=builder --chown=10000:10000 /venv /venv
COPY --from=builder --chown=10000:10000 /home/ch_news/dist .
COPY --chown=10000:10000 ./docker/production/python_server/docker-entrypoint.sh ./
RUN chmod +x docker-entrypoint.sh

RUN . /venv/bin/activate && pip install *.whl

USER ch_news
CMD ["./docker-entrypoint.sh"]

docker-entrypoint.sh

#!/bin/sh

set -e
. /venv/bin/activate

python -m news

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

version: '3.9'
name: ch_news_prod
services:
  ch_news_pro_python:
    build:
      context: ../../
      dockerfile: ./docker/production/python_server/Dockerfile
    container_name: ch_news_pro_python
    depends_on:
      ch_news_pro_postgres:
        condition: service_healthy
    env_file:
      - .env
    image: ch_news_pro_python_image
    networks:
      - network
    restart: 'always'

  ch_news_pro_postgres:
    build:
      context: ../..
      dockerfile: ./docker/production/postgres_server/Dockerfile
    container_name: ch_news_pro_postgres
    env_file:
      - .env
    healthcheck:
      test:
        - 'CMD-SHELL'
        - "pg_isready -d 'host=ch_news_pro_postgres user=ch_api_user port=47289 dbname=ch_api_db_pro'"
      interval: 5s
      timeout: 5s
      retries: 3
      start_period: 10s
    image: ch_news_pro_postgres_image
    networks:
      - network
    ports:
      - '47289:47289'
    restart: 'always'
    volumes:
      - postgres_data:/var/lib/postgresql/data

networks:
  network:
    driver: bridge

volumes:
  postgres_data:
    driver: local

这是我的脚本大致内容:

import asyncio
import signal

def handle_sigterm(signum: int, frame: Any) -> None:
    raise KeyboardInterrupt()

def app() -> None:
    try:
        asyncio.run(periodic_task())
    except KeyboardInterrupt:
        logger.info("因为手动退出而关闭...")
    
signal.signal(signal.SIGTERM, handle_sigterm)
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

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

It does not terminate gracefully

Dockerfile

FROM python:3.10.11-slim as base
ENV PYTHONFAULTHANDLER=1 \
PYTHONHASHSEED=random \
# Turns off buffering for easier container logging
PYTHONUNBUFFERED=1
RUN apt-get update \
&amp;&amp; apt-get install --no-install-recommends -y gcc libffi-dev g++ \
&amp;&amp; apt-get clean \
&amp;&amp; rm -rf /var/lib/apt/lists/*
FROM base as builder
ENV PIP_DEFAULT_TIMEOUT=100 \
PIP_DISABLE_PIP_VERSION_CHECK=1 \
PIP_NO_CACHE_DIR=1 \
POETRY_VERSION=1.5.1
RUN pip install &quot;poetry==$POETRY_VERSION&quot;
RUN python -m venv /venv
WORKDIR /home/ch_news
COPY --chown=10000:10000 pyproject.toml poetry.lock ./
# --no-interaction not to ask any interactive questions
# --no-ansi flag to make your output more log friendly
RUN . /venv/bin/activate &amp;&amp; poetry install --no-interaction --no-dev --no-ansi
COPY --chown=10000:10000 . .
RUN . /venv/bin/activate &amp;&amp; poetry build
FROM base as final
WORKDIR /home/ch_news
RUN groupadd --gid 10000 ch_news \
&amp;&amp; useradd --uid 10000 --gid ch_news --shell /bin/bash --create-home ch_news
COPY --from=builder --chown=10000:10000 /venv /venv
COPY --from=builder --chown=10000:10000 /home/ch_news/dist .
COPY  --chown=10000:10000 ./docker/production/python_server/docker-entrypoint.sh ./
RUN chmod +x docker-entrypoint.sh
RUN . /venv/bin/activate &amp;&amp; pip install *.whl
USER ch_news
CMD [&quot;./docker-entrypoint.sh&quot;]

docker-entrypoint.sh

#!/bin/sh
set -e
. /venv/bin/activate
python -m news

I am running everything inside docker-compose if that helps

version: &#39;3.9&#39; # optional since v1.27.0
name: ch_news_prod
services:
ch_news_pro_python:
build:
context: ../../
dockerfile: ./docker/production/python_server/Dockerfile
container_name: ch_news_pro_python
depends_on:
ch_news_pro_postgres:
condition: service_healthy
env_file:
- .env
image: ch_news_pro_python_image
networks:
- network
restart: &#39;always&#39;
ch_news_pro_postgres:
build:
context: ../..
dockerfile: ./docker/production/postgres_server/Dockerfile
container_name: ch_news_pro_postgres
env_file:
- .env
healthcheck:
test:
[
&#39;CMD-SHELL&#39;,
&quot;pg_isready -d &#39;host=ch_news_pro_postgres user=ch_api_user port=47289 dbname=ch_api_db_pro&#39;&quot;,
]
interval: 5s
timeout: 5s
retries: 3
start_period: 10s
image: ch_news_pro_postgres_image
networks:
- network
ports:
- &#39;47289:47289&#39;
restart: &#39;always&#39;
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
network:
driver: bridge
volumes:
postgres_data:
driver: local

This is what my script roughly looks like

import asyncio
import signal
def handle_sigterm(signum: int, frame: Any) -&gt; None:
raise KeyboardInterrupt()
def app() -&gt; None:
try:
asyncio.run(periodic_task())
except KeyboardInterrupt:
logger.info(&quot;Shutting down because you exited manually...&quot;)
signal.signal(signal.SIGTERM, handle_sigterm)
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 信号。

exec python -m news
#^^^

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

FROM base as final
COPY --from=builder /venv /venv
ENV PATH /venv/bin:$PATH
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.

exec python -m news
#^^^

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.

FROM base as final
COPY --from=builder /venv /venv
ENV PATH /venv/bin:$PATH
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:

确定