Logo
Section 1 / 5

Rails 服务器无法启动?

学习如何解决 Rails 服务器无法启动的错误。

导入

Rails 服务器无法启动?

学习如何解决 Rails 服务器无法启动的错误。

Rails tmp/pids/server.pid 未被清理

哎呀,这可真尴尬。不幸的是,Compose 存在一些令人恼火的问题。由于你可能在前面的章节中遇到过这些问题,或者在今后使用 Compose 时也会遇到,因此忽略它们是不负责任的。接下来我们将逐一快速查看这些问题。

幸运的是,我们可以通过一些努力来绕过第一个问题。不知为何,有时在使用 Compose 终止应用(按下 Ctrl+C)后,Rails 服务器似乎无法干净地关闭,导致 Rails 存放在 tmp/pids/ 目录中的 server.pid 文件未被删除。这意味着再次启动应用时,你可能会在输出中看到如下错误:

1$ docker-compose up 2... 3A server is already running. Check /usr/src/app/tmp/pids/server.pid 4...

pid 文件的存在会让 Rails 服务器误以为已有服务器在运行,因此不会启动。Rails 将 server.pid 文件保存在 tmp/pids 目录中。由于我们将本地应用目录挂载到了容器中,该文件最终会出现在我们本地机器的 tmp/pids/ 目录下,并持续存在直到我们手动删除它。

如何解决这个问题?

第一个解决方案

由于我们已将应用目录挂载到容器中,手动删除 server.pid 文件非常简单:

1$ rm tmp/pids/server.pid

然后重新启动 Rails 服务器:

1$ docker-compose up

此时 Rails 应用将正常运行。

第二个解决方案

然而,如果这个问题反复发生,上述步骤并不能真正解决问题。幸运的是,我们可以设置一个巧妙的绕行方案。

让我们看看这个解决方案,然后再进行讨论:

  1. 我们需要在 Rails 项目根目录下创建一个新文件 docker-entrypoint.sh,内容如下:
1#!/bin/sh 2set -e 3 4if [ -f tmp/pids/server.pid ]; then 5 rm tmp/pids/server.pid 6fi 7 8exec "$@"
  1. 使该文件可执行:
1$ chmod +x docker-entrypoint.sh
  1. 在我们的 Dockerfile 中,于最后一个 CMD 指令之前添加以下一行,指定 ENTRYPOINT 指令:
1ENTRYPOINT ["./docker-entrypoint.sh"]
  1. 停止、重建并重启 web 服务:
1$ docker-compose stop web 2$ docker-compose build web 3$ docker-compose up -d web

🍀 实践

让我们尝试完成以下操作:

  1. 编辑 docker-entrypoint.sh。
  2. 点击 Run 按钮。
  3. 仅启动 web 服务。(不要使用 -d 选项)。
  4. 退出应用。
  5. 验证文件 server.pid 是否已被删除。

注意: 当你看到提示 "You may begin now..." 后,请立即启动 web 服务。如果你对文件做了任何修改,请点击 Run 按钮。

No configuration found for ID: fc4383ee81db919e4aa06b0c1971610fe33b16b34daf067abfe8eea28ba486ed

解决方案如下:

  1. 按如下方式编辑 docker-entrypoint.sh:

    1#!/bin/sh 2set -e 3 4if [ -f tmp/pids/server.pid ]; then 5 rm tmp/pids/server.pid 6fi 7 8exec "$@"
  2. 点击 Run 按钮。

  3. 使用以下命令仅启动 web 服务:

    1docker-compose up web
  4. 使用 Ctrl+C 退出应用。

  5. 为验证文件 server.pid 是否已被删除,请执行以下操作:

    1cat tmp/pids/server.pid

    如果收到如下错误,则说明 server.pid 已被删除,且我们的 docker-entrypoint.sh 工作正常。

    1cat: tmp/pids/server.pid: No such file or directory

注意: 如果你的 .sh 文件格式有误,可能会遇到类似以下错误:

1web_1 | standard_init_linux.go:219: exec user process caused: exec format error 2myapp_web_1 exited with code 1

出现这种情况时,请修正 .sh 文件并点击 Run 按钮。再次启动 web 服务。如果 .sh 文件正确,将能成功启动。

解决方案:

  1. 按如下方式编辑 docker-entrypoint.sh:

    1#!/bin/sh 2set -e 3if [ -f tmp/pids/server.pid ]; then 4rm tmp/pids/server.pid 5fi 6exec "$@"
  2. 点击 Run 按钮。

  3. 仅使用以下命令启动 web 服务:

    1docker-compose up web
  4. 使用 Ctrl+C 退出应用。

  5. 为验证文件 server.pid 是否已被删除,请执行以下操作:

    1cat tmp/pids/server.pid

    如果收到以下错误,说明 server.pid 已被删除,且我们的 docker-entrypoint.sh 正常工作。

    1cat: tmp/pids/server.pid: No such file or directory

注意: 如果你的 .sh 文件格式有误,可能会遇到类似以下错误:

1web_1 | standard_init_linux.go:219: exec user process caused: exec format error 2myapp_web_1 exited with code 1

在这种情况下,请修正你的 .sh 文件并点击 Run 按钮。再次启动 web 服务。如果 .sh 文件正确,将能成功启动。

那么这一切到底在做什么?

入口点(Entrypoint) 是在启动新容器时附加到运行命令前的指令。在我们的情况中,已将 ./docker-entrypoint.sh 设置为 web 服务的 ENTRYPOINT。这意味着,当我们启动一个新的 web 容器时,不再只是简单地运行默认命令:

1bin/rails s -b 0.0.0.0

而是会实际运行这个命令,得益于我们新增的 ENTRYPOINT 指令:

1./docker-entrypoint.sh bin/rails s -b 0.0.0.0

由于这个 shell 脚本会被执行,我们需要像第 2 步那样赋予其执行权限。

那么我们的 docker-entrypoint.sh shell 脚本到底在做什么?

如果你不熟悉 Bash,我们来逐行快速解析一下:

1#!/bin/sh 2set -e 3 4if [ -f tmp/pids/server.pid ]; then 5 rm tmp/pids/server.pid 6fi 7 8exec "$@"
  • 在 Bash 脚本中,最佳实践是在开头加上 set -e(第 2 行)——这使得脚本在后续任意命令出错(非零退出状态)时立即失败。

  • 第 4 行的 if 语句用于检查 tmp/pids/server.pid 文件是否存在。如果存在,则在第 5 行将其删除。这是脚本的清理部分,确保即使留下 server.pid 文件,我们的 Rails 服务器也能始终正常启动。

  • 然而,最终我们希望容器启动的是 Rails 服务器,而不是这个 Bash 脚本。这就是第 8 行 exec 命令的作用。它表示:“用接下来要运行的程序替换当前正在运行的进程(这个 Bash 脚本)”——几乎就像这个 shell 脚本从未存在过一样。

但 exec 到底运行了哪个程序?

$@ 的意思是“传递给此 Bash 脚本的所有参数”,在我们的情况下就是 bin/rails s -b 0.0.0.0。因此,我们实际上是在说:“用 Rails 服务器替换当前运行的 Bash 脚本”。

总结

总而言之,docker-entrypoint.sh 作为一个包装脚本,为我们提供了一个机会,在清理 pid 文件后,再像什么都没发生一样启动 Rails 服务器。现在你可以放心地随意运行 docker-compose up,无需担心这个烦人的 bug 影响你。

入口点(Entrypoints),尤其是遵循这种模式的,是你工具箱中一个非常有用的工具。你可能会发现其他创造性的用途。同时也要知道,使用 ENTRYPOINT 指令,你也可以直接在 Dockerfile 中指定入口点。

App 之外:添加 Redis

服务器无法正常关闭