4.21 项目实战:搭建企业级 Web 应用——Nginx+PHP+MySQL+Redis+监控
预计阅读时间:17 分钟
📖 目录
学习目标
本项目整合第三编所学知识,完成以下目标:
- 部署 Nginx + PHP-FPM + MySQL + Redis 完整 Web 环境
- 配置 SSL/TLS 证书(Let's Encrypt),实现 HTTPS 安全访问
- 搭建 Prometheus + Grafana 监控体系
- 配置 rsyslog + logrotate 日志收集与轮转
- 编写自动化备份脚本,确保数据安全可恢复
前置知识
建议已掌握以下内容再开始本项目:
| 知识领域 | 具体技能 | 对应章节 |
|---|---|---|
| 服务管理 | systemd 服务管理、端口与防火墙基础 | 2.4:进程管理、5.5:防火墙实战 |
| 网络基础 | TCP/IP 基础、端口概念 | 4.5:网络故障排查 |
| Nginx | 安装、基本配置、虚拟主机 | 3.4:Nginx Web服务器 |
| MySQL | 安装、SQL 基础、用户权限 | 3.6:MySQL 数据库 |
| Redis | 安装、基本数据类型、持久化 | 3.10:Redis 缓存服务 |
| SSL/TLS | 证书概念、HTTPS 工作原理 | 3.11:SSL/TLS 证书管理 |
| Shell 脚本 | Bash 基础编程、变量、循环 | 2.1:Shell 脚本入门、2.8:Shell 脚本进阶 |
项目背景与需求分析
假设你是一家初创公司的运维工程师,需在单台服务器上部署内部管理系统(OA/CRM),要求:HTTPS 安全访问、数据库+缓存、监控告警、日志可追溯、数据自动备份。
| 组件 | 选型 | 理由 |
|---|---|---|
| Web 服务器 | Nginx | 高性能反向代理,支持 SSL 终止 |
| 应用运行时 | PHP 8.2 + PHP-FPM | 成熟的 Web 开发语言,FPM 提供进程管理 |
| 关系数据库 | MySQL 8.0 | ACID 事务,InnoDB 引擎成熟稳定 |
| 缓存 | Redis 7 | 内存数据库,毫秒级响应 |
| SSL 证书 | Let's Encrypt | 免费、自动续期、浏览器信任 |
| 监控 | Prometheus + Grafana | 云原生监控标准,可视化强大 |
| 日志 | rsyslog + logrotate | 系统原生工具,无需额外依赖 |
技术架构
反向代理(Reverse Proxy):客户端请求先到达 Nginx,由 Nginx 转发给后端应用服务器。反向代理可以隐藏后端服务器真实 IP、实现负载均衡和 SSL 终止。SSL 终止(SSL Termination):在 Nginx 上解密 HTTPS 流量,后端通信使用明文 HTTP,减轻后端负担。
用户浏览器 → Nginx (反向代理+SSL 终止)
│
┌────────┼────────┐
▼ ▼ ▼
PHP-FPM MySQL Redis
(应用逻辑) (持久存储) (缓存层)
Prometheus + Grafana (监控)
rsyslog + logrotate (日志)
环境准备
本项目基于 Ubuntu 24.04 LTS,推荐 4 核 8GB 内存配置。确保有 root 或 sudo 权限、公网 IP 和已解析的域名。
SSL/TLS(Secure Sockets Layer / Transport Layer Security):用于加密客户端与服务器之间通信的安全协议,HTTPS 就是 HTTP over TLS。PHP-FPM(PHP FastCGI Process Manager):PHP 的进程管理器,以 FastCGI 协议处理 PHP 请求,比传统的 mod_php 更高效、更稳定。OPcache:PHP 内置的字节码缓存器,将 PHP 脚本编译后的字节码缓存到共享内存中,避免重复编译,可提升 50%+ 的执行性能。
| 项目 | 配置 |
|---|---|
| 操作系统 | Ubuntu 24.04 LTS |
| 最低配置 | 2 核 CPU / 4GB 内存 / 40GB 磁盘 |
| 用户权限 | root 或具有 sudo 权限的用户 |
# 端口规划
# 22:SSH 80:HTTP→HTTPS 443:HTTPS 3306:MySQL 6379:Redis
# 9090:Prometheus 3000:Grafana 9100:Node 9104:MySQL 9121:Redis 9113:Nginx
# 防火墙配置
ufw allow 22/tcp # SSH
ufw allow 80/tcp # HTTP(重定向到 HTTPS)
ufw allow 443/tcp # HTTPS
ufw allow 3000/tcp # Grafana 面板
ufw enable
ufw status verbose # 验证规则
第一步:安装 MySQL
# 安装 MySQL
sudo apt update
sudo apt install -y mysql-server mysql-client
# 安全加固
sudo mysql_secure_installation
# 选择 MEDIUM 密码策略 → 设置 root 密码 → 删除匿名用户 → 禁止 root 远程登录 → 删除测试库
1.1 配置 MySQL
# /etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld]
bind-address = 127.0.0.1 # 仅本机监听
character-set-server = utf8mb4
innodb_buffer_pool_size = 1G # 根据内存调整
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 2
log_bin = /var/log/mysql/mysql-bin.log # 二进制日志
expire_logs_days = 14
sudo systemctl restart mysql
1.2 创建数据库和用户
sudo mysql -u root -p
-- 创建应用数据库
CREATE DATABASE webapp CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 创建专用用户(最小权限原则)
CREATE USER 'webapp_user'@'localhost' IDENTIFIED BY 'YourStr0ng!Passw0rd';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX ON webapp.* TO 'webapp_user'@'localhost';
FLUSH PRIVILEGES;
-- 验证连接
-- mysql -u webapp_user -p webapp -e "SELECT '连接成功' AS status;"
第二步:安装 Redis
sudo apt install -y redis-server
2.1 配置 Redis
# /etc/redis/redis.conf
bind 127.0.0.1 ::1 # 仅本机
protected-mode yes
requirepass YourRedisStr0ngPass! # 必须设置密码
# 持久化:RDB + AOF
save 900 1
save 300 10
save 60 10000
appendonly yes
appendfsync everysec
# 危险命令重命名(可选)
rename-command FLUSHALL ""
rename-command CONFIG "CONFIG_a1b2c3d4"
sudo systemctl restart redis-server
# 验证
redis-cli -a YourRedisStr0ngPass! ping # → PONG
第三步:安装 PHP-FPM
# 安装 PHP 及扩展
sudo apt install -y php8.2-fpm php8.2-mysql php8.2-redis \
php8.2-curl php8.2-gd php8.2-mbstring php8.2-xml php8.2-opcache
3.1 配置 PHP-FPM
# /etc/php/8.2/fpm/php.ini 关键配置
memory_limit = 256M
max_execution_time = 60
upload_max_filesize = 50M
display_errors = Off
log_errors = On
opcache.enable = 1
opcache.memory_consumption = 128
date.timezone = Asia/Shanghai
# /etc/php/8.2/fpm/pool.d/www.conf 进程池
listen = /run/php/php8.2-fpm.sock # Unix Socket(比 TCP 更高效)
pm = dynamic
pm.max_children = 50 # 根据内存调整
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 500 # 防止内存泄漏
slowlog = /var/log/php8.2-fpm/www-slow.log
request_slowlog_timeout = 5s
# 启用 Nginx stub_status(监控用)
# 在 server 块中添加:
# location /nginx_status { stub_status on; allow 127.0.0.1; deny all; }
# 启动
sudo php-fpm8.2 -t # 检查配置
sudo systemctl restart php8.2-fpm
ps aux | grep php-fpm # 验证进程
# 预期输出:看到多个 php-fpm: pool www 进程
第四步:安装 Nginx
sudo apt install -y nginx
4.1 主配置优化
# /etc/nginx/nginx.conf
user www-data;
worker_processes auto;
events { worker_connections 1024; use epoll; }
http {
include /etc/nginx/mime.types;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
client_max_body_size 64m;
# Gzip 压缩
gzip on;
gzip_types text/plain text/css text/javascript application/json application/javascript image/svg+xml;
gzip_min_length 1000;
# 安全头
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
# SSL 全局配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
include /etc/nginx/sites-enabled/*;
}
4.2 虚拟主机配置
# /etc/nginx/sites-available/webapp
# HTTP → HTTPS 重定向
server {
listen 80;
server_name nb.iohow.com www.nb.iohow.com;
location /.well-known/acme-challenge/ { root /var/www/html; }
location / { return 301 https://$host$request_uri; }
}
# HTTPS 服务器
server {
listen 443 ssl http2;
server_name nb.iohow.com www.nb.iohow.com;
ssl_certificate /etc/letsencrypt/live/nb.iohow.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/nb.iohow.com/privkey.pem;
root /var/www/webapp/public;
index index.php index.html;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# PHP 转发给 PHP-FPM
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
# 禁止访问隐藏文件和敏感文件
location ~ /\. { deny all; }
location ~* \.(env|log|ini|conf)$ { deny all; }
# 静态文件缓存 30 天
location ~* \.(jpg|jpeg|png|gif|css|js|svg|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
# Nginx 状态页(仅本机)
location /nginx_status { stub_status on; allow 127.0.0.1; deny all; }
}
# 启用站点
sudo ln -sf /etc/nginx/sites-available/webapp /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl restart nginx
# 预期输出:nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful
第五步:配置 Let's Encrypt
# 安装 Certbot
sudo apt install -y certbot python3-certbot-nginx
# 申请证书(确保域名 DNS 已解析到服务器 IP)
sudo certbot --nginx -d nb.iohow.com -d www.nb.iohow.com \
--email admin@nb.iohow.com --agree-tos --no-eff-email
# 验证证书
sudo certbot certificates
# 测试自动续期
sudo certbot renew --dry-run
# 自动续期由 cron 自动配置,每天尝试两次
第六步:部署示例应用
6.1 应用配置
# /var/www/webapp/config/app.php
<?php
return [
'database' => [
'host' => '127.0.0.1',
'dbname' => 'webapp',
'username' => 'webapp_user',
'password' => 'YourStr0ng!Passw0rd',
],
'redis' => [
'host' => '127.0.0.1',
'port' => 6379,
'password' => 'YourRedisStr0ngPass!',
],
];
?>
6.2 PHP 应用入口
# /var/www/webapp/public/index.php
<?php
date_default_timezone_set('Asia/Shanghai');
$config = require __DIR__ . '/../config/app.php';
$page = ltrim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), '/') ?: 'home';
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>企业级 Web 应用</title>
<style>
body { font-family: sans-serif; background: #f5f7fa; margin: 0; }
header { background: linear-gradient(135deg, #1890ff, #096dd9); color: #fff; padding: 30px; text-align: center; }
.container { max-width: 960px; margin: 0 auto; padding: 20px; }
.nav { background: #fff; padding: 15px; text-align: center; border-radius: 8px; margin: 20px 0; }
.nav a { margin: 0 15px; color: #1890ff; text-decoration: none; }
.card { background: #fff; border-radius: 8px; padding: 25px; margin: 20px 0; box-shadow: 0 1px 3px rgba(0,0,0,.1); }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 10px; text-align: left; border-bottom: 1px solid #f0f0f0; }
.ok { color: #52c41a; font-weight: bold; }
</style>
</head>
<body>
<header><h1>企业级 Web 应用</h1><p>Nginx + PHP-FPM + MySQL + Redis</p></header>
<div class="container">
<nav class="nav">
<a href="/">首页</a>
<a href="/status">服务状态</a>
<a href="/database">数据库</a>
<a href="/cache">缓存</a>
</nav>
<?php if ($page === 'home'): ?>
<div class="card">
<h2>欢迎使用</h2>
<p>本项目整合了企业级 Web 环境所需的所有核心组件。</p>
</div>
<?php elseif ($page === 'status'): ?>
<div class="card">
<h2>服务状态</h2>
<table>
<tr><th>服务</th><th>状态</th></tr>
<tr><td>MySQL</td><td><?php try { $pdo = new PDO('mysql:host=127.0.0.1','webapp_user','YourStr0ng!Passw0rd'); echo '<span class="ok">运行中</span> (' . $pdo->query("SELECT VERSION()")->fetchColumn() . ')'; } catch(Exception $e) { echo '异常'; } ?></td></tr>
<tr><td>Redis</td><td><?php try { $r = new Redis(); $r->connect('127.0.0.1',6379); $r->auth('YourRedisStr0ngPass!'); echo '<span class="ok">运行中</span>'; } catch(Exception $e) { echo '异常'; } ?></td></tr>
<tr><td>PHP</td><td><span class="ok">运行中</span> (<?= phpversion() ?>)</td></tr>
</table>
</div>
<?php elseif ($page === 'database'): ?>
<div class="card">
<h2>数据库操作</h2>
<?php
$msg = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$pdo = new PDO('mysql:host=127.0.0.1;dbname=webapp','webapp_user','YourStr0ng!Passw0rd');
$act = $_POST['action'] ?? '';
if ($act === 'create') {
$pdo->exec("CREATE TABLE IF NOT EXISTS users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100), email VARCHAR(255) UNIQUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)");
$msg = 'users 表创建成功';
} elseif ($act === 'insert' && !empty($_POST['name'])) {
$st = $pdo->prepare("INSERT IGNORE INTO users (name,email) VALUES (?,?)");
$st->execute([$_POST['name'], $_POST['email']]);
$msg = '数据插入成功';
}
}
if ($msg) echo "<p class='ok'>$msg</p>";
?>
<form method="post" style="margin-bottom:15px;">
<button name="action" value="create">创建 users 表</button>
</form>
<form method="post">
<input type="hidden" name="action" value="insert">
<input name="name" placeholder="姓名" required>
<input name="email" placeholder="邮箱" required>
<button type="submit">插入</button>
</form>
</div>
<?php elseif ($page === 'cache'): ?>
<div class="card">
<h2>Redis 缓存测试</h2>
<?php
$r = new Redis(); $r->connect('127.0.0.1',6379); $r->auth('YourRedisStr0ngPass!');
$msg = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (($_POST['action'] ?? '') === 'set') {
$r->setex($_POST['key'], (int)($_POST['ttl'] ?? 300), $_POST['value']);
$msg = "已设置: {$_POST['key']} = {$_POST['value']}";
} elseif (($_POST['action'] ?? '') === 'get') {
$v = $r->get($_POST['key'] ?? '');
$msg = $v !== false ? "命中: {$_POST['key']} = $v (TTL:{$r->ttl($_POST['key'])}s)" : "未命中";
}
}
if ($msg) echo "<p class='ok'>$msg</p>";
?>
<form method="post">
<input type="hidden" name="action" value="set">
<input name="key" placeholder="Key" required>
<input name="value" placeholder="Value" required>
<input name="ttl" value="300" style="width:60px">秒
<button>设置</button>
</form>
<form method="post" style="margin-top:10px;">
<input type="hidden" name="action" value="get">
<input name="key" placeholder="Key" required>
<button>读取</button>
</form>
</div>
<?php else: ?>
<div class="card"><h2>404</h2></div>
<?php endif; ?>
</div>
</body></html>
第七步:配置监控
7.1 安装 Prometheus + Exporters
# Prometheus
sudo useradd --no-create-home --shell /bin/false prometheus
PROM_VER="2.48.0"
cd /tmp && wget https://github.com/prometheus/prometheus/releases/download/v${PROM_VER}/prometheus-${PROM_VER}.linux-amd64.tar.gz
tar xzf prometheus-${PROM_VER}.linux-amd64.tar.gz
sudo cp prometheus-${PROM_VER}.linux-amd64/prometheus /usr/local/bin/
sudo mkdir -p /etc/prometheus /var/lib/prometheus
sudo chown prometheus:prometheus /var/lib/prometheus
# Node Exporter(系统指标)
NODE_VER="1.9.0"
wget -q https://github.com/prometheus/node_exporter/releases/download/v${NODE_VER}/node_exporter-${NODE_VER}.linux-amd64.tar.gz
tar xzf node_exporter-${NODE_VER}.linux-amd64.tar.gz
sudo cp node_exporter-${NODE_VER}.linux-amd64/node_exporter /usr/local/bin/
# MySQL Exporter
sudo mysql -u root -p -e "CREATE USER 'exporter'@'localhost' IDENTIFIED BY 'ExporterPass'; GRANT PROCESS,REPLICATION CLIENT ON *.* TO 'exporter'@'localhost'; FLUSH PRIVILEGES;"
echo -e "[client]\nuser=exporter\npassword=ExporterPass" | sudo tee /etc/.mysqld_exporter.cnf
sudo chmod 600 /etc/.mysqld_exporter.cnf
MYSQL_EXP_VER="0.15.1"
wget -q https://github.com/prometheus/mysqld_exporter/releases/download/v${MYSQL_EXP_VER}/mysqld_exporter-${MYSQL_EXP_VER}.linux-amd64.tar.gz
tar xzf mysqld_exporter-${MYSQL_EXP_VER}.linux-amd64.tar.gz
sudo cp mysqld_exporter-${MYSQL_EXP_VER}.linux-amd64/mysqld_exporter /usr/local/bin/
# Redis Exporter
REDIS_EXP_VER="1.55.0"
wget -q https://github.com/oliver006/redis_exporter/releases/download/v${REDIS_EXP_VER}/redis_exporter-v${REDIS_EXP_VER}.linux-amd64.tar.gz
tar xzf redis_exporter-v${REDIS_EXP_VER}.linux-amd64.tar.gz
sudo cp redis_exporter-v${REDIS_EXP_VER}.linux-amd64/redis_exporter /usr/local/bin/
# 清理
rm -rf /tmp/prometheus-* /tmp/node_exporter-* /tmp/mysqld_exporter-* /tmp/redis_exporter-*
7.2 Prometheus 配置与服务
# /etc/prometheus/prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: "prometheus"
static_configs: [{ targets: ["localhost:9090"] }]
- job_name: "node"
static_configs: [{ targets: ["localhost:9100"] }]
- job_name: "mysql"
static_configs: [{ targets: ["localhost:9104"] }]
- job_name: "redis"
static_configs: [{ targets: ["localhost:9121"] }]
- job_name: "nginx"
static_configs: [{ targets: ["localhost:9113"] }]
# 创建 systemd 服务(以 node_exporter 为例,其他类似)
for svc in node_exporter mysqld_exporter redis_exporter; do
cat > /etc/systemd/system/${svc}.service << EOF
[Unit]
Description=${svc}
After=network.target
[Service]
User=prometheus
ExecStart=/usr/local/bin/${svc}
Restart=always
[Install]
WantedBy=multi-user.target
EOF
done
sudo systemctl daemon-reload
sudo systemctl enable --now prometheus node_exporter mysqld_exporter redis_exporter
# 验证:curl http://localhost:9090/-/healthy
7.3 安装 Grafana
wget -q -O - https://apt.grafana.com/gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/grafana.gpg
echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" | sudo tee /etc/apt/sources.list.d/grafana.list
sudo apt update && sudo apt install -y grafana
sudo systemctl enable --now grafana-server
# 默认访问: http://服务器IP:3000 用户名/密码: admin/admin
# 首次登录后请立即修改密码
7.4 添加 Prometheus 数据源
# 通过 API 自动配置 Prometheus 为 Grafana 默认数据源
curl -s -X POST http://localhost:3000/api/datasources \
-H "Content-Type: application/json" \
-u admin:admin \
-d '{
"name": "Prometheus",
"type": "prometheus",
"url": "http://localhost:9090",
"access": "proxy",
"isDefault": true
}'
# 预期返回: {"status":"success","message":"Datasource Prometheus added"}
echo "数据源配置完成"
第八步:配置日志
# rsyslog 分类日志
sudo tee /etc/rsyslog.d/50-webapp.conf << 'EOF'
:programname, isequal, "nginx" /var/log/webapp/nginx.log
:programname, isequal, "mysqld" /var/log/webapp/mysql.log
:programname, isequal, "php-fpm" /var/log/webapp/php-fpm.log
& stop
EOF
sudo mkdir -p /var/log/webapp
sudo systemctl restart rsyslog
# logrotate 轮转
sudo tee /etc/logrotate.d/webapp << 'EOF'
/var/log/webapp/*.log /var/log/nginx/webapp-*.log {
daily
rotate 30
compress
delaycompress
notifempty
create 0640 www-data adm
sharedscripts
postrotate
[ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid) 2>/dev/null || true
endscript
}
EOF
# 测试
sudo logrotate -d /etc/logrotate.d/webapp # 干跑检查
sudo logrotate -f /etc/logrotate.d/webapp # 强制执行一次
第九步:配置备份
sudo mkdir -p /opt/backup/db /var/log/backup
sudo tee /opt/backup/backup.sh << 'BASHEOF'
#!/bin/bash
set -euo pipefail
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/opt/backup/db/${DATE}"
RETENTION=30
MYSQL_PASS="YourStr0ng!Passw0rd"
REDIS_PASS="YourRedisStr0ngPass!"
mkdir -p "${BACKUP_DIR}"
echo "[$(date)] 开始备份..."
# MySQL 完整备份
mysqldump -u root -p"${MYSQL_PASS}" --single-transaction --all-databases | gzip > "${BACKUP_DIR}/mysql.sql.gz"
# 配置文件备份
tar czf "${BACKUP_DIR}/config.tar.gz" -C / etc/nginx etc/mysql etc/redis etc/php/8.2/fpm var/www/webapp/config 2>/dev/null || true
# Redis RDB
redis-cli -a "${REDIS_PASS}" --rdb "${BACKUP_DIR}/redis.rdb" 2>/dev/null || true
# 校验和
cd "${BACKUP_DIR}" && md5sum * > checksums.md5
# 清理旧备份
find /opt/backup/db -maxdepth 1 -type d -mtime +${RETENTION} -exec rm -rf {} \; 2>/dev/null || true
echo "[$(date)] 备份完成: $(du -sh ${BACKUP_DIR} | cut -f1)"
BASHEOF
sudo chmod +x /opt/backup/backup.sh
# 定时任务
sudo crontab -e
# 添加: 0 2 * * * /opt/backup/backup.sh >> /var/log/backup/cron.log 2>&1
# 手动测试
sudo /opt/backup/backup.sh
ls -la /opt/backup/db/ # 验证备份文件
测试验证
echo "===== 服务状态 ====="
for svc in nginx php8.2-fpm mysql redis-server prometheus grafana-server; do
echo "[$(sudo systemctl is-active $svc)] $svc"
done
echo "===== 端口检查 ====="
ss -tlnp | grep -E ':(80|443|3306|6379|9090|3000) '
echo "===== SSL 检查 ====="
curl -sk -o /dev/null -w "HTTPS: %{http_code}\n" https://nb.iohow.com/
echo "===== 数据库连接 ====="
mysql -u webapp_user webapp -e "SELECT 'OK' AS status;" 2>/dev/null
echo "===== Redis 连接 ====="
redis-cli -a YourRedisStr0ngPass! ping 2>/dev/null
echo "===== 备份检查 ====="
LATEST=$(ls -t /opt/backup/db/ 2>/dev/null | head -1)
[ -n "$LATEST" ] && echo "最近备份: $LATEST" && ls -lh "/opt/backup/db/${LATEST}/" || echo "未找到备份"
echo "===== Prometheus 健康 ====="
curl -s http://localhost:9090/-/healthy
echo "===== Grafana 健康 ====="
curl -s http://localhost:3000/api/health | python3 -m json.tool 2>/dev/null | grep -o '"database":"[^"]*"'
常见错误
| 错误 | 原因 | 解决方案 |
|---|---|---|
| 502 Bad Gateway | PHP-FPM 未运行或 Socket 路径不匹配 | 检查 systemctl status php8.2-fpm,确认 fastcgi_pass 与 listen 路径一致 |
| Connection refused (MySQL) | MySQL 未启动或 bind-address 配置错误 | 检查 systemctl start mysql 和 bind-address |
| NOAUTH (Redis) | Redis 设置了密码但未认证 | 所有连接加 -a 参数或代码中调用 auth() |
| ERR_TOO_MANY_REDIRECTS | HTTP→HTTPS 重定向循环 | 确认 Nginx 不会对 HTTPS 请求再做重定向 |
| Certbot 限流 | 7 天内申请超 5 次 | 等 7 天后重试,或用 --staging 测试 |
| Permission denied (Nginx) | Nginx 用户无法访问文件 | chown -R www-data:www-data /var/www/webapp |
| Grafana 页面无法访问 | 服务未启动或端口未放行 | 检查 systemctl status grafana-server,确认安全组放行 3000 |
| Prometheus targets DOWN | Exporter 未启动 | 逐个检查 Exporter 服务,访问 http://localhost:9100/metrics |
| 备份文件为空 | MySQL 密码错误或 Redis 未运行 | 检查 /var/log/backup/cron.log 中的错误信息 |
最佳实践
| 类别 | 实践 | 说明 |
|---|---|---|
| 安全 | 最小权限原则 | 数据库用户只授必要权限,禁用 root 远程登录 |
| 安全 | 仅本机监听 | MySQL、Redis 只绑定 127.0.0.1 |
| 性能 | Unix Socket | Nginx↔PHP-FPM 用 Socket 比 TCP 更高效 |
| 性能 | OPcache + Gzip | PHP OPcache 减少 50%+ 加载时间,Gzip 压缩 60-80% 传输 |
| 运维 | 日志轮转 | logrotate 防止日志无限增长 |
| 运维 | 自动备份 | 每日备份 + 30 天保留 + 定期恢复测试 |
| 运维 | 监控告警 | Prometheus 采集 + Grafana 可视化 |
| 运维 | SSL 自动续期 | certbot cron 确保证书不过期 |
练习题
- (基础)按照本项目步骤搭建完整环境,记录每步结果。
- (进阶)在 Grafana 中创建包含 MySQL 连接数、查询速率、慢查询统计的仪表盘。
- (进阶)编写 Shell 脚本自动检测所有服务状态,异常时发送告警。
- (高级)将部署过程改写为 Ansible Playbook,实现一键部署。
- (高级)配置 Prometheus Alertmanager,CPU >80% 或磁盘 <20% 时发送告警。
- (探究)研究 Nginx
upstream模块,画出多服务器负载均衡架构图。 - (探究)对比 Nginx 与 Apache 的工作模型差异,解释为什么 Nginx 在高并发场景下性能更好。
- (探究)研究 MySQL 主从复制原理,思考如何将本项目的单机数据库扩展为读写分离架构。
- (探究)研究 Docker 容器化部署方案,思考如何将本项目的各组件容器化管理。
恢复测试
备份的最终目的是能恢复。建议每月执行一次恢复演练:
# 在测试环境恢复数据库(注意:会覆盖现有数据)
LATEST=$(ls -t /opt/backup/db/ | head -1)
mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS restore_test;"
gunzip -c "/opt/backup/db/${LATEST}/mysql.sql.gz" | mysql -u root -p restore_test
echo "恢复完成,检查数据完整性"
mysql -u root -p -e "SHOW TABLES FROM restore_test;"
mysql -u root -p -e "DROP DATABASE restore_test;" # 清理测试数据
学习检查点
学完本章后,请检验自己是否掌握以下内容:
| 检查项 | 自测问题 | 验证方法 |
|---|---|---|
| 概念理解 | 能用自己的话解释企业级 Web 应用的完整技术栈架构 | 尝试向他人讲解 |
| 命令操作 | 能不查文档完成 MySQL、Redis、Nginx 的部署和配置 | 在终端实际执行 |
| 原理掌握 | 能说出 Web 应用的请求处理流程和各组件协作原理 | 画出流程图 |
| 故障排查 | 能独立排查 Web 应用 502/504 错误或数据库连接失败 | 模拟故障并修复 |
| 最佳实践 | 能说明为什么需要为每个组件配置独立的日志和监控 | 对比不同方案 |
本章总结
本项目从零搭建了企业级 Web 应用环境,覆盖了从数据库到监控的完整技术栈。以下是核心要点回顾:
| 组件 | 核心配置 | 关键文件 |
|---|---|---|
| MySQL | 仅本机、强密码、最小权限 | /etc/mysql/mysql.conf.d/mysqld.cnf |
| Redis | 密码、仅本机、RDB+AOF | /etc/redis/redis.conf |
| PHP-FPM | Unix Socket、OPcache | /etc/php/8.2/fpm/pool.d/www.conf |
| Nginx | 反向代理、SSL、Gzip | /etc/nginx/sites-available/webapp |
| SSL | Let's Encrypt、自动续期 | /etc/letsencrypt/live/ |
| 监控 | Prometheus + Grafana | /etc/prometheus/prometheus.yml |
| 日志 | rsyslog 分类 + logrotate | /etc/rsyslog.d/50-webapp.conf |
| 备份 | 每日备份 + 30 天保留 | /opt/backup/backup.sh |
掌握这套环境的搭建与运维,是进入 DevOps 领域的重要一步。实际生产中还需考虑高可用、灾备、CI/CD 等进阶话题。
延伸阅读
- 3.4:Nginx Web服务器 Nginx Web 服务器配置与管理——深入学习 Nginx 配置与优化
- 3.6:MySQL 数据库 MySQL/MariaDB 数据库安装与管理——安全、备份与性能调优
- 3.10:Redis 缓存服务 Redis 缓存服务安装与配置——数据类型与应用场景
- 3.11:SSL/TLS 证书管理 SSL/TLS 证书管理——HTTPS 原理与配置
- 3.12:系统监控与告警 Linux 系统监控与告警方案——Prometheus + Grafana 详解
- 3.13:备份策略与恢复 Linux 备份策略与灾难恢复方案——企业级备份方案
- Prometheus 官方文档 | Grafana 文档 | Let's Encrypt 文档
- 书籍推荐:《Linux 系统管理与网络管理》《Prometheus: Up & Running》《Web 性能权威指南》
- 在线资源:DigitalOcean Tutorials、Linux Handbook、Server World(日文)