Vấn Đề Với CloudFormation
Nếu bạn đã từng quản lý hạ tầng AWS, chắc bạn hiểu cái cảm giác đau đó. Một setup đơn giản API Gateway + Lambda + DynamoDB thôi mà có thể phình to thành 500+ dòng YAML. Bạn copy-paste cùng một VPC config qua mười stack, rồi mới phát hiện ra mình quên cập nhật CIDR block ở ba chỗ khác nhau. Việc refactor trở nên như đào bới khảo cổ vậy.
CloudFormation vẫn hoạt động được — nhưng nó coi hạ tầng của bạn như một tài liệu, chứ không phải code. Không có vòng lặp. Không có hàm. Không có kiểm tra kiểu dữ liệu. Khi có sự cố, thông báo lỗi thì mơ hồ khó hiểu. Rollback một stack có độ phức tạp vừa phải? Hãy tính sẵn ít nhất 20 phút.
Vấn đề không nằm ở bản thân CloudFormation — mà là ở mô hình tư duy. Declarative YAML được thiết kế để cấu hình dễ đọc cho con người, chứ không phải để vận hành 15 microservice trên 3 môi trường. Vượt qua một quy mô nhất định, bạn không còn viết hạ tầng nữa. Bạn đang bảo trì YAML full-time.
Khái Niệm Cốt Lõi: AWS CDK Thực Sự Làm Gì
AWS CDK (Cloud Development Kit) đảo ngược mô hình đó. Bạn viết code TypeScript để tự động sinh ra CloudFormation template cho bạn — hoàn toàn tự động, tại thời điểm deploy.
Sự thay đổi này mang lại nhiều lợi ích thực sự. Bạn có được IDE autocomplete và kiểm tra kiểu dữ liệu tại compile-time. Vòng lặp và điều kiện thực sự. Khả năng chia sẻ hạ tầng dưới dạng npm package có versioning. Và quan trọng nhất, lỗi sẽ xuất hiện trước khi deploy — không phải giữa chừng một lần rollout kéo dài 20 phút.
CDK tổ chức tài nguyên theo ba cấp độ construct:
- L1 (các lớp Cfn*): Wrapper trực tiếp cho CloudFormation resource — toàn quyền kiểm soát, nhưng dài dòng. Ánh xạ 1:1 với mọi property trong CloudFormation spec.
- L2: Abstraction cấp cao hơn với các giá trị mặc định hợp lý và helper method. Đây là thứ bạn sẽ dùng cho 90% công việc.
- L3 (Patterns): Các giải pháp được xây dựng sẵn, có quan điểm rõ ràng cho các kiến trúc đa resource phổ biến như ECS service đằng sau ALB.
Chạy cdk deploy và CDK sẽ tổng hợp TypeScript của bạn thành CloudFormation template rồi deploy. YAML vẫn tồn tại bên dưới — bạn chỉ là không bao giờ phải động vào nó.
Sau khi chuyển từ raw template sang CDK, chúng tôi đã giảm từ việc bảo trì khoảng 8.000 dòng YAML trên 12 stack xuống còn một codebase TypeScript mà bất kỳ developer nào cũng có thể đọc hiểu trong một buổi chiều. Sự thay đổi đó đã làm thay đổi cách cả team tư duy về hạ tầng.
Thực Hành
Yêu Cầu và Cài Đặt
Bạn cần Node.js 18+, AWS CLI đã được cấu hình với credentials, và một tài khoản AWS đang hoạt động.
npm install -g aws-cdk
cdk --version
Bootstrap tài khoản của bạn một lần cho mỗi cặp account/region — thao tác này tạo ra S3 bucket và IAM role mà CDK cần để hoạt động:
aws configure # Thiết lập access key, secret và region mặc định của bạn
cdk bootstrap aws://YOUR_ACCOUNT_ID/ap-northeast-1
Tạo Project CDK Đầu Tiên
mkdir my-infra && cd my-infra
cdk init app --language typescript
Hai file đảm nhận toàn bộ công việc thực sự:
my-infra/
├── bin/
│ └── my-infra.ts # Điểm vào của app — khởi tạo các stack
├── lib/
│ └── my-infra-stack.ts # Định nghĩa stack — nơi các resource tồn tại
├── cdk.json
└── package.json
Xây Dựng Stack Thực Tế: S3 + Lambda + API Gateway
Mở lib/my-infra-stack.ts và thay thế nội dung mặc định bằng một pattern thực tế:
import * as cdk from 'aws-cdk-lib';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import { Construct } from 'constructs';
export class MyInfraStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// S3 bucket với versioning và mã hóa phía server
const bucket = new s3.Bucket(this, 'StorageBucket', {
versioned: true,
encryption: s3.BucketEncryption.S3_MANAGED,
removalPolicy: cdk.RemovalPolicy.DESTROY, // Đổi thành RETAIN cho môi trường production
});
// Lambda handler
const handler = new lambda.Function(this, 'ApiHandler', {
runtime: lambda.Runtime.NODEJS_20_X,
code: lambda.Code.fromAsset('src/lambda'),
handler: 'index.handler',
environment: {
BUCKET_NAME: bucket.bucketName,
},
});
// Một dòng này tự động cấp đúng IAM policy
bucket.grantReadWrite(handler);
// API Gateway kết nối với Lambda
const api = new apigateway.RestApi(this, 'MyApi', {
restApiName: 'My Service API',
});
api.root.addMethod('GET', new apigateway.LambdaIntegration(handler));
new cdk.CfnOutput(this, 'ApiUrl', { value: api.url });
}
}
Lệnh gọi bucket.grantReadWrite(handler) đó đang làm việc thực sự. Nó tự động xây dựng IAM policy chính xác và gắn vào Lambda execution role. Đoạn tương đương trong CloudFormation thuần là 15–20 dòng YAML — và bạn phải cập nhật thủ công mỗi khi tên bucket hoặc ARN role thay đổi.
Tạo Lambda handler:
mkdir -p src/lambda
// src/lambda/index.ts
export const handler = async (event: any) => {
return {
statusCode: 200,
body: JSON.stringify({
message: 'Xin chào từ CDK!',
bucket: process.env.BUCKET_NAME,
}),
};
};
Xem Trước Trước Khi Deploy
Đừng deploy mù quáng. Hãy kiểm tra diff trước:
cdk diff # Hiển thị các thay đổi resource — giống như git diff cho hạ tầng
cdk synth # Tạo ra CloudFormation template thô để bạn có thể kiểm tra
Khi diff trông ổn, deploy thôi:
cdk deploy
CDK sẽ nhắc bạn xác nhận mọi thay đổi quyền IAM — đây là lưới bảo vệ được thiết kế cố ý. Sau đó, nó stream tiến trình deploy trực tiếp vào terminal của bạn.
Hỗ Trợ Đa Môi Trường Mà Không Bị Trùng Lặp
Đây là lúc CDK vượt trội rõ ràng so với việc viết template tay. Tham số hóa stack của bạn ở cấp app:
// bin/my-infra.ts
import * as cdk from 'aws-cdk-lib';
import { MyInfraStack } from '../lib/my-infra-stack';
const app = new cdk.App();
const env = app.node.tryGetContext('env') || 'dev';
new MyInfraStack(app, `MyInfra-${env}`, {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
},
});
Một flag duy nhất để deploy lên bất kỳ môi trường nào:
cdk deploy -c env=staging
cdk deploy -c env=prod
Construct Tái Sử Dụng: Khi CDK Trở Nên Nghiêm Túc
Khi bắt đầu xây dựng nhiều stack, bạn sẽ nhận thấy các pattern giống nhau xuất hiện khắp nơi. CDK cho phép bạn trích xuất chúng thành các composable construct tích hợp sẵn tiêu chuẩn của team:
// lib/constructs/monitored-lambda.ts
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
import { Construct } from 'constructs';
interface MonitoredLambdaProps extends lambda.FunctionProps {}
export class MonitoredLambda extends Construct {
public readonly function: lambda.Function;
constructor(scope: Construct, id: string, props: MonitoredLambdaProps) {
super(scope, id);
this.function = new lambda.Function(this, 'Function', {
...props,
tracing: lambda.Tracing.ACTIVE, // X-Ray luôn được bật
});
// Mỗi Lambda tự động nhận được cảnh báo tỷ lệ lỗi
new cloudwatch.Alarm(this, 'ErrorAlarm', {
metric: this.function.metricErrors(),
threshold: 5,
evaluationPeriods: 1,
});
}
}
Từ đây trở đi, mỗi Lambda mà team bạn tạo ra đều tự động có X-Ray tracing và CloudWatch error alarm theo mặc định. Không ai quên. Không có sự lệch lạc cấu hình giữa các stack.
Xóa Bỏ Resource
cdk destroy
CDK sẽ xóa tất cả những gì nó đã provision. Các resource được đánh dấu RemovalPolicy.RETAIN — chẳng hạn như database production — sẽ bị bỏ qua kèm cảnh báo thay vì bị xóa.
Khi Nào Nên Dùng CDK, Khi Nào Thì Không
CDK xứng đáng với chi phí của nó khi độ phức tạp hạ tầng đủ để cần đến abstraction. Pattern lặp lại trên các môi trường, một team đã quen với TypeScript hoặc Python, nhu cầu phân phối hạ tầng dưới dạng package có versioning — bất kỳ điều nào trong số này cũng nghiêng cán cân. Cả ba? Quá rõ ràng rồi.
Với một vài resource tĩnh ít thay đổi, CloudFormation thuần hoặc AWS console thực sự đơn giản hơn. Không cần thủ tục rườm rà. Nếu team bạn đang dùng sâu Terraform, cú pháp HCL và module registry đồ sộ của nó cũng có thể phù hợp hơn — đặc biệt với các setup đa cloud, vì CDK chỉ dành riêng cho AWS.
Nhưng với các team đang vận hành trên AWS ở quy mô thực sự, CDK có một lợi thế cộng hưởng: hạ tầng của bạn trở thành một codebase thực sự. Viết unit test với aws-cdk-lib/assertions. Review thay đổi hạ tầng trong pull request. Phát hiện regression trước khi chúng ảnh hưởng đến production.
Các team tận dụng CDK tốt nhất đối xử với lib/ giống như code ứng dụng — composable construct, độ phủ test, lịch sử commit có ý nghĩa. Kỷ luật đó sẽ nhanh chóng được đền đáp khi bạn đang quản lý 30+ stack trên dev, staging và prod, và một kỹ sư mới có thể nắm bắt toàn bộ kiến trúc chỉ trong một buổi làm việc.

