如何优雅地关闭长期运行的 Docker 容器

DockerDockerBeginner
立即练习

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

简介

Docker 容器被广泛用于运行各种应用程序和服务,但是管理长期运行的容器的生命周期可能是一项挑战。本教程将指导你完成优雅关闭长期运行的 Docker 容器的过程,确保平稳过渡并防止潜在的数据丢失或应用程序问题。


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL docker(("Docker")) -.-> docker/ContainerOperationsGroup(["Container Operations"]) docker/ContainerOperationsGroup -.-> docker/start("Start Container") docker/ContainerOperationsGroup -.-> docker/stop("Stop Container") docker/ContainerOperationsGroup -.-> docker/restart("Restart Container") docker/ContainerOperationsGroup -.-> docker/logs("View Container Logs") docker/ContainerOperationsGroup -.-> docker/inspect("Inspect Container") subgraph Lab Skills docker/start -.-> lab-417742{{"如何优雅地关闭长期运行的 Docker 容器"}} docker/stop -.-> lab-417742{{"如何优雅地关闭长期运行的 Docker 容器"}} docker/restart -.-> lab-417742{{"如何优雅地关闭长期运行的 Docker 容器"}} docker/logs -.-> lab-417742{{"如何优雅地关闭长期运行的 Docker 容器"}} docker/inspect -.-> lab-417742{{"如何优雅地关闭长期运行的 Docker 容器"}} end

理解 Docker 容器生命周期

Docker 容器具有明确的生命周期,开发者需要了解这些知识才能有效地管理他们的应用程序。本节将概述 Docker 容器的生命周期,包括容器可以转换的不同状态以及支撑此生命周期的关键概念。

Docker 容器状态

Docker 容器在其生命周期中可以处于几种不同的状态:

  1. 已创建(Created):容器已创建但尚未启动。
  2. 运行中(Running):容器当前正在执行其主进程。
  3. 暂停(Paused):容器的主进程已暂停,但容器仍在运行。
  4. 已停止(Stopped):容器的主进程已停止。
  5. 重启中(Restarting):容器当前正在重启。
  6. 已退出(Exited):容器已停止且其主进程已退出。

了解这些状态很重要,因为它们决定了你可以对容器执行的操作以及你可以预期的行为。

graph LR Created --> Running Running --> Paused Paused --> Running Running --> Stopped Stopped --> Running Stopped --> Exited

Docker 容器生命周期事件

除了容器状态外,在 Docker 容器的生命周期中还会发生几个关键事件:

  1. 创建(Create):创建一个新容器。
  2. 启动(Start):启动容器的主进程。
  3. 停止(Stop):停止容器的主进程。
  4. 重启(Restart):手动或自动重启容器。
  5. 暂停/恢复(Pause/Unpause):暂停或恢复容器的主进程。
  6. 终止(Kill):强制终止容器的主进程。
  7. 删除(Delete):从系统中删除容器。

了解这些生命周期事件对于管理和自动化 Docker 容器的行为至关重要。

使用 Docker CLI 处理容器生命周期

Docker CLI 提供了几个用于与容器生命周期进行交互的命令:

  • docker create:创建一个新容器。
  • docker start:启动一个已停止的容器。
  • docker stop:停止一个正在运行的容器。
  • docker restart:重启一个容器。
  • docker pause:暂停一个正在运行的容器。
  • docker unpause:恢复一个已暂停的容器。
  • docker kill:强制停止一个正在运行的容器。
  • docker rm:删除一个容器。

这些命令允许你以编程方式管理 Docker 容器的生命周期,并自动化与容器管理相关的各种任务。

优雅地停止长期运行的容器

在处理长期运行的 Docker 容器时,确保它们被优雅地停止非常重要,这能让容器的主进程在容器终止前执行任何必要的清理或关闭任务。本节将探讨优雅地停止长期运行的 Docker 容器的策略。

理解 SIGTERM 信号

优雅地停止 Docker 容器的主要机制是向容器的主进程发送 SIGTERM 信号。这个信号通知进程它应该开始关闭过程并执行任何必要的清理任务。

默认情况下,当你运行 docker stop 命令时,Docker 会向容器的主进程发送 SIGTERM 信号,并等待一个默认的超时时间(通常是 10 秒)让进程退出。如果进程在超时时间内没有退出,Docker 随后会发送一个 SIGKILL 信号,该信号会强制终止进程。

自定义关闭行为

要自定义长期运行的 Docker 容器的关闭行为,你可以使用以下选项:

  1. --stop-signal:在 docker stop 命令期间指定要发送到容器主进程的替代信号。例如,--stop-signal=SIGINT 会发送 SIGINT 信号而不是默认的 SIGTERM
  2. --stop-timeout:指定在发送 SIGKILL 信号之前等待容器主进程退出的秒数。例如,--stop-timeout=30 会给进程 30 秒时间退出,然后再被强制终止。

以下是在启动长期运行的容器时如何使用这些选项的示例:

docker run -d --name my-app --stop-signal=SIGINT --stop-timeout=60 my-app:latest

此命令将启动一个名为 my-app 的长期运行容器,当发出 docker stop 命令时,它会向容器的主进程发送 SIGINT 信号,并最多等待 60 秒让其退出,然后再发送 SIGKILL 信号。

在你的应用程序中实现优雅关闭

为确保你的长期运行的 Docker 容器被优雅地停止,重要的是设计你的应用程序来处理 SIGTERM(或替代)信号,并在退出前执行任何必要的清理任务。这可能涉及以下任务:

  • 将内存中的数据保存到持久存储
  • 关闭网络连接或数据库连接
  • 刷新日志或其他输出
  • 执行任何其他特定于应用程序的清理任务

通过在你的应用程序中实现这种信号处理,你可以确保你的容器以可控且可预测的方式停止,将数据丢失或其他问题的风险降至最低。

优雅关闭的策略

在优雅地关闭长期运行的 Docker 容器方面,你可以采用多种策略来确保关闭过程平稳且可控。本节将探讨一些关键策略以及优雅关闭的最佳实践。

应用程序中的信号处理

优雅关闭最重要的策略之一是在应用程序中实现信号处理。这涉及编写代码来监听 SIGTERM(或替代)信号,并在应用程序退出前执行必要的清理任务。

以下是在 Node.js 应用程序中实现信号处理的示例:

process.on("SIGTERM", () => {
  console.log("Received SIGTERM signal, starting graceful shutdown...");
  // 执行清理任务,例如:
  // - 将内存中的数据保存到持久存储
  // - 关闭网络连接或数据库连接
  // - 刷新日志或其他输出
  // - 执行任何其他特定于应用程序的清理任务
  console.log("Graceful shutdown complete, exiting process.");
  process.exit(0);
});

通过实现这种信号处理,你的应用程序可以确保进行可控的关闭,将数据丢失或其他问题的风险降至最低。

使用健康检查和存活探针

优雅关闭的另一个策略是使用 Docker 内置的健康检查和存活探针功能。这些功能允许你定义检查,Docker 可以使用这些检查来确定容器的健康状态和就绪状态。

在关闭过程中,你可以使用这些探针向 Docker 发出信号,表明你的容器正在关闭过程中,从而使 Docker 在移除容器之前等待关闭完成。

以下是在 Docker 容器中配置健康检查和存活探针的示例:

## Dockerfile
FROM node:14-alpine
COPY. /app
WORKDIR /app
CMD ["node", "server.js"]
HEALTHCHECK --interval=5s --timeout=3s \
  CMD curl -f http://localhost:3000/healthz || exit 1
LABEL com.labex.shutdown.signal=SIGINT
LABEL com.labex.shutdown.timeout=60

在此示例中,HEALTHCHECK 指令定义了一个健康检查,用于检查容器 Web 服务器上的 /healthz 端点。LABEL 指令定义了用于优雅关闭的信号(SIGINT)和超时时间(60 秒)。

在关闭过程中,你的应用程序可以更新健康检查端点,以表明关闭正在进行,从而使 Docker 在移除容器之前等待关闭完成。

利用编排框架

如果你在诸如 Kubernetes 或 Docker Swarm 之类的编排框架中运行 Docker 容器,则可以利用这些框架的内置功能来帮助实现优雅关闭。

例如,在 Kubernetes 中,你可以使用 preStop 钩子来执行在容器终止前执行清理任务的命令或脚本。你还可以使用 terminationGracePeriodSeconds 字段来指定容器应被给予的优雅关闭时间。

以下是如何配置带有 preStop 钩子和优雅终止期的 Kubernetes 部署的示例:

## kubernetes-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: my-app
          image: my-app:latest
          ports:
            - containerPort: 3000
          lifecycle:
            preStop:
              exec:
                command: ["/app/shutdown.sh"]
          terminationGracePeriodSeconds: 60

在此示例中,preStop 钩子运行一个脚本(/app/shutdown.sh),该脚本在容器终止前执行任何必要的清理任务。terminationGracePeriodSeconds 字段为容器提供 60 秒的时间进行优雅关闭,然后再被强制终止。

通过利用这些编排框架功能,你可以进一步提高容器关闭过程的可靠性和可预测性。

总结

在本教程中,你已经了解了理解 Docker 容器生命周期的重要性以及优雅地关闭长期运行容器的策略。通过遵循所述的最佳实践,你可以确保一个平稳且可靠的关闭过程,将数据丢失或应用程序中断的风险降至最低。对于任何 Docker 开发者或管理员来说,掌握优雅的容器关闭技术都是一项至关重要的技能。