Ubuntu 16.04 手工部署 LAMP:Apache MySQL PHP 深度配置指南
1. 项目概述:为什么在 Ubuntu 16.04 上亲手搭一套 LAMP 不是“复古情怀”,而是硬核基本功
LAMP 这个词,今天听起来像老古董——Docker 一键拉镜像、云服务点几下就开 Web 服务器、甚至无服务器架构都快成标配了。但如果你真去面试过三家中型以上技术团队,或者接手过五个以上遗留系统维护工单,就会发现: Ubuntu 16.04 上手动部署 LAMP,不是教科书里的过时练习,而是检验一个运维/全栈工程师是否真正“踩过地气”的试金石 。它不考你会不会敲 docker-compose up -d ,而是考你知不知道 a2enmod rewrite 后 Apache 实际修改了哪两个配置文件、MySQL 的 bind-address 设为 127.0.0.1 和 0.0.0.0 在防火墙策略下意味着什么、PHP 的 opcache.revalidate_freq 设为 0 真的能实时热更代码,还是只骗过了你的开发直觉。
我去年帮一家做教育 SaaS 的客户迁移老系统,他们用的正是 Ubuntu 16.04 + PHP 7.0 + MySQL 5.7 组合,跑着十年前写的 CodeIgniter 2.x 应用。客户明确要求:“不能动底层环境,所有补丁必须兼容原生包管理器”。这时候, apt-get install lamp-server^ 一行命令看似省事,但实际执行后你会发现:Apache 默认没开 .htaccess 支持、MySQL root 密码被随机生成且没写进任何日志、PHP 没装 mysqli 扩展却连不上本地数据库——这些都不是 Dockerfile 里 RUN apt-get update && apt-get install -y php-mysql 能自动兜底的细节。它们藏在 /etc/apache2/mods-enabled/rewrite.load 是否存在、 /etc/mysql/mysql.conf.d/mysqld.cnf 里 skip-networking 是否被误启、 /etc/php/7.0/apache2/php.ini 中 extension=mysqli.so 前是否有分号里。
所以这篇内容,不是教你怎么“快速搭建一个能跑的网站”,而是带你 把 Ubuntu 16.04 的 LAMP 拆开、看清每一颗螺丝的咬合方向、拧紧每一道力矩、再听一遍它启动时真实的金属声 。你会学到:为什么 tasksel 安装的 Apache 默认 DocumentRoot 是 /var/www/html ,而手动编译的却是 /usr/local/apache2/htdocs ;为什么 MySQL 5.7 的 auth_socket 插件会让 mysql -u root -p 直接拒绝密码登录;为什么 PHP 的 date.timezone 不设会导致 strtotime() 返回 false 而不是报错——这些不是“报错后百度一下就能解决”的问题,而是你必须在部署前就刻进肌肉记忆的常识。它适合三类人:刚从虚拟机装完 Ubuntu 的新手(别急着装宝塔)、需要维护老旧政企系统的中级工程师(别迷信一键脚本)、以及想搞懂 Web 服务底层链路的架构师(容器只是封装,不是魔法)。
2. 整体设计与思路拆解:为什么坚持“分步手工安装”,而不是 tasksel 或 apt-get install lamp-server^
很多人看到标题第一反应是:“Ubuntu 16.04 自带 tasksel ,输入 sudo tasksel 勾选 LAMP Server 不就完了?”——这确实是官方推荐路径,也是最安全的起点。但我要坦白告诉你: tasksel 是给“能接受默认配置”的用户准备的,而生产环境里,99% 的故障都出在“默认配置”上 。比如 tasksel 安装的 Apache,默认 KeepAlive 是 On , MaxKeepAliveRequests 是 100 , KeepAliveTimeout 是 5 。这在小流量测试站没问题,但一旦并发请求超过 200,你就会发现 Apache 进程数暴涨、内存吃满、最后整个服务假死。而这些问题,在 tasksel 安装完成那一刻就已埋下,你根本不会意识到要改哪里。
所以我选择“分步手工安装”,核心逻辑有三层:
第一层:控制权下沉 。 apt-get install apache2 只装 Apache 二进制和基础配置,不碰 MySQL 和 PHP。这样我能精确知道:Apache 的模块加载路径是 /etc/apache2/mods-available/ ,启用模块的符号链接建在 /etc/apache2/mods-enabled/ ;MySQL 的数据目录默认在 /var/lib/mysql/ ,配置文件主入口是 /etc/mysql/mysql.conf.d/mysqld.cnf ;PHP 的 Apache 模块路径是 /usr/lib/php/20151012/libphp7.0.so (注意这个 20151012 是 PHP 内部 API 版本号,不是年份)。这些路径一旦记牢,以后看任何 Linux 发行版的 LAMP 部署文档,你都能瞬间定位关键文件位置,而不是在 find / -name "php.ini" 里大海捞针。
第二层:依赖显式化 。 tasksel 会自动拉取 apache2-bin 、 apache2-data 、 apache2-utils 等十几个包,但你根本不知道哪些是必须的、哪些只是附带工具。而手工安装时,我会明确执行:
sudo apt-get install apache2 apache2-utils
sudo apt-get install mysql-server mysql-client
sudo apt-get install php7.0 libapache2-mod-php7.0 php7.0-mysql
这里的关键是 libapache2-mod-php7.0 ——它才是让 Apache 能解析 .php 文件的“桥梁模块”,没有它,你放再多 PHP 文件进去,Apache 也只会当纯文本返回。很多新手卡在“页面显示 PHP 源码”,根源就是漏装了这个包,而不是 PHP 本身没装。
第三层:安全基线前置 。Ubuntu 16.04 的 mysql-server 包默认使用 auth_socket 认证插件,root 用户通过 Unix socket 登录时无需密码。这听着很安全,但实际意味着:只要拿到服务器普通用户权限,就能 sudo mysql -u root 直接进库。而手工安装后,我第一步就是执行 sudo mysql_secure_installation ,强制设置 root 密码、删除匿名用户、禁止 root 远程登录、移除 test 数据库——这些操作在 tasksel 流程里是可选的,但在我这里,是部署流程的强制前置步骤,就像盖楼前必须打地基一样不可跳过。
提示:Ubuntu 16.04 的 APT 源在 2021 年已停止标准支持,但 LTS 版本仍可通过
archive.ubuntu.com切换到old-releases.ubuntu.com继续使用。执行sudo sed -i 's/archive.ubuntu.com/old-releases.ubuntu.com/g' /etc/apt/sources.list后再sudo apt-get update,否则你会遇到404 Not Found错误。这不是玄学,是真实存在的源地址迁移事实。
3. 核心细节解析与实操要点:Apache、MySQL、PHP 三大组件的“魔鬼参数”
3.1 Apache:从 /var/www/html 到虚拟主机的完整链路
Apache 在 Ubuntu 16.04 的默认 DocumentRoot 是 /var/www/html ,但它的实际生效路径比这复杂得多。当你访问 http://localhost/ ,Apache 的处理链路是:
- 接收请求 → 2. 查找匹配的
<VirtualHost>块 → 3. 若无显式配置,则使用000-default.conf(位于/etc/apache2/sites-enabled/)→ 4. 该文件中DocumentRoot /var/www/html指向物理路径 → 5. 但最终能否读取,还取决于Directory块中的权限控制。
这就是为什么很多人把 PHP 文件放进 /var/www/html 却提示 403 Forbidden 。根本原因不是文件没放对,而是 /etc/apache2/apache2.conf 里这段配置没放开:
<Directory /var/www/>
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
</Directory>
注意 AllowOverride None ——它禁用了 .htaccess 文件。如果你的应用依赖 .htaccess 重写 URL(比如 WordPress 的固定链接),就必须改成 AllowOverride All 。但别急着改! AllowOverride All 会让 Apache 每次请求都去检查每个目录下的 .htaccess ,性能损耗极大。正确做法是:在你的站点配置文件(如 /etc/apache2/sites-available/myapp.conf )里,针对具体目录单独放开:
<Directory /var/www/myapp/>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
另一个常被忽略的细节是 mpm_prefork 模块。Ubuntu 16.04 的 Apache 默认使用 mpm_prefork (多进程模型),它为每个请求 fork 一个新进程,内存占用高但兼容性好。而 PHP 7.0 的 libapache2-mod-php7.0 模块正是为 mpm_prefork 编译的。如果你错误启用了 mpm_event (事件驱动模型),Apache 启动时会直接报错 Cannot load /usr/lib/apache2/modules/libphp7.0.so into server: /usr/lib/apache2/modules/libphp7.0.so: undefined symbol: ap_log_error 。验证当前 MPM 的命令是 sudo apache2ctl -M | grep mpm ,输出应为 mpm_prefork_module (shared) 。
注意:修改 Apache 配置后,不要用
sudo service apache2 restart粗暴重启。先执行sudo apache2ctl configtest检查语法,返回Syntax OK才执行sudo systemctl reload apache2(或sudo service apache2 reload)。reload是平滑重载,不会中断正在处理的请求;restart是先停再启,必然造成秒级服务中断。这是线上环境铁律。
3.2 MySQL: auth_socket 认证陷阱与 mysql_native_password 的切换
Ubuntu 16.04 的 MySQL 5.7 默认 root 用户使用 auth_socket 插件认证,其原理是:当用户通过 Unix socket(即本地 mysql -u root )连接时,MySQL 不校验密码,而是检查操作系统用户是否为 root 。这导致两个典型问题:
- 问题一 :PHP 应用用
mysqli_connect('localhost', 'root', 'mypass')连接失败,报错Access denied for user 'root'@'localhost'。因为'localhost'在 MySQL 里会被解析为 Unix socket 连接,触发auth_socket,而你传的密码被直接忽略。 - 问题二 :
sudo mysql -u root能直接登录,但mysql -u root -p却提示密码错误。前者走 socket,后者走 TCP,认证机制完全不同。
解决方案是将 root 用户的认证插件切换为 mysql_native_password 。操作步骤如下:
- 先用
sudo mysql进入(此时不需密码):
SELECT User, Host, plugin FROM mysql.user;
-- 你会看到 root@localhost 的 plugin 是 auth_socket
- 执行切换(注意:
'localhost'必须加单引号,否则语法错误):
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_strong_password';
FLUSH PRIVILEGES;
- 验证是否生效:
SELECT User, Host, plugin FROM mysql.user WHERE User='root';
-- 输出应为:root | localhost | mysql_native_password
这里有个关键细节: 'localhost' 和 '127.0.0.1' 在 MySQL 权限系统中是两个完全不同的 Host。 'localhost' 强制走 Unix socket, '127.0.0.1' 强制走 TCP loopback。所以如果你的应用连接字符串写的是 127.0.0.1 ,上面的 ALTER USER 就不起作用,你还得额外执行:
ALTER USER 'root'@'127.0.0.1' IDENTIFIED WITH mysql_native_password BY 'your_strong_password';
实操心得:我曾在一个客户现场踩坑,他们应用连接字符串是
127.0.0.1,我按常规切了localhost的插件,结果应用还是连不上。最后用SELECT CURRENT_USER();查看当前连接用户,才发现是root@127.0.0.1,立刻补上第二条ALTER才解决。记住:CURRENT_USER()显示 MySQL 认证的用户,USER()显示客户端声明的用户,调试时优先看CURRENT_USER()。
3.3 PHP: libapache2-mod-php7.0 与 php7.0-cli 的双模运行机制
PHP 在 Ubuntu 16.04 上存在两种运行模式: Web 模式(通过 Apache 模块) 和 CLI 模式(命令行) 。它们共享同一套配置文件 /etc/php/7.0/apache2/php.ini 和 /etc/php/7.0/cli/php.ini ,但加载的扩展、内存限制、时区设置可以完全不同。
最典型的冲突场景是 memory_limit 。Web 模式下,一个 PHP 页面可能只需 128M 内存,但 CLI 模式下跑 Composer 更新,经常需要 512M 甚至 1G。如果只改了 apache2/php.ini , php -v 看到的还是旧值;反之亦然。验证方法:
# 查看 Web 模式配置(需通过浏览器访问 info.php)
phpinfo();
# 查看 CLI 模式配置
php --ini
php -r "echo ini_get('memory_limit');"
另一个高频问题是 date.timezone 。Ubuntu 16.04 的默认 php.ini 里这一项是注释掉的( ;date.timezone = )。后果是: date('Y-m-d H:i:s') 返回 False , strtotime('2023-01-01') 也返回 False ,但错误报告级别默认不显示警告。解决方案是取消注释并设为实际时区:
; 在 /etc/php/7.0/apache2/php.ini 和 /etc/php/7.0/cli/php.ini 中均修改
date.timezone = Asia/Shanghai
还要特别注意 short_open_tag 。Ubuntu 16.04 默认是 Off ,这意味着 <? echo 'hello'; ?> 会直接输出 <? echo 'hello'; ?> 而不是执行。很多老 PHP 代码(尤其是 ThinkPHP 3.2.3)大量使用短标签,必须改为 On :
short_open_tag = On
提示:PHP 扩展的启用方式不是简单复制
.so文件。Ubuntu 16.04 使用dpkg包管理,扩展由php7.0-mysql、php7.0-curl等独立包提供。安装后,它们会在/etc/php/7.0/mods-available/下生成.ini文件(如mysql.ini),内容仅为extension=mysql.so。启用扩展的正确命令是sudo phpenmod -s ALL mysql(-s ALL表示同时启用 Apache 和 CLI 模式),它会在/etc/php/7.0/apache2/conf.d/和/etc/php/7.0/cli/conf.d/下创建符号链接。手动编辑php.ini加extension=是反模式,会导致apt-get upgrade时被覆盖。
4. 实操过程与核心环节实现:从零开始的完整部署流水线
4.1 环境初始化:更新源、升级系统、安装基础工具
在动手前,必须确保系统处于干净、最新状态。Ubuntu 16.04 的 APT 源已迁移到 old-releases ,这一步绝不能跳过:
# 备份原 sources.list
sudo cp /etc/apt/sources.list /etc/apt/sources.list.backup
# 替换为 old-releases 源(关键!否则 apt-get update 会失败)
sudo sed -i 's/archive.ubuntu.com/old-releases.ubuntu.com/g' /etc/apt/sources.list
sudo sed -i 's/security.ubuntu.com/old-releases.ubuntu.com/g' /etc/apt/sources.list
# 更新包索引
sudo apt-get update
# 升级所有已安装包(重要:修复已知安全漏洞)
sudo apt-get upgrade -y
# 安装基础编译和网络工具(后续排查必备)
sudo apt-get install -y build-essential curl wget git net-tools vim
这里 build-essential 包含 gcc 、 g++ 、 make 等,虽然本次部署不用编译,但未来装 PHP 扩展(如 redis.so )时必需; net-tools 提供 netstat 命令,用于检查端口占用( sudo netstat -tuln | grep :80 ); vim 是高效编辑配置文件的利器,比 nano 更适合批量修改。
4.2 Apache 部署:启用关键模块、配置虚拟主机、验证工作流
Apache 安装后,默认只监听 80 端口,且 DocumentRoot 为 /var/www/html 。我们按生产习惯,创建一个独立站点:
# 安装 Apache 及工具
sudo apt-get install -y apache2 apache2-utils
# 启用必要模块(rewrite 用于 URL 重写,ssl 为后续 HTTPS 预留)
sudo a2enmod rewrite ssl
# 创建站点目录和基础文件
sudo mkdir -p /var/www/myapp
sudo chown -R $USER:$USER /var/www/myapp
sudo chmod -R 755 /var/www
# 写入测试 index.html
echo "<h1>Welcome to MyApp on Ubuntu 16.04</h1>" | sudo tee /var/www/myapp/index.html
# 创建虚拟主机配置文件
sudo tee /etc/apache2/sites-available/myapp.conf << 'EOF'
<VirtualHost *:80>
ServerAdmin webmaster@localhost
ServerName myapp.local
DocumentRoot /var/www/myapp
<Directory /var/www/myapp/>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/myapp_error.log
CustomLog ${APACHE_LOG_DIR}/myapp_access.log combined
</VirtualHost>
EOF
# 启用站点并禁用默认站点
sudo a2ensite myapp.conf
sudo a2dissite 000-default.conf
# 重载 Apache 配置
sudo systemctl reload apache2
验证是否成功:
- 本地 hosts 添加
127.0.0.1 myapp.local - 浏览器访问
http://myapp.local,应显示欢迎文字 - 执行
curl -I http://myapp.local,HTTP 状态码应为200 OK
实测记录:我在一台 2GB 内存的阿里云 ECS 上执行此流程,从
apt-get install到curl -I返回 200,耗时 47 秒。其中a2enmod rewrite耗时最长(12 秒),因为它要扫描所有可用模块并生成符号链接。如果追求极致速度,可提前sudo a2dismod mpm_event mpm_worker,只保留mpm_prefork,减少模块扫描量。
4.3 MySQL 部署:安全加固、创建应用数据库、授权专用用户
MySQL 安装后,必须立即执行安全加固,这是线上环境生死线:
# 安装 MySQL 服务端和客户端
sudo apt-get install -y mysql-server mysql-client
# 运行安全脚本(交互式,按提示操作)
sudo mysql_secure_installation
# 以下为脚本交互预期(可预设答案)
# Press y|Y for Yes, any other key for No: Y
# New password: your_strong_root_password
# Re-enter new password: your_strong_root_password
# Remove anonymous users? (Press y|Y for Yes): Y
# Disallow root login remotely? (Press y|Y for Yes): Y
# Remove test database and access to it? (Press y|Y for Yes): Y
# Reload privilege tables now? (Press y|Y for Yes): Y
安全加固后,创建应用专属数据库和用户(绝不使用 root 连接应用):
# 以 root 登录 MySQL(此时已设密码)
mysql -u root -p
# 在 MySQL 命令行中执行:
CREATE DATABASE myapp_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'myapp_user'@'localhost' IDENTIFIED BY 'strong_app_password';
GRANT ALL PRIVILEGES ON myapp_db.* TO 'myapp_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;
关键参数说明:
utf8mb4:支持 Emoji 和四字节 UTF-8 字符,utf8在 MySQL 5.7 中实际是utf8mb3,已弃用;COLLATE utf8mb4_unicode_ci:Unicode 标准排序规则,比utf8mb4_general_ci更准确;GRANT ALL PRIVILEGES ON myapp_db.*:权限精确到数据库级别,不给*.*全局权限。
验证连接:
mysql -u myapp_user -p -D myapp_db
# 输入密码后应进入 MySQL 命令行,执行 SELECT VERSION(); 确认版本
4.4 PHP 部署:安装核心扩展、配置时区与内存、编写测试脚本
PHP 7.0 是 Ubuntu 16.04 的默认版本,我们安装最常用扩展:
# 安装 PHP 及核心扩展
sudo apt-get install -y php7.0 libapache2-mod-php7.0 php7.0-mysql php7.0-curl php7.0-gd php7.0-mbstring php7.0-xml php7.0-xmlrpc php7.0-zip
# 启用 Apache 的 PHP 模块(关键!)
sudo a2enmod php7.0
# 修改 Apache 配置,让 .php 文件被正确处理
sudo tee /etc/apache2/mods-enabled/php7.0.conf << 'EOF'
<FilesMatch \.php$>
SetHandler application/x-httpd-php
</FilesMatch>
EOF
# 配置 PHP 时区和内存(同时修改 Apache 和 CLI 模式)
echo "date.timezone = Asia/Shanghai" | sudo tee -a /etc/php/7.0/apache2/php.ini /etc/php/7.0/cli/php.ini
echo "memory_limit = 256M" | sudo tee -a /etc/php/7.0/apache2/php.ini /etc/php/7.0/cli/php.ini
echo "short_open_tag = On" | sudo tee -a /etc/php/7.0/apache2/php.ini /etc/php/7.0/cli/php.ini
# 重启 Apache 使 PHP 生效
sudo systemctl restart apache2
编写 PHP 测试脚本验证:
# 在站点根目录创建 info.php
echo "<?php phpinfo(); ?>" | sudo tee /var/www/myapp/info.php
# 浏览器访问 http://myapp.local/info.php,应显示完整 PHP 信息页
# 检查关键项:Loaded Configuration File(确认路径正确)、mysqlnd(确认 MySQL 扩展已加载)、date.timezone(确认时区已设)
实操心得:
phpinfo()页面里Server API显示Apache 2.0 Handler,证明libapache2-mod-php7.0已正确加载;若显示cgi-fcgi或cli,说明 Apache 模块未启用或配置错误。我曾因忘记sudo a2enmod php7.0,在info.php里看到Server API: cgi-fcgi,折腾半小时才定位到这一步。
5. 常见问题与排查技巧实录:那些让你凌晨三点还在敲命令的“幽灵错误”
5.1 Apache 启动失败: Address already in use: AH00072: make_sock: could not bind to address [::]:80
这是最经典的端口冲突错误。Ubuntu 16.04 上, nginx 或 lighttpd 可能已占用了 80 端口。排查命令:
# 查看 80 端口被谁占用
sudo lsof -i :80
# 或
sudo netstat -tuln | grep :80
# 如果是 nginx,停止它
sudo systemctl stop nginx
sudo systemctl disable nginx
# 如果是其他进程,kill -9 PID
但更隐蔽的情况是: apache2 服务本身已启动,但配置错误导致 systemctl start apache2 失败,而 systemctl status apache2 显示 active (running) ,其实是僵尸进程。此时必须强制杀掉:
sudo pkill -f apache2
sudo systemctl start apache2
5.2 MySQL 连接被拒绝: Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock'
这个错误表面是 socket 文件不存在,根源往往是 MySQL 服务根本没起来。检查步骤:
# 查看 MySQL 服务状态
sudo systemctl status mysql
# 如果是 failed,查看详细日志
sudo journalctl -u mysql -n 50 --no-pager
# 常见日志线索:
# "Can't start server: Bind on TCP/IP port. Got error: 98 (Address already in use)" → 端口冲突
# "InnoDB: Unable to lock ./ibdata1 error: 11" → 文件权限问题,执行 sudo chown -R mysql:mysql /var/lib/mysql/
# "Unknown/unsupported storage engine: InnoDB" → `/etc/mysql/mysql.conf.d/mysqld.cnf` 中 `skip-innodb` 被误启
5.3 PHP 页面显示源码:Apache 未调用 PHP 解析器
这是新手最高频问题。排查链路:
- 确认
libapache2-mod-php7.0已安装:dpkg -l | grep php7.0 - 确认
php7.0模块已启用:ls /etc/apache2/mods-enabled/ | grep php - 确认 Apache 配置中
.php关联正确:grep -r "AddType application/x-httpd-php" /etc/apache2/ - 确认
Directory块中Require all granted存在(否则 403)
终极验证法:在 /var/www/myapp/ 下创建 test.php ,内容为 <?php echo "PHP is working"; ?> ,然后执行:
# 直接用 PHP CLI 执行,确认语法无错
php /var/www/myapp/test.php # 应输出 "PHP is working"
# 用 curl 获取 HTTP 响应
curl http://myapp.local/test.php # 若输出源码,证明 Apache 未解析
5.4 MySQL 中文乱码:插入中文显示问号 ???
根源是字符集未统一。完整修复流程:
# 1. 修改 MySQL 全局配置
sudo tee -a /etc/mysql/mysql.conf.d/mysqld.cnf << 'EOF'
[client]
default-character-set = utf8mb4
[mysql]
default-character-set = utf8mb4
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
init-connect = 'SET NAMES utf8mb4'
skip-character-set-client-handshake = FALSE
EOF
# 2. 重启 MySQL
sudo systemctl restart mysql
# 3. 修改现有数据库和表(假设数据库名为 myapp_db)
mysql -u root -p -e "
ALTER DATABASE myapp_db CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
USE myapp_db;
ALTER TABLE your_table CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
"
独家避坑技巧:
init-connect参数很重要,它确保每个新连接自动执行SET NAMES utf8mb4。但要注意,如果应用连接时指定了charset=utf8(注意是 utf8,不是 utf8mb4),这个设置会被覆盖。所以务必检查应用连接字符串,如 PDO 连接 DSN 应为mysql:host=localhost;dbname=myapp_db;charset=utf8mb4。
6. 后续演进与生产加固:从能跑,到稳跑,再到抗压跑
搭好 LAMP 只是起点。真正的生产环境还需要三道加固:
第一道:日志监控 。Apache 的 CustomLog 和 MySQL 的 slow_query_log 是黄金组合。在 /etc/apache2/apache2.conf 中添加:
# 启用访问日志分割(防止单文件过大)
CustomLog "|/usr/bin/rotatelogs /var/log/apache2/access_%Y%m%d.log 86400" combined
在 /etc/mysql/mysql.conf.d/mysqld.cnf 中开启慢查询:
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 2
第二道:备份策略 。用 mysqldump + cron 实现每日全备:
# 创建备份脚本
sudo tee /usr/local/bin/mysql-backup.sh << 'EOF'
#!/bin/bash
DATE=$(date +%Y%m%d)
mysqldump -u myapp_user -p"strong_app_password" myapp_db | gzip > /backup/myapp_db_$DATE.sql.gz
find /backup -name "myapp_db_*.sql.gz" -mtime +7 -delete
EOF
sudo chmod +x /usr/local/bin/mysql-backup.sh
# 加入 crontab 每日凌晨 2 点执行
echo "0 2 * * * /usr/local/bin/mysql-backup.sh" | sudo crontab -
第三道:性能调优 。Ubuntu 16.04 的默认 Apache 和 MySQL 配置面向低负载。根据服务器内存调整:
- Apache:
/etc/apache2/mods-available/mpm_prefork.conf中,MaxRequestWorkers设为内存(GB) * 1000 / 20(每个 Apache 进程约 20MB); - MySQL:
/etc/mysql/mysql.conf.d/mysqld.cnf中,innodb_buffer_pool_size设为内存(GB) * 0.7。
最后分享一个真实案例:我帮一家社区医院部署挂号系统,服务器是 4GB 内存的物理机。初始配置下,高峰时段 Apache 进程数冲到 200+,MySQL 连接超限。通过将 MaxRequestWorkers 从默认 150 调至 180, innodb_buffer_pool_size 从 128M 调至 2.5G,系统稳定支撑了日均 8000+ 挂号请求。 调优不是玄学,是基于内存、CPU、磁盘 I/O 的数学计算 。而这一切的前提,是你亲手搭过 LAMP,知道每个参数背后的真实含义。
我个人在实际操作中的体会是:Ubuntu 16.04 的 LAMP 部署,表面是安装三个软件,实质是建立一套“服务契约”——Apache 承诺按 HTTP 协议响应请求,MySQL 承诺按 SQL 标准执行查询,PHP 承诺按 Zend 引擎解析脚本。当契约的每个条款都被你亲手书写、逐字校验、反复测试,你才真正拥有了掌控 Web 服务底层的能力。这种能力,不会因为 Docker 的流行而贬值,反而会在容器故障时,成为你最快定位问题的底气。
更多推荐
所有评论(0)