ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

麒麟V10上源码编译安装PHP与配置PHP-FPM实战指南

麒麟V10上源码编译安装PHP与配置PHP-FPM实战指南 上个月接了一台跑在麒麟 V10 (Sword) 上的业务服务器需要把 PHP 从无到有部署起来。本来以为照着 CentOS 那套思路走就完事结果真踩进去才发现Kylin Linux Advanced Server V10 这系统虽然跟 RHEL 系长得像但坑全藏在细节里。这篇文章就记录我在这套系统上从环境准备到编译装完 PHP再到配好 PHP-FPM 和 Nginx 的完整过程重点是每一步背后的理由和实际操作中遇到的那些坑。1. 为什么非要在麒麟V10上源码编译PHP1.1 麒麟V10的系统特殊性Kylin Linux Advanced Server V10 (Sword) 是国内服务器领域很常见的一套操作系统基于 Linux 内核整体兼容 RHEL/CentOS 的包管理方式和编译习惯。我第一次拿到系统时先执行了cat /etc/os-release能看到发行版信息再执行uname -m确认架构这套操作跟 CentOS 上几乎一模一样。但它的软件仓库策略跟 CentOS 有明显区别。直接用yum install php装出来的版本往往比较保守而且官方源里的 PHP 扩展不太全某些自研模块比如自带的加密组件、特定的数据库驱动跟业务代码的兼容性也没法保证。我这次遇到的场景更直接业务方要求 PHP 8.1 以上并且要启用pcntl、swoole这类扩展仓库里的版本满足不了那就只能走源码编译这条路。1.2 源码编译vs二进制包选择理由和代价很多人一听到编译安装就觉得麻烦其实选择源码编译通常就三个原因版本定制、扩展定制、目录定制。仓库里的 RPM 包虽然装起来快但版本被锁死扩展开哪些不开哪些也是别人帮你定好的源码编译则可以从头指定 PHP 版本、指定安装前缀、按需启用或禁用扩展甚至可以针对特定的 CPU 指令集做优化。代价就是编译过程需要一定耐心依赖库要自己装configure、make、make install 这套流程跑下来顺利的话半小时不顺利的话一下午。我这次实际编译了三次才把扩展选型确认下来后面我会一个个说明原因。对于一台生产服务器来说多花点时间在构建上换来的是后续运行期的稳定和可控。注意如果业务方对 PHP 版本没有要求系统仓库自带的 PHP 其实完全够用。只有在需要特定版本或特定扩展时才值得源码编译这个决策要先想清楚。2. 编译前的系统摸底与依赖准备2.1 检查系统版本和编译工具链编译 PHP 的第一步不是下载源码而是摸清系统底子。我先确认了这几项系统版本cat /etc/os-release架构uname -m麒麟 V10 有 x86_64 和 aarch64 两种常见架构编译参数基本一致但某些扩展在 aarch64 下需要额外配置现有 gcc 版本gcc --version是否已有基础编译工具链which make、which cmake、which autoconf麒麟 V10 默认安装的 gcc 版本通常够用但如果系统是精简安装版可能需要先补上基础开发工具。我在第一次编译时就是因为系统没装make和gccconfigure 阶段直接卡住后来统一安装了一组编译工具集。yum install -y gcc gcc-c make automake autoconf建议装完之后顺手验证一下编译链是否正常写个简单 C 程序跑一遍确认 gcc 能正确输出可执行文件。这一步能规避一大批后续编译报错别嫌麻烦真出了问题再排查要费更多时间。2.2 一次性装齐编译依赖库PHP 编译时不光需要编译工具还需要一堆开发库。这些库的作用是为 PHP 提供各种基础能力的底层接口比如访问 MySQL 需要libmysqlclient的头文件、处理 XML 需要libxml2的头文件、做 HTTPS 请求需要 OpenSSL 的开发库。缺了这些头文件configure 阶段就会报configure: error: xml2-config not found或类似错误。以下是我在麒麟 V10 上实际执行过的依赖安装命令覆盖了最常见的扩展需求yum install -y libxml2-devel openssl-devel curl-devel libjpeg-devel \ libpng-devel freetype-devel bzip2-devel libzip-devel oniguruma-devel \ readline-devel sqlite-devel libicu-devel openldap-devel这里特别要提两个容易漏的库oniguruma-devel如果 PHP 要启用正则相关的多字节支持mbstring扩展依赖它缺少这个库会导致 configure 报错。我第一次编译时漏了这个后来补上重跑 configure 才顺利通过。libzip-develPHP 8.0 之后zip扩展是必不可少的如果不装这个开发库--with-zip会失败。还有一个需要注意的点麒麟 V10 的默认 yum 源里可能没有全部包如果执行 yum install 时提示No package需要先启用 EPEL 源或者补充麒麟自己的 DVD 源。epel-release通常可以直接yum install epel-release安装。提示从 PHP 7.4 开始phpize编译扩展时需要pcre2开发库。如果后续要装 PECL 扩展建议提前yum install -y pcre2-devel否则phpize会报找不到pcre2.h。2.3 确认扩展库的底层依赖是否存在这一步很多人忽略但恰恰是最容易埋雷的。libxml2-devel装了不代表libxml2本体就能正常工作openssl-devel装了不代表 PHP 就能成功启用 OpenSSL 扩展。在 configure 阶段PHP 会通过 pkg-config 或小型的探测程序检查这些库是否可用。常用的检查指令pkg-config --exists libxml-2.0 echo libxml ok pkg-config --exists openssl echo openssl ok pkg-config --exists libcurl echo curl ok如果 pkg-config 报错说明对应的-devel包没有正确安装或者安装路径不在 pkg-config 搜索范围。麒麟 V10 上我遇到过 pkg-config 路径不包含/usr/local/lib/pkgconfig的情况可以设置环境变量解决export PKG_CONFIG_PATH/usr/local/lib/pkgconfig:/usr/lib/pkgconfig:$PKG_CONFIG_PATH另外在 ARM64 (aarch64) 架构下某些库的安装路径是/usr/lib64而不是/usr/libPHP 的 configure 脚本默认搜索路径可能漏掉。此时可以在 configure 时手动指定--with-libdirlib64这个参数在 x86_64 上一般不用管但在麒麟 V10 的 ARM 版上非常重要。3. configure配置阶段参数怎么选、为什么这么选3.1 核心配置参数拆解依赖准备好之后下载 PHP 源码就是常规操作。我选择的是 PHP 8.1.27 稳定版这个版本在性能和生态兼容性之间比较均衡。下载解压之后进入源码目录开始 configure。configure 是编译安装的核心环节它的作用有点类似“量体裁衣”——根据你传入的参数检查系统能力、决定哪些功能启用、哪些扩展禁用最终生成 Makefile。参数选得好后面 make 阶段基本一次过参数选得差编译到一半发现缺库返工成本极高。以下是我这次使用的完整 configure 命令并对关键参数做拆解说明./configure \ --prefix/usr/local/php \ --with-config-file-path/usr/local/php/etc \ --with-config-file-scan-dir/usr/local/php/etc/php.d \ --enable-fpm \ --with-fpm-userwww \ --with-fpm-groupwww \ --enable-cli \ --enable-mbstring \ --enable-zip \ --enable-pcntl \ --enable-sockets \ --enable-opcache \ --enable-bcmath \ --with-mysqlimysqlnd \ --with-pdo-mysqlmysqlnd \ --with-openssl \ --with-curl \ --with-zlib \ --with-bz2 \ --with-gettext \ --with-iconv \ --with-libdirlib64几个关键选择--prefix/usr/local/php指定 PHP 根目录便于后续维护。个人习惯把自编译软件统一放/usr/local下升级和删除都方便不会污染系统目录。--with-config-file-path/usr/local/php/etc指定php.ini的存放位置。这个参数在预编译二进制包里经常被忽略导致后面php --ini显示的加载路径不是预期路径。--enable-fpm和--with-fpm-user/group启用 PHP-FPM 进程管理器并指定运行用户。这里先创建好 www 用户避免后面手动调整权限。--with-mysqlimysqlnd和--with-pdo-mysqlmysqlnd采用 mysqlndMySQL Native Driver这是 PHP 官方推荐的驱动不需要额外安装 MySQL 客户端库性能也更好。--enable-pcntl进程控制扩展很多常驻内存的 PHP 脚本比如队列消费者、异步任务处理依赖这个扩展。默认不启用需要显式声明。--with-libdirlib64在 64 位系统下告诉 configure 去lib64目录查找依赖库这也是我在麒麟 V10 上实际踩过的坑缺了它会导致找不到libxml2.so等库。3.2 扩展启用的策略PHP 的扩展从编译期就可以分为静态编译和动态加载两种。静态编译的扩展直接编进 PHP 二进制本体性能好、部署简单动态加载的扩展以.so文件形式存在通过php.ini里的extensionxxx.so加载灵活性高。我个人的策略是核心扩展如 opcache、mbstring、pcntl、sockets静态编译进 PHP避免 .so 版本与 PHP 主版本不匹配的问题非核心的如 redis、swoole用 PECL 动态安装方便后续升级。configure 时还有一类参数值得注意——--disable-*。默认 PHP 会启用一些你也许用不到的扩展比如--disable-fileinfo可以加快编译速度--disable-ipv6在某些纯内网环境可以省掉部分依赖。每少一个扩展make 的时间就能缩短一点同时也减少潜在的攻击面。但业务要用的功能一定要保留比如我这次保留的--enable-filter和默认启用的 JSON 支持。3.3 configure常见失败点configure 失败是编译安装里最常见的拦截关口报错信息通常直接告诉你缺了什么库。我整理了三个高概率报错及解决方式报错一configure: error: xml2-config not found. Please check your libxml2 installation.原因libxml2-devel 未安装或其路径不在搜索范围内。解决yum install -y libxml2-devel必要时设置LIBXML_CFLAGS和LIBXML_LIBS。报错二configure: error: Please reinstall the libcurl distribution - easy.h should be in curl-dir/include/curl/原因curl-devel 缺失或版本过旧。解决yum install -y curl-devel如果已经安装检查是否存在/usr/include/curl/easy.h。报错三configure: error: Package requirements (oniguruma) were not met原因oniguruma-devel 缺失。解决安装后重新 configure注意不要漏掉--enable-mbstring对它的依赖。还有一个通用排查手段configure 出错的日志尾部通常会给出 pkg-config 的检测结果执行pkg-config --list-all | grep 库名可快速定位是哪个包出了问题。4. make编译与安装验证4.1 多核编译与资源控制configure 顺利通过之后就进入make阶段。这一步的本质是调用 gcc 将 PHP 源码编译成可执行文件耗时取决于机器核数和配置的扩展数量。我习惯用make -j$(nproc)让编译进程数与 CPU 核心数匹配能显著提速。不过这里有个注意点如果服务器本身承载着线上业务建议不要无脑使用全部核心否则编译瞬间 CPU 飙到 100%可能影响现有服务的响应。我通常在业务低谷时段用make -j2或make -j4保守编译宁可慢一点也别干扰线上业务。第一次 make 通常会持续 5 到 10 分钟中间会有大量编译输出只要没出现Error字样基本不用管。如果中途报错常见原因是内存不足gcc 进程被 OOM killer 杀掉或某个扩展源文件有问题。内存不足时可以减少并行数或者临时增加 swap。4.2 安装目录与配置文件初始化make 完成之后执行make install将 PHP 安装到/usr/local/php目录。安装完成后需要手工完成两件关键事情复制配置文件、创建必要的用户和目录。首先复制 php.ini 配置模板cp /usr/local/php/etc/php.ini-production /usr/local/php/etc/php.iniPHP 源码目录里自带两个 php.ini 模板php.ini-development适合开发日志更详细和php.ini-production适合生产禁用了部分危险函数。即便后续要调整细节建议也先用 production 模板做基础再按需修改。接着确认运行用户存在id www || useradd www -s /sbin/nologin -MPHP-FPM 需要以非 root 用户运行这是安全基线。如果后续用 Nginx 对接这个用户最好与 Nginx 的 worker 用户保持一致避免 socket 权限问题。最后还要检查/usr/local/php/etc/php-fpm.conf是否存在如果不存在需要从源码目录的sapi/fpm/php-fpm.conf复制并生成对应的php-fpm.d/www.conf。这一步经常被新手忽略直接导致后面启动 PHP-FPM 失败。4.3 验证安装结果的几个命令安装完成后我习惯用一组命令验证安装是否完整。/usr/local/php/bin/php -v /usr/local/php/bin/php -m /usr/local/php/bin/php --iniphp -v看到版本号说明主程序没问题。php -m列出已加载的模块重点检查mysqli、pdo_mysql、mbstring、opcache、pcntl、sockets是否在内。php --ini确认配置文件加载路径符合预期。这里有一个我在麒麟 V10 上遇到过的问题php -v执行时提示缺少libonig.so动态库。原因是系统安装了oniguruma的运行库但 ldconfig 缓存里没有包含它的路径。解决方法是echo /usr/local/lib64 /etc/ld.so.conf.d/local.conf ldconfig或者直接以库的实际安装路径为准执行find / -name libonig.so* 2/dev/null查出路径后再添加到 ldconfig 配置里。这类动态库加载问题在 aarch64 架构上尤其需要注意。5. PHP-FPM与Nginx的无缝衔接5.1 php-fpm.conf与www.conf的调整PHP-FPM 是 PHP 官方自带的进程管理器以独立服务形式运行通过 FastCGI 协议接收 Web 服务器的请求。刚才复制配置时提到了php-fpm.conf和php-fpm.d/www.conf启动前需要重点调整几个参数; php-fpm.conf 中 pid /run/php-fpm.pid error_log /usr/local/php/var/log/php-fpm.log ; php-fpm.d/www.conf 中 user www group www listen 127.0.0.1:9000 ; 或者用unix socket ; listen /run/php-fpm.sock listen.owner www listen.group www listen.mode 0660 pm dynamic pm.max_children 50 pm.start_servers 5 pm.min_spare_servers 5 pm.max_spare_servers 10这里我想解释一下listen的选择。TCP 方式127.0.0.1:9000和 Unix Socket 方式各有适用场景。Unix Socket 通过文件系统通信少了 TCP 协议栈的开销在同一台机器上性能略有优势但需要 Nginx 的 worker 进程有权限访问 socket 文件因此权限配置比较敏感。TCP 方式更适合跨机部署也容易排查问题。我这次业务全部在同一台机器上本可以用 Unix Socket但为了后续可能拆分服务器最终还是选了 TCP 9000 端口。pm dynamic模式适合大多数 Web 场景FPM 启动时先起一批进程空闲进程过多时自动释放请求高峰时自动增开。pm.max_children是最关键的参数设置太大会把服务器内存耗尽设置太小遇到流量波动就返回 502。通常可以根据单进程内存占用来估算假设每个 PHP-FPM 进程占 30MB机器内存 8GB 留给 PHP 4GB那么max_children大约 130但实际写 50 是比较稳妥的起步值跑一段时间后再用ps aux | grep php-fpm看真实内存占用。5.2 systemd管理PHP-FPM服务默认源码编译的 PHP-FPM 不带 systemd 服务文件需要手动创建/etc/systemd/system/php-fpm.service内容如下[Unit] DescriptionPHP FastCGI Process Manager Afternetwork.target [Service] Typeforking PIDFile/run/php-fpm.pid ExecStart/usr/local/php/sbin/php-fpm ExecReload/bin/kill -USR2 $MAINPID ExecStop/bin/kill -QUIT $MAINPID PrivateTmptrue [Install] WantedBymulti-user.target有些版本启动时不会自己创建/run目录下的 pid 文件需要先执行mkdir -p /run/php-fpm并设置好 owner。还有一种更省事的思路是直接使用php-fpm自带的daemonize行为让 systemd 通过Typeforking来跟踪主进程。配置完成后依次执行systemctl daemon-reload systemctl enable php-fpm systemctl start php-fpm启动后立刻检查状态systemctl status php-fpm ss -lnt | grep 9000如果 9000 端口正常监听说明 FPM 已工作。我遇到的常见启动失败原因是配置文件里某个参数值不合法比如pid路径无权限写入查看/usr/local/php/var/log/php-fpm.log通常能直接定位问题。5.3 Nginx location规则与FastCGI参数PHP-FPM 就绪之后还差 Nginx 这最后一块拼图。Nginx 本身并不负责解析 PHP它只是把以.php结尾的请求通过 FastCGI 协议转发给 PHP-FPM。核心配置如下server { listen 80; server_name your.domain.com; root /var/www/html; index index.php index.html; location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } }这段配置里最关键的是fastcgi_param SCRIPT_FILENAME。这个参数告诉 PHP-FPM 要执行的脚本绝对路径如果配置错误PHP-FPM 会返回Primary script unknown错误。很多新手把$document_root写成了别的路径导致请求直接 404。我习惯在location ~ \.php$块内再加几个常用的 FastCGI 参数优化fastcgi_connect_timeout 60; fastcgi_send_timeout 180; fastcgi_read_timeout 180; fastcgi_buffer_size 64k; fastcgi_buffers 32 32k;这几个超时参数是为长耗时脚本准备的比如导出报表、批量导入数据等场景。默认的 timeout 很短遇到耗时的 PHP 脚本很容易 504。但注意超时设太大也有风险——攻击者如果滥用慢请求可能长期占用 FPM 进程。实际值建议根据业务情况调整。配置完成后执行nginx -t检查语法再systemctl reload nginx然后就能通过浏览器或 curl 验证 PHP 是否正常解析了。6. 编译安装后的排错与维护经验6.1 常见启动/运行错误的排查链路整个流程走完我整理了这次实际遇到的错误和排查思路给后来者一条可复用的排查链路。错误一php: error while loading shared libraries: libonig.so.5这个我前面已经提过属于动态库路径问题。排查方法ldd /usr/local/php/bin/php查看缺失的库然后find / -name libonig.so*找到库文件实际位置将目录加入/etc/ld.so.conf.d/并执行ldconfig。错误二Failed to connect to FastCGI server: connect() failed (111: Connection refused)这是 Nginx 访问 PHP-FPM 失败通常有三种原因FPM 没启动、FPM 监听地址和 Nginx 的fastcgi_pass不一致、防火墙阻断了 9000 端口。排查链路是先执行systemctl status php-fpm确认进程状态再用ss -lntp | grep php确认监听地址最后检查fastcgi_pass是否写对。错误三Primary script unknown这个错误曾在网上被大量讨论根因是SCRIPT_FILENAME参数不正确PHP-FPM 拿到的路径指向不存在的文件。排查方法在location ~ \.php$块中添加fastcgi_param SCRIPT_FILENAME $request_filename;因为$request_filename是 Nginx 内部根据 root 和请求 URI 拼出的完整路径多数情况下比$document_root$fastcgi_script_name更可靠。错误四504 Gateway Timeout说明 PHP 脚本执行时间超过了 Nginx 的fastcgi_read_timeout。处理方法有两个方向一是把超时调大二是优化脚本本身。我建议先看/usr/local/php/var/log/php-fpm.log里的慢日志定位执行慢的脚本再决定是否需要调大 timeout。盲调超时不是长久之计。6.2 扩展动态安装的两种方式编译安装完 PHP后续业务大概率需要加装扩展比如 Redis、ImageMagick、sodium 这种。动态安装扩展的方式主要有两种方式一PECL 安装/usr/local/php/bin/pecl install redis /usr/local/php/bin/pecl install swoole装完后在 php.ini 里加extensionredis.so即可。PECL 方式的好处是自动匹配 PHP 版本但依赖phpize和php-config前面已经提醒过要装 pcre2-devel就是为了让phpize能顺利运行。方式二手动编译安装有些扩展没有发布到 PECL只能从 GitHub 拉源码。流程相对固定cd /path/to/ext-source /usr/local/php/bin/phpize ./configure --with-php-config/usr/local/php/bin/php-config make make installphpize的作用是为外部扩展生成编译框架--with-php-config则是告诉扩展编译时要匹配哪个 PHP 版本的 API。如果系统里同时存在多个 PHP 版本这个参数必须写对否则编译出来的 .so 根本加载不上。加载扩展之后用php -m验证是否启用成功。如果加载失败PHP 启动时通常会在 stderr 输出类似Cannot load dynamic library xxx.so的信息先确认 .so 路径是否写对再检查文件权限是不是 www 用户可读。6.3 升级与卸载时注意的事项源码编译安装的 PHP升级逻辑跟 RPM 包完全不同。很多人以为升级就是make install覆盖一下结果是新老文件混杂配置错乱。我习惯的升级路径是先备份现有配置cp -r /usr/local/php/etc /usr/local/php/etc.bak再make install覆盖二进制但保留旧配置目录作为参考对比新的php.ini-production与旧配置文件按需合并修改重启 PHP-FPM 验证这里的风险点在于PHP 小版本升级比如 8.1.10 升到 8.1.27通常不会破坏现有扩展但如果跨主版本升级8.1 升到 8.2所有通过编译安装的 PECL 扩展都必须重新安装因为扩展依赖的是 PHP 内部的 Zend API版本号不匹配会导致加载失败。卸载则相对简单删掉/usr/local/php目录、systemd 服务文件、以及 PATH 环境变量里的相关内容即可。如果系统里没有其他业务依赖这些目录就可以放心清理。提示无论升级还是卸载都建议先执行php -m导出当前扩展清单把php.ini里的配置项也留一份备份。这些文件虽然小但重新还原时能省大量时间。最后说点实操中的体会这次在麒麟 V10 (Sword) 上编译安装 PHP整个过程比想象中曲折一些但把关键点理顺之后回头看其实每个坑都有清晰的原因和解决路径。尤其想强调两件事一是 configure 阶段不要图省事该装的依赖库一个都不能少否则 make 中途报错的排查成本远高于一开始装库二是 PHP-FPM 和 Nginx 的衔接配置不要凭感觉写SCRIPT_FILENAME这个参数的语义一定要吃透它能替你省下大量排查 404 的时间。另外一个很实用的小技巧编译完成后把完整的 configure 参数存到/usr/local/php/etc/compile-args.txt里。这样半年后要升级或加扩展直接查看这个文件就知道当初启用过哪些功能、规避过哪些依赖问题不用重新翻命令行历史。如果你的业务场景也需要在麒麟这类国产系统上从零部署 PHP 环境希望这份记录能帮你少绕几个弯。编译安装这件事本身不复杂但细节决定成败把每一步的原因搞清楚了后面要做的只是耐心等待 make 跑完。
返回列表