Pulumi Giai Đoạn 1: Fundamentals - Infrastructure as Code Đầu Tiên

Chào mừng đến với Giai Đoạn 1 của Lộ Trình Học Pulumi! Trong giai đoạn này, bạn sẽ học các khái niệm cơ bản của Pulumi và deploy resource cloud đầu tiên sử dụng ngôn ngữ lập trình thực.
Đây là Phần 2 của Lộ Trình Học Pulumi
Bạn Sẽ Học Được Gì
Sau khi hoàn thành giai đoạn này, bạn sẽ có thể:
✅ Hiểu kiến trúc Pulumi và khác biệt với các công cụ IaC khác
✅ Tạo và tổ chức project và stack
✅ Định nghĩa và deploy resource cloud
✅ Làm việc với input, output, và resource dependency
✅ Quản lý configuration và secret an toàn
✅ Hiểu quản lý state và tùy chọn backend
✅ Sử dụng stack reference để giao tiếp cross-stack
Yêu Cầu Tiên Quyết
Trước khi bắt đầu, đảm bảo bạn có:
- Ngôn ngữ lập trình đã cài đặt (Node.js cho TypeScript, Python, hoặc Go)
- Pulumi CLI đã cài (
curl -fsSL https://get.pulumi.com | sh) - Tài khoản cloud (AWS, Azure, hoặc GCP) với credential đã cấu hình
- Hiểu biết cơ bản về khái niệm cloud (VM, storage, networking)
1. Hiểu Kiến Trúc Pulumi
Cách Pulumi Hoạt Động
Pulumi sử dụng kiến trúc độc đáo tách biệt chương trình của bạn khỏi engine triển khai:
┌─────────────────────────────────────────────────────────────┐
│ Chương Trình Pulumi │
│ (TypeScript, Python, Go, C#, Java, YAML) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Pulumi Language Host │
│ (Thực thi chương trình, theo dõi khai báo resource) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Pulumi Engine │
│ (So sánh trạng thái mong muốn vs hiện tại, lên kế hoạch) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Resource Provider │
│ (AWS, Azure, GCP, Kubernetes, v.v. - gọi API) │
└─────────────────────────────────────────────────────────────┘Thành Phần Chính:
- Language Host: Chạy chương trình và capture khai báo resource
- Engine: So sánh trạng thái mong muốn với hiện tại, xác định thay đổi cần thiết
- Provider: Thực hiện gọi API đến cloud provider
- State: Lưu trữ trạng thái hiện tại của hạ tầng
Sự Khác Biệt Chính: Pulumi vs Terraform
Khác biệt cơ bản là Pulumi thực thi code để xây dựng resource graph, trong khi Terraform diễn giải khai báo HCL.
// Pulumi: Đây là code TypeScript thực sự được thực thi
const buckets = [];
for (let i = 0; i < 3; i++) {
buckets.push(new aws.s3.Bucket(`bucket-${i}`));
}
// Bạn có thể dùng mọi tính năng ngôn ngữ: vòng lặp, điều kiện, hàm, class# Terraform: Đây là HCL, ngôn ngữ cấu hình khai báo
resource "aws_s3_bucket" "bucket" {
count = 3
bucket = "bucket-${count.index}"
}
# Giới hạn với cấu trúc HCL: count, for_each, dynamic block2. Project và Stack
Project Là Gì?
Project là thư mục chứa chương trình Pulumi. Được định nghĩa bởi file Pulumi.yaml:
# Pulumi.yaml
name: my-infrastructure
runtime: nodejs # hoặc python, go, dotnet, java
description: Project Pulumi đầu tiên của tôiStack Là Gì?
Stack là một instance độc lập của project. Các trường hợp sử dụng phổ biến:
- Môi trường:
dev,staging,prod - Region:
us-east-1,eu-west-1 - Tenant:
customer-a,customer-b
# Tạo stack cho các môi trường khác nhau
pulumi stack init dev
pulumi stack init staging
pulumi stack init prod
# Chuyển đổi giữa các stack
pulumi stack select dev
# Liệt kê tất cả stack
pulumi stack lsTạo Project Đầu Tiên
# Tạo project mới với TypeScript
mkdir my-first-pulumi && cd my-first-pulumi
pulumi new aws-typescript
# Lệnh này tạo:
# - Pulumi.yaml (định nghĩa project)
# - Pulumi.dev.yaml (cấu hình stack)
# - index.ts (chương trình của bạn)
# - package.json (dependency)
# - tsconfig.json (cấu hình TypeScript)File index.ts được tạo:
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
// Tạo S3 bucket
const bucket = new aws.s3.Bucket("my-bucket");
// Export tên bucket
export const bucketName = bucket.id;3. Resource: Khối Xây Dựng
Hiểu Về Resource
Resource đại diện cho một component hạ tầng cloud. Mỗi resource có:
- Type: Loại resource (vd:
aws:s3/bucket:Bucket) - Name: Tên logic trong chương trình (vd:
"my-bucket") - Argument: Tùy chọn cấu hình (input)
- Property: Giá trị trả về sau khi tạo (output)
import * as aws from "@pulumi/aws";
// Tạo S3 bucket
const bucket = new aws.s3.Bucket("my-bucket", {
// Argument (input)
website: {
indexDocument: "index.html",
errorDocument: "error.html",
},
tags: {
Environment: "dev",
Project: "my-app",
},
});
// Truy cập property (output)
export const bucketName = bucket.bucket; // Tên bucket thực tế
export const bucketArn = bucket.arn; // ARN của bucket
export const websiteEndpoint = bucket.websiteEndpoint; // URL websiteResource Option
Mỗi resource chấp nhận ResourceOptions tùy chọn:
const bucket = new aws.s3.Bucket("my-bucket", {
// Resource argument
}, {
// Resource option
protect: true, // Ngăn xóa vô tình
dependsOn: [otherResource], // Dependency rõ ràng
parent: parentResource, // Phân cấp tổ chức
provider: customProvider, // Sử dụng cấu hình provider cụ thể
ignoreChanges: ["tags"], // Bỏ qua thay đổi property cụ thể
});4. Input và Output
Hiểu Hệ Thống Kiểu
Pulumi có hệ thống kiểu đặc biệt để xử lý giá trị có thể chưa biết cho đến khi deploy:
Input<T>: Giá trị có thể là promise hoặc OutputOutput<T>: Giá trị sẽ có sẵn sau khi resource được tạo
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
// Khi tạo resource, một số giá trị chưa được biết
const bucket = new aws.s3.Bucket("my-bucket");
// bucket.id là Output<string>, không phải string
// Giá trị thực tế chưa biết cho đến khi AWS tạo bucket
console.log(bucket.id); // In: OutputImpl { ... }, không phải ID thực
// Để sử dụng giá trị Output, bạn phải dùng .apply()
bucket.id.apply(id => {
console.log(`Bucket ID: ${id}`); // In ID thực sau khi tạo
});Làm Việc Với Output
const bucket = new aws.s3.Bucket("my-bucket");
// Chuyển đổi output
const bucketUrl = bucket.bucket.apply(name => `https://${name}.s3.amazonaws.com`);
// Kết hợp nhiều output
const combined = pulumi.all([bucket.bucket, bucket.arn]).apply(([name, arn]) => {
return `Bucket ${name} có ARN ${arn}`;
});
// Dùng pulumi.interpolate cho string interpolation (cú pháp sạch hơn)
const message = pulumi.interpolate`Bucket: ${bucket.bucket}, ARN: ${bucket.arn}`;Dependency Tự Động
Pulumi tự động theo dõi dependency khi bạn dùng output làm input:
// Pulumi tạo: VPC → Subnet → Security Group → EC2 Instance
// (theo thứ tự đó, tự động)
const vpc = new aws.ec2.Vpc("vpc", { cidrBlock: "10.0.0.0/16" });
const subnet = new aws.ec2.Subnet("subnet", {
vpcId: vpc.id, // Phụ thuộc vào VPC
cidrBlock: "10.0.1.0/24",
});
const instance = new aws.ec2.Instance("instance", {
ami: "ami-0c55b159cbfafe1f0",
instanceType: "t2.micro",
subnetId: subnet.id, // Phụ thuộc vào Subnet
});5. Configuration và Secret
Stack Configuration
Mỗi stack có file cấu hình riêng (Pulumi.<stack>.yaml):
# Pulumi.dev.yaml
config:
aws:region: us-east-1
my-infrastructure:instanceType: t2.micro
my-infrastructure:dbPassword:
secure: AAABADQx... # Secret được mã hóaĐặt Configuration
# Đặt giá trị cấu hình plain
pulumi config set instanceType t2.micro
# Đặt secret (được mã hóa trong state)
pulumi config set --secret dbPassword "my-secret-password"
# Đặt cấu hình cho provider cụ thể
pulumi config set aws:region us-east-1
# Xem cấu hình
pulumi configĐọc Configuration Trong Code
import * as pulumi from "@pulumi/pulumi";
// Tạo đối tượng Config
const config = new pulumi.Config();
// Đọc giá trị (throw nếu không được đặt)
const instanceType = config.require("instanceType");
const dbPassword = config.requireSecret("dbPassword");
// Đọc giá trị với default
const environment = config.get("environment") || "dev";
const port = config.getNumber("port") || 3000;Secret Trong Pulumi
Secret được mã hóa khi lưu trữ và đánh dấu là secret trong state:
const config = new pulumi.Config();
// Đây là Output<string> secret
const dbPassword = config.requireSecret("dbPassword");
// Secret tự động được mã hóa trong state
// và được che trong log
// Khi bạn sử dụng secret, kết quả cũng là secret
const connectionString = pulumi.interpolate`postgres://admin:${dbPassword}@localhost:5432/mydb`;
// connectionString cũng là secret6. Quản Lý State
State Là Gì?
Pulumi state là file JSON theo dõi:
- Tất cả resource được quản lý bởi Pulumi
- Property hiện tại của chúng
- Dependency giữa các resource
- Secret được mã hóa
State Backend
Pulumi Cloud (Mặc định):
pulumi login # State được lưu tại app.pulumi.comBackend Tự Host:
# AWS S3
pulumi login s3://my-pulumi-state-bucket
# Azure Blob Storage
pulumi login azblob://my-container
# Google Cloud Storage
pulumi login gs://my-pulumi-state-bucket
# Local filesystem (chỉ development)
pulumi login --localĐể hướng dẫn đầy đủ về migration sang self-hosted state, xem Di Chuyển Pulumi State sang Azure Blob Storage.
7. Workflow Pulumi
Preview, Update, Destroy
# Bước 1: Preview thay đổi (dry-run)
pulumi preview
# Bước 2: Áp dụng thay đổi
pulumi up
# Bước 3: Xem output
pulumi stack output
# Bước 4: Hủy resource (khi hoàn thành)
pulumi destroyHiểu Output Preview
Previewing update (dev)
Type Name Plan
+ pulumi:pulumi:Stack my-project-dev create
+ ├─ aws:s3:Bucket my-bucket create
+ └─ aws:s3:BucketPolicy my-bucket-policy create
Resources:
+ 3 to createKý hiệu:
+tạo mới-xóa~cập nhật+-thay thế (xóa rồi tạo)
8. Stack Reference
Stack reference cho phép chia sẻ output giữa các stack:
Export Output
// Trong network-stack
const vpc = new aws.ec2.Vpc("vpc", { cidrBlock: "10.0.0.0/16" });
// Export giá trị cho stack khác sử dụng
export const vpcId = vpc.id;Import Output
// Trong app-stack
const networkStack = new pulumi.StackReference("organization/network-stack/dev");
// Lấy output từ network stack
const vpcId = networkStack.getOutput("vpcId");
// Sử dụng trong resource
const instance = new aws.ec2.Instance("app-server", {
subnetId: networkStack.getOutput("publicSubnetId"),
});9. Project Thực Hành: Static Website
Xây dựng website tĩnh hoàn chỉnh trên S3:
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
const siteName = config.get("siteName") || "my-static-site";
// Tạo S3 bucket cho website hosting
const bucket = new aws.s3.Bucket(siteName, {
website: {
indexDocument: "index.html",
errorDocument: "error.html",
},
});
// Bucket policy cho public read access
const bucketPolicy = new aws.s3.BucketPolicy(`${siteName}-policy`, {
bucket: bucket.id,
policy: bucket.arn.apply(arn => JSON.stringify({
Version: "2012-10-17",
Statement: [{
Effect: "Allow",
Principal: "*",
Action: "s3:GetObject",
Resource: `${arn}/*`,
}],
})),
});
// Export URL website
export const websiteUrl = bucket.websiteEndpoint;Deploy:
npm install @pulumi/aws
pulumi preview
pulumi up
pulumi stack output websiteUrl10. Best Practice Giai Đoạn 1
Quy Ước Đặt Tên
// Sử dụng tên mô tả, nhất quán
const productionVpc = new aws.ec2.Vpc("production-vpc", { ... });
// Sử dụng tên stack trong tên resource
const stackName = pulumi.getStack();
const bucket = new aws.s3.Bucket(`${stackName}-data-bucket`, { ... });Sử dụng Configuration Cho Khác Biệt Môi Trường
const config = new pulumi.Config();
const environment = pulumi.getStack();
// Kích thước instance khác nhau theo môi trường
const instanceType = config.get("instanceType") ||
(environment === "prod" ? "t3.large" : "t3.micro");Bảo Vệ Resource Quan Trọng
const database = new aws.rds.Instance("production-db", {
// ... cấu hình
}, {
protect: true, // Không thể xóa mà không gỡ bảo vệ trước
});Tóm Tắt
Trong Giai Đoạn 1, bạn đã học:
✅ Kiến Trúc Pulumi: Cách Pulumi thực thi code để xây dựng resource graph
✅ Project và Stack: Tổ chức hạ tầng theo môi trường
✅ Resource: Tạo và cấu hình resource cloud
✅ Input và Output: Làm việc với hệ thống kiểu của Pulumi
✅ Configuration và Secret: Quản lý giá trị theo môi trường an toàn
✅ Quản Lý State: Hiểu nơi Pulumi lưu trữ state hạ tầng
✅ Stack Reference: Chia sẻ output giữa các stack
Tiếp Theo Là Gì?
Trong Giai Đoạn 2: Cloud Provider, bạn sẽ học:
- Deep dive vào hạ tầng AWS (VPC, EC2, RDS, Lambda)
- Hạ tầng Azure (Resource Group, VNet, VM, Functions)
- Hạ tầng GCP (VPC, Compute Engine, Cloud Storage)
- Triển khai Kubernetes với Pulumi
- Pattern cross-cloud và abstraction
🔗 Tiếp tục Giai Đoạn 2: Cloud Provider →
Bài Tập Thực Hành
- Static Website: Deploy website tĩnh lên S3
- Multi-Stack Setup: Tạo stack
devvàprodvới cấu hình khác nhau - Stack Reference: Tạo network stack và application stack tham chiếu đến nó
- Import Resource Có Sẵn: Import S3 bucket có sẵn vào Pulumi state
Điều Hướng Series:
- Trước đó: Lộ Trình Học Pulumi
- Tiếp theo: Giai Đoạn 2: Cloud Provider →
- Liên quan: Di Chuyển Pulumi State sang Azure Blob Storage
📬 Đăng ký nhận bản tin
Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.
Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.
💬 Comments
Đăng nhập để bình luận
Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.