【问题标题】:Dockerize PHP Application for ProductionDockerize 用于生产的 PHP 应用程序
【发布时间】:2019-12-14 08:03:14
【问题描述】:

我们有一个 PHP(特别是 Laravel)应用程序,应该为生产环境进行 docker 化。但是与 Web Server 和 PHP-FPM 容器共享应用程序源代码存在问题。

Nginx 和 PHP-FPM 都应该可以访问应用程序源代码,所以这里是网络上建议的解决方法:

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

    每个容器应该有一个关注点(或正在运行的进程)。

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

是否有其他解决方案可以在生产环境上运行?不得不提的是,我们将在不久的将来使用 SwarmKubernetes

谢谢。

【问题讨论】:

    标签: php docker containers


    【解决方案1】:

    一般来说,这两种方法在生产中都应该避免,但是如果我比较卷挂载和每个容器两个进程,我会选择每个容器两个进程,而不是将宿主代码挂载到容器,

    在某些情况下,第一种方法会失败,例如 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 位于同一目录中。

    但如果您正在寻找主管,您可以查看shutdown supervisor once one of the programs is killed 和其他类似方法来监控流程。

    【讨论】:

    • 感谢您的回复。我之前在 docker 文档中读过那篇文章。正如您所说,这些方法都不适合生产。我的问题是这样做的正确方法是什么?
    • 我提到最好每个容器运行两个进程,并将 docker 中建议的一些监控放入文档中,这是您问题中的第二个选项。
    • 是的,我明白了,但第二种情况感觉就像在坏和更坏之间选择坏。像这样的东西还有一个好的选择吗?
    • 我确实这么认为,有很多大型云服务正在使用第二种方法。 github.com/TrafeX/docker-php-nginx 和这个 github.com/richarvey/nginx-php-fpm/tree/master/scripts 有 10M+ 的下载量,顺便说一句,这里是监听 php-fpm 端口的统计数据,而不是代码。 shiphp.com/blog/2018/nginx-php-fpm-with-env
    • Fargate 现在支持通过 EFS 卷进行持久存储。我不得不承认,Fargate(以及其他相关服务)从一开始就不支持标准的 Docker 存储机制是很奇怪的。
    【解决方案2】:

    您可以创建两个单独的 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错误。
    • 我会使用 try_filesfastcgi_pass 指令的组合;您还可以设置一个规则,以便无论文件是否存在,所有.php URL 都会发送到后端(这可能是一个更好的设置)。
    • 假设我们正在使用第二种方法(将所有.php URL 发送到后端)。如果文件丢失会怎样?我们如何发回 404 错误?
    猜你喜欢
    • 2021-08-19
    • 1970-01-01
    • 1970-01-01
    • 2023-03-12
    • 2018-01-10
    • 2019-06-17
    • 2022-01-11
    • 1970-01-01
    • 2019-04-29
    相关资源
    最近更新 更多