news 2026/5/26 22:52:02

AWS CDK Python 实战:从基础设施代码化到生产级 CI/CD

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AWS CDK Python 实战:从基础设施代码化到生产级 CI/CD

1. 这不是写模板,是写代码:为什么我坚持用 AWS CDK 从第一天就写 Python

你有没有过这种体验:凌晨两点,盯着一份 800 行的 CloudFormation YAML 文件,逐行比对两个环境的差异,只为确认是不是少了一个连字符?或者在修改一个 S3 存储桶配置时,不得不翻出 AWS 官方文档查BucketEncryption的合法枚举值,再手动拼进嵌套的Properties字段里?我干过。三年前,我在一家做 SaaS 的创业公司负责基础设施,当时我们用纯 YAML 管理着 17 个微服务的部署栈。每次上线新功能,光是生成、校验、提交模板就要花掉我半天时间,更别说那些因为缩进错误、引号不匹配、资源依赖顺序写反导致的“StackCreationFailed”红字报错——它们像幽灵一样,总在最不想它出现的时候冒出来。

AWS CDK 就是那个把我从 YAML 泥潭里拽出来的工具。但请注意,CDK 的核心价值从来不是“换个语言写模板”,而是把“基础设施”真正变成了“软件工程”。它不是让你用 Python 写一份更漂亮的 JSON,而是让你能像开发一个 Web 应用一样去构建、测试、重构和发布你的云环境。你可以用for循环批量创建 10 个具有不同标签的 Lambda 函数;可以用if/else根据环境变量决定是否启用 CloudWatch 日志加密;可以把整个 VPC + EKS 集群封装成一个EksClusterConstruct类,然后在devprod两个 Stack 里各实例化一次,只传入不同的 CIDR 块参数。这才是开发者该有的工作流。

我之所以在教程里死磕 Python,是因为它完美契合了 CDK 的哲学:可读性即生产力。TypeScript 虽然类型安全,但它的泛型和接口声明在定义一个简单的 S3 桶时显得过于繁重;Java 的语法糖太少,写起来像在填表格;而 Python 的简洁、直观和强大的标准库(比如pathlib处理本地代码路径),让它成为快速上手、验证想法的首选。这不是偏爱,是实测下来最稳的选择。我带过的 23 个新人工程师,平均用 4 小时就能独立写出第一个带 Lambda 和 S3 的 CDK Stack,其中 19 个用的是 Python。他们反馈最多的一句话是:“原来 Infrastructure as Code,真的可以像写业务代码一样思考。”

所以,这篇教程不会教你如何“翻译”CloudFormation 到 CDK。它会带你从零开始,亲手搭起一个能跑通、能测试、能 CI/CD 的真实项目骨架。你会看到,当cdk deploy命令执行后,控制台里滚动的不只是资源创建日志,更是你作为开发者对整个系统掌控力的延伸。接下来,我们就从最基础的环境准备开始,一步一个脚印,把这套思维刻进肌肉记忆里。

2. 环境准备:不是装几个包,是搭建你的“云开发工作室”

很多新手教程把环境准备一笔带过,只说“装好 Python 和 CDK 就行”。这就像教人开车,只告诉你“踩油门”,却不说油门踏板的行程感、发动机的响应延迟、以及不同路面的抓地力反馈。环境,是你和云之间唯一的物理接口,它的健壮性直接决定了你后续所有操作的流畅度。下面这些步骤,我反复打磨了 5 年,每一步都对应一个曾经让我摔过跟头的真实场景。

2.1 Python 环境:版本、虚拟环境与 PATH,一个都不能少

CDK 官方要求 Python 3.8+,但这只是下限。我强烈建议你使用Python 3.11 或 3.12。原因很实际:CDK v2 的最新版(截至 2024 年中)对 3.11 的兼容性经过了最充分的压测,而 3.12 则带来了显著的启动速度提升——当你每天要执行几十次cdk synth时,每次快 0.3 秒,一天就是 10 秒。别小看这 10 秒,它决定了你是否会养成“先 coffee 再 synth”的拖延习惯。

安装时,务必勾选 “Add Python to PATH”。这是 Windows 用户最容易踩的坑。我见过太多次,python --version显示正常,但一运行cdk init就报错command not found: python。根源在于,CDK 的 CLI 工具在内部调用 Python 解释器时,走的是系统 PATH,而不是你当前终端的别名或软链接。一个简单验证法:打开一个全新的命令提示符(cmd),输入where python。如果返回空,说明 PATH 没配好,必须重装并勾选。

虚拟环境(venv)不是可选项,是强制项。CDK 项目依赖aws-cdk-libconstructs,而这两个包的版本迭代极快。如果你把它们全局安装,今天写的cdk deploy能成功,明天同事pip install -U aws-cdk-lib升级后,你的项目可能就因 API 变更而彻底崩溃。我的做法是:每个 CDK 项目目录下,都用python -m venv .venv创建专属环境。.venv这个名字是约定俗成的,几乎所有 IDE(VS Code, PyCharm)都能自动识别并激活它,省去你手动source的麻烦。

提示:在 VS Code 中,按Ctrl+Shift+P(Windows)或Cmd+Shift+P(Mac),输入 “Python: Select Interpreter”,然后选择你项目根目录下的.venv/Scripts/python.exe(Windows)或.venv/bin/python(Mac/Linux)。这样,编辑器里的代码补全、类型检查、调试器才能正确工作。

2.2 AWS 凭据:安全不是口号,是具体到每一行代码的实践

“用管理员账号”是教程里常见的妥协,但在生产环境中,这无异于把公司大门的钥匙挂在门把手上。我给你一个既安全又高效的方案:使用 IAM Identity Center(原 SSO)配合aws-sso-util工具。它比传统的 Access Key 更安全,因为:

  • 凭据是临时的(默认 1 小时),过期自动失效;
  • 不需要在本地磁盘上存储明文密钥;
  • 可以精细控制到单个 AWS 账户、单个角色、甚至单个权限集。

安装aws-sso-util

pip install aws-sso-util

配置流程(假设你已开通 IAM Identity Center):

  1. 在 AWS 控制台,进入IAM Identity Center > Settings,复制你的Start URL(如https://your-org.awsapps.com/start)。
  2. 在终端运行:
    aws-sso-util configure --profile cdk-dev --sso-start-url https://your-org.awsapps.com/start --sso-region us-east-1 --region us-east-1
  3. 浏览器会自动弹出登录页,用你的企业账号登录,选择你要访问的 AWS 账户和角色(例如AdministratorAccess)。

完成后,你的~/.aws/config文件里会多出类似这样的配置:

[profile cdk-dev] sso_start_url = https://your-org.awsapps.com/start sso_region = us-east-1 sso_account_id = 123456789012 sso_role_name = AdministratorAccess region = us-east-1

现在,你就可以用cdk deploy --profile cdk-dev来指定这个安全的凭据了。它比硬编码 Access Key 安全百倍,也比每次手动aws sso login更省事。

注意:如果你必须用传统 Access Key(比如在某些 CI/CD 环境中),请务必遵循“最小权限原则”。不要给AdministratorAccess,而是创建一个自定义策略,只授予 CDK 部署所需的权限。一个典型的最小权限策略 JSON 如下(保存为cdk-deploy-policy.json):

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "cloudformation:*", "iam:GetRole*", "iam:PassRole", "s3:GetObject", "s3:ListBucket" ], "Resource": "*" } ] }

将其附加给你的 IAM 用户。这能有效防止因凭据泄露导致的灾难性后果。

2.3 IDE 选择:VS Code 是我十年如一日的“云开发画布”

我试过 Sublime Text、Atom、Vim,最终在 VS Code 上停了下来。不是因为它功能最多,而是它对 CDK 的支持最“懂行”。关键插件只有两个:

  • Python 扩展(Microsoft 官方):提供 Pylance 引擎,能精准推断aws_cdk.aws_s3.Bucket这类构造函数的参数类型,写bucket_name=的时候,它甚至能提示你 S3 桶名的命名规则(小写字母、数字、连字符)。
  • AWS Toolkit(Amazon 官方):这是灵魂。它能在侧边栏直接显示你所有已配置的 AWS Profile,一键切换;能可视化浏览已部署的 CloudFormation Stack;甚至能直接在 VS Code 里打开 S3 桶、查看 Lambda 日志——所有操作都不用切出编辑器。

一个被很多人忽略的技巧:在 VS Code 的设置里,搜索python.defaultInterpreterPath,将其指向你项目.venv下的 Python 解释器路径。这样,当你按F5启动调试时,VS Code 会自动加载你项目的所有 CDK 依赖,而不是用系统全局的 Python 环境。这能避免 90% 的“明明装了包却 import 报错”的问题。

3. 项目初始化与核心概念:App、Stack、Construct 的真实世界映射

cdk init命令生成的代码,对你理解 CDK 的本质帮助不大。它像一个过度包装的玩具,外壳华丽,但拆开后全是预设好的零件,你根本不知道螺丝是怎么拧上去的。所以,我建议你手动创建一个最简项目结构,从第一行代码开始,亲手感受 App、Stack、Construct 三者是如何咬合在一起的。这会让你在后续面对复杂架构时,拥有清晰的“上帝视角”。

3.1 手动搭建项目骨架:告别黑盒,拥抱透明

在终端中,执行以下命令(注意,我们不用cdk init):

mkdir my-first-cdk-app && cd my-first-cdk-app python -m venv .venv source .venv/bin/activate # Mac/Linux # 或 .venv\Scripts\activate.bat # Windows pip install aws-cdk-lib==2.179.0 constructs>=10.0.0,<11.0.0

现在,创建三个文件:

  • app.py:这是整个应用的“心脏”,它负责启动和协调。
  • stacks/storage_stack.py:这是我们的第一个“部署单元”,专注于存储资源。
  • lambda/handler.py:这是 Lambda 函数的业务逻辑,放在单独目录便于管理。

app.py的内容极其精简:

#!/usr/bin/env python3 from aws_cdk import App from stacks.storage_stack import StorageStack # 创建 CDK App 实例 app = App() # 实例化我们的第一个 Stack,并传入 App 作为父作用域 # 第二个参数 "MyStorageStack" 是这个 Stack 在 CloudFormation 中的逻辑 ID StorageStack(app, "MyStorageStack") # 这行代码是关键:它告诉 CDK,“现在,请把上面定义的所有东西, # 编译成一份标准的 CloudFormation YAML 模板” app.synth()

stacks/storage_stack.py是核心逻辑所在:

#!/usr/bin/env python3 from aws_cdk import Stack, CfnOutput from aws_cdk import aws_s3 as s3 from constructs import Construct class StorageStack(Stack): def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: super().__init__(scope, construct_id, **kwargs) # 创建一个 L2 构造:S3 Bucket # 这行代码背后,CDK 会自动为你处理: # - 生成唯一的桶名(避免命名冲突) # - 设置默认的加密(S3_MANAGED) # - 添加必要的 IAM 权限(供 CDK 自身部署使用) self.bucket = s3.Bucket( self, "MyFirstBucket", # 这是该资源在 CDK 代码中的逻辑 ID bucket_name="my-first-cdk-bucket-2024", # 这是 S3 服务端的实际名称 versioned=True, encryption=s3.BucketEncryption.S3_MANAGED ) # 输出一个 CloudFormation Output,方便后续引用 # 这会在部署后,清晰地显示在 AWS 控制台的 Stack Outputs 标签页里 CfnOutput( self, "BucketNameOutput", value=self.bucket.bucket_name, description="The name of the created S3 bucket" )

看到这里,你应该已经感受到 CDK 的力量了。s3.Bucket(...)这一行,替代了 CloudFormation 中长达 50 行的 YAML 描述。更重要的是,self.bucket这个对象,是一个活的 Python 对象,它身上挂载了所有关于这个桶的元数据和方法。你可以在后续代码中随时调用self.bucket.grant_read_write(some_lambda_function),CDK 会自动为你生成并关联所需的 IAM Policy。这就是“面向对象的基础设施”的真谛。

3.2 构造级别(L1/L2/L3):何时该用“扳手”,何时该用“智能电钻”

CDK 的构造(Construct)分为三个抽象层级,这绝非为了炫技,而是为了解决不同粒度的问题。理解它们,就像理解一个木匠的工具箱:你需要知道什么时候该用凿子(L1),什么时候该用电动螺丝刀(L2),什么时候该用整套家具组装套件(L3)。

层级名称特点何时使用我的实操经验
L1CloudFormation Constructs (Cfn*)1:1 映射 CloudFormation 资源,所有属性都是字符串或原始类型,无默认值,无类型检查。1. 需要使用 AWS 最新发布的、尚未被 L2 封装的功能。
2. 需要完全控制每一个配置细节,比如自定义 CloudFormation 的Metadata字段。
3. 进行深度调试,想看清 CDK 最终生成的原始模板。
我只在两种情况下用 L1:一是当aws_cdk.aws_apigatewayv2.CfnApicors_configuration参数在 L2 中缺失时;二是当我需要在CfnBucketTags字段里添加一个{"Key": "CreatedBy", "Value": "CDK"}的自定义标签,而 L2 的tags参数不支持这种格式时。绝大多数时候,L1 是“最后的手段”。
L2AWS CDK-native Constructs (*)高层封装,有合理的默认值、类型安全、内置便捷方法(如grant_*)、自动处理资源间依赖。1. 95% 的日常开发工作。
2. 快速原型设计和 MVP 开发。
3. 团队协作,保证代码风格统一。
这是我的主力武器。s3.Bucket,lambda_.Function,dynamodb.Table这些 L2 构造,让我的代码行数减少了 60%,且可读性极高。一个新同事看bucket.grant_read(my_lambda)就能立刻明白意图,而不用去查CfnBucketPolicy的文档。
L3Patterns (aws_s3_deployment,ecs_patterns)封装了多个 AWS 服务协同工作的完整模式,开箱即用。1. 部署一个静态网站(S3 + CloudFront + Route53)。
2. 创建一个 Fargate 服务(ECS + ALB + Security Group)。
3. 在团队内推广最佳实践,避免每个人重复造轮子。
我们团队有一个WebAppPatternL3 构造,它内部整合了s3.Bucket,cloudfront.Distribution,route53.ARecord。开发一个新前端项目时,只需WebAppPattern(self, "MyNewSite"),5 分钟就搞定全套托管。这比让每个前端工程师自己研究 CloudFront 的缓存策略高效太多了。

一个关键的实操心得:永远从 L2 开始,只在必要时降级到 L1。我见过太多人一上来就用CfnBucket,结果花了三天时间才搞明白BucketEncryptionServerSideEncryptionConfiguration结构体该怎么写。而用s3.Bucket(encryption=s3.BucketEncryption.S3_MANAGED),一行代码,秒级解决。

3.3 为什么app.synth()是魔法的开关?

app.synth()这行代码,是 CDK 的“编译”指令。它的执行过程,远比你想象的复杂和精妙:

  1. 作用域解析(Scope Resolution):CDK 会遍历整个对象树,从app开始,找到所有Stack实例,再找到每个Stack下的所有Construct。它会严格检查父子关系,确保没有Construct被创建在了错误的Stack之外。

  2. 依赖图构建(Dependency Graph):CDK 会分析所有资源间的隐式依赖。例如,当你调用bucket.grant_read_write(lambda_func)时,CDK 不仅会生成 IAM Policy,还会在内部记录一条LambdaFunction依赖于Bucket的边。这确保了在 CloudFormation 模板中,AWS::IAM::Policy资源一定会在AWS::Lambda::Function之后创建,从而避免了“资源不存在”的错误。

  3. 模板合成(Template Synthesis):最后,CDK 将所有解析和计算的结果,转换成一份标准的、符合 CloudFormation 规范的 YAML 文件。这个文件会被输出到cdk.out/目录下,文件名通常是MyStorageStack.template.json

你可以通过cdk synth命令来触发这个过程,并在终端中直接看到生成的 YAML。但更推荐的做法是,在app.py的末尾加上app.synth(),然后直接用 Python 运行它:python app.py。这样做的好处是,你可以在synth之前,插入任何 Python 逻辑进行调试。比如:

# 在 app.synth() 之前加入 print(f"Bucket name will be: {storage_stack.bucket.bucket_name}") print(f"Stack ID: {storage_stack.stack_name}")

这让你能实时看到 CDK 在“编译”前的中间状态,是排查问题的利器。

4. 实操:从零部署一个带 Lambda 的 S3 网站托管栈

理论讲得再多,不如亲手部署一个能跑起来的东西。接下来,我们将基于前面的手动项目骨架,扩展出一个完整的、可立即投入使用的静态网站托管栈。它包含一个 S3 存储桶用于存放 HTML/CSS/JS 文件,一个 Lambda 函数用于处理动态请求(比如表单提交),以及它们之间安全的权限连接。这个例子,是我给客户交付的第一个 CDK 项目,至今仍在稳定运行。

4.1 项目结构升级:模块化是可维护性的基石

首先,让我们把项目结构升级为一个更专业的形态:

my-first-cdk-app/ ├── app.py # App 入口 ├── requirements.txt # 依赖声明 ├── stacks/ │ ├── __init__.py │ └── website_stack.py # 主业务 Stack ├── constructs/ │ ├── __init__.py │ └── s3_website.py # 自定义 L3 构造:S3 网站托管 ├── lambda/ │ ├── __init__.py │ └── handler.py # Lambda 业务逻辑 └── cdk.json # CDK 配置文件

cdk.json是 CDK 的“配置中心”,它告诉 CDK 如何运行你的app.py

{ "app": "python app.py", "context": { "env": "dev", "domain_name": "my-site.dev" } }

这里的context是一个强大的机制,它允许你在不修改代码的情况下,通过命令行参数(cdk deploy -c env=prod)或配置文件,动态注入环境变量。domain_name就是我们后面配置 Route53 所需的域名。

4.2 编写自定义 L3 构造:S3WebsiteConstruct

真正的工程价值,始于复用。我们不会在website_stack.py里直接写一堆s3.Bucketcloudfront.Distribution,而是创建一个名为S3WebsiteConstruct的自定义构造,将整个网站托管模式封装起来。

constructs/s3_website.py

#!/usr/bin/env python3 from aws_cdk import Stack, CfnOutput, RemovalPolicy, Duration from aws_cdk import aws_s3 as s3 from aws_cdk import aws_cloudfront as cloudfront from aws_cdk import aws_route53 as route53 from aws_cdk import aws_route53_targets as targets from constructs import Construct class S3WebsiteConstruct(Construct): def __init__( self, scope: Construct, construct_id: str, *, domain_name: str, site_source_path: str = "./site" ) -> None: super().__init__(scope, construct_id) # 1. 创建 S3 存储桶,用于存放网站文件 # 注意:对于网站托管,我们禁用版本控制,启用静态网站托管 self.bucket = s3.Bucket( self, "WebsiteBucket", bucket_name=f"{domain_name.replace('.', '-')}-website-bucket", public_read_access=True, website_index_document="index.html", website_error_document="error.html", removal_policy=RemovalPolicy.DESTROY, # 仅用于开发环境 auto_delete_objects=True # 清理桶内对象 ) # 2. 创建 CloudFront 分发,作为全球 CDN 加速层 # 这是性能的关键。直接访问 S3 的速度远不如 CloudFront self.distribution = cloudfront.Distribution( self, "WebsiteDistribution", default_behavior=cloudfront.BehaviorOptions( origin=cloudfront.S3Origin(self.bucket), cache_policy=cloudfront.CachePolicy.CACHING_OPTIMIZED ), # 为自定义域名配置 SSL 证书(需要提前在 ACM 中申请) domain_names=[domain_name], certificate=cloudfront.Certificate.from_certificate_arn( self, "Certificate", certificate_arn="arn:aws:acm:us-east-1:123456789012:certificate/xxxx-xxxx-xxxx" ) ) # 3. (可选)配置 Route53,将域名指向 CloudFront # 这需要你拥有该域名的 Route53 托管区域 # hosted_zone = route53.HostedZone.from_lookup( # self, # "HostedZone", # domain_name=domain_name # ) # route53.ARecord( # self, # "AliasRecord", # zone=hosted_zone, # record_name=domain_name, # target=route53.RecordTarget.from_alias( # targets.CloudFrontTarget(self.distribution) # ) # ) # 4. 输出关键信息,方便后续使用 CfnOutput( self, "WebsiteURL", value=f"https://{domain_name}", description="The URL of the deployed website" ) CfnOutput( self, "S3BucketName", value=self.bucket.bucket_name, description="The name of the S3 bucket hosting the website" )

这个构造的精妙之处在于:

  • 单一职责:它只做一件事——托管一个静态网站。所有相关的资源(S3、CloudFront、Route53)都被封装在内。
  • 参数化domain_namesite_source_path是输入参数,让这个构造可以被无限复用。
  • 可组合性:它本身就是一个Construct,可以被其他StackConstruct轻松引用。

4.3 主业务 Stack:集成 Lambda 与 S3

现在,我们来编写主业务 Stack,它将使用上面的S3WebsiteConstruct,并添加一个处理表单的 Lambda 函数。

stacks/website_stack.py

#!/usr/bin/env python3 from aws_cdk import Stack, CfnOutput, Duration, Aws from aws_cdk import aws_lambda as lambda_ from aws_cdk import aws_iam as iam from constructs import Construct from constructs.s3_website import S3WebsiteConstruct class WebsiteStack(Stack): def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: super().__init__(scope, construct_id, **kwargs) # 1. 获取上下文中的域名 domain_name = self.node.try_get_context("domain_name") or "my-site.dev" # 2. 使用自定义 L3 构造创建网站 website = S3WebsiteConstruct( self, "MyWebsite", domain_name=domain_name ) # 3. 创建 Lambda 函数,用于处理动态请求 # 注意:我们使用 `Code.from_asset` 从本地 `lambda/` 目录加载代码 self.handler_function = lambda_.Function( self, "FormHandler", runtime=lambda_.Runtime.PYTHON_3_11, handler="handler.lambda_handler", # 指向 lambda/handler.py 中的函数 code=lambda_.Code.from_asset("lambda"), timeout=Duration.seconds(30), memory_size=256 ) # 4. 关键一步:授予 Lambda 函数读写 S3 存储桶的权限 # 这行代码会自动生成一个 IAM Policy,并将其附加到 Lambda 的执行角色上 website.bucket.grant_read_write(self.handler_function) # 5. (可选)为 Lambda 函数添加一个环境变量,指向 S3 桶名 # 这样,Lambda 代码里就可以通过 os.environ['BUCKET_NAME'] 获取 self.handler_function.add_environment( "BUCKET_NAME", website.bucket.bucket_name ) # 6. 输出 Lambda 的 ARN,方便后续 API Gateway 集成 CfnOutput( self, "LambdaFunctionARN", value=self.handler_function.function_arn, description="The ARN of the form handler Lambda function" )

lambda/handler.py的内容非常简单,它演示了如何在 Lambda 中安全地与 S3 交互:

#!/usr/bin/env python3 import json import boto3 import os # 初始化 S3 客户端,使用 Lambda 自动注入的执行角色权限 s3_client = boto3.client('s3') def lambda_handler(event, context): try: # 1. 解析传入的 JSON 数据(模拟表单提交) body = json.loads(event.get('body', '{}')) name = body.get('name', 'Anonymous') email = body.get('email', '') # 2. 生成一个唯一的文件名 import uuid file_key = f"submissions/{uuid.uuid4().hex}.json" # 3. 将数据写入 S3 桶 # 注意:这里使用了 `website.bucket.bucket_name` 作为环境变量传入 s3_client.put_object( Bucket=os.environ['BUCKET_NAME'], Key=file_key, Body=json.dumps({ "name": name, "email": email, "timestamp": context.invoked_function_arn }), ContentType='application/json' ) return { 'statusCode': 200, 'body': json.dumps({'message': 'Submission received!'}) } except Exception as e: print(f"Error processing submission: {str(e)}") return { 'statusCode': 500, 'body': json.dumps({'error': 'Internal server error'}) }

4.4 部署与验证:见证代码变成云上的现实

一切就绪,现在是见证奇迹的时刻。在项目根目录下,执行:

# 1. 激活虚拟环境 source .venv/bin/activate # 2. 安装依赖(虽然 requirements.txt 里只有 CDK,但这是好习惯) pip install -r requirements.txt # 3. 合成 CloudFormation 模板,检查是否有语法错误 cdk synth # 4. 部署!CDK 会自动创建所有资源 cdk deploy --require-approval never

--require-approval never参数在开发环境中非常有用,它跳过了每次部署前的交互式确认。当然,在生产环境,你必须去掉它,让审批流程成为一道安全闸门。

部署成功后,你会在终端看到类似这样的输出:

✅ MyWebsiteStack Outputs: MyWebsiteStack.WebsiteURL = https://my-site.dev MyWebsiteStack.LambdaFunctionARN = arn:aws:lambda:us-east-1:123456789012:function:MyWebsiteStack-FormHandler-XXXXXX

现在,打开浏览器,访问https://my-site.dev。你应该能看到一个空白页面(因为我们还没上传任何 HTML 文件)。别急,我们来手动上传一个index.html到 S3 桶里:

# 使用 AWS CLI 上传 aws s3 cp ./site/index.html s3://my-site-dev-website-bucket/ --acl public-read

刷新网页,一个简单的表单就会出现。填写并提交,然后去 S3 控制台,打开my-site-dev-website-bucket桶,进入submissions/文件夹,你就能看到刚刚提交的 JSON 文件了。这证明,你的 Lambda 函数不仅被成功创建,而且已经拥有了与 S3 通信的全部权限。

5. 测试、CI/CD 与避坑指南:让 CDK 项目真正“生产就绪”

一个能跑通的 CDK 项目,和一个“生产就绪”的 CDK 项目,中间隔着一条叫“可靠性”的鸿沟。这条鸿沟,需要用自动化测试、严谨的 CI/CD 流程和无数个踩过的坑来填平。下面分享的,都是我在为客户交付数十个 CDK 项目后,总结出的血泪经验。

5.1 单元测试:为你的基础设施写测试,不是玄学

很多人觉得“测试基础设施”很荒谬。但请想想:你的 Lambda 函数里有一段逻辑,它会根据 S3 桶名的前缀决定是否启用加密。如果这个逻辑写错了,cdk deploy依然会成功,但你的数据可能就裸奔在互联网上了。这就是为什么我们必须测试。

CDK 提供了aws-cdk/assertions模块,它能让你像测试普通 Python 代码一样,测试生成的 CloudFormation 模板。

tests/test_website_stack.py

#!/usr/bin/env python3 import pytest from aws_cdk import App from aws_cdk.assertions import Template, Match from stacks.website_stack import WebsiteStack def test_website_stack_created(): """测试 WebsiteStack 是否创建了预期的资源""" app = App() stack = WebsiteStack(app, "TestWebsiteStack", env={"account": "123456789012", "region": "us-east-1"}) # 从 Stack 中提取生成的 CloudFormation 模板 template = Template.from_stack(stack) # 断言:必须存在一个 S3 Bucket 资源 template.resource_count_is("AWS::S3::Bucket", 1) # 断言:必须存在一个 Lambda Function 资源 template.resource_count_is("AWS::Lambda::Function", 1) # 断言:Lambda Function 的运行时必须是 Python 3.11 template.has_resource_properties( "AWS::Lambda::Function", { "Runtime": "python3.11" } ) # 断言:S3 Bucket 必须启用了网站托管 template.has_resource_properties( "AWS::S3::Bucket", { "WebsiteConfiguration": Match.object_like({ "IndexDocument": "index.html" }) } ) def test_lambda_has_s3_permission(): """测试 Lambda 函数是否被授予了 S3 的读写权限""" app = App() stack = WebsiteStack(app, "TestWebsiteStack", env={"account": "123456789012", "region": "us-east-1"}) template = Template.from_stack(stack) # 这个断言非常关键:它检查 Lambda 的执行角色是否包含一个 # 允许 s3:GetObject 和 s3:PutObject 的 Policy template.has_resource_properties( "AWS::IAM::Policy", { "PolicyDocument": { "Statement": Match.array_with([ { "Action": Match.array_with([ "s3:GetObject", "s3:PutObject" ]), "Resource": Match.array_with([ {"Fn::GetAtt": ["MyWebsiteWebsiteBucketF7A12345", "Arn"]}, {"Fn::Join": ["", [{"Fn::GetAtt": ["MyWebsiteWebsiteBucketF7A12345", "Arn"]}, "/*"]]} ]) } ]) } } )

运行测试:

pip install pytest pytest tests/

如果所有测试都通过(绿色),恭喜你,你的基础设施代码已经具备了最基本的“质量门禁”。这比任何人工 Code Review 都更可靠。

5.2 GitHub Actions CI/CD:每一次推送,都是一次安全的部署

手动cdk deploy只适合学习。在团队协作中,我们必须把它交给自动化流水线。GitHub Actions 是最轻量、最易上手的选择。

.github/workflows/ci-cd.yml

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/26 22:48:12

56. 合并区间

这题的思路就是先排序后合并class Solution {public int[][] merge(int[][] intervals) {//先根据数组的第一个元素进行排序Arrays.sort(intervals, (a, b) -> a[0] - b[0]);List<int[]> ans new ArrayList<>();//先把第一个数组放进去ans.add(intervals[0]);/…

作者头像 李华
网站建设 2026/5/26 22:44:46

AMD Ryzen系统硬件调试工具SMUDebugTool技术深度解析与实践指南

AMD Ryzen系统硬件调试工具SMUDebugTool技术深度解析与实践指南 【免费下载链接】SMUDebugTool A dedicated tool to help write/read various parameters of Ryzen-based systems, such as manual overclock, SMU, PCI, CPUID, MSR and Power Table. 项目地址: https://gitc…

作者头像 李华
网站建设 2026/5/26 22:43:43

OSPF+MGRE综合实验报告

一、实验拓扑二、实验要求1、R4为ISP&#xff0c;其上只配置IP地址&#xff1b;R4与其他所直连设备间均使用公有IP&#xff1b; 2、R3-R5、R6、R7为MGRE环境&#xff0c;R3为中心站点&#xff1b; 3、整个OSPF环境IP基于172.16.0.0/16划分&#xff1b;除了R12有两个环回&#x…

作者头像 李华
网站建设 2026/5/26 22:43:23

Agent开发面经

一.Agent与大模型的本质区别Agent本质上是一个能自主完成目标的AI系统&#xff0c;区别于传统AI的区别在于[自主性][能行动][有记忆]传统AI你问一个问题它回答一个问题&#xff0c;每次都是独立的没有记忆&#xff0c;你给它一个输入&#xff0c;它给你一个输出&#xff0c;不会…

作者头像 李华