4. 基础设施即代码与AWS CDK入门
当平台团队开始把“能跑起来”的环境变成“可重复交付”的产品时,手工点控制台的方式就会迅速失效:同一个 VPC 在不同账号里子网划分不一致,安全组规则靠记忆补,S3 桶策略改过之后没人知道原始版本。基础设施即代码(Infrastructure as Code,IaC)要解决的核心问题,是把基础设施的期望状态写成可版本化、可评审、可重复执行的代码,让环境本身成为交付物的一部分。
在 AWS 上,IaC 有多种实现路径。CloudFormation 是原生的声明式模板引擎,YAML/JSON 描述资源,由 AWS 负责编排与状态跟踪;Terraform 通过 HCL 和 provider 管理多云资源;而 AWS CDK(Cloud Development Kit)则把基础设施定义提升到通用编程语言层面——你可以用 TypeScript、Python、Java、C#、Go 编写代码,CDK 在合成阶段将其转换为 CloudFormation 模板,再交由 CloudFormation 部署。对平台工程团队而言,CDK 的价值在于:可以用类型系统、循环、条件、函数和包管理来组织复杂基础设施,同时保留 CloudFormation 的漂移检测、回滚和变更集能力。
CDK 的三个核心概念:构造器、堆栈与阶段
理解 CDK,关键是理解它的三层抽象。最底层是 Construct(构造器),它是 CDK 应用的基本组成单元,代表一个或多个云资源的集合。CDK 提供了 L1、L2、L3 三个层次的构造器:L1 构造器(Cfn 前缀)与 CloudFormation 资源一一对应,参数最全但最原始;L2 构造器在 L1 之上封装了合理默认值、便捷方法和资源间关联,例如 s3.Bucket 会自动处理加密、版本控制、访问日志等常见配置;L3 构造器也叫 Pattern,用于表达常见架构模式,例如 ApplicationLoadBalancedFargateService 一次性创建 ALB、ECS 集群、任务定义、服务和安全组。
中间层是 Stack(堆栈),它对应一个 CloudFormation 堆栈,是部署的最小单元。一个 Stack 内的资源会一起被创建、更新或删除,因此堆栈边界往往决定了故障域和部署节奏。例如把网络层放在 NetworkStack,把应用层放在 AppStack,把数据层放在 DataStack,可以让网络变更与应用发布解耦。
最上层是 Stage(阶段),它把一个或多个 Stack 组合成一个可部署的环境单元,例如 DevStage、StagingStage、ProdStage。Stage 的意义在于:同一套代码可以通过不同参数合成出多个环境,并且每个 Stage 可以独立部署、独立回滚。CDK 还支持 cdk deploy --all 按依赖顺序部署整个 Stage,也支持 cdk diff 查看某个 Stage 与当前实际状态的差异。
编写第一个平台基础设施项目
下面以一个平台工程中常见的“网络 + 对象存储”最小项目为例,演示从初始化到部署的完整过程。假设使用 TypeScript,Node.js 18 以上,已配置好 AWS CLI 凭证。
# 1. 安装 CDK CLI 并初始化项目
npm install -g aws-cdk
mkdir platform-infra && cd platform-infra
cdk init app --language typescript
# 2. 安装所需构造器库
npm install @aws-cdk/aws-ec2 @aws-cdk/aws-s3 @aws-cdk/aws-iam
# 3. 查看项目结构
# bin/platform-infra.ts —— 应用入口,定义 Stage 与 Stack
# lib/network-stack.ts —— 网络堆栈
# lib/storage-stack.ts —— 存储堆栈
# cdk.json —— CDK 应用配置
在 bin/platform-infra.ts 中定义 Stage 与 Stack 的组合关系。这里把 Dev 和 Prod 两个环境用同一个 Stage 类参数化,避免复制粘贴:
import * as cdk from 'aws-cdk-lib';
import { NetworkStack } from '../lib/network-stack';
import { StorageStack } from '../lib/storage-stack';
const app = new cdk.App();
interface EnvProps {
account: string;
region: string;
cidr: string;
bucketName: string;
}
const environments: Record<string, EnvProps> = {
dev: { account: '111122223333', region: 'us-east-1', cidr: '10.10.0.0/16', bucketName: 'platform-dev-assets' },
prod: { account: '777788889999', region: 'us-east-1', cidr: '10.20.0.0/16', bucketName: 'platform-prod-assets' },
};
for (const [name, props] of Object.entries(environments)) {
const env = { account: props.account, region: props.region };
const network = new NetworkStack(app, `${name}-NetworkStack`, { env, cidr: props.cidr });
new StorageStack(app, `${name}-StorageStack`, {
env,
bucketName: props.bucketName,
vpc: network.vpc,
});
}
在 lib/network-stack.ts 中,使用 L2 构造器 ec2.Vpc 创建带公有子网和私有子网的 VPC。注意 maxAzs: 2 表示跨两个可用区,natGateways: 1 在 Dev 环境控制成本,Prod 环境可改为 2 实现高可用:
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import { Construct } from 'constructs';
interface NetworkStackProps extends cdk.StackProps {
cidr: string;
}
export class NetworkStack extends cdk.Stack {
public readonly vpc: ec2.Vpc;
constructor(scope: Construct, id: string, props: NetworkStackProps) {
super(scope, id, props);
this.vpc = new ec2.Vpc(this, 'PlatformVpc', {
ipAddresses: ec2.IpAddresses.cidr(props.cidr),
maxAzs: 2,
natGateways: 1,
subnetConfiguration: [
{ name: 'Public', subnetType: ec2.SubnetType.PUBLIC, cidrMask: 24 },
{ name: 'Private', subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, cidrMask: 24 },
],
});
cdk.Tags.of(this.vpc).add('Environment', this.node.id.split('-')[0]);
cdk.Tags.of(this.vpc).add('ManagedBy', 'aws-cdk');
}
}
在 lib/storage-stack.ts 中创建 S3 桶,并显式开启版本控制、服务端加密和生命周期规则。这里使用 L2 构造器,它会自动生成合理的桶策略和阻止公共访问配置:
import * as cdk from 'aws-cdk-lib';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import { Construct } from 'constructs';
interface StorageStackProps extends cdk.StackProps {
bucketName: string;
vpc: ec2.Vpc;
}
export class StorageStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: StorageStackProps) {
super(scope, id, props);
const bucket = new s3.Bucket(this, 'AssetBucket', {
bucketName: props.bucketName,
versioned: true,
encryption: s3.BucketEncryption.S3_MANAGED,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
lifecycleRules: [
{ transitions: [{ storageClass: s3.StorageClass.INFREQUENT_ACCESS, transitionAfter: cdk.Duration.days(30) }] },
{ expiration: cdk.Duration.days(365) },
],
removalPolicy: cdk.RemovalPolicy.RETAIN,
});
new cdk.CfnOutput(this, 'BucketArn', { value: bucket.bucketArn });
}
}
代码写完后,执行 cdk synth 会在 cdk.out/ 下生成 CloudFormation 模板。建议先查看模板中的资源逻辑 ID 和属性,确认没有意外资源。随后执行 cdk diff dev-NetworkStack 查看与当前账号实际状态的差异,再执行 cdk deploy dev-NetworkStack dev-StorageStack 部署。部署完成后,在 CloudFormation 控制台可以看到两个堆栈,在 S3 控制台可以看到桶已开启版本控制和加密。
cdk bootstrap aws://账号ID/区域,CDK 会在目标账号创建用于存放模板和资产的 S3 桶及 IAM 角色。每个账号+区域只需执行一次。如果使用多个环境,需要对每个账号分别 bootstrap。
removalPolicy 建议保持 RETAIN,避免误删堆栈导致数据丢失。
如何判断项目结构是否合理
一个可维护的 CDK 项目通常满足几个特征:Stack 边界与变更频率一致,网络、数据、应用分层清晰;环境差异通过参数或 Stage 表达,而不是复制多份代码;构造器优先使用 L2 而非 L1,除非确实需要 L1 的完整参数控制;所有资源都打上 Environment、ManagedBy、CostCenter 等标签,便于成本分摊和审计。部署后可以通过 cdk diff 验证代码与实际状态是否一致,如果 diff 输出为空,说明没有漂移;如果出现非预期差异,应优先排查是否有人通过控制台手工修改了资源。
掌握构造器、堆栈与阶段这三个概念之后,后续就可以在此基础上引入 CI/CD 流水线,把 cdk deploy 纳入自动化流程,实现从代码提交到环境更新的完整闭环。