AWS EC2 Web 应用程序部署指南
对于需要一致性能、自定义软件堆栈和可预测定价的 Web 应用程序,EC2 仍然是 AWS 中最灵活的计算选项。虽然 ECS、EKS 和 Lambda 在云原生领域受到更多关注,但 EC2 为您提供了完全控制的服务器 — 没有容器编排复杂性、没有冷启动延迟、没有意外的调用成本。
本指南涵盖在 EC2 上部署生产 Node.js Web 应用程序:实例选择、安全组配置、应用程序部署、Nginx 反向代理、Cloudflare 的 SSL、CloudWatch 监控以及保持 EC2 部署正常运行的持续维护模式。
要点
- t3.large 是全栈 Node.js + PostgreSQL 部署的正确起点
- 使用 Ubuntu 24.04 LTS — 支持到 2029 年,文档广泛,软件包可用性出色
- 弹性 IP 是强制性的 — 如果没有它,您的 EC2 IP 在每次停止/启动时都会发生变化
- 安全组是有状态的——您只需要入站规则;出站通常是允许所有
- 将您的部署 SSH 密钥存储在单独的
.pem文件中;永远不要将其提交到 git- 尽可能使用 EC2 实例连接或会话管理器而不是直接 SSH(零密钥管理)
- CloudWatch 代理为您提供内存和磁盘指标(默认情况下不可用)
- 与按需实例相比,预留实例或节省计划可将 EC2 成本降低 40-60%
实例选择
正确的实例类型取决于您的工作负载:
| 工作量 | 推荐实例 | vCPU | 内存 | 费用/月 |
|---|---|---|---|---|
| 轻(博客、小应用程序) | t3.小 | 2 | 2GB | ~$18 |
| 中型(全栈应用程序) | t3.中 | 2 | 4GB | ~$35 |
| 生产(多服务) | t3.大 | 2 | 8GB | ~$70 |
| 重型(高流量API) | c6i.xlarge | 4 | 8GB | 〜$ 140 |
| 占用大量内存(ML/缓存) | r6i.大 | 2 | 16GB | 〜$ 120 |
对于具有 5 个 Node.js 应用程序(Next.js、NestJS、Docusaurus、2 个品牌站点)加上 Docker 基础设施(PostgreSQL、Redis、Authentik)的 monorepo,t3.large 是最低可行配置。 t3 系列采用“突发”性能 — 正常运行期间性能非常出色,但持续较高的 CPU 会触发节流。
对于持续较高的 CPU 工作负载(视频处理、ML 推理、重型加密),请改用 c6i(计算优化)实例。
初始服务器设置
使用 Ubuntu 24.04 启动 EC2 实例后:
# Connect via SSH
ssh -i your-key.pem [email protected]
# Update system packages
sudo apt update && sudo apt upgrade -y
# Install essential tools
sudo apt install -y \
git curl wget unzip \
build-essential \
nginx \
certbot python3-certbot-nginx \
docker.io docker-compose-plugin \
htop ncdu iotop
# Install Node.js via NVM (allows easy version management)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install 22
nvm use 22
nvm alias default 22
# Install pnpm
curl -fsSL https://get.pnpm.io/install.sh | sh -
source ~/.bashrc
# Install PM2 globally
npm install -g pm2
# Install PM2 log rotation immediately
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 50M
pm2 set pm2-logrotate:retain 7
pm2 set pm2-logrotate:compress true
安全组配置
安全组是您的 EC2 实例的防火墙。仔细配置:
Inbound Rules:
┌─────────┬──────────┬─────────────┬──────────────────────────────────┐
│ Type │ Protocol │ Port Range │ Source │
├─────────┼──────────┼─────────────┼──────────────────────────────────┤
│ SSH │ TCP │ 22 │ Your IP only (not 0.0.0.0/0!) │
│ HTTP │ TCP │ 80 │ 0.0.0.0/0 (Cloudflare IPs only) │
│ HTTPS │ TCP │ 443 │ 0.0.0.0/0 (Cloudflare IPs only) │
└─────────┴──────────┴─────────────┴──────────────────────────────────┘
Note: Internal app ports (3000, 3001, 3002, etc.) should NOT be
in the security group — traffic goes through Nginx only
对于 Cloudflare 代理的域,将 HTTP/HTTPS 限制为 Cloudflare IP 范围:
# Cloudflare IPv4 ranges — restrict port 80/443 source to these
103.21.244.0/22
103.22.200.0/22
103.31.4.0/22
104.16.0.0/13
104.24.0.0/14
108.162.192.0/18
131.0.72.0/22
141.101.64.0/18
162.158.0.0/15
172.64.0.0/13
173.245.48.0/20
188.114.96.0/20
190.93.240.0/20
197.234.240.0/22
198.41.128.0/17
这可以防止绕过 Cloudflare 的 WAF 和 DDoS 保护直接访问您的服务器。
应用程序部署
# Create application directory
sudo mkdir -p /opt/ecosire/app
sudo chown ubuntu:ubuntu /opt/ecosire/app
# Clone the repository
git clone https://github.com/your-org/your-repo.git /opt/ecosire/app
cd /opt/ecosire/app
# Create .env.local from template
cp .env.example .env.local
# Edit with production values
nano .env.local
# Install dependencies
pnpm install --frozen-lockfile
# Build everything
npx turbo run build
# Run database migrations
pnpm --filter @ecosire/db db:migrate
# Start infrastructure (PostgreSQL, Redis, Authentik)
docker compose -f infrastructure/docker-compose.dev.yml up -d
# Wait for services to be healthy
sleep 30
# Start Node.js applications
pm2 start ecosystem.config.cjs
# Save process list for reboot persistence
pm2 save
# Configure PM2 to start on system boot
pm2 startup
# Run the command it outputs
弹性IP和DNS
每次停止和启动 EC2 实例时,其公共 IP 都会发生变化。弹性IP提供永久IP:
# In AWS Console:
# 1. EC2 > Network & Security > Elastic IPs
# 2. Allocate Elastic IP address
# 3. Associate it with your instance
# Your IP is now permanent — update Cloudflare DNS to point to it
# A record: ecosire.com → 13.223.116.181 (your Elastic IP)
# A record: api.ecosire.com → 13.223.116.181
# A record: auth.ecosire.com → 13.223.116.181
在 Cloudflare 中,将这些记录设置为 Web 流量的“代理”(橙色云)。 Cloudflare 代理隐藏您的实际 EC2 IP,提供 DDoS 保护。
存储:EBS 卷管理
EC2 实例包含根 EBS 卷。对于生产,您需要足够的空间来存储构建工件、日志和 Docker 数据:
# Check current disk usage
df -h
# Check which directories are consuming space
ncdu /
# Typical space requirements for a 5-app monorepo:
# - /opt/ecosire/app: ~2GB (code + node_modules + .next builds)
# - Docker data (/var/lib/docker): ~5GB
# - PM2 logs (/var/log/pm2): ~1GB (with rotation)
# - System: ~5GB
# Total: ~13GB minimum, recommend 30GB+ root volume
# If you need to resize an EBS volume (no downtime needed):
# 1. In AWS Console: EC2 > Volumes > Modify Volume
# 2. After resize completes, grow the filesystem:
sudo growpart /dev/xvda 1
sudo resize2fs /dev/xvda1
CloudWatch 监控
EC2 默认提供基本的 CPU 和网络指标。对于内存和磁盘指标,请安装 CloudWatch 代理:
# Download and install CloudWatch agent
wget https://s3.amazonaws.com/amazoncloudwatch-agent/ubuntu/amd64/latest/amazon-cloudwatch-agent.deb
sudo dpkg -i amazon-cloudwatch-agent.deb
# Create configuration
sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-config-wizard
// /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json
{
"agent": {
"metrics_collection_interval": 60,
"run_as_user": "cwagent"
},
"metrics": {
"append_dimensions": {
"AutoScalingGroupName": "${aws:AutoScalingGroupName}",
"ImageId": "${aws:ImageId}",
"InstanceId": "${aws:InstanceId}",
"InstanceType": "${aws:InstanceType}"
},
"metrics_collected": {
"mem": {
"measurement": ["mem_used_percent"],
"metrics_collection_interval": 60
},
"disk": {
"measurement": ["used_percent"],
"metrics_collection_interval": 60,
"resources": ["/", "/opt/ecosire"]
}
}
},
"logs": {
"logs_collected": {
"files": {
"collect_list": [
{
"file_path": "/var/log/pm2/ecosire-api.err.log",
"log_group_name": "/ec2/ecosire/api-errors",
"log_stream_name": "{instance_id}"
},
{
"file_path": "/var/log/nginx/ecosire-error.log",
"log_group_name": "/ec2/ecosire/nginx-errors",
"log_stream_name": "{instance_id}"
}
]
}
}
}
}
# Start the agent
sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
-a fetch-config \
-m ec2 \
-c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json \
-s
自动备份
设置自动 PostgreSQL 备份到 S3:
# Create backup script
cat > /opt/ecosire/scripts/backup-db.sh << 'EOF'
#!/bin/bash
set -e
DATE=$(date +%Y-%m-%d-%H%M%S)
BACKUP_FILE="/tmp/ecosire-db-${DATE}.sql.gz"
S3_BUCKET="s3://your-backups-bucket/postgres"
# Dump the database (connects via Docker network)
docker exec ecosire-postgres pg_dump \
-U ecosire \
-d ecosire_dev \
--no-owner \
--no-privileges \
| gzip > "$BACKUP_FILE"
# Upload to S3
aws s3 cp "$BACKUP_FILE" "$S3_BUCKET/"
# Clean up local file
rm "$BACKUP_FILE"
# Delete backups older than 30 days from S3
aws s3 ls "$S3_BUCKET/" \
| awk '{print $4}' \
| sort \
| head -n -30 \
| xargs -I {} aws s3 rm "$S3_BUCKET/{}" 2>/dev/null || true
echo "Backup complete: ${DATE}"
EOF
chmod +x /opt/ecosire/scripts/backup-db.sh
# Schedule daily backups at 3 AM UTC
(crontab -l 2>/dev/null; echo "0 3 * * * /opt/ecosire/scripts/backup-db.sh >> /var/log/db-backup.log 2>&1") | crontab -
IAM 角色配置
将 IAM 角色附加到您的 EC2 实例以进行 AWS 服务访问(S3、CloudWatch、SES):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::your-products-bucket",
"arn:aws:s3:::your-products-bucket/*",
"arn:aws:s3:::your-backups-bucket",
"arn:aws:s3:::your-backups-bucket/*"
]
},
{
"Effect": "Allow",
"Action": [
"ses:SendEmail",
"ses:SendRawEmail"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"cloudwatch:PutMetricData",
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
}
]
}
将 IAM 角色附加到您的实例后,AWS SDK 会自动调用使用实例凭证 - 环境变量中不需要访问密钥/秘密密钥。
常见陷阱和解决方案
陷阱 1:没有弹性 IP — 重新启动时 IP 发生变化
停止和启动(而不是重新启动)EC2 实例会分配新的公共 IP。如果没有弹性 IP,您的 DNS 就会崩溃。启动实例后立即分配并关联弹性 IP。
陷阱 2:SSH 访问被锁定
如果您丢失 SSH 密钥或因安全组配置错误而将自己锁定,请从 AWS 控制台或会话管理器(默认情况下随 Ubuntu 附带)使用 EC2 Instance Connect(基于浏览器的 SSH)。作为最后的手段,分离根 EBS 卷,将其附加到另一个实例,修复authorized_keys 文件,然后重新附加。
陷阱 3:部署期间磁盘空间不足
.next 构建缓存和 node_modules 在开发过程中会大幅增长。使用 df -h 监控磁盘使用情况,并在 disk_used_percent > 80% 时设置 CloudWatch 警报。 ncdu 命令 (ncdu /) 标识哪些目录正在消耗空间。
陷阱 4:Node.js OOM 导致内存耗尽
Node.js 具有默认内存限制(64 位上为 1.4GB),这可能会导致大型应用程序发生 OOM 崩溃。在 PM2 生态系统文件中设置 node_args: '--max-old-space-size=1024' 以显式限制内存使用量。将 max_memory_restart 设置为略高于此值,以便在超出时自动重新启动。
陷阱 5:持续负载下 T3 CPU 节流
T3 实例使用“CPU 积分”来实现突发性能。扩展的高 CPU 操作(大型构建、繁重的数据库查询)会耗尽积分,导致“基准”性能受到限制。在 CloudWatch 中监控 CPUCreditBalance。如果积分持续耗尽,请升级到 c6i 实例或启用“无限制”模式(每 CPU 小时的额外成本高于基准)。
常见问题
我应该使用 EC2 还是 AWS Elastic Beanstalk 等托管服务?
EC2 为您提供完全控制:确切的 Node.js 版本、文件系统访问、运行 Docker sidecar 容器的能力以及自定义 Nginx 配置。 Elastic Beanstalk 管理底层基础设施,但会限制您的选择并增加故障排除的复杂性。对于熟悉 Linux 服务器管理的团队来说,采用 PM2 + Nginx 的 EC2 比托管平台更简单、更可预测。如果您希望平台自动处理扩展和运行状况管理,请使用 Beanstalk。
如何在 EC2 上处理零停机部署?
PM2 pm2 reload 命令为集群模式进程提供零停机时间(NestJS API)。对于Next.js(fork模式),首先构建新版本,然后重新加载PM2。在 PM2 切换进程的几秒钟内,Nginx 对传入请求进行排队(有一个小的超时)。为了真正实现零停机,请在 ALB(应用程序负载均衡器)后面使用两个 EC2 实例,并部署到其中一个实例,而另一个实例提供流量。
什么时候应该使用自动缩放?
自动扩展显着增加了操作复杂性——运行状况检查、启动模板、负载均衡器和会话亲和性注意事项。对于具有可预测流量的应用程序,具有垂直扩展(更大的实例类型)的适当大小的 EC2 实例比水平自动扩展更简单且通常更便宜。当流量峰值超过基线的 5 倍并且运行永久更大实例的成本超过自动扩展的复杂性时,请考虑自动扩展。
以后如何从 EC2 迁移到容器?
首先使用 Docker 容器化您的应用程序(为每个应用程序编写一个 Dockerfile)。使用 Docker Compose 在本地进行测试。然后选择 ECS Fargate(无服务器容器,更简单)或 EKS(Kubernetes,更强大但更复杂)。如果您增量容器化,迁移不会造成中断 - 在相同的 Nginx/Cloudflare 设置后面运行容器化版本,验证行为,然后切换。
在生产环境中运行 EC2 最具成本效益的方式是什么?
为您的基准实例购买 1 年期预留实例(无需预付或部分预付) — 比按需实例便宜 40%。要在流量高峰期间获得额外容量,如果您的应用程序可以处理中断,请使用 Spot 实例(最多便宜 90%)。在每月预算的 80% 处设置 CloudWatch 账单警报,以便及早发现意外的成本增加。对于生产 Web 应用程序,预留实例提供了成本和可靠性的最佳平衡。
后续步骤
在 EC2 上运行生产 Web 应用程序需要持续的运营关注 - 安全补丁、磁盘管理、性能监控和部署自动化。 ECOSIRE 运行一个生产 EC2 t3.large 实例,为多个域中的 5 个应用程序提供服务,并具有自动备份、CloudWatch 监控和零停机 PM2 部署。
无论您需要 AWS 基础设施咨询、EC2 部署设置还是 Node.js 应用程序的完整开发运营支持,探索我们的服务 以了解我们如何提供帮助。
作者
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
相关文章
OpenClaw 多租户生产部署架构
OpenClaw 多租户部署模式:租户隔离、共享与专用运行时、消息总线设计、秘密、可观察性和扩展。
ERP 云托管:AWS、Azure、Google Cloud
对 2026 年 ERP 托管的 AWS、Azure 和 Google Cloud 进行详细比较。涵盖性能、成本、区域可用性、托管服务和特定于 ERP 的建议。
2026 年云与本地 ERP:权威指南
2026 年云与本地 ERP:总成本分析、安全性比较、可扩展性、合规性以及适合您业务的正确部署模型。