Back to blog

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

pulumiinfrastructuredevopsiaccloudtypescript
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:

  1. Language Host: Chạy chương trình và capture khai báo resource
  2. 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
  3. Provider: Thực hiện gọi API đến cloud provider
  4. 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 block

2. 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ôi

Stack 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 ls

Tạ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 website

Resource 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 Output
  • Output<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à secret

6. 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.com

Backend 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 destroy

Hiể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 create

Ký 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 websiteUrl

10. 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

  1. Static Website: Deploy website tĩnh lên S3
  2. Multi-Stack Setup: Tạo stack dev và prod với cấu hình khác nhau
  3. Stack Reference: Tạo network stack và application stack tham chiếu đến nó
  4. Import Resource Có Sẵn: Import S3 bucket có sẵn vào Pulumi state

Điều Hướng Series:

📬 Đă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.