1. 为什么离线部署LNMP值得单独拿出来讲
搞过内网交付的人都有一个共识:能联网的环境里装LNMP,一条包管理命令的事;一旦进了没有外网出口的机房,同样一套东西,难度直接翻好几倍。centos7.9离线部署LNMP这个标题,说的就是在完全断网或者只开放极少数端口的CentOS 7.9服务器上,把Linux、Nginx、MySQL、PHP这套经典Web运行环境从零搭起来。它解决的核心问题不是"怎么装软件",而是"在没有网络的情况下,怎么把依赖关系、软件包、配置、服务启动这一整条链路走通"。
这套东西适合谁看?一类是做内网项目交付的运维和实施人员,客户机房物理隔离,机器搬进去就出不来;一类是负责等保、专网、生产隔离区环境搭建的工程师;还有一类是想在自己实验室或测试环境里复现一套可控Web栈的开发者。哪怕你只是想在虚拟机上练手,把离线流程走一遍,对理解RPM依赖、systemd服务管理、PHP与Nginx的通信机制都有实打实的帮助。
我先说清楚一个前提:CentOS 7.9已经在2024年6月30日停止维护,官方源和镜像站陆续下架,这恰恰让"离线部署"从可选项变成了必选项——你没法再指望在线yum install了,所有包必须提前准备好。这也是为什么这个话题最近被反复提起。下面我按实际交付的顺序,把整套流程拆开讲,包括我踩过的坑和几个能省半天时间的技巧。
2. 整体方案设计与离线物料准备
2.1 为什么选"本地源+离线RPM"而不是源码编译
离线装LNMP有两条路:源码编译和RPM离线源。很多人第一反应是源码编译"更可控",但我实际交付下来,除非客户明确要求特定版本或需要打补丁,否则优先选RPM离线源。原因很直接:源码编译要处理gcc、make、cmake、各种-devel包,这些依赖本身在离线环境里就是一堆麻烦;而且编译参数一旦记错,排查成本极高。RPM方案的优势是依赖关系由yum的本地仓库自动解析,装完即用,systemd服务单元也是现成的。
具体做法是:在一台能联网、且系统版本与目标机完全一致的CentOS 7.9机器上(这点极其重要,版本不一致会导致glibc依赖对不上),用yumdownloader或reposync把Nginx、MySQL、PHP及其全部依赖下载下来,打包成一个本地仓库目录,拷到目标机后配置local.repo指向它。这样目标机上执行yum install时,yum会从这个本地目录解析依赖,效果和在线一模一样。
注意:准备物料的机器和目标机必须是同一小版本(都是7.9),架构一致(都是x86_64)。我见过用7.6的机器准备包,结果目标机7.9上glibc版本不匹配,装到一半报依赖冲突,只能重来。
2.2 离线物料清单与目录规划
物料准备阶段我习惯按下面的结构组织,拷过去之后一目了然:
| 目录 | 内容 | 说明 |
|---|---|---|
/opt/offline/rpm-repo | 所有RPM包及repodata | 本地yum源根目录 |
/opt/offline/soft | Nginx/MySQL/PHP安装包 | 若用官方二进制包则放这里 |
/opt/offline/conf | 预置配置文件 | nginx.conf、my.cnf、php.ini |
/opt/offline/scripts | 部署脚本 | 初始化、启动、校验脚本 |
软件版本选择上,CentOS 7.9自带的PHP是5.4,太老,实际项目基本都要7.x。我的常规组合是:Nginx 1.20+、MySQL 5.7或8.0、PHP 7.4。这里有个关键决策点——PHP 7.4在CentOS 7上需要额外启用remi源或epel源来获取,所以准备物料时要把这些源的包一起down下来。MySQL 8.0对glibc要求较高,如果目标机是很老的硬件环境,5.7更稳妥。
物料下载的核心命令(在联网准备机上执行):
# 安装下载工具 yum install -y yum-utils createrepo # 创建仓库目录 mkdir -p /opt/offline/rpm-repo # 下载nginx、mysql、php及其依赖(--resolve 解析全部依赖) yumdownloader --resolve --destdir=/opt/offline/rpm-repo \ nginx mysql-server php php-fpm php-mysql php-gd php-mbstring # 生成repodata索引 createrepo /opt/offline/rpm-repo--resolve这个参数是灵魂,它会把依赖树整个拉下来,少了它你到目标机上必然缺包。下载完用createrepo生成索引,目标机的yum才能识别这是一个合法仓库。
2.3 依赖完整性的预校验思路
物料拷到目标机之前,我强烈建议先在准备机上做一次"模拟安装"校验。方法是在准备机上临时把本地源配好,执行yum install --downloadonly或者干脆yum deplist看依赖是否齐全。更彻底的做法是找一台干净的同版本虚拟机,断网后实际装一遍,能装通再往客户现场搬。这一步花半小时,能避免现场抓瞎两小时。
还有一个容易被忽略的点:SELinux和防火墙。CentOS 7.9默认SELinux是enforcing,离线环境下如果没提前处理,Nginx访问PHP文件、MySQL写数据目录都可能被拦。我的做法是部署脚本里统一把SELinux设为permissive(setenforce 0并改配置文件),防火墙按需放行80/443/3306,而不是等到出问题再回头查。这个决策的理由是:内网交付环境通常已有边界防护,主机层再叠一层SELinux规则,排查成本远大于收益。
3. 核心组件安装与配置细节拆解
3.1 本地yum源的落地配置
目标机上第一步是把本地源配起来。把/opt/offline/rpm-repo整个目录拷到目标机同路径,然后新建/etc/yum.repos.d/local.repo:
[local] name=Local Offline Repo baseurl=file:///opt/offline/rpm-repo enabled=1 gpgcheck=0gpgcheck=0是因为离线包通常没导入GPG key,关掉校验避免报错。配好后执行yum clean all && yum makecache,如果能看到仓库元数据加载成功,说明源通了。这里有个细节:如果目标机原来配了在线源,建议把/etc/yum.repos.d/下其他repo文件临时改名或移走,否则yum会尝试连外网,超时拖慢整个安装过程。
提示:
yum makecache报"repodata不存在"时,八成是createrepo没执行或者目录层级不对。baseurl指向的必须是包含repodata子目录的那一层。
3.2 Nginx安装与关键配置项
源通了之后,yum install -y nginx基本一路顺风。装完先别急着启动,几个配置项要提前改。Nginx默认的nginx.conf里user是nginx,worker_processes是1,生产环境我一般改成worker_processes auto,让它按CPU核数自动分配。worker_connections默认1024,内网高并发场景可以提到4096。
真正影响LNMP联动的是location ~ \.php$这一段。默认配置里fastcgi_pass指向127.0.0.1:9000,这要求PHP-FPM监听9000端口。我倾向于保持TCP监听而不是unix socket,原因是跨进程排查时ss -lntp能直接看到端口状态,比查socket文件直观。对应的PHP-FPM配置在/etc/php-fpm.d/www.conf,要确认listen = 127.0.0.1:9000,user和group与Nginx保持一致(都是nginx),否则会出现权限拒绝导致502。
fastcgi_param SCRIPT_FILENAME这一行必须指向$document_root$fastcgi_script_name,这是Nginx把PHP文件路径传给PHP-FPM的关键。很多"PHP页面返回空白或下载文件"的问题,根源就是这行配错或者被注释掉了。
3.3 MySQL初始化与安全配置
yum install -y mysql-server装完后,CentOS 7上MySQL 5.7/8.0首次启动会自动初始化数据目录。启动命令systemctl start mysqld,然后从日志/var/log/mysqld.log里找临时root密码(8.0才有,5.7默认空密码)。拿到后执行mysql_secure_installation改密码、删匿名用户、禁止root远程登录。
这里有个离线环境特有的坑:mysql_secure_installation的密码强度策略在8.0里默认是MEDIUM,要求大小写数字符号齐全,随便设个简单密码会报错。如果项目允许,可以在my.cnf里加validate_password.policy=LOW降低要求,但生产环境我不建议这么干,老老实实设个复杂密码更省心。
数据目录权限也要留意:/var/lib/mysql的属主必须是mysql用户,如果之前有人手动改过权限,启动会失败。字符集方面,my.cnf里加上character-set-server=utf8mb4和collation-server=utf8mb4_general_ci,避免中文乱码——这是国内项目的高频需求,提前配好省得后面改表。
3.4 PHP-FPM与扩展的联动
PHP装完核心包后,php-fpm服务要单独启动。扩展方面,php-mysql(连接MySQL)、php-gd(图像处理)、php-mbstring(多字节字符串)、php-xml基本是标配,物料准备阶段就要一起down下来。装完用php -m确认扩展加载,用php -v确认版本。
PHP-FPM的进程管理参数值得调一调。pm = dynamic配合pm.max_children、pm.start_servers这几个值,默认配置偏保守。内网小项目max_children设20-30够用,具体按内存估算:每个PHP进程约占30-50MB,max_children乘以单进程内存不能超过可用内存的70%。这个计算过程很多人跳过,结果并发一上来就OOM。
Nginx和PHP-FPM都起来后,在web根目录放一个info.php(内容<?php phpinfo(); ?>),浏览器访问验证。能出页面说明整条链路通了;出502说明PHP-FPM没起或端口不对;出403说明权限或SELinux问题;直接下载文件说明Nginx没把php交给FPM处理。这四种现象基本覆盖了90%的联动故障,记住这个对照关系排查效率极高。
4. 完整实操流程与现场记录
4.1 从零到服务可用的执行顺序
我把整个部署拆成可脚本化的阶段,实际现场按这个顺序走,每步都有校验点:
- 系统基线检查:
cat /etc/redhat-release确认7.9,uname -m确认x86_64,getenforce看SELinux状态,systemctl status firewalld看防火墙。这一步是防止在错误的环境上白干。 - 物料落地:拷贝
/opt/offline目录,校验文件完整性(md5sum对比准备机生成的清单)。 - 本地源配置:写
local.repo,yum clean all && yum makecache,yum repolist确认local源可见。 - 组件安装:
yum install -y nginx mysql-server php php-fpm php-mysql php-gd php-mbstring,观察是否有依赖报错。 - 配置下发:把预置的
nginx.conf、my.cnf、www.conf覆盖到对应位置。 - 服务启动与开机自启:
systemctl enable --now nginx php-fpm mysqld。 - 联动验证:放
info.php,curl -I http://127.0.0.1/info.php看返回200。 - 安全收尾:
mysql_secure_installation,清理测试文件,SELinux置permissive。
这个顺序的关键在于"先校验环境再动手",以及"配置先于启动"。我见过先启动服务再改配置,结果改完忘了reload,排查半天以为是配置没生效。
4.2 关键配置文件的实操内容
Nginx的server块我通常这样写,直接可抄:
server { listen 80; server_name _; root /var/www/html; index index.php index.html; location / { try_files $uri $uri/ =404; } 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; } }try_files这行保证静态文件找不到时返回404而不是把请求漏给PHP,安全性和性能都更好。server_name _表示匹配所有域名,内网IP访问场景够用。
PHP-FPM的www.conf关键几行:
listen = 127.0.0.1:9000 user = nginx group = nginx pm = dynamic pm.max_children = 30 pm.start_servers = 5 pm.min_spare_servers = 5 pm.max_spare_servers = 10pm.max_children = 30是按一台4GB内存机器估的,30乘40MB约1.2GB,留足余量。这个值不是拍脑袋,是"可用内存×0.7÷单进程内存"算出来的,你可以按自己机器调整。
MySQL的my.cnf最小可用配置:
[mysqld] datadir=/var/lib/mysql socket=/var/lib/mysql/mysql.sock character-set-server=utf8mb4 collation-server=utf8mb4_general_ci max_connections=200max_connections=200对中小项目足够,调太高反而占内存。字符集两行是中文项目的刚需。
4.3 服务自启与开机验证
systemctl enable --now一条命令同时完成开机自启和立即启动,比先enable再start省事。三个服务都起来后,systemctl status nginx php-fpm mysqld逐个确认active (running)。然后做一次重启验证——reboot之后再看三个服务是否自动起来。这一步很多人省略,结果交付后客户一断电重启,服务全挂,现场尴尬。重启验证是离线交付的必做项,别偷懒。
重启后如果某个服务没起来,journalctl -u 服务名 -n 50看最近日志,八成是配置语法错误或者依赖服务没起(比如PHP-FPM依赖网络就绪)。nginx -t可以单独校验Nginx配置语法,这个命令在改完配置后养成习惯执行一下。
5. 常见问题排查与避坑经验实录
5.1 高频故障速查表
| 现象 | 可能原因 | 排查命令 | 解决 |
|---|---|---|---|
| yum报无法解析依赖 | 物料缺包或repodata未生成 | yum deplist 包名 | 回准备机补down,重新createrepo |
| 访问PHP返回502 | PHP-FPM未启动或端口不符 | ss -lntp | grep 9000 | 启动php-fpm,核对listen配置 |
| 访问PHP返回403 | 文件权限或SELinux | ls -lZ 文件 | chmod/chown,setenforce 0 |
| 访问PHP直接下载 | Nginx未匹配php location | nginx -T看配置 | 补location ~ .php$段 |
| MySQL启动失败 | 数据目录权限或残留锁 | tail /var/log/mysqld.log | 修属主,清ib_logfile重试 |
| 中文乱码 | 字符集未设utf8mb4 | show variables like 'char%' | 改my.cnf重启 |
这张表是我这些年现场排查沉淀下来的,覆盖了绝大多数情况。遇到问题先对号入座,比盲目翻日志快得多。
5.2 几个只有踩过才知道的坑
第一个坑:yum makecache成功但yum install仍报找不到包。这种情况通常是包名写错,或者包在子目录里而repodata没递归索引。createrepo默认只索引当前目录,如果RPM分散在子目录,要加-g或者把包都挪到同一层。我现在的习惯是物料目录扁平化,所有rpm放一层,省得索引出问题。
第二个坑:PHP-FPM启动报"Address already in use"。9000端口被占,可能是之前装过别的PHP或者有残留进程。ss -lntp | grep 9000找到占用进程,kill掉再启动。如果确实有别的服务要用9000,就改PHP-FPM的listen端口,同时同步改Nginx的fastcgi_pass,两边必须一致。
第三个坑:MySQL 8.0在离线环境初始化卡住。8.0首次启动初始化数据目录耗时较长,如果机器IO慢,systemctl start mysqld可能超时被systemd判定失败。这时候别急着重装,journalctl看日志,如果还在初始化就等它跑完,或者调大TimeoutStartSec。我遇到过一次,等了近两分钟才初始化完,差点误判。
第四个坑:SELinux导致的"玄学"权限问题。文件权限明明对,Nginx就是读不了。ls -Z看安全上下文,/var/www/html下的文件上下文应该是httpd_sys_content_t。离线环境不方便装semanage工具,最省事的办法就是setenforce 0并改/etc/selinux/config为permissive,重启生效。这个决策前面提过,内网环境权衡下来是合理的。
5.3 交付前的自检清单
现场交付前我固定跑一遍这个清单,逐项打勾:
- 三个服务
systemctl is-enabled都是enabled - 重启后三个服务自动起来
curl -I http://127.0.0.1/返回200info.php能正常显示PHP信息- MySQL能用业务账号登录并建库
- 防火墙放行了业务端口
- 测试文件已清理,root密码已改
这个清单看着简单,但每一条都对应过一次真实的翻车。尤其是"重启验证"和"测试文件清理",前者防交付后掉链子,后者防信息泄露。清单化执行,比凭记忆靠谱。
6. 我个人在离线交付中的几点体会
离线部署LNMP这件事,技术门槛其实不高,难在"没有退路"——在线环境出问题可以搜、可以重装,离线环境里每一步都得提前想清楚。我最大的体会是:物料准备的完整性决定了现场效率的上限。准备阶段多花一小时把依赖拉全、在干净虚拟机上预演一遍,现场就能少折腾半天。反过来,物料缺一个包,现场可能就要打电话回公司让人远程传文件,尴尬又低效。
另一个体会是配置的"预置化"。把nginx.conf、my.cnf、www.conf这些提前写好放在conf目录,现场直接覆盖,比现场手敲靠谱得多。手敲容易漏行、错字符,覆盖则是一次性的、可复现的。配合部署脚本,整套流程能压缩到二十分钟以内。
最后分享一个小技巧:物料目录里放一个README,写清楚每个包的版本、来源、下载日期,以及部署步骤和回滚方法。这东西平时没人看,但半年后别人接手或者自己回头维护时,它就是救命文档。离线环境最怕的就是"当时怎么装的没人记得",一份简单的记录能省掉大量考古时间。这套流程我用了很多次,每次微调一点点,现在基本能做到一次到场、一次装通。