离线安装LEMP环境:用本地YUM源解决内网依赖问题 1. 先想清楚一件事离线安装到底在装什么做运维的都知道内网服务器意味着什么没有外网、没有 YUM 源、没有 PIP 仓库连个文档都翻不了。前阵子我接手一台完全断网的服务器要求在上面部署一套 NginxPHPMySQL 环境跑一个内部管理系统。当时同事的反馈是直接不联网就装呗结果真上手才发现这事没想象中那么简单。先说结论离线安装的核心不是装软件包本身而是解决依赖链。Nginx 依赖 libstdc、openssl、zlib、pcre 这些基础库PHP 至少需要几十个动态依赖库还要编译线程、xml、curl、gd 等扩展MySQL 的 rpm 包之间还有严格顺序装错一步就是连环报错。你手上如果没有一份完整的依赖包清单装到一半会发现某个.so文件缺失然后整个环境卡死在半路。所以开工前最该做的不是找安装包而是定方案。我这次把三条主流离线路线都试了一遍踩了一圈之后才确定最适合内网场景的做法。这篇就把整个过程捋一遍给正被断网环境折磨的同学当个参考。2. 三条离线路线对比为什么我最后选了本地仓库方案2.1 方案一源码编译最自由也最费命源码编译是很多人的第一反应因为只要拿到源码包理论上不依赖任何仓库。但实际操作下来坑比想象中深Nginx 编译需要 pcre-devel、openssl-devel、zlib-devel这三个只是主依赖前提是服务器本身装了 gcc 和 make。如果内网服务器是纯净的 CentOS 最小安装几乎必然缺这些编译工具链继续离线找二进制依赖会更痛苦。PHP 编译是重灾区。./configure时你至少要指定--with-curl、--with-gd、--enable-fpm、--with-pdo-mysql每开一个参数就多一串需要系统库的支持。缺一个libxml2-develconfigure 阶段直接中止。MySQL 源码编译更劝退编译时间按小时计算过程异常敏感内存不够时随时 OOM。一句话源码编译适合定制编译参数这种特殊场景不适合尽快把一个标准环境跑起来。内网环境你连查错都比较费劲没必要给自己叠加难度。2.2 方案二单包 rpm 手动安装考验耐心这个方案是手动收集所有.rpm包然后按顺序rpm -ivh逐个安装。听起来最直接但我试到 PHP 的时候几乎崩溃。举个例子PHP 的 rpm 包比如php-fpm会依赖libgcc、glibc、libxml2、openssl、sqlite-libs等十几项而libxml2又依赖libicu、libuuid等。你得用rpm -ivh --test一次次模拟安装每次报缺就再去集齐一个包。我收集到第四层依赖时数量和顺序已经完全变成一场手工作业而且没有任何文档能保证顺序正确。2.3 方案三内网 YUM 源本次采用把联网状态下用yumdownloadercreaterepo做出来的完整 rpm 依赖池整体搬到内网然后在内网机器上做成一个本地 YUM 源用yum install安装。这个方案有几个致命优点依赖解析由 YUM 自动完成你不需要手动管理包顺序yum会根据 rpm 包的 requires 关系自动排序。安装方式和平时一模一样减少陌生感和操作失误。同样的依赖池可以复数复制到多台内网机器一次制源多台复用。我当时的选择就很干脆在一台版本相同CentOS 7.9 x86_64、有外网权限的机器上把所有需要的包全部拉下来打包传到内网然后用本地 YUM 源完成安装。整个过程中最脆弱、最多坑的地方是收集依赖和部署仓库两步下面单独展开。3. 在联网机器上把软件包搬家到内网3.1 先准备一台同版本同架构的联网机器这一步很容易被忽略但它决定了整套流程的稳定性。内网服务器是 CentOS 7.9 64 位我就找了一台同样是 CentOS 7.9 64 位、能连外网的机器做打包机。不建议用 8.x 或 9.x 的机器代理拉包因为不同版本的底层依赖库基准不同拉出来的 rpm 版本可能在内网上解析失败。确认的方法很简单cat /etc/redhat-release uname -m如果版本架构对不上别省略这一步老老实实找同款系统。我见过有人用 Ubuntu 的 apt 下 deb 包去给 CentOS 用那根本是另一个生态属于完全行不通的操作。3.2 用 yumdownloader 把依赖一次性拉全很多教程会让你用yum install --downloadonly --downloaddir/path但这个命令在部分 CentOS 7 机器上需要额外安装yum-plugin-downloadonly插件。我更喜欢直接用yumdownloader干净利落。先把要装的软件确定好我这次计划装的是nginx官方源或者 EPEL 源里的版本都行php-fpm、php-cli、php-mysqlnd、php-mbstring、php-json、php-xmlmysql-server从官方 YUM 源拉在打包机上配置好对应的 YUM 源EPEL、Nginx、MySQL 官方源然后执行mkdir -p /data/lemp-packages cd /data/lemp-packages # 拉取默认源里的 nginx yumdownloader --resolve --destdir/data/lemp-packages nginx # 拉取 php 相关注意加 --resolve 才会自动带上所有依赖 yumdownloader --resolve --destdir/data/lemp-packages php-fpm php-cli php-mysqlnd php-mbstring php-json php-xml php-gd # 拉取 mysql-server 以及配套命令工具 yumdownloader --resolve --destdir/data/lemp-packages mysql-community-server mysql-community-client--resolve是灵魂参数。它会把目标包的所有依赖不管已经装没装全部下载下来这样你在内网拿着这一堆包就不会因为缺少某个.rpm而中断。拉完检查一下文件数量通常会有 80 到 150 个 rpm 包这很正常别嫌多。3.3 createrepo 生成仓库元数据光有一堆 rpm 文件还不够YUM 需要一个仓库索引才能识别目录里有哪些包、各自需要什么依赖。这个索引就是repodata目录由createrepo生成。yum install -y createrepo createrepo /data/lemp-packages执行后目录里会出现repodata文件夹里面是元数据文件。没有这个文件夹即使你把 rpm 包搬进内网yum install也识别不了这本地仓库。3.4 打包传输的内网部署仓库先压缩再传输比直接拉文件稳妥因为文件量大、容易因为中断丢包。cd /data tar -czf lemp-packages.tar.gz lemp-packages然后传输到内网服务器/data/目录下。传输方式根据内网实际条件来可以用 U 盘、局域网共享、跳板机 scp怎么都行。关键是传完之后在目标机上解压并确认文件完整mkdir -p /data/lemp-packages tar -xzf lemp-packages.tar.gz -C /data/ ls /data/lemp-packages/ | wc -l到这里搬运依赖这个大难题就算解决了一半。接下来把内网机器上的 YUM 源切换成这个本地目录。4. 配置内网本地 YUM 源让 yum 变本地人4.1 备份原有 repo 配置并关闭外部源内网机器上默认会有 CentOS-Base.repo、epel.repo 这类指向外网的源文件。连不上外网的时候yum install会长时间卡在等待超时上非常崩溃。所以第一步先把它们全部移走mkdir -p /etc/yum.repos.d/backup mv /etc/yum.repos.d/*.repo /etc/yum.repos.d/backup/然后新建一个本地源配置文件cat /etc/yum.repos.d/lemp-local.repo EOF [lemp-local] nameLEMP Local Repository baseurlfile:///data/lemp-packages enabled1 gpgcheck0 EOFgpgcheck0这一步很关键。打包机拉下来的 rpm 包虽然保留了签名信息但内网仓库的 key 不在这台机器上开启gpgcheck1会导致所有包校验失败。内网环境对签名要求不高的场景关掉是最省事的做法。配置完先验证yum clean all yum makecache yum list | grep -E nginx|php|mysql能看到 nginx、php、mysql 相关包出现在列表里就说明仓库生效了。4.2 一个小习惯先装 tree 和 zip 这类小工具验证仓库我每次搭完本地仓库第一件事不是直接装大软件而是先yum install -y tree unzip这类小工具试水。这是为了快速验证仓库本身是否可用把问题隔离在依赖问题出现之前。如果连 tree 都装不了说明元数据或者 repo 配置有问题此时应尽早排查而不是等装 Nginx 才报错白白浪费大量时间。5. Nginx 安装与基础配置的关键细节本地仓库生效之后Nginx 的安装就变得异常简单yum install -y nginx装完先检查一下版本和安装位置nginx -v rpm -ql nginx | headCentOS 7 的 YUM 包安装的 Nginx配置文件入口在/etc/nginx/nginx.conf站点配置目录是/etc/nginx/conf.d/。我记得最清楚的一点是默认配置中server块监听的是 80 端口但它的root目录是/usr/share/nginx/html不是传统印象里的/var/www/html。如果你后续准备放项目代码建议先建一个独立的站点目录比如mkdir -p /data/wwwroot/default然后在/etc/nginx/conf.d/default.conf里加上服务配置server { listen 80; server_name _; root /data/wwwroot/default; index index.php index.html; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass unix:/run/php-fpm/www.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } }启动并加入开机自启systemctl start nginx systemctl enable nginx systemctl status nginx这里顺便提一个我踩过的坑如果状态下显示 active但浏览器访问内网 IP 不通先检查防火墙再检查 SELinux。CentOS 7 默认 firewall 会阻隔 80 端口需要firewall-cmd --permanent --add-port80/tcp firewall-cmd --reload至于 SELinux如果你不懂策略细节又急着把环境跑起来临时setenforce 0可以验证是不是它从中阻隔。但别一关了之生产环境要保持 SELinux enforcing后续我会在第五部分专门展开怎么处理。6. PHP 安装与 php-fpm 联调常见故障与处理方式6.1 别漏装这些 PHP 扩展PHP 不只是php-fpm一个包框架和业务代码往往还需要一大堆扩展。我当时用这条命令一次装齐yum install -y php-fpm php-cli php-mysqlnd php-mbstring php-json php-xml php-gd php-bcmath逐个解释一下为什么需要php-mysqlnd新版 PHP 客户端库是pdo_mysql和mysqli的基础。没有它PHP 代码连不上 MySQL这个最容易被漏装。php-mbstring很多中文系统的字符串处理依赖它没有会直接报Call to undefined function mb_strlen。php-gd处理图片验证码、缩略图场景必备。php-bcmath处理高精度计算很多支付类、账务类应用需要。php-xmlPHP 操作 XML 数据的基础很多框架核心依赖它遗漏后 composer 或框架初始化直接报错。安装确认php -v php -m | grep -E pdo_mysql|mysqli|mbstring|gd6.2 调整 php-fpm 的监听方式PHP-FPM 默认配置里监听的是127.0.0.1:9000TCP 端口还是 Unix socket取决于安装的发行版CentOS 7 的默认配置通常是listen /run/php-fpm/www.sock。我推荐直接使用 Unix socket理由很简单socket 文件方式少了 TCP 协议栈开销性能略高不暴露 9000 端口减少无谓的网络暴露面。但前提是 Nginx 配置里的fastcgi_pass要和 php-fpm 的监听地址完全一致。我见过至少三次事故Nginx 那边写unix:/run/php-fpm/php-fpm.sock实际 php-fpm 监听unix:/run/php-fpm/www.sock路径对不上网页直接 502。所以别偷懒配置后一定检查grep -E ^listen /etc/php-fpm.d/www.conf grep fastcgi_pass /etc/nginx/conf.d/*.conf当 php-fpm 使用 socket 方式时还要注意权限问题。/run/php-fpm/www.sock默认用户是apache或nobodyNginx 的 worker 进程用户是nginx两侧用户不一致的话照样 502。最稳的做法是把 PHP-FPM 的进程用户改为和 Nginx 一致vim /etc/php-fpm.d/www.conf # 修改 user nginx # 修改 group nginx改完重启服务systemctl restart php-fpm systemctl enable php-fpm6.3 使用 php-fpm 状态页辅助联调很多教程到这里就让你直接访问页面测试但我更喜欢先把状态页打开。在www.conf里按如下配置可以通过 Nginx 直接看到 php-fpm 的实时连接数、队列情况对后续排查性能问题帮助很大pm.status_path /status然后在 Nginx 里加一个不经过 PHP 解析的 locationlocation ~ ^/status$ { fastcgi_pass unix:/run/php-fpm/www.sock; include fastcgi_params; }浏览器访问http://IP/status如果能看到一段文本输出就意味着 php-fpm 和 Nginx 的整个 PHP 解析链路已经打通。这比单纯访问 index.php 返回空白页更直观能快速定位问题到底是在 Nginx 还是 PHP 层。7. MySQL 离线安装rpm 依赖链与初始化实践7.1 安装顺序和包选择MySQL 的 YUM 包在拉取时往往包含一堆子包。我这次安装时先明确只需要 server 和 clientyum install -y mysql-community-server mysql-community-client在本地 YUM 源仓库中万一解析出现多个 MySQL 版本可以用yum list | grep mysql-community-server确认和你想要的版本一致后再装。依赖在本地仓库的前提下yum会自己处理好顺序但有一点要提醒MySQL 的 rpm 包之间有时会有版本互相锁定的关系即 server、client、common、libs 必须同版本。如果你打包时混了几个版本比如 server 是 8.0.36client 是 8.0.35安装时会报Requires: mysql-community-client(x86-64) 8.0.36这类奇奇怪怪的错。因此打包阶段建议全部加上--resolve之外还可以把mysql-community-server的完整版本固定下来yumdownloader --resolve --destdir/data/lemp-packages mysql-community-server-8.0.36-1.el7.x86_647.2 mysqld 初始化与初始密码的坑CentOS 7 上安装完 MySQL 8.0 之后默认的数据目录是/var/lib/mysql。直接启动之前要先初始化mysqld --initialize-insecure --usermysql注意我特意用了--initialize-insecure而不是--initialize。差异在于--initialize会生成一个临时 root 密码写在/var/log/mysqld.log里你需要去日志里翻密码再登录步骤繁琐。--initialize-insecureroot 账号无密码启动后可以直接mysql -uroot空密码进入后续再手动设置密码。内网环境刚初始化完要快速测试这招更灵。初始化完成后启动服务systemctl start mysqld systemctl enable mysqld然后进 MySQL 设置 root 密码并允许远端应用主机连接注意只允许特定 IP不要用全网段通配mysql -uroot ALTER USER rootlocalhost IDENTIFIED BY 你的强密码; CREATE USER appuser10.0.0.% IDENTIFIED BY 应用密码; GRANT ALL PRIVILEGES ON *.* TO appuser10.0.0.%; FLUSH PRIVILEGES;这一步完成后务必确认 PHP 连接 MySQL 的测试能过。我当时写了一个临时探针文件?php $dsn mysql:host127.0.0.1;port3306;dbnamemysql; $user root; $pass 你的密码; try { $pdo new PDO($dsn, $user, $pass); echo OK; } catch (PDOException $e) { echo Error: . $e-getMessage(); }浏览器访问这个 PHP 页面输出OK就说明 Nginx → PHP-FPM → MySQL 全链路已经打通。如果探针报could not find driver说明php-mysqlnd其实没装成功用php -m | grep pdo_mysql复查。7.3 SELinux 环境下的最终检查之前提到临时setenforce 0可以快速测试但重启后 SELinux 会恢复 enforcing站点可能又打不开。在生产环境保持 SELinux 开启的前提下正确的做法是放行 Nginx 对外访问和 PHP-FPM 连接# 放行 Nginx 监听端口 setsebool -P httpd_can_network_connect 1 # 如果 php-fpm 需要反向代理或访问外部网络 setsebool -P httpd_can_network_connect_db 1另外如果你的站点目录放在自定义位置比如/data/wwwroot需要给目录打上正确的 SELinux 上下文类型否则 Nginx 有权限读用户文件却会被 SELinux 拦截semanage fcontext -a -t httpd_sys_content_t /data/wwwroot(/.*)? restorecon -Rv /data/wwwroot这一步在 Debian/Ubuntu 上没有对应概念但对于 RHEL/CentOS 系的离线部署来说它是装好缺一个验证最后的一道高频暗坑。8. 回看整个过程离线安装的通用方法论8.1 核心方法论先补链再安装最后验证整个离线安装流程可以抽象成一条固定步骤线环境锁定记录内网机器的发行版、版本号、架构。依赖收集在打包机上用yumdownloader --resolve把目标软件及其依赖完整下载。本地化仓库用createrepo生成仓库元数据整体搬运到内网。替换源禁用外部源配置本地源yum install解依赖。逐层验证先装小工具验证源再装 Nginx再装 PHP再装 MySQL每层独立验证。联动测试搭静态页验证 Nginx搭探针页验证 PHP-FPM搭数据库连接页验证 PHPMySQL。这条流程不只适用于 LEMP 环境你做内网 Docker 离线包、内网 Python 包、内网 Node 离线安装核心思路完全一样——先把依赖关系收拢成一个整体再用标准工具yum/pip/npm在本地消费这个整体。8.2 我实际踩过的三个高频坑打包机和内网机的系统版本不一致我当时用了一台 CentOS 7.4 的机器打包内网机器是 7.9结果部分 rpm 因为 glibc 版本差异装不上。后来重新用 7.9 打包才顺利通过。所以打包机尽量用“小版本也要一致”的镜像。少装了快速验证小工具个别时候yum list能看到 nginx但yum install -y nginx却提示找不到本地包检查了半天发现repodata是在另一台机器生成后又改过目录导致元数据和实际文件不对应。把repodata放在与 rpm 同一父目录、生成后不再移动文件能规避这个问题。忽略了 PDO 驱动PHP 探针页面能显示版本和函数列表但连数据库报Class PDO not found。这是因为光装了 php-fpm没装 php-mysqlnd。所以我在前面特别强调离线环境一次装齐扩展不要在能连外网的时候漏包。8.3 一小段实用建议这套流程跑通之后务必保留一份完整的依赖包目录和版本清单。下次再遇到同架构同系统版本的内网服务器不需要再找打包机重新拉包直接把上次的/data/lemp-packages目录原封不动复制过去改个 repo 路径就能用。如果后续要装新的扩展包也可以单独在打包机上拉几个 rpm 并丢进同一个仓库目录重新跑一次createrepo就能增量更新。这个习惯能帮你把一次性部署变成可复用资产。另外内网部署这种东西靠记忆不靠文档趁热打铁写一份简短的部署记录记录下包数量、仓库配置路径、服务启停命令、初始密码设置方式两周后你会感谢当时的自己。我个人在实际操作中的体会是离线安装最大的障碍从来不是软件有多难装而是依赖链没有收拢干净。只要把依赖层这件事做扎实了把一次性工作变成可复用的本地仓库哪怕后面要再装 Redis、RabbitMQ、Elasticsearch也都能顺着同一条路走通。这一套虽然折腾但值得投入因为断网服务器不只是一台机器它的背后往往是安全要求和合规约束而运维的基本功正是能在各种受限条件下把服务提供出来。