如何正确连接php-fpm和Nginx Docker容器?

142

我想要将两个独立的容器连接起来:

问题在于,PHP脚本无法正常工作。也许是因为php-fpm配置不正确。以下是我的代码库中的源代码。这是文件docker-compose.yml的内容:

nginx:
    build: .
    ports:
        - "80:80"
        - "443:443"
    volumes:
        - ./:/var/www/test/
    links:
        - fpm
fpm:
    image: php:fpm
    ports:
        - "9000:9000"

以下是我用来基于 nginx 镜像构建自定义镜像的 Dockerfile

FROM nginx

# Change Nginx config here...
RUN rm /etc/nginx/conf.d/default.conf
ADD ./default.conf /etc/nginx/conf.d/

最后,这是我的自定义Nginx虚拟主机配置:

server {
    listen  80;

    server_name localhost;
    root /var/www/test;

    error_log /var/log/nginx/localhost.error.log;
    access_log /var/log/nginx/localhost.access.log;

    location / {
        # try to serve file directly, fallback to app.php
        try_files $uri /index.php$is_args$args;
    }

    location ~ ^/.+\.php(/|$) {
        fastcgi_pass 192.168.59.103:9000;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param HTTPS off;
    }
}

有人可以帮我正确配置这些容器以执行php脚本吗?

P.S. 我通过像这样的docker-compose运行容器:

docker-compose up

从项目根目录中运行。


1
你到目前为止尝试过如何配置它们或者使用了什么代码?请不要让我猜测,我很菜。 - Matthew Brown aka Lord Matt
1
@MatthewBrown 嗯,我把我的代码放到了 GitHub 上的公共仓库,认为这已经足够了,但你说得对,在我的问题中也最好展示一下代码。 - Victor Bocharsky
当图像启动时,您能否docker exec进入正在运行的容器并ping fpm? - Vincent De Smet
1
@MatthewBrown 是的,我赢了,谢谢。 - Victor Bocharsky
1
我还实现了一个工作解决方案,将 NginxPHP-FPM 与 Vagrant 和 Ansible 链接在一起。如果您有兴趣,请查看我的存储库 https://github.com/bocharsky-bw/vagrant-ansible-docker。 - Victor Bocharsky
显示剩余2条评论
8个回答

133

我知道这篇文章有点老,但我遇到了同样的问题,而且一直不明白为什么你的代码不起作用。经过很多次测试,我终于找到了原因。

看起来fpm从nginx接收到完整路径后,会尝试在fpm容器中查找文件,所以它必须与nginx配置中的server.root完全相同,即使在nginx容器中不存在。

为了证明这一点:

docker-compose.yml

nginx:
    build: .
    ports:
        - "80:80"
    links:
        - fpm
fpm:
    image: php:fpm
    ports:
        - ":9000"

    # seems like fpm receives the full path from nginx
    # and tries to find the files in this dock, so it must
    # be the same as nginx.root
    volumes:
        - ./:/complex/path/to/files/

/etc/nginx/conf.d/default.conf

server {
    listen  80;

    # this path MUST be exactly as docker-compose.fpm.volumes,
    # even if it doesn't exist in this dock.
    root /complex/path/to/files;

    location / {
        try_files $uri /index.php$is_args$args;
    }

    location ~ ^/.+\.php(/|$) {
        fastcgi_pass fpm:9000;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

Dockerfile

FROM nginx:latest
COPY ./default.conf /etc/nginx/conf.d/

11
做得好!这正是关键所在!我将 nginx 的根目录设置为一个替代路径,而不是/var/www/html,但失败了。 - Alfred Huang
4
另外需要注意的是,:9000 是容器中使用的端口,而不是暴露给主机的端口。我花了两个小时才弄清楚这一点。希望你不必再重复我的错误。 - shriek
2
services.fpm.ports is invalid: Invalid port ":9000", should be [[remote_ip:]remote_port[-remote_port]:]port[/protocol] - 030
9
在这里,实际上您不需要包括一个 ports 部分。如果它已经在镜像中(很可能是),您只需要将其 expose 出来即可。如果您正在进行容器间通信,则 不应该 暴露 PHP-FPM 端口。 - Seer
2
一直在寻找解决方案,以解决 AH01071: Got error 'Primary script unknown\n' 的问题。而与 Web 节点共享同一目录的 php-fpm 容器则是解决方案! - cptPH
显示剩余5条评论

40
不要在nginx配置中硬编码容器的ip地址,docker link会将链接到的机器的主机名添加到容器的hosts文件中,因此您应该能够通过主机名ping通。
编辑:Docker 1.9 Networking不再需要您链接容器,当多个容器连接到同一网络时,它们的hosts文件将被更新,以便它们可以通过主机名相互访问。
每次从镜像启动docker容器(甚至停止/启动现有容器),容器都会被分配新的IP,这些IP不在与实际机器相同的子网中。 请参阅docker链接文档(这是compose在后台使用的)。 但在链接和公开方面,docker-compose文档更清晰地解释了这一点

links

links:
 - db
 - db:database
 - redis

An entry with the alias' name will be created in /etc/hosts inside containers for this service, e.g:

172.17.2.186  db
172.17.2.186  database
172.17.2.187  redis

expose

Expose ports without publishing them to the host machine - they'll only be accessible to linked services. Only the internal port can be specified.

如果您设置项目通过环境变量获取端口和其他凭据,链接会自动设置一堆系统变量

要查看服务可用的环境变量,请运行docker-compose run SERVICE envname_PORT 完整的URL,例如DB_PORT=tcp://172.17.0.5:5432 name_PORT_num_protocol 完整的URL,例如DB_PORT_5432_TCP=tcp://172.17.0.5:5432 name_PORT_num_protocol_ADDR 容器的IP地址,例如DB_PORT_5432_TCP_ADDR=172.17.0.5 name_PORT_num_protocol_PORT 公开的端口号,例如DB_PORT_5432_TCP_PORT=5432 name_PORT_num_protocol_PROTO 协议(tcp或udp),例如DB_PORT_5432_TCP_PROTO=tcp name_NAME 完全限定的容器名称,例如DB_1_NAME=/myapp_web_1/myapp_db_1

2
你也不需要在主机上公开端口9000,因为链接的Docker容器之间的端口是开放的,除非你想直接从主机上进行端口故障排除。 - Vincent De Smet
是的,你说得对,谢谢。在我的情况下,我应该使用 fastcgi_pass fpm:9000 而不是直接使用 IP 地址。我不知道 Docker 会自动将其添加到主机中,这是我的错误。 - Victor Bocharsky
端口方面,使用“暴露”指令比“端口”更好吗?或者我可以不使用任何端口和暴露指令,因为链接的容器将可以访问此端口? - Victor Bocharsky
这是Docker Compose链接的完整参考 - http://docs.docker.com/compose/yml/#links 我将此信息添加到我的答案中。 - Vincent De Smet
3
根据你参考的Docker文档,--links现在已经过时了。虽然它们目前仍得到支持,但显然计划是将它们废除。 - therobyouknow
显示剩余3条评论

28

如前所述,问题在于fpm容器无法看到这些文件。然而,为了在容器间共享数据,推荐的模式是使用数据容器(如此文章中所解释的)。

简而言之:创建一个仅保存数据的容器,并通过卷将其与其他容器共享,在应用程序中使用volumes_from链接此卷。

使用compose(我机器上的版本为1.6.2),docker-compose.yml文件如下:

version: "2"
services:
  nginx:
    build:
      context: .
      dockerfile: nginx/Dockerfile
    ports:
      - "80:80"
    links:
      - fpm
    volumes_from:
      - data
  fpm:
    image: php:fpm
    volumes_from:
      - data
  data:
    build:
      context: .
      dockerfile: data/Dockerfile
    volumes:
      - /var/www/html

请注意,data发布了与nginxfpm服务链接的卷。然后是data服务的Dockerfile,其中包含您的源代码:

FROM busybox

# content
ADD path/to/source /var/www/html

以下是用于 Nginx 的 Dockerfile,只需替换默认配置:

FROM nginx

# config
ADD config/default.conf /etc/nginx/conf.d
为了完整起见,这是示例所需的配置文件:
server {
    listen 0.0.0.0:80;

    root /var/www/html;

    location / {
        index index.php index.html;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass fpm:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
    }
}

这只是告诉nginx将共享卷用作文档根目录,并为nginx设置正确的配置以便能够与fpm容器通信(即:正确的HOST:PORT,由于compose定义的主机名是fpm:9000SCRIPT_FILENAME)。


看起来数据没有从主机更新到容器中,当我执行docker ps -a时,我发现数据容器已经停止了,这是一个问题吗? - Aftab Naveed
2
这是预期的行为。一个仅包含数据的容器不会运行任何命令,只会被列为已停止状态。此外,数据容器的 Dockerfile 在构建时将源代码复制到容器中。这就是为什么如果您在主机上更改文件,则它们不会被更新。如果您想在主机和容器之间共享源代码,则需要挂载目录。在组合文件中将 data 服务更改为加载 image: busybox,并在 volumes 部分输入 ./sources:/var/www/html,其中 ./sources 是主机上源代码的路径。 - iKanor
1
博客文章的链接已经失效:https://web.archive.org/web/20200621204615/http://crosbymichael.com/advanced-docker-volumes.html - Stefan

18

新答案

Docker Compose已经更新了,现在有一个版本2文件格式

Compose 1.6.0+支持版本2文件,需要Docker Engine的版本为1.10.0 +。

他们现在支持Docker的网络功能,运行时会设置一个名为myapp_default的默认网络。

根据他们的文档,您的文件应该类似于以下内容:

version: '2'

services:
  web:
    build: .
    ports:
      - "8000:8000"
  fpm:
    image: phpfpm
  nginx
    image: nginx

由于这些容器已自动添加到默认网络myapp_default中,它们可以相互通信。然后在Nginx配置中将会有:

fastcgi_pass fpm:9000;

此外,正如@treeface在评论中提到的那样,请确保PHP-FPM正在侦听9000端口,这可以通过编辑/etc/php5/fpm/pool.d/www.conf来完成,您将需要listen = 9000

旧答案

我将以下信息保留在此处,供使用较旧版本的Docker/Docker-compose的人员使用。

当我尝试寻找答案时,在Google上不断碰到这个问题,但由于Q/A强调docker-compose(目前只支持docker网络功能实验版),与我所要寻找的不太一样。因此,这是我根据自己的学习总结而得出的结论。

Docker最近已 废除了其链接功能,取而代之的是其网络功能

因此,使用Docker Networks功能,您可以按照以下步骤链接容器。有关选项的完整解释,请阅读先前链接的文档。

首先创建您的网络

docker network create --driver bridge mynetwork

接下来运行您的PHP-FPM容器,确保打开9000端口并将其分配给您的新网络(mynetwork)。

docker run -d -p 9000 --net mynetwork --name php-fpm php:fpm

重要的是在命令末尾加上 --name php-fpm,这是名称,稍后我们会用到它。

接下来再次运行您的Nginx容器,并将其分配到您创建的网络中。

docker run --net mynetwork --name nginx -d -p 80:80 nginx:latest

对于 PHP 和 Nginx 容器,您还可以根据需要添加 --volumes-from 命令等。

现在是 Nginx 配置。它应该类似于这样:

server {
    listen 80;
    server_name localhost;

    root /path/to/my/webroot;

    index index.html index.htm index.php;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass php-fpm:9000; 
        fastcgi_index index.php;
        include fastcgi_params;
    }
}

注意在location块中的fastcgi_pass php-fpm:9000;。这意味着要联系端口9000上的容器php-fpm。当你将容器添加到Docker桥接网络中时,它们都会自动获得主机文件更新,其中将它们的容器名称与IP地址对应。因此,当Nginx看到这个信息时,它将知道要联系那个你之前命名为php-fpm且分配给你的mynetwork Docker网络的PHP-FPM容器。

你可以在Docker容器的构建过程中或之后添加该Nginx配置,具体由你决定。


还要记得确保 php-fpm 正在监听 9000 端口。这可以在 /etc/php5/fpm/pool.d/www.conf 中设置为 listen = 9000 - treeface
谢谢@treeface,好建议。我已经根据您的评论进行了更新。 - DavidT

11

之前的答案已经解决了,但需要非常明确地说明:php代码需要存在于php-fpm容器中,而静态文件需要存在于nginx容器中。为简单起见,大多数人只是将所有代码附加到这两个容器中,就像我下面做的一样。在未来,我很可能会将自己项目中的不同代码部分分开,以最小化哪些容器可以访问哪些部分。

以下是我用最新的发现更新的示例文件(感谢@alkaline)

这似乎是Docker 2.0及更高版本的最小设置 (因为Docker 2.0使事情变得更加容易)

docker-compose.yml:

version: '2'
services:
  php:
    container_name: test-php
    image: php:fpm
    volumes:
      - ./code:/var/www/html/site
  nginx:
    container_name: test-nginx
    image: nginx:latest
    volumes:
      - ./code:/var/www/html/site
      - ./site.conf:/etc/nginx/conf.d/site.conf:ro
    ports:
      - 80:80

(上面的docker-compose.yml已进行更新:对于具有css、javascript、静态文件等的网站,您需要使这些文件可访问nginx容器。同时仍然需要将所有php代码可访问fpm容器。因为我的基础代码是混合了css、js和php的一团乱麻,所以此示例只是将所有代码都附加到了两个容器中。)

在同一个文件夹中:

site.conf:

server
{
    listen   80;
    server_name site.local.[YOUR URL].com;

    root /var/www/html/site;
    index index.php;

    location /
    {
        try_files $uri =404;
    }

    location ~ \.php$ {
        fastcgi_pass   test-php:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_params;
    }
}

在文件夹code中:

./code/index.php:

<?php
phpinfo();

而且不要忘记更新您的 hosts 文件:

127.0.0.1 site.local.[YOUR URL].com

并运行您的docker-compose up命令

Translated:

and run your docker-compose up command

$docker-compose up -d

并尝试使用您喜爱的浏览器访问网址

site.local.[YOUR URL].com/index.php

1
你的nginx配置文件假设你的网站只有php文件。最佳实践是为静态文件(jpg、txt、svg等)创建一个nginx位置规则,并避免使用php解释器。在这种情况下,nginx和php容器都需要访问网站文件。@iKanor上面的答案已经处理了这个问题。 - Bernard
感谢 @Alkaline,静态文件是我原始答案的问题。实际上,为了使nginx正常工作,至少需要将css和js文件本地化到该机器。 - Phillip

9
我认为我们还需要给fpm容器分配卷,不是吗?所以 =>
fpm:
    image: php:fpm
    volumes:
        - ./:/var/www/test/

如果我不这样做,当发出请求时,我会遇到这个异常,因为fpm找不到请求的文件:
[error] 6#6: *4 FastCGI发送了stderr:"Primary script unknown" while reading response header from upstream, client: 172.17.42.1, server: localhost, request: "GET / HTTP/1.1", upstream: "fastcgi://172.17.0.81:9000", host: "localhost"

1
是的,你是对的!我们必须使用fpm和nginx共享文件。 - Victor Bocharsky
我在 GitHub 上有一个关于 NginxPHP-FPM 的工作示例。 - Victor Bocharsky

1

如果其他人在使用index.php时遇到Nginx 403错误:“[folder]的目录索引被禁止”,而index.html却可以完美运行,并且已经在其站点配置文件的服务器块中的索引中包含了index.php,则需要进行以下处理。

server {
    listen 80;

    # this path MUST be exactly as docker-compose php volumes
    root /usr/share/nginx/html;

    index index.php

    ...
}

请确保您的nginx.conf文件位于/etc/nginx/nginx.conf,并在http块中加载您的站点配置...
http {

    ...

    include /etc/nginx/conf.d/*.conf;

    # Load our websites config 
    include /etc/nginx/sites-enabled/*;
}

谢谢这个,虽然有点老但仍然很有用,我不得不使用 /usr/share/nginx/html ,谢谢。 - Simon Davies

1

使用官方镜像和nginxconfig

nginx-php.yaml(您需要添加端口或网络方法以访问nginx服务)

services:
  nginx:
    image: nginx
    volumes:
      - /root/nginx/root:/usr/share/nginx/html:ro
      - /root/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - /root/nginx/templates:/etc/nginx/templates:ro
      - /root/nginx/log:/var/log/nginx

  php:
    image: php:fpm
    volumes:
      - /root/nginx/root:/usr/share/nginx/html:ro

example.com.conf.template

server {
    listen      80;
    listen      [::]:80;
    server_name example.com;
    set         $base /usr/share/nginx/html/example.com;
    root        $base/;

    # logging
    access_log  /var/log/nginx/access.log combined buffer=512k flush=1m;
    error_log   /var/log/nginx/error.log warn;

    # index.php
    index       index.php;

    # index.php fallback
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # handle .php
    location ~ \.php$ {
        fastcgi_pass                  php:9000;

        # 404
        try_files                     $fastcgi_script_name =404;

        # default fastcgi_params
        include                       fastcgi_params;

        # fastcgi settings
        fastcgi_index                 index.php;
        fastcgi_buffers               8 16k;
        fastcgi_buffer_size           32k;

        # fastcgi params
        fastcgi_param DOCUMENT_ROOT   $realpath_root;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param PHP_ADMIN_VALUE "open_basedir=$base/:/usr/lib/php/:/tmp/";
    }
}

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