生产环境下的 PHP 应用程序 Docker 化

10

我们有一个使用PHP(具体来说是Laravel)编写的应用程序,应该为生产环境进行docker化。但是将应用程序源代码与Web服务器和PHP-FPM容器共享存在问题。

Nginx和PHP-FPM都应该能够访问应用程序源代码,因此以下是网上建议的解决方法:

  1. 为Nginx和PHP-FPM分别设置两个独立的容器,并将源代码挂载到主机上并创建卷。然后将此卷分配给这些容器。这种解决方案不理想,因为每当应用程序代码更改时,整个堆栈都应该重新构建并且创建的卷应该被清除。此外,这些任务应该在所有服务器上执行,可能会浪费很多时间。
  2. 在同一个容器中同时运行PHP-FPM和Nginx,并使用supervisorentrypoint脚本保持它们的进程运行。在这个解决方案中,当源代码更改时,我们只需要构建一次映像,希望不需要清除共享卷,所以这似乎是一个好的解决方案。但是,这种解决方案的主要问题是违反了容器化背后的思想。Docker在其文档中表示:

    每个容器应该只负责一个关注点(或运行进程)。

    但是在这里,我们有两个运行进程!

有没有其他解决方案可在生产环境中运行?需要说明的是,我们将在不久的将来使用SwarmKubernetes

谢谢。

2个回答

2
总的来说,生产环境中应该避免使用这两种方法,但如果我比较卷载挂载和一个容器中运行两个进程,我会选择容器中运行两个进程而不是将主机代码挂载到容器中。
在某些情况下,第一种方法失败了,比如在Fargate这样的无服务器主机上,那么你肯定会选择在一个容器中运行两个进程。
运行多个进程的主要问题是“如果php-fpm崩溃了,Nginx进程怎么办”。但你可以通过多种方式处理这种情况,可以查看docker文档提供的建议方法。 docker-multi-service_container Docker文档使用自定义脚本或supervisord来处理这种情况。
如果您需要在一个容器内运行多个服务,有几种不同的方法可以实现。
一种方法是将所有命令放入包含测试和调试信息的包装脚本中,并将该包装脚本作为CMD运行。以下是一个非常简单的示例。首先是包装脚本:
#!/bin/bash

# Start the first process
./my_first_process -D
status=$?
if [ $status -ne 0 ]; then
  echo "Failed to start my_first_process: $status"
  exit $status
fi

# Start the second process
./my_second_process -D
status=$?
if [ $status -ne 0 ]; then
  echo "Failed to start my_second_process: $status"
  exit $status
fi

# Naive check runs checks once a minute to see if either of the processes exited.
# This illustrates part of the heavy lifting you need to do if you want to run
# more than one service in a container. The container exits with an error
# if it detects that either of the processes has exited.
# Otherwise it loops forever, waking up every 60 seconds

while sleep 60; do
  ps aux |grep my_first_process |grep -q -v grep
  PROCESS_1_STATUS=$?
  ps aux |grep my_second_process |grep -q -v grep
  PROCESS_2_STATUS=$?
  # If the greps above find anything, they exit with 0 status
  # If they are not both 0, then something is wrong
  if [ $PROCESS_1_STATUS -ne 0 -o $PROCESS_2_STATUS -ne 0 ]; then
    echo "One of the processes has already exited."
    exit 1
  fi
done

使用类似supervisord的进程管理器。这是一种中等重量级的方法,需要您将supervisord及其配置打包到镜像中(或者基于包含supervisord的镜像),以及它管理的不同应用程序。然后启动supervisord,它会为您管理进程。以下是一个使用此方法的Dockerfile示例,假设预先编写的supervisord.conf、my_first_process和my_second_process文件都与您的Dockerfile在同一目录中。
但如果您正在寻找监督程序,可以检查关闭其中一个程序后监督程序和其他类似的方法来监视进程。

2
谢谢您的回复。我之前已经在Docker文档中阅读了那篇文章。正如您所说,这些方法都不适合生产环境。我的问题是:正确的做法是什么? - Erfun
我提到最好每个容器运行两个进程,并像Docker文档中建议的那样进行一些监控,这是你问题中的第二个选项。 - Adiii
是的,我明白你的意思,但第二种情况感觉就像在两个不好的选择中做出更糟糕的选择。对于这样的事情,是否真的有一个“好”的选择呢? - Erfun
我认为有很多巨型云服务正在使用第二种方法。https://github.com/TrafeX/docker-php-nginx,以及这个https://github.com/richarvey/nginx-php-fpm/tree/master/scripts已经有超过1000万次下载了,顺便说一下,这里的方法是仅监听php-fpm端口而不是代码。https://www.shiphp.com/blog/2018/nginx-php-fpm-with-env - Adiii
Fargate现在通过EFS卷支持持久性存储。我必须承认,从一开始就对于Fargate(以及其他服务)不支持标准的Docker存储机制感到奇怪。 - Alexander Shcheblikin

1
你可以创建两个独立的Docker镜像,一个只包含静态资源,另一个包含可运行的后端代码。静态资源镜像可以尽可能地精简。
# Dockerfile.nginx
FROM nginx:latest
COPY . /usr/share/nginx/html

不要在任何地方绑定挂载。确保您的CI系统构建两个镜像。

TAG=20191214
docker build -t myname/myapp-php:$TAG .
docker build -t myname/myapp-nginx:$TAG -f Dockerfile.nginx .

现在,您可以运行两个独立的容器(不违反每个容器一个进程的指导方针),独立扩展它们(3个nginx但30个PHP),而无需手动复制源代码。另一个有用的技术是将静态资源发布到某些外部托管系统;如果您已经在AWS上运行,那么S3在这里很有效。您仍然需要某种代理来将请求转发到资产存储库或后端服务,但现在可以使用带有自定义配置文件的Nginx;它不需要任何应用程序代码。(在Kubernetes中,您可以使用指向具有nginx.conf文件的配置映射的Nginx部署运行此操作。)在设置CI系统时,绝对不应在构建或集成测试时将代码绑定到容器中。测试您正在构建的容器中的内容,而不是源代码的其他副本。

nginx容器中没有index.php文件时,它如何知道应该处理index.php并将其传递给上游?Nginx首先查找请求的文件,如果找不到,则返回404错误。 - Erfun
1
我会使用 try_filesfastcgi_pass 指令的组合;您还可以设置规则,使所有 .php 的 URL 都被发送到后端,无论文件是否存在(这可能是更好的设置方式)。 - David Maze
假设我们正在使用第二种方法(将所有“.php” URL发送到后端)。如果文件丢失会发生什么?我们如何发送回404错误? - Erfun

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接