AWS EC2 Deployment Guide for Web Applications

Complete AWS EC2 deployment guide: instance selection, security groups, Node.js deployment, Nginx reverse proxy, SSL, auto-scaling, CloudWatch monitoring, and cost optimization.

E
ECOSIRE Research and Development Team
|2026年3月19日6 分钟阅读1.3k 字数|

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.小22GB~$18
中型(全栈应用程序)t3.中24GB~$35
生产(多服务)t3.大28GB~$70
重型(高流量API)c6i.xlarge48GB〜$ 140
占用大量内存(ML/缓存)r6i.大216GB〜$ 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 应用程序的完整开发运营支持,探索我们的服务 以了解我们如何提供帮助。

E

作者

ECOSIRE Research and Development Team

在 ECOSIRE 构建企业级数字产品。分享关于 Odoo 集成、电商自动化和 AI 驱动商业解决方案的洞见。

通过 WhatsApp 聊天