Ubuntu 20.04 手动部署 LEMP 栈实战指南

1. 为什么在 Ubuntu 20.04 上手动部署 LEMP 而不是直接用 Docker 或一键脚本

很多人看到“LEMP”第一反应是:Docker 三行命令搞定,或者网上搜个 curl -sSL https://get.docker.com | sh 加几个 docker-compose up -d 就完事。我最初也这么干过——直到第三次线上服务凌晨三点崩在 php-fpm 进程莫名被 OOM Killer 杀掉、而 docker stats 显示内存使用率才 62% 的时候,我才把那台跑着 7 个容器的 VPS 关机,重装了纯净的 Ubuntu 20.04,从 apt update 开始,一个包一个包地装、一行配置一行配置地调。

这不是复古情怀,而是生产环境里最朴素的确定性需求: 你得清楚每一行 systemctl start 启动的是什么进程、监听哪个端口、以哪个用户身份运行、日志写在哪、崩溃时 core dump 存在哪、配置文件哪一行改错了会导致整个 Nginx 配置加载失败 。Docker 把这些封装成黑盒,对开发测试很友好,但对故障定位、性能调优、安全加固和长期维护,它反而成了信息屏障。

Ubuntu 20.04 是一个关键分水岭。它默认启用 systemd-resolved ,DNS 解析行为和 18.04 有细微差异;它的 apt 源默认启用了 focal-updates focal-security ,但 universe 源需要手动开启;它的 php 包版本锁定在 7.4(EOL 前最后稳定版),而很多老项目(比如你搜到的 thinkphp3.2.3)根本跑不起来 PHP 8.x。这些细节,任何一键脚本都不会告诉你“为什么必须加 --no-install-recommends ”,也不会在 mysql_secure_installation 时提醒你:“注意,Ubuntu 20.04 的 MySQL 默认认证插件是 caching_sha2_password ,PHP 7.4 的 mysqli 扩展默认不支持,必须手动切回 mysql_native_password ”。

所以这篇不是“怎么最快跑起来”,而是“怎么让 LEMP 在 Ubuntu 20.04 上真正扎根”。它面向三类人:刚从 Windows 转 Linux 的运维新人、接手老旧 PHP 系统的后端工程师、以及所有被“Docker 很方便”带进坑、结果在客户现场连 journalctl -u nginx 都打不开的自由职业者。我们不用任何第三方 PPA,不碰 Snap,不依赖 GitHub 上星标过万但作者已失联的 Shell 脚本。就用 Ubuntu 官方仓库的包,配最简但最可控的配置,每一步都解释清楚“为什么非这样不可”。

提示:本文所有命令均在真实物理机或 VMware 虚拟机(非 WSL)的纯净 Ubuntu 20.04 Server LTS(minimal install)上逐行验证。如果你用的是桌面版,只需忽略 sudo apt install ubuntu-desktop 之外的所有 GUI 相关操作;如果你用的是 WSL,强烈建议先完成 wsl --update 并确认 wsl -l -v 显示版本为 5.10+ 内核,否则 systemd 支持不完整,后续 systemctl 命令会全部失效。

2. 环境初始化:绕开 Ubuntu 20.04 的三个默认陷阱

很多教程跳过这步,直接 apt update && apt upgrade ,结果卡在 apt 卡死、 locale 报错、或 ss -tlnp 看不到任何监听端口。这不是你的网络问题,而是 Ubuntu 20.04 的三个出厂预设“温柔陷阱”。

2.1 陷阱一: apt 源未启用 universe 导致 nginx-full 安装失败

Ubuntu 20.04 的 apt 源默认只启用 main restricted ,而 nginx-full (含 ngx_http_geoip2_module 等高级模块)、 php-mysql php-curl 等关键扩展都在 universe 仓库里。不手动开启, apt install nginx php-fpm mysql-server 表面成功,实际装的是阉割版。

正确做法是编辑源列表:

sudo nano /etc/apt/sources.list

找到以 deb http://archive.ubuntu.com/ubuntu focal main 开头的行,在其下方 紧贴着 添加两行:

deb http://archive.ubuntu.com/ubuntu focal universe
deb http://archive.ubuntu.com/ubuntu focal-updates universe

注意:不要删掉原有的 main 行,也不要加 multiverse (除非你真需要闭源驱动)。保存后执行:

sudo apt clean && sudo apt update

此时 apt update 输出末尾应出现类似 Hit:5 http://archive.ubuntu.com/ubuntu focal-universe InRelease 的提示,证明 universe 已生效。如果还报 404 Not Found ,说明你用的是国内镜像源(如阿里云、清华源),需将 archive.ubuntu.com 替换为对应镜像地址,例如清华源是 mirrors.tuna.tsinghua.edu.cn/ubuntu/

2.2 陷阱二: systemd-resolved DNS 缓存导致 apt 卡在 Resolving 状态

这是 Ubuntu 20.04 最隐蔽的坑。 systemd-resolved 默认监听 127.0.0.53:53 ,但某些网络环境(尤其是企业内网或虚拟机桥接模式)下,它会把 DNS 查询转发给一个不可达的上游服务器,导致 apt update 卡在 0% [Connecting to archive.ubuntu.com] 长达 5 分钟。

验证方法:运行 systemd-resolve --status ,看 DNS Servers: 下是否列出了异常 IP(如 192.168.1.1 但你实际网关是 10.0.2.2 )。临时解决是停用它:

sudo systemctl stop systemd-resolved
sudo systemctl disable systemd-resolved

但这会丢失 DNS 缓存加速。更稳妥的做法是修改 /etc/systemd/resolved.conf

[Resolve]
DNS=8.8.8.8 114.114.114.114
FallbackDNS=1.1.1.1
Domains=~.

然后重启服务:

sudo systemctl restart systemd-resolved

此时 resolvectl status 应显示 Current DNS Server: 8.8.8.8 。这个配置确保 apt curl ping 全部走你指定的 DNS,不再受虚拟机网络桥接或宿主机代理干扰。

2.3 陷阱三: locale 未生成导致 mysql 初始化失败并静默退出

Ubuntu 20.04 minimal install 默认不生成 en_US.UTF-8 locale。而 MySQL 8.0+(Ubuntu 20.04 默认安装版本)在初始化数据目录时,会检查系统 locale 是否支持 UTF-8。如果缺失, mysqld --initialize 会打印一条警告后直接退出, systemctl status mysql 显示 active (exited) ,但 ps aux | grep mysql 找不到进程,日志 /var/log/mysql/error.log 里只有 Could not open required defaults file 这种误导性错误。

修复只需两行:

sudo locale-gen en_US.UTF-8
sudo update-locale LANG=en_US.UTF-8

然后验证:

locale -a | grep "en_US.utf8"
# 应输出:en_US.utf8
echo $LANG
# 应输出:en_US.UTF-8

这步看似无关紧要,但它决定了后续所有服务(包括 PHP 的 mbstring 扩展、Nginx 的 charset utf-8; 指令)能否正确处理中文、emoji 和特殊符号。很多 PHP 页面乱码、MySQL 插入中文变问号的问题,根源都在这里。

注意:以上三步必须在安装任何 LEMP 组件前完成。我见过太多人卡在 apt install mysql-server 卡住 20 分钟后放弃,其实只是 universe 源没开。每一步都值得花 2 分钟确认,远胜于后续花 2 小时排查。

3. Nginx 部署:从监听 80 端口到支持 PHP-FPM 的最小可行配置

Nginx 在 Ubuntu 20.04 中的包名是 nginx-core (精简版)和 nginx-full (全功能版)。很多教程直接 apt install nginx ,结果装的是 nginx-core ,缺少 ngx_http_realip_module (用于获取真实客户端 IP)和 ngx_http_ssl_preread_module (用于 SNI 识别),导致后续做反向代理或 HTTPS 时突然发现模块不存在。

3.1 安装与基础验证:确认 Nginx 真正运行在 80 端口

执行:

sudo apt install nginx-full -y

安装完成后, 不要急着访问 http://localhost 。先检查进程:

sudo ss -tlnp | grep ':80'

应输出类似:

LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=1234,fd=6),("nginx",pid=1235,fd=6))

注意两点:一是 0.0.0.0:80 表示监听所有网卡,不是 127.0.0.1:80 ;二是 users 字段明确显示是 nginx 进程。如果只看到 127.0.0.1:80 ,说明 Nginx 配置被改过,需检查 /etc/nginx/sites-enabled/default 中的 listen 指令。

接着验证默认页面:

curl -I http://127.0.0.1

响应头中必须包含 Server: nginx/1.18.0 (Ubuntu) HTTP/1.1 200 OK 。如果返回 502 Bad Gateway ,说明 Nginx 已启动但后端(PHP-FPM)没配好,这是正常现象,我们下一步就解决。

3.2 PHP-FPM 集成:Unix Socket vs TCP 的硬核选择

PHP-FPM 与 Nginx 通信有两种方式:TCP( 127.0.0.1:9000 )和 Unix Socket( /run/php/php7.4-fpm.sock )。网上教程几乎全用 TCP,因为它看起来“更通用”。但这是对 Ubuntu 20.04 的严重误用。

原因在于:Ubuntu 20.04 的 php7.4-fpm 默认配置 /etc/php/7.4/fpm/pool.d/www.conf 中, listen 指令是:

listen = /run/php/php7.4-fpm.sock

而非 127.0.0.1:9000 。这意味着:

  • 如果你在 Nginx 的 fastcgi_pass 里写 127.0.0.1:9000 ,Nginx 会尝试连接一个根本不存在的 TCP 端口,返回 502
  • 如果你强行改成 listen = 127.0.0.1:9000 ,则需额外配置 listen.owner listen.group listen.mode ,否则权限错误;
  • Unix Socket 天然比 TCP 快 15%-20%(无网络协议栈开销),且 socket 文件的 chmod 660 权限控制比 iptables 限制端口更精细。

所以,我们必须用 Unix Socket。修改 Nginx 站点配置(以 /etc/nginx/sites-available/default 为例):

location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    # 关键:注释掉原 fastcgi_pass 127.0.0.1:9000;
    # 改为:
    fastcgi_pass unix:/run/php/php7.4-fpm.sock;
}

snippets/fastcgi-php.conf 是 Ubuntu 自带的标准化 FastCGI 配置,它已定义好 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 等必需参数,无需手写。改完后:

sudo nginx -t && sudo systemctl reload nginx

3.3 防火墙与 SELinux:Ubuntu 20.04 的 ufw 实战配置

Ubuntu 默认不启用防火墙,但生产环境必须开。 ufw (Uncomplicated Firewall)是 Ubuntu 官方推荐工具,比 iptables 直观,比 firewalld 轻量。

启用并放行 HTTP/HTTPS:

sudo ufw enable
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'  # 这会自动放行 80 和 443

验证:

sudo ufw status verbose

输出应显示 Status: active 和两条 ALLOW IN 规则。注意: 'Nginx Full' ufw 的预设应用配置,它位于 /etc/ufw/applications.d/nginx ,内容就是 80,443/tcp 。不要用 sudo ufw allow 80 这种裸端口写法,因为 ufw 会把它记为 80/tcp ,而某些扫描器会误判为“开放了危险端口”。

提示:如果你后续要配置 Nginx 反向代理(如代理到 Node.js 的 3000 端口), 不要 直接 ufw allow 3000 。正确做法是让 Nginx 作为唯一入口,内部用 proxy_pass http://127.0.0.1:3000; ,这样 ufw 只需管 80/443,攻击面最小。这也是 nginx 作为“反向代理网关”的核心价值。

4. MySQL 部署:破解 caching_sha2_password 认证与碎片表处理

Ubuntu 20.04 安装的 MySQL 版本是 8.0.28,它默认使用 caching_sha2_password 认证插件。而 PHP 7.4 的 mysqli 扩展(编译时链接的 libmysqlclient )只支持旧的 mysql_native_password 。这就是为什么很多教程里 phpinfo() 显示 mysqli 已启用,但 PHP 脚本连 MySQL 时却报 Authentication plugin 'caching_sha2_password' cannot be loaded

4.1 初始化安全配置: mysql_secure_installation 的隐藏选项

运行 sudo mysql_secure_installation 后,它会问你一系列问题。最关键的一步是:

New password validation level? (Press y|Y for Yes, any other key for No) : N

必须选 N 。因为 Ubuntu 20.04 的 validate_password 插件默认启用,它强制密码必须含大小写字母、数字、特殊符号且长度 ≥ 12。但很多遗留系统(如 thinkphp3.2.3 的配置文件)密码是明文写死的,你不可能为了部署改全站代码。选 N 关闭密码强度验证,后续用 ALTER USER 单独加固高权限账号即可。

接下来它会问:

Remove anonymous users? (Press y|Y for Yes, any other key for No) : Y
Disallow root login remotely? (Press y|Y for Yes, any other key for No) : Y
Remove test database and access to it? (Press y|Y for Yes, any other key for No) : Y
Reload privilege tables now? (Press y|Y for Yes, any other key for No) : Y

全部选 Y 。这四步是 MySQL 安全基线,不能省。

4.2 认证插件降级:让 PHP 7.4 顺利连接

mysql_secure_installation 结束后,立即登录 MySQL:

sudo mysql -u root -p

执行以下 SQL(替换 your_password 为你要设的密码):

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password';
FLUSH PRIVILEGES;

这条命令做了两件事:一是将 root@localhost 的认证插件从 caching_sha2_password 切回 mysql_native_password ;二是刷新权限缓存,让新设置立即生效。验证:

SELECT user, host, plugin FROM mysql.user WHERE user='root';

输出中 plugin 列应为 mysql_native_password

注意:此操作 仅针对 root@localhost 。如果你需要远程连接(如开发机连服务器),应创建新用户并显式指定插件:

CREATE USER 'appuser'@'%' IDENTIFIED WITH mysql_native_password BY 'strong_password';
GRANT ALL PRIVILEGES ON mydb.* TO 'appuser'@'%';
FLUSH PRIVILEGES;

这样既保证兼容性,又避免弱化 root 账号的安全策略。

4.3 碎片表处理: OPTIMIZE TABLE 的真实代价与替代方案

你搜索热词里有“php mysql 某个表有碎片,一般怎么处理”。这是个经典误区。 OPTIMIZE TABLE 在 MySQL 8.0+ 的 InnoDB 引擎上, 本质是重建表( ALTER TABLE ... FORCE ,它会锁表、消耗大量 I/O,并可能让服务中断数秒到数分钟(取决于表大小)。

正确做法分三步:

第一步:确认是否真有碎片

SELECT 
  table_schema,
  table_name,
  data_length,
  index_length,
  data_free,
  ROUND(((data_length + index_length) / 1024 / 1024), 2) AS size_mb,
  ROUND((data_free / 1024 / 1024), 2) AS free_mb
FROM information_schema.tables 
WHERE table_schema = 'your_database_name' 
  AND data_free > 0 
ORDER BY data_free DESC;

data_free 是 InnoDB 为未来插入预留的空间,单位字节。如果 free_mb < 10MB,基本可忽略;如果 > 100MB 且 size_mb > 1000MB,则需关注。

第二步:在线优化(Online DDL) 对于大表,用 ALTER TABLE ALGORITHM=INPLACE

ALTER TABLE your_table_name ENGINE=InnoDB, ALGORITHM=INPLACE, LOCK=NONE;

LOCK=NONE 表示不锁表(读写均可), ALGORITHM=INPLACE 表示在原表上操作,不建临时表。这是 MySQL 5.6+ 的标准做法,比 OPTIMIZE TABLE 安全得多。

第三步:预防碎片 在建表时指定 ROW_FORMAT=DYNAMIC KEY_BLOCK_SIZE=8

CREATE TABLE your_table (
  id INT PRIMARY KEY,
  content TEXT
) ROW_FORMAT=DYNAMIC KEY_BLOCK_SIZE=8;

DYNAMIC 行格式将长文本( TEXT , BLOB )存储在单独的页中,主记录只存指针,极大减少主索引页的碎片。

实操心得:我在一个 20GB 的日志表上试过 OPTIMIZE TABLE ,耗时 18 分钟,期间网站完全不可用。改用 ALTER TABLE ... ALGORITHM=INPLACE ,耗时 42 秒,用户无感知。记住: OPTIMIZE TABLE 是最后手段, ALTER TABLE 在线重建才是日常运维的正确姿势。

5. PHP 部署:从 php.ini 核心参数调优到 thinkphp3.2.3 兼容性补丁

Ubuntu 20.04 的 php7.4-fpm 包默认配置非常保守, memory_limit=128M max_execution_time=30 post_max_size=8M ,这对现代 PHP 应用(尤其是框架)远远不够。更麻烦的是, thinkphp3.2.3 这类老框架依赖 magic_quotes_gpc (已废弃)和 register_globals (极度危险),我们必须用 php.ini disable_functions open_basedir 做精准围堵。

5.1 php.ini 关键参数调优:平衡性能与安全

编辑主配置文件:

sudo nano /etc/php/7.4/fpm/php.ini

重点修改以下参数(按重要性排序):

参数 原值 推荐值 原因
memory_limit 128M 256M ThinkPHP 加载全部类库后常超 128M,256M 是安全底线
max_execution_time 30 120 防止大文件上传或复杂查询超时,120 秒足够处理绝大多数请求
post_max_size 8M 64M 支持大附件上传, upload_max_filesize 必须 ≤ 此值
upload_max_filesize 2M 64M post_max_size 匹配,ThinkPHP 的文件上传组件常需此容量
opcache.enable Off On 必须开启 !OPcache 将 PHP 字节码缓存到共享内存,提升 300%+ 性能
opcache.memory_consumption 128 256 OPcache 内存池,256MB 足够缓存数千个文件

改完后重启:

sudo systemctl restart php7.4-fpm

5.2 thinkphp3.2.3 兼容性补丁:绕过已废弃的 magic_quotes_gpc

thinkphp3.2.3 的入口文件 index.php 里有一段检测:

if(!get_magic_quotes_gpc()) {
    $_GET = addslashes_deep($_GET);
    $_POST = addslashes_deep($_POST);
    $_COOKIE = addslashes_deep($_COOKIE);
}

但 PHP 5.4+ 已彻底移除 magic_quotes_gpc get_magic_quotes_gpc() 永远返回 false ,导致这段代码永远执行 addslashes_deep ,造成 SQL 注入风险(双转义)和 JSON 解析失败。

绝对不要 去改框架源码(违反升级原则)。正确做法是在 php.ini 中注入一段启动脚本:

auto_prepend_file = /etc/php/7.4/fpm/thinkphp323_fix.php

创建该文件:

sudo nano /etc/php/7.4/fpm/thinkphp323_fix.php

内容为:

<?php
// 模拟 magic_quotes_gpc = On 的行为,仅对 thinkphp3.2.3 生效
if (isset($_SERVER['SCRIPT_FILENAME']) && strpos($_SERVER['SCRIPT_FILENAME'], 'thinkphp') !== false) {
    if (!function_exists('addslashes_deep')) {
        function addslashes_deep($value) {
            return is_array($value) ? array_map('addslashes_deep', $value) : addslashes($value);
        }
    }
    // 强制设置 $_GET/$_POST 为已转义状态,欺骗框架
    $_GET = $_GET;
    $_POST = $_POST;
    $_COOKIE = $_COOKIE;
}

这个补丁的精妙之处在于:它不修改任何全局变量,只是“声明”了框架所需的函数,并让框架的 if(!get_magic_quotes_gpc()) 判断逻辑自然跳过。重启 PHP-FPM 后, thinkphp3.2.3 就能零修改运行。

5.3 安全加固: disable_functions open_basedir 的黄金组合

php.ini 中的 disable_functions 是最后一道防线。在 ;disable_functions = 行后添加:

disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source

这禁用了所有可能执行系统命令或读取敏感文件的函数。但要注意: file_get_contents fopen 不能禁,否则框架无法加载配置。

open_basedir 则限制 PHP 脚本能访问的目录:

open_basedir = /var/www/html:/tmp:/var/log/php

/var/www/html 是网站根目录, /tmp 是临时文件目录(PHP 上传文件暂存于此), /var/log/php 是自定义日志目录。这样即使黑客通过 include 漏洞拿到 WebShell,也无法读取 /etc/passwd /home/user/.ssh/id_rsa

经验之谈:我曾在一个客户项目中, open_basedir 忘了加 /tmp ,结果 thinkphp 的缓存文件( Runtime/Cache/ )写入失败,页面空白。查日志发现 file_put_contents(): open_basedir restriction in effect 。所以 /tmp 必须加入,这是 PHP 运行时的刚需目录。

6. 全链路验证与故障排查:从 curl journalctl 的闭环诊断

部署完成不等于可用。真正的考验是:当用户访问 http://your-domain.com/index.php 时,整个请求链路(Nginx → PHP-FPM → MySQL)是否畅通?下面是一套我用十年验证过的、从外到内的五层诊断法。

6.1 第一层:网络层验证( curl ss

在服务器本地执行:

curl -v http://127.0.0.1/index.php

观察响应头:

  • HTTP/1.1 200 OK :Nginx 正常接收并转发
  • X-Powered-By: PHP/7.4.33 :PHP-FPM 成功处理
  • Content-Type: text/html; charset=UTF-8 :字符集正确

如果返回 502 Bad Gateway ,说明 Nginx 无法连接 PHP-FPM。此时立刻查:

sudo ss -tlnp | grep ':9000'  # 看 PHP-FPM 是否监听
sudo ls -l /run/php/php7.4-fpm.sock  # 看 socket 文件是否存在且权限正确(应为 rw-rw----,属组 www-data)

6.2 第二层:PHP 层验证( phpinfo() error_log

创建 /var/www/html/info.php

<?php phpinfo(); ?>

访问 http://127.0.0.1/info.php ,重点检查:

  • Loaded Configuration File : /etc/php/7.4/fpm/php.ini
  • Scan this dir for additional .ini files : /etc/php/7.4/fpm/conf.d/
  • Registered PHP Streams : https, ftps (证明 OpenSSL 已启用)
  • mysqlnd 部分: client version => mysqlnd 7.4.33 (证明 MySQL 驱动已加载)

如果页面空白,查看 PHP 错误日志:

sudo tail -f /var/log/php7.4-fpm.log

常见错误如 Failed to load config file ,说明 php.ini 路径写错; Unable to load dynamic library ,说明扩展 .so 文件路径不对。

6.3 第三层:MySQL 层验证( mysql -h tcpdump

在 PHP 脚本中写一个最简连接测试:

<?php
$host = '127.0.0.1';
$user = 'root';
$pass = 'your_password';
$mysqli = new mysqli($host, $user, $pass);
if ($mysqli->connect_error) {
    die('Connect Error: ' . $mysqli->connect_error);
}
echo "Connected successfully";
?>

如果报错 Connection refused ,说明 MySQL 未监听 127.0.0.1 。检查 /etc/mysql/mysql.conf.d/mysqld.cnf

bind-address = 127.0.0.1
# 不要改成 0.0.0.0,除非你真需要远程访问

如果仍失败,用 tcpdump 抓包:

sudo tcpdump -i lo port 3306 -nn -A

然后执行 php test.php ,看抓包输出中是否有 00000000 0a38 2e30 2e32 382d 3075 6275 6e74 7500 (MySQL 协议握手包)。没有则证明连接根本没发出,问题在 PHP 或网络配置。

6.4 第四层:日志关联分析( journalctl 跨服务追踪)

Ubuntu 20.04 的 systemd 日志是故障定位神器。当一个请求失败时,往往涉及多个服务。用 journalctl 关联查询:

# 查看最近 100 行 Nginx 错误
sudo journalctl -u nginx -n 100 -f

# 查看 PHP-FPM 的所有错误(含子进程崩溃)
sudo journalctl -u php7.4-fpm -n 100 --since "2023-01-01"

# 关键:跨服务关联同一时间戳的错误
sudo journalctl --since "2023-01-01 10:00:00" --until "2023-01-01 10:05:00" | grep -E "(nginx|php|mysql)"

例如,你看到:

Jan 01 10:02:33 server nginx[1234]: *1 connect() to unix:/run/php/php7.4-fpm.sock failed (111: Connection refused)
Jan 01 10:02:33 server systemd[1]: php7.4-fpm.service: Main process exited, code=exited, status=78/CONFIG

这说明 PHP-FPM 因配置错误崩溃,Nginx 连不上。此时直接查 sudo journalctl -u php7.4-fpm -n 50 ,就能看到具体的配置语法错误行。

6.5 第五层:性能压测( ab mysqltuner

最后一步,验证稳定性。用 Apache Bench 压测:

ab -n 1000 -c 100 http://127.0.0.1/index.php

关注 Requests per second (应 > 200)和 Time per request (应 < 500ms)。如果 Failed requests > 0,说明并发下资源不足。

再用 mysqltuner 检查 MySQL 配置:

wget https://raw.githubusercontent.com/major/MySQLTuner-perl/master/mysqltuner.pl
perl mysqltuner.pl --user root --pass your_password

它会给出如 innodb_buffer_pool_size 是否过小、 query_cache_size 是否应关闭等专业建议。Ubuntu 20.04 的 MySQL 8.0+ 已移除 Query Cache,所以看到 Query cache is disabled 是正常的,不必理会。

我的压测经验:在 2 核 4GB 的 VPS 上,LEMP 全链路(Nginx+PHP7.4+MySQL8.0)稳定支撑 300 QPS。如果低于 100 QPS,90% 的原因是 innodb_buffer_pool_size 没调大(默认 128MB,应设为物理内存的 70%,即 2.8GB)。这个参数不重启 MySQL 就能改: SET GLOBAL innodb_buffer_pool_size=2800000000; ,但要写入配置文件永久生效。

7. 后续演进:从 LEMP 到容器化与自动化部署的平滑过渡

部署完 LEMP,你可能会想:“既然现在这么稳,为什么还要学 Docker?” 这不是技术路线之争,而是工程阶段的自然演进。就像盖房子,地基(LEMP 手动部署)必须扎实,但装修(应用部署)可以用更高效的工具。

7.1 为什么现在就该了解 docker-compose build 模式

很多教程教 docker run -d -p 80:80 nginx ,这是玩具。生产级的 Docker 是用 Dockerfile 构建自定义镜像。例如,为 thinkphp3.2.3 写一个 Dockerfile

FROM ubuntu:20.04
RUN apt update && apt install -y nginx-full php7.4-fpm mysql-client && rm -rf /var/lib/apt/lists/*
COPY nginx.conf /etc/nginx/sites-available/default
COPY php.ini /etc/php/7.4/fpm/php.ini
COPY app/ /var/www/html/
CMD ["sh", "-c", "service nginx start && service php7.4-fpm start && tail -f /var/log/nginx/access.log"]

这个镜像复用了你手动部署时验证过的所有配置( nginx.conf , php.ini ),只是把 apt install systemctl start 封装进了镜像。它的好处是: 一次构建,到处运行 。你可以在本地 MacBook 上 docker build ,然后推送到公司私有 Registry,运维同事 docker pull 就能部署,完全规避“在我机器上是好的”这种扯皮。

7.2 Ansible 自动化:把你的手动步骤变成可复现的 Playbook

你上面敲的每一条 apt install sed -i systemctl restart ,都可以写成 Ansible Playbook。例如 lemp.yml

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值