ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

从零手动部署LAMP架构:原理、配置与调优全攻略

从零手动部署LAMP架构:原理、配置与调优全攻略 真正搞过服务器的人对LAMP这三个字母应该都不陌生。Linux Apache MySQL PHP这套组合能跑起来的Web环境算是最经典的构建方式。我原本对这套“老掉牙”的架构不太上心直到自己接手一个需要从零手工部署的环境才意识到一键面板虽然爽但真到生产环境你得知道哪里装、哪里配、哪里出了故障面板不会替你背锅。这篇文章不打算讲怎么用面板一把梭而是带你把LAMP架构部署配置的完整过程走一遍。从系统准备、软件安装、组件联动到联调测试、性能调优、安全加固每一步“为什么这么做”我也会讲清楚。适合想深入了解Web服务原理的开发者、刚入门运维的同学或者说白了任何不想被图形界面挡在真实世界之外的工程师。接下来我以某RHEL兼容发行版为主来展开命令在主流发行版上稍有差异关键位置我会补上对照。目标很简单一台干净的服务器从零变成一个能稳定运行PHP站点、带MySQL数据库的完整Web环境。看完你就能照着落地也能自己动手排查环境里那些逼疯新人的问题。1. 为什么要手动搭LAMP先想清楚再动手1.1 LAMP是什么为什么这套组合打不倒LAMP不是一个单一软件而是一条完整的数据流转链路。浏览器发来请求Apache负责接住并决定要不要处理遇到PHP文件时把代码交给PHP解释器跑起来PHP需要读写数据就通过MySQL客户端协议找数据库要结果最后再把拼装好的HTML回给浏览器。这一套分工非常清晰Web服务管接入、应用服务管逻辑、数据库管存储。这么多年过去微服务、容器化、Serverless都轮番炒过几轮了LAMP依然大量存在。原因也很朴素它足够简单、足够稳定、资料足够多。一个团队里哪怕换了几波人只要系统是LAMP的新来的人看一眼配置就能上手。这套组合就像装修里的水电基础不管你后面用多时髦的框架底层还是那几样东西在兜底。1.2 手动部署的价值你不是在装软件是在理解系统很多人觉得现在都上云了镜像市场里一堆现成的LAMP镜像点一下就能建站为什么还要自己敲命令我的看法是现成镜像帮你省掉了部署时间但也帮你省掉了排查能力。镜像里哪个组件怎么配的、PHP-FPM池子多大、MySQL缓冲开多少你完全不知道。等线上出现502或者数据库连接数被打满你会发现镜像没给你留任何“手感”。手动部署一遍你会被迫知道每个配置文件在哪、每个服务的启动顺序是什么、PHP处理器是通过什么协议跟Apache对话的。这些东西不是八股知识而是你在生产环境定位问题的“地图”。我在实际项目中见过太多人遇到502第一反应是重启服务重启不行就重装环境重装还不行就找人帮忙——问题往往就在一个SELinux布尔值或者一个socket权限上你手动装过一遍就不会在这些地方卡太久。1.3 版本选型的几个原则部署之前先把版本定下来这个能省掉后面无数的兼容性麻烦。我个人遵循几个原则优先选发行版自带仓库里的版本因为安全补丁和系统默认策略是配套的追求新功能再考虑官方源或第三方源生产环境不要追太新的版本稳定期版本足够。以我这次用的RHEL兼容发行版为例系统默认仓库自带Apache 2.4、PHP 8.0、MariaDB或者MySQL相关依赖。我计划用MySQL官方源单独装MySQL 8.0因为业务明确指定了MySQL而且8.0的utf8mb4默认策略、窗口函数、JSON能力比历史版本好用太多。如果你没有特殊要求直接用系统自带的数据库组件也不丢人开源社区版本的质量很高还能少维护一个第三方源。2. 环境准备与基础安装2.1 系统初始状态确认我假设你手里有一台装好了系统的机器不管是物理机、虚拟机还是云主机第一步别急着装软件先确认系统状态。检查系统发行版和版本号确认网络源可用确认时间同步正常再看一下SELinux是否强制模式。cat /etc/redhat-release dnf repolist timedatectl status getenforce free -g df -h这几条命令花不了三十秒但是能帮你提前暴露两类问题一是源配置错误导致后面装软件时依赖解析半天二是磁盘满了导致数据库初始化失败。特别是磁盘MySQL启动时如果数据目录空间不够报错信息会非常绕容易把新手带偏到权限和配置上去。我踩过一次当时查了两小时最后发现就是根分区只剩下几十兆。2.2 Apache安装与基础配置在RHEL兼容发行版上Apache的包名叫httpd。安装和启动很简单dnf install -y httpd systemctl enable --now httpd装完后先别急着改配置确认服务真的起来了systemctl status httpd curl -I 127.0.0.1看到HTTP 200或者403都算正常403不代表服务坏了只是默认欢迎页没有可访问的索引文件而已。如果curl提示拒绝连接先看端口和防火墙状态排查方法和后面常见问题章节里写的是一样的思路。Apache的主配置文件在/etc/httpd/conf/httpd.conf这个发行版额外加载了/etc/httpd/conf.d/和/etc/httpd/conf.modules.d/下面的全部.conf文件。这个设计很好你自己加站点配置的时候不用去动主文件单独扔一个文件进conf.d就行升级系统时不容易被覆盖。2.3 MySQL安装与初始化MySQL 8.0我选择从官方源安装因为系统自带源里的MySQL包版本可能不如官方新而且官方源安装后服务管理方式很标准。先装官方仓库包再装服务端dnf install -y https://dev.mysql.com/get/mysql80-community-release-el9-版本号.noarch.rpm dnf install -y mysql-community-server安装完成后启动MySQL之前最好先看一眼数据目录ls -ld /var/lib/mysql systemctl enable --now mysqldMySQL 8.0在第一次启动时会自动完成数据目录初始化并生成一个临时root密码写在日志里grep temporary password /var/log/mysqld.log拿到临时密码后马上执行安全初始化脚本mysql_secure_installation这个脚本会引导你设置root密码、移除匿名用户、禁止root远程登录、清理测试库。网上有些人为了省事直接跳过我强烈不建议。MySQL的root远程登录一旦开着等于把整个数据库暴露在有密码保护的大门里。就算你有强密码也应该遵守最小暴露原则生产环境尤其如此。初始化完成后我通常还会顺手建一个业务专用账号不让应用碰root。这个习惯后面讲业务部署的时候会细说这里你先建立一个认知MySQL的用户权限要按“最小够用”来分应用账号只给某个库的权限绝不授予全局管理权限。3. PHP安装与Apache/PHP协作3.1 PHP安装的关键选择系统自带仓库里就有PHP版本是8.0对绝大多数业务足够用了。安装时尽量把常用扩展一起装齐不然后面编译WordPress或者某个框架时发现缺扩展再回来补装很打断节奏。dnf install -y php php-fpm php-mysqlnd php-cli php-gd php-mbstring php-xml php-json php-curl php-zip这里的php-fpm非常重要。在LAMP架构里PHP有两种跑法一种是作为Apache的模块跑叫mod_php另一种是作为独立服务跑PHP-FPM。我下面单独解释一下为什么我们要用php-fpm。3.2 让Apache认识PHPmod_php还是php-fpmmod_php是前辈方案装完Apache就能直接识别.php后缀看起来省事。但它的问题是PHP被塞进Apache进程里每个Apache子进程都背上PHP解释器的内存开销一个站点并发一高内存噌噌往上涨。另外PHP代码一旦出问题可能会拖垮整个Apache进程。php-fpm则是把PHP单独跑成一组进程Apache通过FastCGI协议把PHP文件转发给FPM处理。好处很明显PHP进程跟Web服务进程隔离内存可控、安全边界清晰、进程可以单独重启还支持不同站点用不同PHP版本。我个人的原则是新部署一律php-fpmmod_php只在维护别人留下的老环境时才见。在Apache里配置代理转发先确认相关模块已经加载httpd -M | grep proxy_fcgi如果没有输出说明模块没加载需要在/etc/httpd/conf.modules.d/里取消对应行的注释。RHEL系默认应该已经加载确认一下最稳妥。然后在虚拟主机配置里加上一句告诉Apache把PHP文件请求转发给FPMFilesMatch \.php$ SetHandler proxy:fcgi://127.0.0.1:9000 /FilesMatch这样就完成了Apache和PHP-FPM之间的握手Apache只负责接收HTTP请求看到PHP文件就扔给127.0.0.1:9000这个FPM监听地址。3.3 PHP连接MySQL扩展与选型PHP要连MySQL需要用到pdo_mysql扩展。在前面安装的php-mysqlnd包里已经带上了。确认扩展有没有生效可以在web目录放一个临时探针文件?php phpinfo(); ?浏览器打开后搜索pdo_mysql看到版本信息说明扩展OK。这个探针文件务必在验证结束后删掉切记。phpinfo输出会展示非常详细的服务器内部信息线上留着等于给攻击者送情报。另外要提一下字符集。MySQL 8.0默认编码就是utf8mb4PHP端连接时最好也在DSN里显式指定$dsn mysql:host127.0.0.1;dbnameappdb;charsetutf8mb4;这样查询结果返回中文不会乱码也不会出现“问号一片”的经典尴尬。字符集问题看起来小实践中是最容易忽略却又最影响体验的。4. 业务部署与联调测试4.1 虚拟主机配置一个站点一套配置正式跑业务前先把虚拟主机建好。虚拟主机让一台Apache可以承载多个域名对应不同目录是LAMP部署里最常用的手法。我在/etc/httpd/conf.d/下新建一个站点配置文件命名为example.confVirtualHost *:80 ServerName example.tld DocumentRoot /var/www/html/example Directory /var/www/html/example AllowOverride All Require all granted /Directory FilesMatch \.php$ SetHandler proxy:fcgi://127.0.0.1:9000 /FilesMatch ErrorLog logs/example-error_log CustomLog logs/example-access_log combined /VirtualHost这里的AllowOverride All允许使用.htaccess通常重写规则、访问控制需要它。但如果你的站点流量很大、追求极致性能生产环境我更推荐把重写规则写进虚拟主机配置里配合AllowOverride None这样Apache每次请求不用向上查找.htaccess性能会好一些。两种方式各有适用场景你根据业务类型取舍。配置写完后先验证语法再重载服务apachectl configtest systemctl reload httpd4.2 目录规划与权限控制站点目录我建议放/var/www/html/下这是RHEL系的默认路径。创建目录后权限规划要细心mkdir -p /var/www/html/example对于纯静态或PHP代码文件属主设为root:apache就够文件权限644、目录755。如果业务里有文件上传、缓存目录单独给这些目录开写权限而不是把整个站点目录都交给Apache写mkdir -p /var/www/html/example/upload chown apache:apache /var/www/html/example/upload chmod 775 /var/www/html/example/upload为什么要这么抠权限原因很简单PHP代码在Apache/FPM进程里跑如果整个站目录是apache可写的一旦业务存在文件上传漏洞攻击者就能直接把恶意PHP文件写到web根目录拿到一句话木马。只放开必要的写目录能把这类风险压到最低。好的权限策略应当是你主动开哪些口子就算哪些口子而不是默认全开再慢慢补。如果站点目录放在/var/www之外的自定义位置RHEL系还需要处理SELinux文件上下文。默认/var/www路径已经有httpd_sys_content_t的标注自定义路径没有Apache读到可能直接返回403semanage fcontext -a -t httpd_sys_content_t /data/www(/.*)? restorecon -Rv /data/www这条命令的含义是告知SELinux/data/www下的文件都按Web内容来管然后递归刷新上下文标注。很多人在自定义路径部署后遇到403第一反应是权限问题chmod 777一顿操作最后才发现是SELinux在管着。4.3 联调测试与日志验证文件放好后写一个最简单的测试页?php echo PHP OK; ?浏览器访问能看到“PHP OK”说明Apache到PHP这条链路已经走通。再写一个数据库连通性测试?php $dsn mysql:host127.0.0.1;dbnametestdb;charsetutf8mb4; try { $pdo new PDO($dsn, appuser, 这里填密码); echo $pdo-query(SELECT VERSION())-fetchColumn(); } catch (PDOException $e) { echo 连接失败: . $e-getMessage(); } ?如果页面能显示MySQL版本号说明完整链路通了浏览器 → Apache → PHP-FPM → MySQL整个LAMP架构就算正式跑起来了。看到版本号那一刻还是挺有成就感的这套东西虽然基础但每一步都踩实的感觉是面板给不了的。测试过程中日志是你最重要的朋友。Apache错误日志在/var/log/httpd/error_logPHP-FPM错误日志在/var/log/php-fpm/error.logMySQL日志在/var/log/mysqld.log。出问题先翻对应日志别凭感觉瞎猜。我见过太多人连日志路径都不知道在哪出了问题就重启大法这其实是很多线上故障越拖越久的根源。5. 性能调优与安全加固5.1 Apache和PHP-FPM的并发与内存调优环境通了之后不要急着上线先做一轮基础调优。很多人默认配置直接上生产并发一上来就各种卡顿其实不是LAMP不行而是默认参数太保守。先看Apache的MPM模式。RHEL系默认是event模式适合高并发场景。主要调三个参数MaxRequestWorkers、StartServers、MinSpareThreads。MaxRequestWorkers决定Apache最多同时处理多少请求这个值要结合内存算。假设服务器内存4GBApache每个线程按2MB估算系统预留1GB给操作系统和MySQL那Apache能用的内存约3GBMaxRequestWorkers设1500左右从内存角度看够用但实际还要看请求的响应时间和业务开销建议从保守值开始压测再逐步上调。PHP-FPM的调优是重头主要看/etc/php-fpm.d/www.conf里的pm.*系列参数。这里有一个简单的内存计算法可用内存 总共内存 - 操作系统占用 - MySQL缓冲 - Apache占用 估算进程数 可用内存 / 单个PHP进程平均内存单个PHP进程的内存占用因业务而异常见在20MB到50MB之间。比如4GB内存的机器MySQL缓冲1GBApache约500MB剩余约1.5GB给FPM按平均30MB一个进程算pm.max_children可以设50左右。这是起点之后用压测观察内存水位再微调。pm模式建议用dynamic动态让FPM根据流量自动增减worker进程。pm.start_servers、pm.min_spare_servers、pm.max_spare_servers这三个参数决定进程池的弹性和下限一般分别设10、5、20配合max_children一起生效。5.2 MySQL内核参数配置MySQL调优最关键的参数是innodb_buffer_pool_size它决定InnoDB引擎在内存里能缓存多少数据页和索引页。读多写少的业务增大这个值能明显减少磁盘IO效果立竿见影。如果MySQL是独占一台机器业界常见的建议是设物理内存的60%到70%。如果跟Apache、PHP-FPM同机部署就要重新分配。前面那个4GB的例子MySQL缓冲给1GB就比较合适大概占总内存的25%到30%之间为其他组件留余地。修改方式在/etc/my.cnf.d/下新建一个调优文件比如/etc/my.cnf.d/tuning.cnf[mysqld] innodb_buffer_pool_size 1G max_connections 151max_connections默认151一般够用。要注意的是连接数并不是越大越好每个连接都有内存开销如果业务并发确实很高优先从慢查询和业务侧优化而不是单纯拉高连接数。改完配置后重启MySQLsystemctl restart mysqld重启前可以先用mysql -e SHOW VARIABLES LIKE innodb_buffer_pool_size;确认当前值方便前后对比。5.3 基础安全项盘点SELinux、防火墙、账户权限安全这块我不讲那些花里胡哨的WAF规则只说LAMP环境最基本的几件事。第一SELinux不要轻易关闭。RHEL系默认强制SELinux很多人嫌麻烦直接setenforce 0这等于拆掉了一层系统级防护。正确的做法是按需打开对应的布尔值。比如PHP需要连本机的MySQL需要打开setsebool -P httpd_can_network_connect_db on如果PHP还需要访问外部网络比如调用远程API那就再打开一个setsebool -P httpd_can_network_connect on注意加-P让配置持久化不然重启后又会变回去。SELinux相关的报错可以通过ausearch -m AVC查看我后面排查部分会展开。第二防火墙按需放行。RHEL系默认firewalld只放行HTTP/HTTPS就够firewall-cmd --permanent --add-servicehttp firewall-cmd --permanent --add-servicehttps firewall-cmd --reloadMySQL的3306端口默认不要对外网开放。如果真有远程连接需求要么限制来源IP要么走SSH隧道总之别把3306裸奔到公网。第三MySQL账号权限收紧。业务应用绝不使用root单独建一个账号限定登录来源为127.0.0.1授权范围只限业务库CREATE DATABASE appdb CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; CREATE USER appuser127.0.0.1 IDENTIFIED BY 一个足够长的强密码; GRANT ALL PRIVILEGES ON appdb.* TO appuser127.0.0.1; FLUSH PRIVILEGES;这里appdb.*表示只对这个库下面所有表有权限其他库一概无权。说实话能在生产环境坚持这个习惯的人数据库安全事故能少一半。6. 常见问题排查与避坑技巧6.1 最容易踩的坑权限、SELinux、防火墙LAMP部署中90%的奇怪问题最后都归结到这三类文件权限、SELinux上下文、防火墙规则。它们有个共同特点表面现象非常像互相推诿日志还不会直接告诉你原因。典型场景一浏览器访问站点返回403服务在跑、端口在通、文件权限也是644。先别急着chmod 777看一下SELinux上下文ls -Z /var/www/html/example restorecon -Rv /var/www/html/example如果上下文不是httpd_sys_content_trestorecon能恢复。如果问题依旧打开SELinux审核日志ausearch -m AVC -ts recent这条命令会显示最近被SELinux拦截的操作里面会写明哪个进程想访问哪个资源、被什么策略挡住。我第一次看懂AVC日志时真的有种看见“系统内部监控录像”的感觉。典型场景二PHP连不上MySQL报Connection refused或Permission denied。本机能连、PHP-FPM服务也在跑、账号密码都对那大概率还是SELinux。执行getsebool httpd_can_network_connect_db如果返回off打开它。这个布尔值不开Apache/FPM的进程连本机3306都会被拦报错信息极具迷惑性。典型场景三外部访问不到站点本机curl却一切正常。先查防火墙firewall-cmd --list-all没有http服务就放行有就再看看云平台安全组。这个问题出在平台层的概率也很大排查思路其实是一致的从内到外逐层检查别在某一层死磕。6.2 502与500PHP-FPM问题的分水岭502 Bad Gateway是LAMP环境里最具标志性的报错。出现502说明Apache已经把请求转发给了FPM但FPM没有有效响应。按顺序排查systemctl status php-fpm ss -ltnp | grep 9000 tail -f /var/log/php-fpm/error.logFPM如果没启动自然没有进程监听9000端口。如果FPM在跑但502依旧多半是进程池被占满或者某个worker卡死查看日志找线索。有一种情况很隐蔽PHP-FPM配置里改了listen地址而Apache那边SetHandler proxy:fcgi://127.0.0.1:9000还指向旧地址两边对不上。此时用ss -ltnp | grep 9000确认实际监听地址再对比配置往往一抓一个准。500 Internal Server Error则多半是PHP代码本身的错误比如语法错误、函数不存在、内存超标。查/var/log/php-fpm/error.log能看到具体的PHP Error信息。95%的500问题日志里都会直接告诉你出错文件和行号照着改就行。怕的是不看日志瞎改权限和配置越改越乱。6.3 排查工具集锦与日志速查表最后整理一个我日常排查LAMP环境的高频工具清单算是掏家底了。场景工具/命令说明服务状态systemctl status 服务名看运行状态、最近日志端口监听ss -ltnp确认端口被谁占用HTTP测试curl -I快速看返回码和响应头Apache配置检查apachectl configtest改配置后必跑PHP错误/var/log/php-fpm/error.logFPM主日志MySQL错误/var/log/mysqld.log启动和运行错误慢查询tail -f /var/log/php-fpm/www-slow.log需要开启慢日志参数SELinux拦截ausearch -m AVC -ts recent安全策略拦截详情性能压测ab -n 1000 -c 100 http://127.0.0.1/快速验证并发能力还有一个经验改任何配置之前先备份原文件或者在编辑后立即用apachectl configtest校验。这个习惯帮我避免了无数次“改完配置服务起不来大半夜还找不到原文件”的尴尬。另外性能压测跑完要看平均响应时间不只是看请求成功率。成功率是100%不代表服务健康如果P95响应时间从几十毫秒飙到几秒那也是明显的性能拐点信号。调优不是把参数调大就完事要盯着压测结果反复调整直到找到一个稳定区间。写在最后的一些大实话手动部署LAMP这件事技术上真不难难的是养成一种面对报错“往上查进程、往下查日志、反向查SELinux”的排查习惯。我自己刚开始也是一遇到502就慌后来把常见问题捋成清单发现90%的坑就那么几个踩过一次就有了肌肉记忆。最后再分享一个小技巧每次部署完环境我会把这个环境的版本号、关键配置路径、非默认端口、还有我在调优时最终确定的那几个数值记成一个简单的文本文档放在服务器一个固定目录里。下次要改什么东西或者半年后接手维护先打开这个文档十分钟就能恢复全部上下文。有人觉得这是冗余事务但真到线上出问题、半小时搜不回当初配置思路的时候你会感谢当时那个多写了几行字的自己。这套LAMP架构跑起来之后你其实已经打通了Web服务最核心的一条链。后面无论你去折腾Nginx、容器化还是各种云原生方案底层“接入层—应用层—存储层”的思路都是相通的。把这套基础吃透遇到更复杂的环境才不容易慌。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进