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 Research and Development Team
在 ECOSIRE 构建企业级数字产品。分享关于 Odoo 集成、电商自动化和 AI 驱动商业解决方案的洞见。
相关文章
Cloud Hosting for ERP: AWS vs Azure vs Google Cloud
A detailed comparison of AWS, Azure, and Google Cloud for ERP hosting in 2026. Covers performance, cost, regional availability, managed services, and ERP-specific recommendations.
Cloud vs On-Premise ERP in 2026: The Definitive Guide
Cloud vs on-premise ERP in 2026: total cost analysis, security comparison, scalability, compliance, and the right deployment model for your business.
Docker Compose for Development: Local Infrastructure
Docker Compose for local development: PostgreSQL, Redis, Authentik, networking, health checks, volume management, and environment-specific configurations for TypeScript monorepos.