hongzz0618/aws-containerized-web-app
GitHub: hongzz0618/aws-containerized-web-app
该项目是一个使用 Terraform 在 AWS ECS Fargate 上运行容器化 Node.js Web 应用的精简基础设施参考模板。
Stars: 1 | Forks: 0
# AWS 容器化 Web 应用
本仓库是一个精简的参考项目,用于在 AWS 上使用 ECS Fargate、Application Load Balancer、私有子网和 Terraform 运行容器化的 Node.js Web 应用程序。
配置有意保持精简,以便于检查基础设施之间的关系。这是一个基准部署,而不是一个完整的容器平台。
## 使用场景
该项目代表了一个小型的 Web 应用程序或内部服务,它作为容器在负载均衡器后方运行,同时将 ECS 任务保留在私有子网中。
实际的示例包括简单的后端 Web 服务或内部服务,其中 ALB 处理公共 HTTP 路由,而 ECS 在私有子网中运行应用程序容器。
## 本实验展示的内容
- 在 AWS Fargate 上运行容器任务
- 通过 Application Load Balancer 路由公共 HTTP 流量
- 将 ECS 任务放置在私有子网中
- 配置具有 CPU 和内存目标跟踪的受限 ECS 服务自动伸缩
- 使用 ECS 部署防护机制进行滚动替换和回滚
- 添加一小组 ALB 和 ECS 的 CloudWatch 警报
- 使用 Terraform 模块分离 VPC、ALB 和 ECS 的关注点
- 识别关于公共访问、私有任务网络和网络成本的权衡
## 架构概述

Terraform 配置创建了一个包含公共和私有子网的 VPC、一个面向 Internet 的 ALB,以及一个 ECS Fargate 服务。ALB 监听 HTTP 端口 80,并将请求转发到端口 3000 上的 Node.js 应用程序容器。
运行时请求流:
- 用户请求到达公共 ALB。
- ALB 将请求转发到端口 3000 上的 ECS 目标组。
- Fargate 任务从私有子网提供 Node.js 应用程序服务。
- ALB 目标健康检查调用 `GET /health`。
- 容器日志被发送到 CloudWatch Logs 用于基本的运行时检查。
- Application Auto Scaling 在配置的小范围内调整 ECS 期望计数。
- CloudWatch 警报跟踪持续不健康的目标、目标 5XX 响应以及 ECS CPU/内存饱和度。
ECS 服务以较低的默认期望计数启动,并可以在配置的最小/最大容量范围内进行伸缩。配置了基本的部署回滚、显式的滚动部署百分比以及 ALB 目标健康行为。
## 使用的 AWS 服务
| 服务 | 在本实验中的角色 |
| --- | --- |
| Amazon VPC | 提供公共和私有网络 |
| Amazon ECS | 运行容器服务 |
| AWS Fargate | 提供 Serverless 容器计算 |
| Amazon ECR | 在私有不可变标签仓库中存储应用程序镜像 |
| Elastic Load Balancing | 将 HTTP 流量路由到 ECS 任务 |
| Application Auto Scaling | 在配置的限制内调整 ECS 服务的期望计数 |
| AWS IAM | 提供 ECS 任务执行和任务角色 |
| Amazon CloudWatch | 存储 ECS 任务日志并评估运行时警报 |
## Terraform 创建的内容
Terraform 配置提供:
- 包含公共和私有子网的 VPC
- 用于私有子网出站访问的 NAT 网关,这在部署期间会产生持续的每小时成本
- 具有不可变标签、推送时扫描、AES256 加密和生命周期保留的私有 ECR 仓库
- 可选的 GitHub OIDC IAM 角色,用于手动将已验证的镜像发布到 ECR 仓库
- Application Load Balancer 和目标组
- ECS 集群、任务定义和服务
- ECS 服务 Application Auto Scaling 目标和 CPU/内存目标跟踪策略
- 用于 ALB 目标健康、目标 5XX 响应和 ECS 服务饱和度的 CloudWatch 警报
- 用于 ALB 和 ECS 的安全组
- 用于 ECS 任务执行和任务权限的 IAM 角色
- 用于 ECS 任务日志的 CloudWatch 日志组
## 仓库布局
| 路径 | 用途 |
| --- | --- |
| `main.tf` | VPC、ALB 和 ECS 模块的根 Terraform 连线 |
| `variables.tf` | 区域、命名、应用程序镜像和运行时输入 |
| `app/` | 最小化的 TypeScript 示例应用和 Dockerfile |
| `.github/workflows/ci.yml` | 用于应用程序、容器和 Terraform 验证的 GitHub Actions 工作流 |
| `.github/workflows/release-container-image.yml` | 用于批准的 ECR 镜像发布的 GitHub Actions 手动工作流 |
| `docs/deployment-runbook.md` | 手动 ECR 镜像发布和摘要锁定的部署工作流 |
| `docs/adr/` | 关于操作权衡的架构决策记录 |
| `modules/vpc/` | VPC、子网和 NAT 网关 |
| `modules/alb/` | ALB、监听器、目标组和 ALB 安全组 |
| `modules/ecs-fargate/` | ECS 集群、任务定义、服务、自动伸缩和 IAM 角色 |
| `modules/ecr/` | 私有 ECR 仓库和生命周期策略 |
| `modules/observability/` | 专注的 CloudWatch 运行时警报 |
| `scripts/` | CI 使用的静态 Terraform 回归检查 |
| `diagram/` | 架构图 |
## 如何部署
前置条件:
- 已在本地配置 AWS 凭证
- 已安装 Terraform
- 为 `app_image_uri` 提供一个摘要锁定的应用程序镜像引用
- 一个提供所选服务的区域
ECR 存在引导依赖:仓库必须在第一个镜像被推送之前存在,而完整的 ECS 部署需要一个现有的镜像。请使用部署运行手册进行受控的手动发布流程:
[容器镜像部署运行手册](docs/deployment-runbook.md)
初始审查:
```
terraform init
cp terraform.tfvars.example terraform.tfvars
terraform plan
```
在 ECR 仓库被引导之后,手动发布一个不可变的 `git-` 镜像标签,检索其摘要,将 `app_image_uri` 设置为 `@sha256:`,并使用常规的 Terraform plan/apply。在执行该受控部署之前,真实的 AWS 运行时验证仍处于待定状态。
默认的 ECS 容量是有意设置的较小值:
- `service_desired_count = 1`
- `service_min_capacity = 1`
- `service_max_capacity = 3`
Application Auto Scaling 可以在运行时调整期望计数。Terraform 保留初始的期望计数用于创建,但会忽略随后的 `desired_count` 漂移,这样它就不会在常规的 apply 期间与自动伸缩器发生冲突。
## 本地容器应用
`app/` 目录包含一个小型的 Node.js 和 TypeScript HTTP 服务,具有 `GET /`、`GET /health` 以及针对未知路由的 404 响应。包含的 Dockerfile 构建并在端口 3000 上运行编译后的服务。
在本地验证应用:
```
cd app
npm ci
npm run typecheck
npm test
npm run build
```
构建容器镜像:
```
docker build -t aws-containerized-web-app-local .
```
当 Docker 守护进程处于活动状态时,从 `app/` 运行容器冒烟测试:
```
npm run test:container
```
冒烟测试会构建镜像,在 `127.0.0.1` 上启动一个临时容器,以只读根文件系统运行它,丢弃 Linux capabilities,启用 `no-new-privileges`,检查 `GET /health` 和 `GET /`,验证运行时 UID 不是 root,通过 Docker 停止容器,检查关闭日志,并删除临时容器。
CI 使用相同的 Dockerfile 和构建上下文,但使用特定于提交的本地标签构建一次最终镜像,然后将同一个本地镜像重用于冒烟测试、SBOM 生成和漏洞扫描。CI 不会将镜像标记为 `latest`、将其推送到 ECR 或将其部署到 AWS。
Terraform 管理用于应用程序镜像的私有 ECR 仓库。仓库标签是不可变的,启用了推送时扫描,并且生命周期策略会在 7 天后使未标记的镜像过期,同时保留最新的 10 个带有 `git-` 标签的镜像。
CI 验证应用程序容器,但不发布镜像。镜像发布由单独的 GitHub Actions 手动工作流处理,该工作流需要 `container-release` GitHub 环境,使用 GitHub OIDC 获取短期的 AWS 凭证,向 ECR 发布不可变的 `git-` 标签,并记录远程摘要。发布镜像并不是 ECS 部署;ECS 仍然通过 `app_image_uri` 接收完整的镜像引用,最好是在遵循运行手册后使用 `@sha256:`。
## CI 验证
本仓库包含一个用于本地样式验证的 GitHub Actions CI 工作流。该工作流:
- 使用 `npm ci` 安装示例应用依赖项
- 对 TypeScript 应用进行类型检查、构建和测试
- 构建最终的示例应用 Docker 镜像一次,并针对该镜像运行容器冒烟测试
- 使用 Trivy `v0.71.2` 从相同的最终镜像生成 CycloneDX JSON SBOM
- 从相同的最终镜像生成 Trivy JSON 漏洞报告,并在 CI 日志中打印人类可读的摘要
- 仅当 Trivy 在最终镜像中发现可修复的 CRITICAL 漏洞时才会失败
- 将 SBOM 和漏洞报告作为 14 天的 GitHub Actions 构件上传
- 运行 `terraform fmt -check -recursive`
- 运行 `terraform init -backend=false -input=false`
- 运行 `terraform validate -no-color`
- 运行专注的静态 Terraform 和 CI 回归检查,涵盖自动伸缩、部署防护机制、警报维度、范围控制、单镜像扫描、构件保留和最小工作流权限
该工作流验证应用程序、容器构建、容器报告和 Terraform 配置。它不会上传 SARIF、向 AWS 进行身份验证、推送到 ECR 或将资源部署到 AWS。
漏洞报告保留所有报告的严重级别以供审查。初始的关口有意设定得较窄:HIGH 漏洞可见但不会阻断 CI,未修复的 CRITICAL 漏洞保留在报告中但不会阻断当前关口,而可修复的 CRITICAL 漏洞会导致工作流失败。这是参考项目的起始策略,并不声称镜像没有风险或 SBOM 是完整的合规清单。
## 手动镜像发布
`Release Container Image` 工作流仅支持 `workflow_dispatch`。它要求从 `main` 运行、确认完整的 40 个字符的 SHA,并通过固定的 `container-release` GitHub 环境的批准,然后才能担任可选的 ECR 发布角色。该工作流构建最终镜像一次,对同一个本地镜像进行冒烟测试和扫描,然后使用 OIDC 仅将不可变的 `git-` 标签推送到现有的 ECR 仓库。
发布工作流将解析出的 ECR 摘要 URI 写入作业摘要,并且不会更新 ECS 任务定义、更新 ECS 服务、创建 GitHub Release、为镜像签名、生成出处证明或部署 AWS 资源。
## 如何清理
```
terraform destroy
```
在确认之前审查销毁计划。负载均衡器、NAT 网关、Fargate 任务和 CloudWatch 资源如果被遗留下来,会继续产生成本。自动伸缩可以将服务增加到 `service_max_capacity`,而滚动部署可能会暂时运行替换任务,直到达到配置的部署最大百分比。
ECR 仓库使用 `force_delete = false`。如果此设置保持为 false,Terraform 无法销毁非空仓库;请在完全清理之前有意删除镜像。这可以保护存储的镜像免遭意外删除。
## 安全说明
- ALB 监听器是出于学习/演示目的的端口 80 上的公共 HTTP。真实的部署应使用 ACM 添加 HTTPS,将 HTTP 重定向到 HTTPS,并审查安全控制。
- ECS 任务入口仅限于 ALB 安全组。
- ECS 任务定义启用只读根文件系统并丢弃 Linux capabilities。ECS 任务定义不公开等同于本地 Docker 冒烟测试的直接 `no-new-privileges` 设置。
- 执行角色用于 ECS 启动操作,例如拉取镜像和写入日志。任务角色没有额外的应用程序策略,因为该应用程序不调用 AWS API。
- 私有 ECS 任务继续使用 NAT 进行出站访问,包括镜像拉取和服务调用。
- 演示安全组中的出站流量范围很广。
- 未实现身份验证或应用层授权。
## 成本说明
潜在的持续成本驱动因素包括:
- NAT 网关的小时收费和数据处理
- Application Load Balancer 的小时收费
- Fargate 任务运行时
- CloudWatch Logs 存储和摄取
- CloudWatch 警报
如果您不需要它继续运行,请在测试后销毁堆栈。
## 操作说明
- ECS 目标跟踪使用 65% 的 CPU 和 75% 的内存。
- 扩容冷却时间短于缩容冷却时间,以便容量能够更快地响应压力,并在突发后更缓慢地移除容量。
- CPU 或内存目标跟踪都可以请求扩容。缩容是保守的,只有在目标跟踪策略同意可以减少容量时才会继续。
- 在 ECS 部署期间,目标跟踪缩容会被暂停,而扩容仍然可以发生。`service_max_capacity` 仍然是硬性的自动伸缩限制。
- 即使通知操作为空,也会创建运行时警报。
- 要发送通知,请通过 `alarm_action_arns` 和 `ok_action_arn` 传递现有的操作 ARN。
- 空的操作列表意味着警报在 CloudWatch 中可见,但不会发送通知。
## 限制
- ALB 使用 HTTP,而不是 HTTPS。
- 未配置自定义域或证书。
- ECR 镜像发布是手动的;CI 不会推送镜像或部署到 AWS。
- 手动 ECR 发布工作流和 IAM 配置是静态验证的。实时的 OIDC 担任和 ECR 发布需要 Terraform 部署、GitHub 环境设置和手动工作流执行。
- ECS 任务使用 `PORT=3000` 和 `SHUTDOWN_TIMEOUT_MS=10000`。ECS `stopTimeout` 设置为 15 秒,并且 ALB 目标组注销延迟设置为 30 秒。
- CI 工作流验证更改,但不发布容器镜像或部署到 AWS。
- 自动伸缩活动、警报数据和回滚行为仍然需要受控的 AWS 部署验证。
- 警报阈值是初始参考值,应在观察到真实流量后进行审查。
- 在学习或沙箱账户之外使用此模式之前,应审查 IAM 和安全组规则。
## 架构权衡
- 与自我管理的容器主机相比,ECS Fargate 减少了计算管理,但对底层运行时环境的控制较少。
- 具有私有 ECS 任务的公共 ALB 保持任务网络私有,同时仍然暴露 HTTP 入口点。
- NAT 网关简化了私有子网的出站访问,但它增加了持续成本。
- 目标跟踪保持容量受限且简单,但在依赖阈值之前仍然需要真实的运行时验证。
- CloudWatch 警报提高了可见性,但空的操作列表不会通知任何人。
- 紧凑的 Terraform 结构更易于检查,但不包含完整容器平台所期望的控制。
## 下一步改进
- 添加对 ACM 的 HTTPS 监听器支持。
- 执行受控的 AWS 部署并捕获运行时验证证据。
## 项目成熟度
成熟度:基准参考项目。
这个仓库对于讨论 Fargate 网络、ALB 路由、容器健康检查、受限的服务伸缩和镜像部署权衡非常有用。在将其调整为适用于真实工作负载之前,它需要额外的安全性、操作和部署验证。
标签:AWS, DPI, ECS, ECS Fargate, MITM代理, NIDS, Terraform, 容器化, 自定义脚本, 请求拦截, 运维