AWS 计算服务实战部署指南:从零搭建 EC2、Lambda、ECS、Auto Scaling 与 LightSail

📅 · 海外云服务器自建服务教程 - ChengziCloud

一句话结论

> 关键词:AWS EC2部署教程、Lambda函数创建、ECS容器部署、Auto Scaling配置、LightSail建站、AWS CLI实战、云计算运维、AWS新手教程

本文要点
  • 引言:看懂文档和真正上手,隔着一条鸿沟
  • EC2 实战:从零创建并加固一台生产级云服务器
  • Lambda 实战:部署一个完整的无服务器 CRUD API
  • ECS 实战:容器化 Node.js 应用从镜像到上线
  • Auto Scaling 实战:为 EC2 应用穿上弹性盔甲
  • LightSail 实战:5 分钟上线一个 WordPress 网站
  • 五大服务部署复杂度对比
  • 排坑指南:跨服务的三个致命错误

关键词:AWS EC2部署教程、Lambda函数创建、ECS容器部署、Auto Scaling配置、LightSail建站、AWS CLI实战、云计算运维、AWS新手教程

引言:看懂文档和真正上手,隔着一条鸿沟

很多开发者把 AWS 官方文档翻了一遍又一遍,选型对比图也烂熟于心,但一打开终端输入 aws ec2 run-instances,立刻卡在安全组规则、IAM 角色和网络配置的连环坑里。更糟糕的是,文档通常只告诉你"某个参数是做什么的",却很少告诉你"这个参数填错了会导致生产事故"。

本文不讨论"选哪个"——那是上篇文章的事。本文只做一件事:带你用 AWS CLI 一行行敲出每个计算服务的核心部署流程,从零到生产可用的最小配置。 所有命令基于 aws-cli v2,建议在 Linux/macOS 终端中边看边执行。每一节最后都附有实战中踩过的深坑和避坑方案,这些才是你在任何官方文档中都找不到的干货。


一、EC2 实战:从零创建并加固一台生产级云服务器

1.1 前期准备:确认 CLI 与密钥对


# 确认 AWS CLI 已安装并配置凭证
aws --version
aws sts get-caller-identity   # 验证身份

# 创建 SSH 密钥对(新机器必备)
aws ec2 create-key-pair \
  --key-name my-prod-key \
  --query 'KeyMaterial' \
  --output text > ~/.ssh/my-prod-key.pem

chmod 400 ~/.ssh/my-prod-key.pem

1.2 创建安全组:最小权限原则

安全组是 EC2 的第一道防火墙,默认拒绝所有入站流量。以下配置仅开放必要端口:


# 获取默认 VPC ID
VPC_ID=$(aws ec2 describe-vpcs \
  --filters Name=isDefault,Values=true \
  --query 'Vpcs[0].VpcId' --output text)

# 创建安全组
SG_ID=$(aws ec2 create-security-group \
  --group-name web-server-sg \
  --description "Web server security group" \
  --vpc-id $VPC_ID \
  --query 'GroupId' --output text)

# 添加规则:SSH(仅限你的 IP)、HTTP、HTTPS
MY_IP=$(curl -s https://checkip.amazonaws.com)

aws ec2 authorize-security-group-ingress --group-id $SG_ID \
  --protocol tcp --port 22 --cidr ${MY_IP}/32

aws ec2 authorize-security-group-ingress --group-id $SG_ID \
  --protocol tcp --port 80 --cidr 0.0.0.0/0

aws ec2 authorize-security-group-ingress --group-id $SG_ID \
  --protocol tcp --port 443 --cidr 0.0.0.0/0

echo "安全组 ID: $SG_ID"

1.3 编写 User Data 启动脚本

User Data 在实例首次启动时以 root 身份执行,用于自动安装软件和配置环境:


cat > /tmp/user-data.sh << 'EOF'
#!/bin/bash
yum update -y
amazon-linux-extras enable nginx1
yum install -y nginx git htop

# 启动 Nginx 并设置开机自启
systemctl start nginx
systemctl enable nginx

# 写入一个简单首页
cat > /usr/share/nginx/html/index.html << 'HTML'
<!DOCTYPE html>
<html><body>
<h1>AWS EC2 实战部署成功!</h1>
<p>实例启动时间:$(date)</p>
</body></html>
HTML

# 安装 CloudWatch Agent 用于监控
yum install -y amazon-cloudwatch-agent
/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
  -a fetch-config -m ec2 -s
EOF

1.4 启动实例并验证


# 查询最新的 Amazon Linux 2023 AMI
AMI_ID=$(aws ec2 describe-images \
  --owners amazon \
  --filters "Name=name,Values=al2023-ami-2023*-x86_64" \
  --query 'reverse(sort_by(Images, &CreationDate))[0].ImageId' \
  --output text)

# 启动实例
INSTANCE_ID=$(aws ec2 run-instances \
  --image-id $AMI_ID \
  --instance-type t3.micro \
  --key-name my-prod-key \
  --security-group-ids $SG_ID \
  --user-data file:///tmp/user-data.sh \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=prod-web-01}]' \
  --query 'Instances[0].InstanceId' \
  --output text)

echo "实例 ID: $INSTANCE_ID"

# 等待实例进入 running 状态
aws ec2 wait instance-running --instance-ids $INSTANCE_ID

# 获取公网 IP
PUBLIC_IP=$(aws ec2 describe-instances \
  --instance-ids $INSTANCE_ID \
  --query 'Reservations[0].Instances[0].PublicIpAddress' \
  --output text)

echo "公网 IP: $PUBLIC_IP"
echo "访问: http://$PUBLIC_IP"

1.5 EC2 部署中常见的三个坑

坑现象解决方案
安全组过于开放SSH 端口被暴力破解将 0.0.0.0/0 替换为你的静态 IP 或使用 Session Manager
未启用终止保护误操作 terminate-instances生产实例必须 aws ec2 modify-instance-attribute --disable-api-termination
忘记分配弹性 IP重启后公网 IP 变化创建 EIP 并绑定:aws ec2 associate-address --instance-id $ID --allocation-id $EIP_ID

二、Lambda 实战:部署一个完整的无服务器 CRUD API

2.1 编写 Lambda 函数代码(Python 3.12)

创建一个待办事项(Todo)API,演示 Lambda 如何处理 CRUD 操作:


# todo_lambda.py
import json
import os
import uuid
from datetime import datetime

import boto3
from boto3.dynamodb.conditions import Key

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(os.environ['TABLE_NAME'])


def lambda_handler(event, context):
    """处理 API Gateway 转发来的 HTTP 请求"""
    http_method = event.get('httpMethod', 'GET')
    path_params = event.get('pathParameters', {}) or {}

    try:
        if http_method == 'GET' and path_params.get('id'):
            return get_todo(path_params['id'])
        elif http_method == 'GET':
            return list_todos()
        elif http_method == 'POST':
            return create_todo(json.loads(event.get('body', '{}')))
        elif http_method == 'PUT' and path_params.get('id'):
            return update_todo(path_params['id'], json.loads(event.get('body', '{}')))
        elif http_method == 'DELETE' and path_params.get('id'):
            return delete_todo(path_params['id'])
        else:
            return response(405, {'error': 'Method not allowed'})
    except Exception as e:
        print(f"Error: {str(e)}")
        return response(500, {'error': str(e)})


def create_todo(data):
    item = {
        'id': str(uuid.uuid4()),
        'title': data.get('title', 'Untitled'),
        'completed': False,
        'createdAt': datetime.utcnow().isoformat()
    }
    table.put_item(Item=item)
    return response(201, item)


def get_todo(todo_id):
    result = table.get_item(Key={'id': todo_id})
    if 'Item' not in result:
        return response(404, {'error': 'Todo not found'})
    return response(200, result['Item'])


def list_todos():
    result = table.scan(Limit=50)
    return response(200, result.get('Items', []))


def update_todo(todo_id, data):
    update_expr = 'SET '
    expr_values = {}
    for key in ['title', 'completed']:
        if key in data:
            update_expr += f'{key} = :{key}, '
            expr_values[f':{key}'] = data[key]
    update_expr = update_expr.rstrip(', ')

    table.update_item(
        Key={'id': todo_id},
        UpdateExpression=update_expr,
        ExpressionAttributeValues=expr_values
    )
    return response(200, {'id': todo_id, 'updated': True})


def delete_todo(todo_id):
    table.delete_item(Key={'id': todo_id})
    return response(200, {'id': todo_id, 'deleted': True})


def response(status_code, body):
    return {
        'statusCode': status_code,
        'headers': {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*'
        },
        'body': json.dumps(body, ensure_ascii=False)
    }

2.2 创建 DynamoDB 表与 IAM 角色


# 创建 DynamoDB 表
aws dynamodb create-table \
  --table-name todos \
  --attribute-definitions AttributeName=id,AttributeType=S \
  --key-schema AttributeName=id,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST

# 创建 IAM 执行角色
ROLE_ARN=$(aws iam create-role \
  --role-name lambda-todo-role \
  --assume-role-policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Principal": {"Service": "lambda.amazonaws.com"},
      "Action": "sts:AssumeRole"
    }]
  }' --query 'Role.Arn' --output text)

# 附加 DynamoDB 和 CloudWatch Logs 权限
aws iam attach-role-policy --role-name lambda-todo-role \
  --policy-arn arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess

aws iam attach-role-policy --role-name lambda-todo-role \
  --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

# 等待角色传播(IAM 角色需要几秒钟才能被 Lambda 使用)
echo "等待 IAM 角色传播..."
sleep 10

2.3 打包并创建 Lambda 函数


# 打包代码为 zip
zip todo_lambda.zip todo_lambda.py

# 创建函数
FUNCTION_ARN=$(aws lambda create-function \
  --function-name todo-api \
  --runtime python3.12 \
  --role $ROLE_ARN \
  --handler todo_lambda.lambda_handler \
  --zip-file fileb://todo_lambda.zip \
  --environment Variables="{TABLE_NAME=todos}" \
  --timeout 10 \
  --memory-size 256 \
  --query 'FunctionArn' --output text)

echo "函数 ARN: $FUNCTION_ARN"

# 创建函数 URL(快速测试,生产环境建议走 API Gateway)
aws lambda create-function-url-config \
  --function-name todo-api \
  --auth-type NONE

FUNC_URL=$(aws lambda get-function-url-config \
  --function-name todo-api \
  --query 'FunctionUrl' --output text)

echo "函数 URL: $FUNC_URL"

# 测试调用
curl -X POST $FUNC_URL \
  -H "Content-Type: application/json" \
  -d '{"title": "学习 AWS Lambda 实战"}'

2.4 Lambda 冷启动优化速查

策略原理额外成本适用场景
Provisioned Concurrency预置 N 个"热"实例有(按预置数 × 时长计费)对延迟敏感的生产 API
减小部署包体积轻量依赖 → 更快初始化解包无所有场景
使用 SnapStart(Java)以快照启动替代冷初始化有限Java 11+ 函数
定时 PingCloudWatch 定时事件保持实例温热调用费(极少)非关键路径

三、ECS 实战:容器化 Node.js 应用从镜像到上线

3.1 准备示例应用


// app.js — 一个简单的 Express API
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

app.get('/', (req, res) => {
  res.json({
    service: 'ECS Fargate Demo',
    version: '1.0.0',
    timestamp: new Date().toISOString(),
    instance: process.env.HOSTNAME || 'unknown'
  });
});

app.get('/health', (req, res) => {
  res.status(200).json({ status: 'healthy' });
});

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

# Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY app.js .
EXPOSE 3000
CMD ["node", "app.js"]

// package.json
{
  "name": "ecs-demo",
  "version": "1.0.0",
  "dependencies": {
    "express": "^4.18.2"
  }
}

3.2 构建镜像并推送到 ECR


# 登录 ECR
ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
REGION=$(aws configure get region)

aws ecr get-login-password --region $REGION | \
  docker login --username AWS --password-stdin ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com

# 创建仓库
aws ecr create-repository --repository-name ecs-demo-app

# 构建并推送
docker build -t ecs-demo-app .
docker tag ecs-demo-app:latest ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/ecs-demo-app:latest
docker push ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/ecs-demo-app:latest

3.3 注册任务定义并创建 Fargate 服务


# 注册任务定义
aws ecs register-task-definition \
  --family ecs-demo-task \
  --network-mode awsvpc \
  --requires-compatibilities FARGATE \
  --cpu 512 \
  --memory 1024 \
  --execution-role-arn arn:aws:iam::${ACCOUNT_ID}:role/ecsTaskExecutionRole \
  --container-definitions "[{
    \"name\": \"demo-app\",
    \"image\": \"${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/ecs-demo-app:latest\",
    \"portMappings\": [{\"containerPort\": 3000, \"protocol\": \"tcp\"}],
    \"essential\": true,
    \"logConfiguration\": {
      \"logDriver\": \"awslogs\",
      \"options\": {
        \"awslogs-group\": \"/ecs/demo-app\",
        \"awslogs-region\": \"${REGION}\",
        \"awslogs-stream-prefix\": \"ecs\"
      }
    }
  }]"

# 创建 ECS 集群
aws ecs create-cluster --cluster-name demo-cluster

# 创建 ECS 服务
aws ecs create-service \
  --cluster demo-cluster \
  --service-name demo-service \
  --task-definition ecs-demo-task \
  --desired-count 2 \
  --launch-type FARGATE \
  --network-configuration "awsvpcConfiguration={
    subnets=[$(aws ec2 describe-subnets --query 'Subnets[0:2].SubnetId' --output text | tr '\t' ',')],
    assignPublicIp=ENABLED
  }"

echo "服务已创建,等待任务启动..."

3.4 ECS Fargate 配置速查表

参数可选范围推荐值(小型 Web 应用)
task CPU256–16384 (单位:1/1024 vCPU)512 (0.5 vCPU)
task Memory512–30720 (MiB)1024 (1 GB)
desired count1–N2 (跨 AZ 高可用)
健康检查路径自定义/health(返回 200)
日志保留期1–3650 天30 天(平衡调试与成本)

四、Auto Scaling 实战:为 EC2 应用穿上弹性盔甲

4.1 创建启动模板

启动模板是 Auto Scaling 的"模具",定义了每个新实例的规格:


aws ec2 create-launch-template \
  --launch-template-name web-asg-template \
  --version-description "v1 - Amazon Linux 2023 + Nginx" \
  --launch-template-data '{
    "ImageId": "'"$AMI_ID"'",
    "InstanceType": "t3.micro",
    "KeyName": "my-prod-key",
    "SecurityGroupIds": ["'"$SG_ID"'"],
    "UserData": "'"$(base64 -w0 /tmp/user-data.sh)"'",
    "TagSpecifications": [{
      "ResourceType": "instance",
      "Tags": [{"Key": "Name", "Value": "web-asg-instance"}]
    }]
  }'

4.2 创建 Auto Scaling Group(ASG)


# 获取两个不同可用区的子网 ID
SUBNET_IDS=$(aws ec2 describe-subnets \
  --filters Name=default-for-az,Values=true \
  --query 'Subnets[0:2].SubnetId' \
  --output text | tr '\t' ',')

echo "子网列表: $SUBNET_IDS"

# 创建 ASG
aws autoscaling create-auto-scaling-group \
  --auto-scaling-group-name web-asg \
  --launch-template "LaunchTemplateName=web-asg-template,Version=\$Latest" \
  --min-size 2 \
  --max-size 6 \
  --desired-capacity 2 \
  --vpc-zone-identifier "$SUBNET_IDS" \
  --health-check-type ELB \
  --health-check-grace-period 120 \
  --default-cooldown 180 \
  --tags "Key=Environment,Value=production,PropagateAtLaunch=true"

4.3 配置目标追踪伸缩策略

目标追踪策略会自动调整实例数量,将指定指标维持在目标值附近:


# 基于平均 CPU 使用率的伸缩策略
aws autoscaling put-scaling-policy \
  --auto-scaling-group-name web-asg \
  --policy-name cpu-target-tracking \
  --policy-type TargetTrackingScaling \
  --target-tracking-configuration '{
    "PredefinedMetricSpecification": {
      "PredefinedMetricType": "ASGAverageCPUUtilization"
    },
    "TargetValue": 60.0,
    "DisableScaleIn": false
  }'

# 基于 ALB 请求数的伸缩策略(需先绑定 ALB)
aws autoscaling put-scaling-policy \
  --auto-scaling-group-name web-asg \
  --policy-name request-count-tracking \
  --policy-type TargetTrackingScaling \
  --target-tracking-configuration '{
    "PredefinedMetricSpecification": {
      "PredefinedMetricType": "ALBRequestCountPerTarget"
    },
    "TargetValue": 1000.0
  }'

echo "伸缩策略已就绪:CPU 目标 60%,每目标请求数 1000"

4.4 Auto Scaling 关键参数解释

参数含义生产环境建议值
min-size最少保持的实例数≥2(避免单点故障)
max-size最多扩容到的实例数根据预算设上限
desired-capacity初始目标实例数介于 min 和 max 之间
health-check-grace-period新实例豁免健康检查的时间(秒)120–300(取决于启动耗时)
default-cooldown两次伸缩活动之间的冷却期(秒)180–300(防止频繁振荡)
cooldown vs step adjustment简单伸缩用冷却,步进伸缩更精确步进伸缩适合大流量场景

五、LightSail 实战:5 分钟上线一个 WordPress 网站

5.1 一条命令创建 LightSail 实例

LightSail 的核心哲学是简单——不需要 VPC、安全组、IAM 角色等概念:


# 创建 WordPress 实例(预装好的蓝图)
aws lightsail create-instances \
  --instance-names wp-blog \
  --availability-zone us-east-1a \
  --blueprint-id wordpress \
  --bundle-id small_1_0 \
  --user-data 'WORDPRESS_SITE_TITLE=我的技术博客
WORDPRESS_ADMIN_USER=admin
[email protected]'

# 查询实例状态
aws lightsail get-instance --instance-name wp-blog \
  --query 'instance.{Name:name,State:state.name,IP:publicIpAddress}'

5.2 配置静态 IP 与防火墙


# 分配静态 IP(重启不会变)
aws lightsail allocate-static-ip --static-ip-name wp-blog-ip

aws lightsail attach-static-ip \
  --static-ip-name wp-blog-ip \
  --instance-name wp-blog

# 开放 443 端口(HTTPS)
aws lightsail open-instance-public-ports \
  --instance-name wp-blog \
  --port-info fromPort=443,toPort=443,protocol=TCP

# 查询当前防火墙规则
aws lightsail get-instance-port-states --instance-name wp-blog

5.3 创建自动快照

LightSail 提供自动快照功能,每天自动备份,保留最近 7 天:


# 启用自动快照
aws lightsail enable-add-on \
  --resource-name wp-blog \
  --add-on-request addOnType=AutoSnapshot,autoSnapshotAddOnRequest=\{snapshotTimeOfDay="06:00"\}

# 手动创建即时快照(变更前必做)
aws lightsail create-instance-snapshot \
  --instance-name wp-blog \
  --instance-snapshot-name wp-blog-pre-change-$(date +%Y%m%d)

5.4 LightSail 套餐选择决策表

使用场景推荐套餐月费关键考量
个人博客 / 作品集$3.50 (512 MB)~¥25够用,CDN 分担流量
中小企业官网$5.00 (1 GB)~¥36WordPress + 静态缓存
WooCommerce 独立站$10.00 (2 GB)~¥722 GB 内存支撑 PHP + MySQL
多站点 / 社区论坛$20.00 (4 GB)~¥144建议配合 Lightsail 负载均衡器
API 后端 + 数据库$40.00 (8 GB)~¥288此时应评估是否迁移到 EC2

六、五大服务部署复杂度对比

维度EC2LambdaECS FargateAuto ScalingLightSail
首次上手时间15–30 分钟10–20 分钟30–60 分钟20–40 分钟(在 EC2 基础上)3–5 分钟
需要了解的概念数~10 (VPC/SG/AMI/EBS/ENI...)~4 (角色/触发器/层/并发)~8 (集群/任务/服务/ECR...)~6 (模板/策略/冷却/钩子...)~3 (实例/静态IP/快照)
CLI 命令数(最小部署)5–8 条4–6 条8–12 条5–8 条1–3 条
是否需要 Docker否否是否(但通常配合容器)否
自动化部署工具CloudFormation / CDK / TerraformSAM / Serverless FrameworkCopilot / CDKCloudFormation控制台为主

七、排坑指南:跨服务的三个致命错误

7.1 将 EC2 的思维套用到 Lambda

最常见的错误是在 Lambda 函数中写 setInterval、维护全局连接池、或期望两次调用之间"记住"状态。Lambda 每次调用可能运行在完全不同的容器实例上,函数外的全局变量不可靠。


错误做法:
let connection = null;           // ← 全局变量,冷启动后可能被复用

正确做法:
在 handler 内部创建连接,或使用 parameter store / DynamoDB 存储状态

7.2 忘记为 Fargate 任务配置日志驱动

如果任务定义的 containerDefinitions 中没有 logConfiguration,容器崩溃后你完全看不到任何错误日志——CloudWatch 里空空如也,ECS 控制台也只显示 STOPPED (Essential container in task exited)。

7.3 Auto Scaling 缩容过于激进

同时设置低的 default-cooldown(如 60 秒)和激进的目标追踪策略,会导致"抖振"(flapping)——流量轻微下降立即缩容,缩容后剩余实例负载飙升又触发扩容,循环往复。生产环境冷却期建议不低于 180 秒,并结合步进伸缩使用多个告警阈值。


结语:从"看会了"到"真会了"

在这篇文章中,我们用纯 CLI 命令走完了 AWS 五大计算服务的完整部署路径:从 EC2 实例的创建与安全加固,到 Lambda 无服务器 API 的编码与上线;从 ECS 容器化应用的镜像构建与服务部署,到 Auto Scaling 的弹性策略配置,最后用 LightSail 三行命令跑起一个 WordPress。

所有这些命令都可以直接复制到你的终端执行(替换占位符后),不需要任何鼠标点击。如果你跟着每一节的操作走下来,现在你已经拥有一台运行中的 EC2 服务器、一个可调用的 Lambda CRUD 接口、一个双副本的 Fargate 容器服务、一套自动伸缩的 Web 集群,以及一个可直接发文章的 WordPress 站点。这五个组件放在一起,本身就是一套微缩版的 AWS 生产架构。

真正的云计算能力,不来自记忆几百个服务名称,而来自反复踩坑后形成的那一套可复用的自动化脚本。 把本文的每个命令敲一遍,你就完成了从"看会了"到"真会了"的质变。下一步,将这些手动命令封装成 CloudFormation 模板或 Terraform 模块,实现基础设施即代码,那才是迈向高级云架构师的关键一步。


本文由 5.chengzicloud.cloud 提供,点击访问首页了解更多