Bài viết thuộc series “Chinh phục CDK”
Giới thiệu
Trong bài trước, chúng ta đã tìm hiểu cách sử dụng CDK để cung cấp hạ tầng trên AWS. Trong bài này, chúng ta sẽ tìm hiểu về các thành phần cơ bản của CDK và cách tổ chức code.
Ở bài này, chúng ta cùng xây dựng hạ tầng cho một ứng dụng là Q&A: ứng dụng cho phép người dùng tạo câu hỏi + câu trả lời. Và các khái niệm cơ bản của CDK sẽ được giải thích thông qua ví dụ Q&A.
Chuẩn bị
Tạo thư mục và khởi tạo ứng dụng:
mkdir question-service && cd question-service
cdk init --language go
Tải thư viện:
go get
Ta bắt đầu với hạ tầng cơ bản nhất. Ứng dụng Q&A với một máy chủ để triển khai API và Database để lưu dữ liệu. Hình minh họa:
Để tạo hạ tầng trên ta mở tệp tin question-service.go
và dán đoạn code sau vào.
package main
import (
"os"
"github.com/aws/aws-cdk-go/awscdk/v2"
"github.com/aws/aws-cdk-go/awscdk/v2/awsec2"
"github.com/aws/aws-cdk-go/awscdk/v2/awsrds"
"github.com/aws/constructs-go/constructs/v10"
"github.com/aws/jsii-runtime-go"
)
type QuestionServiceStackProps struct {
awscdk.StackProps
}
func NewQuestionServiceStack(scope constructs.Construct, id string, props *QuestionServiceStackProps) awscdk.Stack {
var sprops awscdk.StackProps
if props != nil {
sprops = props.StackProps
}
stack := awscdk.NewStack(scope, &id, &sprops)
// VPC Construct
vpc := awsec2.Vpc_FromLookup(scope, jsii.String("DefaultVPC"), &awsec2.VpcLookupOptions{IsDefault: jsii.Bool(true)})
// RDS Construct
awsrds.NewDatabaseInstance(stack, jsii.String("Postgres"), &awsrds.DatabaseInstanceProps{
Engine: awsrds.DatabaseInstanceEngine_POSTGRES(),
InstanceType: awsec2.NewInstanceType(jsii.String("t3.micro")),
Credentials: awsrds.Credentials_FromPassword(
jsii.String("question"),
awscdk.NewSecretValue("question", &awscdk.IntrinsicProps{}),
),
PubliclyAccessible: jsii.Bool(true),
VpcSubnets: &awsec2.SubnetSelection{SubnetType: awsec2.SubnetType_PUBLIC},
Vpc: vpc,
})
// EC2 Construct
awsec2.NewInstance(stack, jsii.String("Server"), &awsec2.InstanceProps{
InstanceType: awsec2.NewInstanceType(jsii.String("t3.micro")),
MachineImage: awsec2.NewAmazonLinuxImage(&awsec2.AmazonLinuxImageProps{
Generation: awsec2.AmazonLinuxGeneration_AMAZON_LINUX_2,
}),
Vpc: vpc,
})
return stack
}
func main() {
defer jsii.Close()
// App
app := awscdk.NewApp(nil)
// Stack
NewQuestionServiceStack(app, "QuestionServiceStack", &QuestionServiceStackProps{
awscdk.StackProps{
Env: env(),
},
})
app.Synth(nil)
}
func env() *awscdk.Environment {
return &awscdk.Environment{
Account: jsii.String(os.Getenv("CDK_DEFAULT_ACCOUNT")),
Region: jsii.String(os.Getenv("CDK_DEFAULT_REGION")),
}
}
Tiếp theo ta sẽ tìm hiểu các thành phần cơ bản của CDK trong đoạn code.
Thành phần cơ bản
Một ứng dụng AWS CDK bao gồm 3 phần cơ bản:
- App
- Stack
- Construct
Với App là định danh cho một ứng dụng, trong một App bao gồm một hay nhiều Stack.
Stack là tập hợp các tài nguyên có liên quan với nhau, trong một Stack bao gồm một hay nhiều Construct.
Construct là thành phần cơ bản nhất của CDK, được dùng để định danh cho một tài nguyên cụ thể trên AWS, ví dụ Amazon Simple Storage Service (Amazon S3).
Hạ tầng ban đầu của ta được mô tả trong CDK như sau:
App
Trong Go thì chương trình của ta sẽ bắt đầu với hàm main()
, nên ta khởi tạo App ở hàm main()
.
func main() {
defer jsii.Close()
// App
app := awscdk.NewApp(nil)
...
app.Synth(nil)
}
Stack
Tiếp theo ta sẽ tạo Stack, cú pháp để tạo Stack:
func main() {
defer jsii.Close()
// App
app := awscdk.NewApp(nil)
// Stack
stack := awscdk.NewStack(app, jsii.String("QuestionServiceStack"), &QuestionServiceStackProps{
awscdk.StackProps{
Env: env(),
},
})
app.Synth(nil)
}
Ta truyền biến app
vào trong hàm NewStack
với mục đích báo cho Stack biết nó thuộc App nào. Và để dễ cho việc phân biệt và tập họp các Construct liên quan lại với nhau thì thay vì viết code ở ngoài hàm main()
ta nên tạo cho mỗi Stack một hàm riêng.
func NewQuestionServiceStack(scope constructs.Construct, id string, props *QuestionServiceStackProps) awscdk.Stack {
var sprops awscdk.StackProps
if props != nil {
sprops = props.StackProps
}
stack := awscdk.NewStack(scope, &id, &sprops)
...
return stack
}
func main() {
defer jsii.Close()
// App
app := awscdk.NewApp(nil)
// Stack
NewQuestionServiceStack(app, "QuestionServiceStack", &QuestionServiceStackProps{
awscdk.StackProps{
Env: env(),
},
})
app.Synth(nil)
}
Construct
Cuối cùng ta khai báo các Construct cho các tài nguyên liên quan. Các hàm dùng để tạo Construct thường có các thông tin như sau:
<NameOfConstruct>(scope, id, props)
scope
: dùng để Construct biết nó thuộc Stack nàoid
: định danh cho Construct trong CDKprops
: các thuộc tính liên quan của tài nguyên
VPC Construct:
func NewQuestionServiceStack(scope constructs.Construct, id string, props *QuestionServiceStackProps) awscdk.Stack {
var sprops awscdk.StackProps
if props != nil {
sprops = props.StackProps
}
stack := awscdk.NewStack(scope, &id, &sprops)
// VPC Construct
vpc := awsec2.Vpc_FromLookup(stack, jsii.String("DefaultVPC"), &awsec2.VpcLookupOptions{IsDefault: jsii.Bool(true)})
...
return stack
}
Ta dùng hàm Vpc_FromLookup
để lấy VPC mặc định thay vì tạo một VPC mới.
RDS Construct:
func NewQuestionServiceStack(scope constructs.Construct, id string, props *QuestionServiceStackProps) awscdk.Stack {
var sprops awscdk.StackProps
if props != nil {
sprops = props.StackProps
}
stack := awscdk.NewStack(scope, &id, &sprops)
// VPC Construct
vpc := awsec2.Vpc_FromLookup(stack, jsii.String("DefaultVPC"), &awsec2.VpcLookupOptions{IsDefault: jsii.Bool(true)})
// RDS Construct
awsrds.NewDatabaseInstance(stack, jsii.String("Postgres"), &awsrds.DatabaseInstanceProps{
Engine: awsrds.DatabaseInstanceEngine_POSTGRES(),
InstanceType: awsec2.NewInstanceType(jsii.String("t3.micro")),
Credentials: awsrds.Credentials_FromPassword(
jsii.String("question"),
awscdk.NewSecretValue("question", &awscdk.IntrinsicProps{}),
),
PubliclyAccessible: jsii.Bool(true),
VpcSubnets: &awsec2.SubnetSelection{SubnetType: awsec2.SubnetType_PUBLIC},
Vpc: vpc,
})
...
return stack
}
Để tạo RDS ta dùng hàm NewDatabaseInstance
, ở trên ta tạo RDS với loại là Postgres và Instance Type là db.t3.micro
. Ta khai báo thông tin truy cập vào Postgres với username
và password
là question
. Để đơn giản cho ví dụ ta tạo RDS ở Public Subnet và cấu hình cho nó có thể truy cập được từ bên ngoài.
EC2 Construct:
func NewQuestionServiceStack(scope constructs.Construct, id string, props *QuestionServiceStackProps) awscdk.Stack {
var sprops awscdk.StackProps
if props != nil {
sprops = props.StackProps
}
stack := awscdk.NewStack(scope, &id, &sprops)
// VPC Construct
vpc := awsec2.Vpc_FromLookup(stack, jsii.String("DefaultVPC"), &awsec2.VpcLookupOptions{IsDefault: jsii.Bool(true)})
...
// EC2 Construct
awsec2.NewInstance(stack, jsii.String("Server"), &awsec2.InstanceProps{
InstanceType: awsec2.NewInstanceType(jsii.String("t3.micro")),
MachineImage: awsec2.NewAmazonLinuxImage(&awsec2.AmazonLinuxImageProps{
Generation: awsec2.AmazonLinuxGeneration_AMAZON_LINUX_2,
}),
Vpc: vpc,
})
return stack
}
Để tạo EC2 ta dùng hàm NewInstanceType
, ở trên ta tạo EC2 với OS là Amazon Linx 2 và Instance Type là t3.micro
.
Tạo hạ tầng
Chạy câu lệnh sau để xem trước các tài nguyên sẽ được tạo:
cdk synth
Chạy câu lệnh deploy
để tạo hạ tầng:
cdk deploy
✨ Synthesis time: 5.5s
QuestionServiceStack: building assets...
...
[████████████████████████████████▏·························] (5/9)
3:42:36 PM | CREATE_IN_PROGRESS | AWS::CloudFormation::Stack | QuestionServiceStack
3:42:51 PM | CREATE_IN_PROGRESS | AWS::RDS::DBInstance | Postgres
3:43:04 PM | CREATE_IN_PROGRESS | AWS::IAM::InstanceProfile | Server/InstanceProfile
Sau khi CDK chạy xong mở AWS Console lên kiểm tra thì ta sẽ thấy EC2 và RDS đã được tạo.
Nâng cấp hạ tầng
Hạ tầng hiện tại của ta cho ứng dụng Q&A có rất nhiều điểm không tốt, ở bài tiếp theo chúng ta sẽ cùng tìm hiểu cách mở rộng hạ tầng cho ứng dụng Q&A như bên dưới.
Nhớ chạy câu lệnh destroy để xóa tài nguyên:
cdk destroy
Kết luận
Vậy là ta đã tìm hiểu xong về các thành phần cơ bản của CDK, ba thành phần chính của một ứng dụng CDK là: App, Stack, Construct. Bạn có thể hình dung CDK như là trò chơi ghép lego, ta lắp ráp một sản phẩm hoàn hiện từ các chi tiết nhỏ nhất.
Tác giả Quân Huỳnh
Nếu bài viết có gì sai hoặc cần cập nhật thì liên hệ Admin.
Tham gia nhóm chat của DevOps VN tại Telegram.
Kém tiếng Anh và cần nâng cao trình độ giao tiếp: Tại sao bạn học không hiệu quả?