docker运行php-fpm,并使用本地nginx反向代理

写在前面

运行一个php站点最简单的办法是,直接运行一个nginx+php或apache+php的docker镜像组,或者直接开一台虚机直接安装nginx+php。

本文情况特殊,由于想用本地nginx+容器php,且避免二次反向代理,所以踩了一些坑,在此记录一下。新手建议还是先从简单的方案上手,而不是本文的方案。

背景

已有一台机器,本地已经运行nginx(使用systemd运行),现在需要增加服务一个php站点,并有要求将php-fpm在docker中运行。所以需要配置docker容器,并配置nginx vhost主机。

一些事实

  1. 一个php站点需要由两部分才能组成:
    • web服务器:用于反向代理、负载均衡、静态资源分发(如站点中图片、html等除.php文件资源,文件本身就是响应)
    • php-fpm / php-cgi 进程:站点中的php文件实际上是脚本,脚本运行后的结果才是要返回客户端的响应,所以需要web服务器将请求发到php,获得运行结果后,web服务器再将响应返回到客户端。
  2. 大部分情况下,一个php站点中,静态资源和php脚本通常是混合存放的。所以web服务器和php-fpm这两个程序通常都需要访问同一个站点资源目录。请求到达web服务器后,web服务器将判断客户实际请求的是普通的静态资源,还是一个php脚本。如果是后者,则web服务器将通过fastcgi协议调用php,调用时,需要指定一系列参数,其中最重要的一个就是SCRIPT_FILENAME参数,这个参数告诉了php本次要运行的是哪一个脚本,你需要确保这个参数指向的文件可被php进程读取。
  3. 在本文中,web服务器使用nginx(本地systemd运行),php使用docker容器(Bitnami/php-fpm)。关于fpm和cgi的区别可以网络搜索,一般使用fpm更通用。

操作过程

  1. 创建存放php站点文件的目录。
    从前提部分可知,需要一个公共的目录,以同时供web服务器和php服务访问。此处,可以指定nginx默认的html目录(如/usr/share/nginx/html/var/www/html等,取决于不同的发行版)。
    由于此php站点计划使用专门的域名提供服务,所以新建一个文件夹作为站点文件目录。
mkdir /usr/share/nginx/php-site/
  1. 将文件拷贝到站点目录
    此处仅作演示目的,创建一个php文件和一个html文件。
mkdir /usr/share/nginx/php-site/app01
echo '<?php echo "The time is " . date("h:i:sa"); ?>' > /usr/share/nginx/php-site/app01/index.php
echo '<html><body><h1>hello</h1></body></html>' > /usr/share/nginx/php-site/app01/example.html
  1. 创建php docker容器
docker run -d --name=phpfpm\
    -e TZ=Asia/Shanghai \
    -p 9000:9000 \
    -w /usr/share/nginx/php-site \
    -v /usr/share/nginx/php-site:/usr/share/nginx/php-site \
    bitnami/php-fpm:latest

-w参数也可以不加,这个参数在这个镜像中只影响你docker exec的时候默认会进到哪个目录。
-v挂载的目录/usr/share/nginx/php-site必须是第1步中创建的路径,具体看结尾解释

  1. 创建一个nginx vhost
    /etc/nginx/sites-enabled/m01.example.com.conf
# include /etc/nginx/conf.d/php-fpm.conf; # 此文件已在http块引入
server {
    listen       80;
    #listen       [::]:80; # 监听IPv6 80端口
    server_name  m01.example.com;
    root         /usr/share/nginx/php-site;

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

    include /etc/nginx/default.d/php.conf;  # 发行版自带的的php配置文件,后文贴出
}

此外,还需要指定一个php-fpm的upstream,此处直接修改自带配置文件,注意此文件已经在http块中引入:
/etc/nginx/conf.d/php-fpm.conf

# PHP-FPM FastCGI server
# network or unix domain socket configuration

upstream php-fpm {
# 注释下面这行,nginx默认假设phpfpm是本地socket连接的,不适合本文情况
#        server unix:/run/php-fpm/www.sock; # 
   server 127.0.0.1:9000;
}

更新完成后,执行nginx -t测试配置文件,若无错误,执行nginx -s reload

附:自带配置文件:/etc/nginx/default.d/php.conf

# pass the PHP scripts to FastCGI server
#
# See conf.d/php-fpm.conf for socket configuration
#
index index.php index.html index.htm;

location ~ \.(php|phar)(/.*)?$ {
    fastcgi_split_path_info ^(.+\.(?:php|phar))(/.*)$;

    fastcgi_intercept_errors on;
    fastcgi_index  index.php;
    include        fastcgi_params;
    fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    fastcgi_param  PATH_INFO $fastcgi_path_info;
    fastcgi_pass   php-fpm;
}

附:自带配置文件:/etc/nginx/fastcgi_params


fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;

fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;
fastcgi_param  REQUEST_SCHEME     $scheme;
fastcgi_param  HTTPS              $https if_not_empty;

fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;

fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;

# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param  REDIRECT_STATUS    200;
  1. 测试运行
    使用curl http://m01.example.com/app01/example.html可以获得响应hello,证明html文件serve正常;
    使用curl http://m01.example.com/app01/ 可以获得当前时间响应,说明php文件serve正常。

错误排除

  1. Primary script unknown 错误
    日志内容:

[error] 21029#21029: *998 FastCGI sent in stderr: "Primary script unknown" while reading response header from upstream, client: 1.2.3.4, server: m01.example.com, request: "GET /app01/ HTTP/2.0", upstream: "fastcgi://127.0.0.1:9000"

这一般来说是由于fastcgi调用php时传递的SCRIPT_FILENAME参数存在错误产生的。搜索资料可知,文件不存在、文件权限错误都会导致产生这个问题,本文中php在容器中为root用户,不存在权限问题,所以100%为文件不存在,或参数错误。

调试方法是修改nginx.conf,将error_log /var/log/nginx/error.log error;一行最后的error改为debug,然后执行nginx -sreload,然后观察/var/log/nginx/error.log文件中的debug信息。(生产环境谨慎开启debug,建议使用server块级别的error配置)

搜索SCRIPT_FILENAME找到这行fastcgi调用参数的打印:

2024/09/21 17:55:50 [debug] 21024#21024: *908 fastcgi param: "SCRIPT_FILENAME: /usr/share/nginx/php-site/app01/index.php"

然后进入到php容器(如docker exec),确认一下这个文件是否存在。此处文件已经通过docker -v挂载,所以访问正常。

总结

很久没有用过php,踩坑的原因是对web服务、php服务协作方式不够了解,对SCRIPT_FILENAME参数理解不够,也不了解fastcgi调用参数的debug方法。

实际上,SCRIPT_FILENAME参数怎么设置都可以,只要在php容器中存在这个参数指向的文件,就能正常运行到php脚本。

但这个参数默认情况下都是被设置为$document_root$fastcgi_script_name,所以web服务的document_root需要设置为php资源的目录,且php容器挂载目录时需要和document_root路径一致不能修改。设置不对就会导致报错404或者返回一个File not found.

如果一定想要不一致,你只需要:

  1. SCRIPT_FILENAME设置为/app$fastcgi_script_name;
  2. 挂载时指定-v /usr/share/nginx/php-site:/app

关键词

php-fpm nginx docker script_filename

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容