Skip to content

实现站点弹性伸缩

项目说明
文档难度入门级
预计耗时60-90 分钟
任务目标部署可自动扩缩的 Grav 站点,实现高可用和负载均衡
使用环境AWS 管理控制台

前置条件:

  • 已有 AWS 账户并具备控制台登录权限
  • 已有 VPC,包含至少 2 个可用区的公有子网
  • 已有可用的安全组(允许 HTTP 80、SSH 22 入站)

%%{init: {"flowchart": {"defaultRenderer": "elk"}}}%%
flowchart TB
    subgraph Internet
        U[用户请求]
    end
    
    subgraph AWS["AWS Cloud"]
        subgraph VPC
            ALB[Application Load Balancer]
            
            subgraph AZ1["可用区 A"]
                EC2_1[EC2 实例<br/>Grav 核心代码]
                EFS_MT1[EFS 挂载点]
            end
            
            subgraph AZ2["可用区 B"]
                EC2_2[EC2 实例<br/>Grav 核心代码]
                EFS_MT2[EFS 挂载点]
            end
            
            EFS[(Amazon EFS<br/>/user 目录)]
            
            ASG[Auto Scaling Group]
        end
        
        CF[CloudFront CDN<br/>静态资源缓存]
    end
    
    U --> CF
    CF --> ALB
    ALB --> EC2_1
    ALB --> EC2_2
    EC2_1 --> EFS_MT1
    EC2_2 --> EFS_MT2
    EFS_MT1 --> EFS
    EFS_MT2 --> EFS
    ASG -.管理.-> EC2_1
    ASG -.管理.-> EC2_2

架构说明:

组件作用
CloudFront缓存静态资源,减少 EFS 读取,降低延迟
ALB接收请求,分发到健康实例,执行健康检查
Auto Scaling Group根据策略自动增加或终止实例
EC2 实例运行 PHP + Grav 核心代码(本地存储)
EFS仅存储 /user 目录(内容、配置、插件),跨实例共享

流量路径:

用户 → CloudFront → ALB → EC2 实例(本地 Grav 代码 + EFS /user 目录)

自动扩缩机制:

  • 扩展:负载升高或到达指定时间,ASG 启动新实例,新实例自动挂载 EFS、注册到 ALB
  • 缩减:负载下降或到达指定时间,ASG 先从 ALB 移除实例,等待连接排空后终止实例

在开始配置前,根据业务场景选择一种扩展策略:

策略适用场景示例
Target Tracking流量不可预测,需根据实时负载调整新闻站点、API 服务
Scheduled Scaling流量有固定规律工作日高峰、促销活动

最佳实践:如果不确定流量模式,选择 Target Tracking


Amazon EFS 用于存储 Grav 的 /user 目录,实现跨实例内容共享。

  1. 进入 EFS 控制台 → 点击 Create file system

[创建EFS-入口]

  1. 点击 Customize 进入详细配置

  2. 步骤 1 - 文件系统设置

    • Name: 输入名称(如 grav-user-data
    • Storage class: 选择 Standard
    • Automatic backups: 根据需要启用
    • Encryption: 建议启用
  3. 步骤 2 - 网络访问

    • VPC: 选择目标 VPC
    • Mount targets: 在每个可用区选择子网,选择允许 NFS(端口 2049)入站的安全组

创建EFS-网络配置

  1. 完成创建,记录 File system ID(如 fs-0123456789abcdef0

创建一台 EC2 实例,安装 PHP、Grav,配置 EFS 挂载,作为自定义 AMI 的基础。

  1. 进入 EC2 控制台 → 点击 Launch instances

  2. 配置实例:

    • Name: grav-base-instance
    • AMI: 选择 Amazon Linux 2023
    • Instance type: 选择 t3.small 或更高
    • Key pair: 选择已有密钥对
    • Network settings: 选择目标 VPC 和公有子网,启用公有 IP
    • Security groups: 选择允许 HTTP 80、SSH 22、NFS 2049 的安全组
    • 启动实例
  3. 启动实例并通过 SSH 连接


在基准实例上安装运行环境和 Grav CMS。

通过 SSH 连接实例后,执行以下操作:

关闭selinux

sudo sed -i 's/SELINUX=permissive/SELINUX=disabled/g' /etc/selinux/config
sudo setenforce 0

安装 PHP 和必要扩展:

进入实例终端,安装 PHP 8.x 及 Grav 所需的扩展(curl、gd、mbstring、xml、zip 等),安装 Apache 或 Nginx 作为 Web 服务器。

sudo yum update -y
sudo yum install -y php php-curl php-gd php-json php-mbstring php-xml php-zip php-apcu nginx php-fpm unzip
sudo systemctl disable --now httpd

安装 Grav:

Grav 官方下载页面 下载最新版本,解压到 Web 服务器根目录(如 /var/www/grav)。

# 解压并移动到正确的目录
wget https://github.com/getgrav/grav/releases/download/1.7.49.5/grav-admin-v1.7.49.5.zip
unzip grav-admin-v1.7.49.5.zip
sudo mv grav-admin /var/www/grav
# 配置权限
sudo chown -R nginx:nginx /var/www/grav
sudo chmod -R 775 /var/www/grav
sudo mkdir -p /var/lib/php/session
sudo chown -R nginx:nginx /var/lib/php/session
sudo chmod -R 733 /var/lib/php/session

配置 OPcache(关键性能优化):非必要

编辑 PHP 配置文件/etc/php/8.3/fpm/php.ini,启用并优化 OPcache:

配置项建议值说明
opcache.enable1启用 OPcache
opcache.memory_consumption256缓存内存大小(MB)
opcache.validate_timestamps0禁用时间戳验证,生产环境必须
opcache.revalidate_freq0配合上条使用

安装 APCu 用户缓存:非必要

安装 php-pecl-apcu 扩展,Grav 会自动检测并使用。上面的命令已经包含。

配置Nginx

添加grav.conf文件;创建/etc/nginx/conf.d/grav.conf文件

server {
listen 80;
server_name 服务器IP或域名;
root /var/www/grav;
index index.php index.html;
# 日志
access_log /var/log/nginx/grav-access.log;
error_log /var/log/nginx/grav-error.log;
# Grav 后台
location /admin {
try_files $uri $uri/ /index.php?$query_string;
}
# 主路由
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# PHP 处理
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.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;
}
}

修改/etc/php-fpm.d/www.conf

user = nginx
group = nginx

启动服务

sudo systemctl enable --now nginx
sudo systemctl enable --now php-fpm

验证安装:

通过浏览器访问实例公有 IP,确认 Grav 首页正常显示。注意要用http://

安装Grav-验证页面 安装完成


将 EFS 挂载为 Grav 的 /user 目录。

安装 EFS 挂载助手:

安装 amazon-efs-utils 软件包,该工具简化 EFS 挂载并支持加密传输。

sudo yum install amazon-efs-utils -y

参考:安装 Amazon EFS 客户端

迁移现有 /user 目录内容:

# 将/var/www/grav/user目录移动到/user
cd /var/www/grav
sudo mv user /tmp
# 挂载EFS到/var/www/grav/user
sudo mkdir user
sudo mount -t efs fs-08db381dd79fa11c8 user
# 拷回文件
sudo cp -r /tmp/user/* user/
# 重新授权
sudo chown -R nginx:nginx user
sudo chmod -R 775 user

多种挂载方式

配置永久挂载:

编辑 /etc/fstab,添加 EFS 挂载配置,确保实例重启后自动挂载。

挂载目标为 Grav 的 /user 目录路径(如 /var/www/grav/user)。

挂载选项建议:

永久挂载官方文档

选项说明
tls启用传输加密
_netdev标记为网络文件系统,确保网络就绪后再挂载
noresvport推荐选项,提高连接稳定性
nofail挂载失败继续启动系统
fs-08db381dd79fa11c8 /var/www/grav/user efs _netdev,noresvport,nofail 0 0

fstab

查看挂载

mount -l | grep grav
# 或者
df -TH

验证挂载:

测试大文件数据写入

# 写入 1GB 文件到 EFS
dd if=/dev/zero of=/var/www/grav/user/test.img bs=1M count=1024 status=progress
# 参数说明:
# if=/dev/zero - 输入源(零数据)
# of=/var/www/grav/user/test.img - 输出文件(EFS 挂载点)
# bs=1M - 块大小 1MB
# count=1024 - 写入 1024 个块 = 1GB
# status=progress - 显示进度

测试小文件写入

# 写入 100MB(快速测试)
dd if=/dev/zero of=/var/www/grav/user/test-small.img bs=1M count=100 status=progress

为了确保严谨,可以重启实例,确认 EFS 自动挂载,Grav 站点正常访问。

Section titled “为了确保严谨,可以重启实例,确认 EFS 自动挂载,Grav 站点正常访问。”

将配置好的实例保存为自定义 AMI,用于 Auto Scaling 启动新实例。

sudo cloud-init clean --logs --seed && \
sudo rm -rf /var/lib/cloud/instances/* && \
sudo truncate -s 0 /etc/machine-id && \
sudo shred -u /etc/ssh/*_key* && \
history -c && \
cat /dev/null > ~/.bash_history && \
sudo cat /dev/null > /root/.bash_history && \
sudo poweroff
  1. 进入 EC2 控制台 → 选择基准实例

  2. 点击 操作映像和模板创建映像

创建AMI-入口

  1. 配置镜像:

    • 映像名称: 输入名称(如 grav-ami-v1.0
    • 映像描述: 输入描述(如 Grav + PHP 8.x + EFS mount
    • 不重启: 不勾选(确保文件系统一致性)
  2. 点击 创建映像

  3. 等待 AMI 状态变为 Available(约 5-10 分钟)

创建AMI-状态确认


Target Group 定义后端实例和健康检查规则。

  1. 进入 EC2 控制台 → 左侧导航栏选择负载平衡 -> 目标组 → 点击 创建目标组

  2. 步骤 1 - 基本配置

    • 目标类型: 选择 实例
    • 目标组名称: 输入名称(如 grav-tg
    • 协议/端口: HTTP / 80
    • VPC: 选择目标 VPC
  3. 步骤 2 - 健康检查

    • 健康检查协议: HTTP
    • 健康检查路径: / 或 Grav 的健康检查端点

健康检查建议配置:

参数建议值说明
Healthy threshold2连续 2 次成功判定为健康
Unhealthy threshold3连续 3 次失败判定为不健康
Timeout5 秒响应超时时间
Interval30 秒检查间隔

创建Target Group-健康检查配置

  1. 点击 下一步,暂不注册目标(ASG 会自动注册),点击 创建目标组

Application Load Balancer 分发流量到后端实例。

  1. 进入 EC2 控制台 → 左侧导航栏选择 负载平衡 → 点击 负载均衡器

  2. 选择 Application Load Balancer → 点击 创建

[创建ALB-选择类型]

  1. 基本配置

    • 负载均衡器名称: 输入名称(如 grav-alb
    • 模式: 选择 面向互联网
    • IP address type: IPv4
  2. 网络映射

    • VPC: 选择目标 VPC
    • Mappings: 选择至少 2 个可用区及其公有子网

创建ALB-网络配置

  1. 安全组:选择允许 HTTP 80(和 HTTPS 443,如需要)入站的安全组

  2. 监听器和路由

    • 协议 / 端口: HTTP / 80
    • 默认操作: 转发到目标组 → 选择上一步创建的目标组

设置目标组

  1. 点击 创建负载均衡器

  2. 等待 ALB 状态变为 Active,记录 DNS name

创建ALB-DNS记录

将此DNS记录作为域名解析的cname值。


启动模板 定义 Auto Scaling 启动新实例的配置。

  1. 进入 EC2 控制台 → 左侧导航栏选择实例 -> 启动模板 → 点击 创建启动模板

[创建启动模板-入口]

  1. 基本信息

    • 启动模板名称: 输入名称(如 grav-launch-template
    • 模板版本说明: v1.0 - Initial version
  2. AMI:选择步骤 5 创建的自定义 AMI

AMI启动模板

  1. 实例类型: 选择 t3.small 或根据负载选择

  2. 密钥对: 选择已有密钥对

  3. 网络设置:不选择(将自动在之前选择的多个可用去中随机创建)

    • 安全组: 选择允许 HTTP、SSH、NFS 的安全组
    • 高级网络设置:自动分配共有IP:禁用
  4. 高级详细信息:(可选)

    1. IAM实例配置文件:如需 CloudWatch 监控或 Systems Manager,选择相应角色
      1. CloudWatchAgentServerPolicy:CloudWatch代理所需的权限策略
      2. AmazonSSMManagedInstanceCore:SSM代理所需的权限策略
  5. 点击 创建启动模板


Auto Scaling Group 管理实例集合,实现自动扩缩。

  1. 进入 EC2 控制台 → 左侧导航栏选择 Auto Scaling Groups → 点击 Create Auto Scaling group

  2. 步骤 1 - 选择启动模板

    • 弹性伸缩组名称: 输入名称(如 grav-asg
    • 启动模板: 选择上一步创建的启动模板

启动模板

  1. 步骤 2 - 选择实例启动选项
    • VPC: 选择目标 VPC
    • 可用区分布: 选择至少 2 个可用区的子网

创建ASG-网络配置

  1. 步骤 3 - 配置高级选项
    • 负载均衡: 选择 附加到现有负载均衡器
    • 选择负载均衡的目标组: 选择之前创建的目标组
    • 应用程序恢复控制器(ARC)可用区转移:可选
    • 健康检查类型: 选择 弹性负载均衡(推荐)
    • 健康检查周期: 300 秒

创建ASG-负载均衡配置

  1. 步骤 4 - 配置组大小
参数建议值说明
所需容量2正常运行实例数
最小容量2保证高可用
最大容量6预留扩展空间
  1. 步骤 5 - 配置扩展策略:根据你选择的策略配置(见下节)
  2. 添加通知:在SNS中创建订阅用于接收通知的邮件(略过)
  3. 完成创建

根据业务场景,选择一种策略配置。

适用于流量不可预测的场景。

  1. 在 ASG 详情页 → 选择 弹性伸缩 标签页;区域点击 添加策略

Target Tracking-创建策略

  1. 配置:

    • 策略类型: Target tracking scaling
    • 伸缩策略名称: grav-cpu-target-tracking
    • 指标类型: 平均CPU使用率
    • 目标值: 70
  2. 实例预热: 300 秒(PHP 应用和 EFS 挂载需要时间)

  3. 点击 创建


适用于流量有固定规律的场景。

  1. 在 ASG 详情页 → 选择 弹性伸缩 标签页

  2. 计划操作 区域点击 创建计划操作

Scheduled Scaling-创建计划

  1. 配置扩容计划(示例:工作日早高峰):
    • 名称: scale-out-weekday-morning
    • 所需容量: 4
    • 最小值: 4
    • 最大值: 6
    • 循环: Corn 0 8 * * MON-FRI
    • 时区: 选择你的时区
    • 开始日期:自选
    • 开始时间:自选
    • 结束日期:自选
    • 结束时间:自选

配置扩容计划

  1. 创建缩容计划(示例:工作日晚间)同上
    • Name: scale-in-weekday-evening
    • Desired capacity: 2
    • Min: 2
    • Max: 6
    • Recurrence: 0 20 * * MON-FRI
    • Time zone: 选择你的时区

检查项验证方法预期结果
ALB 状态查看 Load Balancer 详情页状态为 Active
目标组 健康状态查看 目标组 → Targets 标签页所有实例显示 healthy
ASG 实例数量查看 Auto Scaling Group 详情页实例数量符合 所需值
Grav 站点访问浏览器访问 ALB DNS name正常显示 Grav 首页
EFS 内容共享在一个实例修改内容,从另一个实例访问内容同步

验证配置-Target Group健康状态

压测CPU实现扩容

sudo yum install -y stress-ng

基础压测

# 压满所有 CPU 核心,持续 5 分钟
stress-ng --cpu 0 --timeout 5m
# 压满 2 个 CPU 核心
stress-ng --cpu 2 --timeout 5m
# 压到指定百分比(80%)
stress-ng --cpu 0 --cpu-load 80 --timeout 5m
# 查看实时 CPU
top # 按 1 查看每个核心
htop # 更直观(需要安装)

高级压测

# 逐步增加负载(模拟真实流量)
stress-ng --cpu 1 --timeout 2m &
sleep 120
stress-ng --cpu 2 --timeout 2m &
sleep 120
stress-ng --cpu 4 --timeout 2m &
# 脉冲式负载(模拟突发流量)
for i in {1..10}; do
stress-ng --cpu 0 --timeout 30s
sleep 60
done

还可以:

  1. 手动调整 ASG 的 所需容量,观察实例增减
  2. 确认新实例自动注册到 目标群组
  3. 确认终止的实例从 目标群组 移除

当需要更新 Grav 核心代码或 PHP 配置时,采用不可变基础设施方式。

%%{init: {"flowchart": {"defaultRenderer": "elk"}}}%%
flowchart LR
    A[启动新实例<br/>基于当前 AMI] --> B[更新 Grav 代码]
    B --> C[测试验证]
    C --> D[创建新版本 AMI]
    D --> E[更新启动模板]
    E --> F[ASG 实例刷新]
    F --> G[旧实例逐步替换]

1. 启动临时实例

从当前 AMI 启动一台新实例用于更新。

2. 更新 Grav 代码

通过 SSH 连接实例,执行 Grav 更新(下载新版本、替换文件、清理缓存)。

3. 测试验证

确保更新后的 Grav 正常运行。

4. 创建新版本 AMI

5. 更新启动模板

  1. 进入 启动模板 → 选择启动模板
  2. 点击 操作修改模板 (创建新版本)
  3. 选择新的 AMI
  4. 创建新版本

更新启动模板-创建新版本 选择新版本AMI

6. 更新 ASG 使用新模板版本

  1. 进入 ASG 详情页
  2. 点击 Edit
  3. Launch template 中选择 Latest 或指定新版本
  4. 保存

编辑启动模板

选择新版启动模板

7. 执行实例刷新

  1. 在 ASG 详情页 → 实例刷新 标签页
  2. 点击 启动实例刷新
  3. 配置:
    • 设置运行正常百分比: 50(保证一半实例在线)
    • 实例预热: 300 秒
  4. 启动刷新

实例刷新-配置

ASG 会逐步终止旧实例并启动新实例,确保服务不中断。

8. 清理

  • 终止临时更新实例
  • 保留旧版本 AMI 用于回滚(建议保留最近 2-3 个版本)

检查 EFS 挂载是否成功。通过 SSH 连接实例,检查 /var/www/grav/user 目录是否包含内容。如果为空,检查 /etc/fstab 配置和 EFS 安全组是否允许 NFS 流量。

可能原因:

  • Health check grace period 过短,实例未完成启动
  • 安全组未允许 ALB 到实例的 HTTP 流量
  • Grav 应用未正常运行

检查实例的 Web 服务状态和安全组入站规则。

确保已配置:

  • PHP OPcache(opcache.validate_timestamps = 0
  • APCu 用户缓存
  • 仅将 /user 目录放在 EFS,核心代码保持本地

增加 设置运行正常百分比(如设为 90%),确保更多实例保持在线。同时检查 Health check grace period 是否足够。

内容更新后部分实例显示旧内容

Section titled “内容更新后部分实例显示旧内容”

Grav 的缓存可能导致此问题。登录 Grav 管理后台清理缓存,或通过 SSH 在实例上清理 /cache 目录。由于 OPcache 设置了 validate_timestamps = 0,PHP 代码变更需要重启 PHP-FPM 或 Apache。


实践项建议
多可用区部署ASG 跨至少 2 个可用区,EFS 在对应可用区创建挂载点
代码与数据分离Grav 核心代码放 AMI(本地),/user 目录放 EFS
启用 OPcachevalidate_timestamps = 0,生产环境必须配置
健康检查类型关联 ALB 时使用 ELB 健康检查
不可变基础设施通过 AMI 版本管理代码更新,便于回滚
监控告警配置 CloudWatch 告警 监控 CPU、EFS 吞吐量
静态资源缓存使用 CloudFront 缓存静态文件,减少 EFS 读取