Use Cases (更新: 2026/6/2)

Claude Code AWS 部署实战:CDK、GitHub Actions、IAM、日志与回滚

用Claude Code把小型API安全部署到AWS:CDK实现、OIDC、最小权限、日志、回滚和成本护栏。

Claude Code AWS 部署实战:CDK、GitHub Actions、IAM、日志与回滚

用 Claude Code 做 AWS 部署时,最容易出问题的不是代码写得慢,而是让它生成一张看起来很完整的架构图,却没有可运行的 IaC、权限边界、日志、回滚和成本控制。真正能上线的部署方案,必须能被团队重复执行,也能在失败时恢复。

本文选择一条适合小型 Web 应用和 API 的安全路径:API Gateway + Lambda + AWS CDK + GitHub Actions。CDK 是 Infrastructure as Code,也就是把 AWS 控制台里的手动配置写成代码。IAM 可以理解为“谁能对哪些 AWS 资源做什么”的权限表。OIDC 则让 GitHub Actions 用临时凭证登录 AWS,不需要把长期 AWS Access Key 放进 GitHub Secrets。

Claude Code 的价值,是读取项目结构、生成多文件修改、运行 cdk synthcdk diff、解释部署错误,并把运维步骤写成团队可复用的规则。Claude Code 本身请参考 Claude Code 官方文档。AWS 侧的关键依据包括 AWS CDK NodejsFunctionAPI Gateway Lambda proxy integrationLambda 环境变量Lambda CloudWatch LogsIAM 最佳实践

先决定 AWS 部署目标

不要让 Claude Code 自己猜服务组合。先把业务形态说清楚,生成结果会稳定很多。

方案适合场景优点注意点
Lambda + API Gateway小型 API、联系表单、Webhook、轻量后台服务器管理少,低流量起步成本低不适合长时间任务、持久连接、大型容器
ECS/Fargate + ALBDocker API、常驻后端、API 与 worker 混合容器自由度高,迁移已有服务更自然VPC、ALB、任务定义、镜像构建和扩缩容都要设计
Amplify 或 S3 + CloudFront静态站点、SPA、前端为主的应用CDN 分发快,运维轻API、认证、后台任务需要另行设计

本文实现 Lambda + API Gateway,因为咨询中最常见的问题是:“我有一个小产品或内部 API,想安全地放到 AWS 上,但不想一开始就过度架构。”延伸阅读可以看 Claude Code AWS Lambda 指南Claude Code AWS IAM 指南Claude Code API Gateway 指南Claude Code 安全最佳实践

flowchart LR
  User[用户] --> Api[API Gateway]
  Api --> Fn[Lambda Node.js API]
  Fn --> Secret[Secrets Manager]
  Fn --> Logs[CloudWatch Logs]
  GitHub[GitHub Actions OIDC] --> CDK[AWS CDK deploy]
  CDK --> Api
  CDK --> Fn

4 个实际用例

第一个用例是网站联系表单或线索表单。前端可以继续放在现有站点,只把提交接口放到 API Gateway + Lambda。API Gateway 的节流、API key 和 CloudWatch Logs 能帮助你排查垃圾提交和故障。

第二个用例是 SaaS MVP API。先做 /v1/health/v1/contact/v1/webhook 这类边界清楚的小接口。给 Claude Code 的指令要写清楚:现在不需要 VPC、RDS、队列或容器,除非需求明确出现。

第三个用例是内部自动化 Webhook,例如 Slack 通知、CMS 发布回调、审批通知、轻量管理操作。只要每个请求能快速结束,Lambda 很合适。如果需要重试、长时间处理或扇出,再有意识地加入 SQS 或 Step Functions。

第四个用例是把手动部署迁移到 CI/CD。很多团队还在本地执行部署命令,或者在 AWS 控制台点击修改。Claude Code 可以帮助把这些动作转成 CDK 和 GitHub Actions,但生产边界、审批流程、回滚策略仍然必须由人决定。

给 Claude Code 写部署规则

先在项目根目录写 CLAUDE.md,避免一个小 API 被生成成昂贵的平台工程。

# AWS deployment rules

- Use AWS CDK v2 and TypeScript.
- Target region: ap-northeast-1 unless explicitly changed.
- Do not create VPC resources for this small API unless required.
- Prefer API Gateway + Lambda for the first release.
- Runtime secrets must come from AWS Secrets Manager, not plaintext env vars.
- Use IAM grants such as secret.grantRead(handler) instead of wildcard policies.
- Add CloudWatch log retention and reserved concurrency.
- Before deploy, run npm test, npx cdk synth, and npx cdk diff.
- Never paste AWS access keys into files or GitHub Secrets.

然后这样请求 Claude Code:

请在这个仓库中添加 CDK v2 的 SmallApiStack。
创建 API Gateway REST API 和 Node.js 20 Lambda,包含 /v1/health 与 /v1/contact。
运行时配置从 Secrets Manager 的 prod/claude-code-demo/api 读取。
Lambda 执行角色保持最小权限,并加入 CloudWatch 日志保留、reserved concurrency、API Gateway throttling。
GitHub Actions 部署只能使用 OIDC,不要使用长期 AWS key。
修改后运行 npm test、npx cdk synth、npx cdk diff,并解释结果。

创建 CDK 项目

本地需要 Node.js、AWS CLI v2、AWS CDK CLI,以及已经登录的 AWS profile。以下命令使用 Bash;Windows 用户建议使用 WSL 或 Git Bash。

mkdir claude-code-aws-api
cd claude-code-aws-api
npx cdk init app --language typescript
npm install @aws-sdk/client-secrets-manager
npm install --save-dev esbuild @types/aws-lambda
mkdir -p lambda
aws sts get-caller-identity

先创建运行时配置。真实 API key、数据库密码、外部服务 token 不要提交到 Git,也不要直接放进明文环境变量。

aws secretsmanager create-secret \
  --name prod/claude-code-demo/api \
  --secret-string '{"supportQueue":"aws-consulting"}' \
  --region ap-northeast-1

实现 Lambda handler

创建 lambda/handler.ts。下面的代码只记录运维需要的元数据,不打印完整请求正文,也不打印 secret。

import type {
  APIGatewayProxyEvent,
  APIGatewayProxyResult,
} from "aws-lambda";
import {
  GetSecretValueCommand,
  SecretsManagerClient,
} from "@aws-sdk/client-secrets-manager";

const secrets = new SecretsManagerClient({});
let cachedConfig: Record<string, string> | undefined;

async function loadConfig(): Promise<Record<string, string>> {
  if (cachedConfig) return cachedConfig;

  const secretArn = process.env.SECRET_ARN;
  if (!secretArn) throw new Error("SECRET_ARN is not configured");

  const result = await secrets.send(
    new GetSecretValueCommand({ SecretId: secretArn }),
  );
  cachedConfig = JSON.parse(result.SecretString ?? "{}");
  return cachedConfig;
}

function json(statusCode: number, body: unknown): APIGatewayProxyResult {
  return {
    statusCode,
    headers: {
      "content-type": "application/json",
      "cache-control": "no-store",
    },
    body: JSON.stringify(body),
  };
}

export async function handler(
  event: APIGatewayProxyEvent,
): Promise<APIGatewayProxyResult> {
  try {
    if (event.httpMethod === "GET" && event.path.endsWith("/v1/health")) {
      return json(200, {
        ok: true,
        stage: process.env.STAGE ?? "dev",
      });
    }

    if (event.httpMethod === "POST" && event.path.endsWith("/v1/contact")) {
      const body = JSON.parse(event.body ?? "{}") as {
        email?: string;
        message?: string;
      };

      if (!body.email || !body.message) {
        return json(400, { ok: false, message: "email and message required" });
      }

      const config = await loadConfig();
      const emailDomain = body.email.split("@")[1] ?? "unknown";

      console.log(
        JSON.stringify({
          event: "contact_received",
          emailDomain,
          messageLength: body.message.length,
        }),
      );

      return json(202, {
        ok: true,
        routedTo: config.supportQueue ?? "manual",
      });
    }

    return json(404, { ok: false, message: "not found" });
  } catch (error) {
    console.error("handler_error", error);
    return json(500, { ok: false, message: "internal error" });
  }
}

实现 CDK stack

lib/small-api-stack.ts 替换为下面内容。重点是 appSecret.grantRead(handler)、日志保留、reserved concurrency、API Gateway throttling,以及 dataTraceEnabled: false,避免生产环境把请求正文写进日志。

import * as cdk from "aws-cdk-lib";
import * as apigateway from "aws-cdk-lib/aws-apigateway";
import * as iam from "aws-cdk-lib/aws-iam";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as nodejs from "aws-cdk-lib/aws-lambda-nodejs";
import * as logs from "aws-cdk-lib/aws-logs";
import * as secretsmanager from "aws-cdk-lib/aws-secretsmanager";
import { Construct } from "constructs";
import * as path from "path";

export class SmallApiStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const appSecret = secretsmanager.Secret.fromSecretNameV2(
      this,
      "AppSecret",
      "prod/claude-code-demo/api",
    );

    const handler = new nodejs.NodejsFunction(this, "ApiHandler", {
      functionName: "claude-code-small-api-prod",
      entry: path.join(__dirname, "../lambda/handler.ts"),
      handler: "handler",
      runtime: lambda.Runtime.NODEJS_20_X,
      architecture: lambda.Architecture.ARM_64,
      memorySize: 256,
      timeout: cdk.Duration.seconds(10),
      logRetention: logs.RetentionDays.ONE_MONTH,
      reservedConcurrentExecutions: 20,
      environment: {
        STAGE: "prod",
        SECRET_ARN: appSecret.secretArn,
      },
      bundling: {
        minify: true,
        sourceMap: true,
        externalModules: ["@aws-sdk/*"],
      },
    });

    appSecret.grantRead(handler);

    handler.addToRolePolicy(
      new iam.PolicyStatement({
        actions: ["cloudwatch:PutMetricData"],
        resources: ["*"],
        conditions: {
          StringEquals: {
            "cloudwatch:namespace": "ClaudeCodeLab/SmallApi",
          },
        },
      }),
    );

    const api = new apigateway.RestApi(this, "SmallApi", {
      restApiName: "claude-code-small-api",
      cloudWatchRole: true,
      deployOptions: {
        stageName: "prod",
        metricsEnabled: true,
        loggingLevel: apigateway.MethodLoggingLevel.INFO,
        dataTraceEnabled: false,
        throttlingRateLimit: 20,
        throttlingBurstLimit: 40,
      },
    });

    const v1 = api.root.addResource("v1");
    v1.addResource("health").addMethod(
      "GET",
      new apigateway.LambdaIntegration(handler),
    );

    v1.addResource("contact").addMethod(
      "POST",
      new apigateway.LambdaIntegration(handler),
      { apiKeyRequired: true },
    );

    const apiKey = api.addApiKey("ClientApiKey", {
      apiKeyName: "claude-code-small-api-prod-client",
    });

    const usagePlan = api.addUsagePlan("BasicUsagePlan", {
      throttle: { rateLimit: 10, burstLimit: 20 },
      quota: { limit: 1000, period: apigateway.Period.MONTH },
    });
    usagePlan.addApiKey(apiKey);
    usagePlan.addApiStage({ stage: api.deploymentStage });

    new cdk.CfnOutput(this, "ApiUrl", { value: api.url });
  }
}

更新 bin/ 下的入口文件。

#!/usr/bin/env node
import * as cdk from "aws-cdk-lib";
import { SmallApiStack } from "../lib/small-api-stack";

const app = new cdk.App();

new SmallApiStack(app, "SmallApiProdStack", {
  env: {
    account: process.env.CDK_DEFAULT_ACCOUNT,
    region: process.env.CDK_DEFAULT_REGION ?? "ap-northeast-1",
  },
});

部署前检查 diff

CDK 第一次部署到某个账号和区域前,需要 bootstrap。

export AWS_REGION=ap-northeast-1
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)

npx cdk bootstrap "aws://${AWS_ACCOUNT_ID}/${AWS_REGION}"
npm test
npx cdk synth
npx cdk diff SmallApiProdStack
npx cdk deploy SmallApiProdStack --require-approval never

让 Claude Code 解释 cdk diff,尤其是 IAM 变化。如果它不能说明某个权限为什么必要,就不要不经人工审查直接上线。

测试 API 与查看日志

部署后,从 CloudFormation output 取 API URL,并测试两个 route。

API_URL=$(aws cloudformation describe-stacks \
  --stack-name SmallApiProdStack \
  --query "Stacks[0].Outputs[?OutputKey=='ApiUrl'].OutputValue" \
  --output text)

curl "${API_URL}v1/health"

KEY_ID=$(aws apigateway get-api-keys \
  --name-query claude-code-small-api-prod-client \
  --query "items[0].id" \
  --output text)

API_KEY=$(aws apigateway get-api-key \
  --api-key "$KEY_ID" \
  --include-value \
  --query "value" \
  --output text)

curl -X POST "${API_URL}v1/contact" \
  -H "content-type: application/json" \
  -H "x-api-key: ${API_KEY}" \
  -d '{"email":"masa@example.com","message":"AWS deployment consultation"}'

查看 Lambda 日志:

aws logs tail "/aws/lambda/claude-code-small-api-prod" \
  --since 1h \
  --follow

CloudWatch Logs 可能需要几分钟才出现。刚部署完看不到日志,不一定代表部署失败。

用 GitHub Actions OIDC 部署

不要把 AWS Access Key 保存到 GitHub Secrets。AWS 中创建 OIDC provider 和部署角色,并把 trust policy 限制到具体仓库和 main 分支。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
        },
        "StringLike": {
          "token.actions.githubusercontent.com:sub": "repo:your-org/your-repo:ref:refs/heads/main"
        }
      }
    }
  ]
}

创建 .github/workflows/deploy-aws.yml

name: deploy-aws-cdk

on:
  push:
    branches: ["main"]
  workflow_dispatch:

permissions:
  id-token: write
  contents: read

concurrency:
  group: prod-aws-cdk
  cancel-in-progress: false

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm

      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-cdk-deploy-prod
          aws-region: ap-northeast-1

      - run: npm ci
      - run: npm test
      - run: npx cdk synth
      - run: npx cdk diff SmallApiProdStack
      - run: npx cdk deploy SmallApiProdStack --require-approval never

部署角色的权限策略取决于你的 CDK bootstrap 模型、CloudFormation 执行角色和组织的权限边界。生产环境不要直接使用 AdministratorAccess。更稳妥的做法是收紧 OIDC trust condition,再用权限边界和 IAM Access Analyzer 逐步削减权限。

加入成本护栏

Serverless 小 API 也可能因为循环调用、垃圾流量或过量日志产生费用。上面的 CDK 已加入 API throttling、usage plan 和 Lambda reserved concurrency。生产前再加 AWS Budgets。

budget.json:

{
  "BudgetName": "small-api-monthly-guardrail",
  "BudgetLimit": {
    "Amount": "20",
    "Unit": "USD"
  },
  "TimeUnit": "MONTHLY",
  "BudgetType": "COST"
}

notifications-with-subscribers.json:

[
  {
    "Notification": {
      "NotificationType": "ACTUAL",
      "ComparisonOperator": "GREATER_THAN",
      "Threshold": 80,
      "ThresholdType": "PERCENTAGE"
    },
    "Subscribers": [
      {
        "SubscriptionType": "EMAIL",
        "Address": "owner@example.com"
      }
    ]
  }
]
aws budgets create-budget \
  --account-id "$AWS_ACCOUNT_ID" \
  --budget file://budget.json \
  --notifications-with-subscribers file://notifications-with-subscribers.json

AWS Budgets 可以跟踪成本和用量,但图表和通知不一定立即出现。不要等到账单异常后才设置。

回滚与常见坑

小型 Lambda + API Gateway 服务最现实的回滚方式,通常是回退坏提交并重新部署。

git revert <bad-commit-sha>
git push origin main

随后检查 API、CloudFormation events 和日志。

curl "${API_URL}v1/health"
aws cloudformation describe-stack-events \
  --stack-name SmallApiProdStack \
  --max-items 20
aws logs tail "/aws/lambda/claude-code-small-api-prod" --since 30m

常见失败模式有三个。第一,部署后有人在 AWS 控制台手动修改,导致 CDK diff 难以理解。第二,把 secret 放进 Lambda 环境变量或日志。第三,生产环境打开 API Gateway request body tracing,把用户请求正文写进日志。本文的 stack 用 dataTraceEnabled: false 避开第三类问题,但团队仍然需要审查习惯。

让 Claude Code 做部署前审查

让 Claude Code 审查时,要求它给出证据,而不是只说“看起来没问题”。

请做 AWS 部署前审查。
检查:
- CDK diff 是否新增过度的 IAM wildcard
- Lambda 是否只从 Secrets Manager 读取秘密信息
- API Gateway 是否有 throttling、usage plan、logging
- CloudWatch Logs 是否可能暴露个人信息或完整请求正文
- rollback 步骤是否写在文档中
- npm test、cdk synth、cdk diff 是否通过

请用表格返回 severity、file、line、fix。

Claude Code 不是最终生产审批人。账号边界、成本风险、安全例外仍然要由人负责。但它可以把审查流程固定下来,减少重复漏检。

总结

Claude Code 做 AWS 部署的价值,不只是更快生成 CDK 代码,而是把服务选择、IaC、CI/CD、IAM、secret、日志、回滚和成本护栏变成一套可重复的部署流程。

小型 Web API 通常可以从 Lambda + API Gateway + CDK 开始。真正需要常驻容器时再转向 ECS/Fargate;主要是静态前端时再考虑 S3 + CloudFront 或 Amplify。关键不是一开始搭得多复杂,而是边界清楚、可回滚、可审查。

如果你的团队想把 Claude Code 规则、AWS CDK 部署、GitHub Actions OIDC、IAM 最小权限和运维检查落到现有仓库,可以预约 Claude Code 咨询。Masa 会结合你的仓库结构、AWS 账号现状和部署失败记录,整理出可以执行的改进计划。

实际试下来,只要 AWS 账号准备好,这类小型线索 API 通常半天到一天可以跑通部署、日志和成本通知。最需要认真处理的不是 Lambda 代码,而是 IAM、secret、回滚和费用可见性。让 Claude Code 加快实现,人类保留生产风险判断,这是安全推进 AWS 部署自动化的关键。

#Claude Code #AWS部署 #CDK #Lambda #GitHub Actions #IAM
免费

免费 PDF: Claude Code 速查表

输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。

我们会妥善保护你的信息,不发送垃圾邮件。

把 Claude Code 变成真正能带来结果的工作流

先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。

Masa

关于作者

Masa

专注 Claude Code 实务流程、团队导入和内容转化的工程师。