Trong bài viết này, tôi sẽ chia sẻ cách tôi sử dụng Pulumi để quản lý hạ tầng API AI một cách có hệ thống, thay vì thủ công qua giao diện cloud console. Sau hơn 2 năm vận hành nền tảng AI production với hàng triệu request mỗi ngày, tôi nhận ra rằng Infrastructure as Code (IaC) không chỉ là best practice — mà là yêu cầu bắt buộc khi bạn cần scale nhanh, rollback an toàn, và đảm bảo tính nhất quán giữa môi trường dev, staging, và production.

Tại sao chọn Pulumi thay vì Terraform?

Trước khi đi vào code, tôi muốn giải thích ngắn gọn lý do tôi chọn Pulumi. Sau khi dùng thử cả Terraform và Pulumi cho dự án riêng, đây là những gì tôi rút ra:

Kiến trúc Hạ tầng AI API

Trước khi viết code, hãy xác định rõ kiến trúc mà chúng ta sẽ triển khai. Với use case phổ biến — một ứng dụng web/giao diện cần gọi đến API AI — tôi thường thiết kế theo mô hình sau:

Cài đặt Pulumi và Thiết lập Dự án

Đầu tiên, tôi cài đặt Pulumi CLI và thiết lập dự án TypeScript. Toàn bộ quá trình mất khoảng 5 phút.

# Cài đặt Pulumi CLI trên macOS/Linux
curl -fsSL https://get.pulumi.com | sh

Hoặc cài qua Homebrew (macOS)

brew install pulumi

Kiểm tra phiên bản

pulumi version

Output: v3.115.0

Đăng nhập Pulumi (dùng local state hoặc Pulumi Service)

pulumi login

Tạo dự án mới

mkdir ai-api-infrastructure && cd ai-api-infrastructure pulumi new typescript

Các câu hỏi khi tạo project:

- Project name: ai-api-infra

- Stack name: dev

- AWS region: us-east-1

- Description: AI API Infrastructure with Pulumi

Cài đặt các provider cần thiết

npm install @pulumi/pulumi @pulumi/aws @pulumi/awsx

Sau khi cài đặt, tôi kiểm tra cấu trúc thư mục và xác nhận mọi thứ hoạt động đúng:

# Kiểm tra cấu trúc dự án
ls -la ai-api-infrastructure/

Kết quả:

Pulumi.yaml

Pulumi.dev.yaml

tsconfig.json

package.json

index.ts

Chạy thử để đảm bảo Pulumi hoạt động

pulumi preview

Nếu thành công sẽ hiển thị "Previewing" với danh sách resources sẽ tạo

Triển khai Hạ tầng với Code Chi tiết

2.1. File Cấu hình Chính (index.ts)

Đây là file chính nơi tôi định nghĩa toàn bộ hạ tầng. Tôi đã tổ chức code theo module pattern để dễ bảo trì và mở rộng.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";

// ============================================
// CẤU HÌNH CHÍNH
// ============================================
const config = new pulumi.Config();
const environment = pulumi.getStack(); // dev | staging | production
const isProduction = environment === "production";

// HolySheep AI Configuration - Đây là AI API provider chính của tôi
// Chi phí chỉ bằng 15% so với OpenAI, hỗ trợ WeChat/Alipay
const holySheepConfig = {
    baseUrl: "https://api.holysheep.ai/v1",
    apiKey: config.requireSecret("holySheepApiKey"),
    models: {
        gpt41: "gpt-4.1",
        claudeSonnet: "claude-sonnet-4.5",
        geminiFlash: "gemini-2.5-flash",
        deepseekV3: "deepseek-v3.2",
    },
};

// Tags chuẩn cho toàn bộ tài nguyên
const commonTags = {
    Project: "ai-api-platform",
    Environment: environment,
    ManagedBy: "Pulumi",
    CostCenter: "engineering",
};

// ============================================
// VPC & NETWORKING
// ============================================
const vpc = new aws.ec2.Vpc("ai-vpc", {
    cidrBlock: "10.0.0.0/16",
    enableDnsHostnames: true,
    enableDnsSupport: true,
    tags: { ...commonTags, Name: ai-vpc-${environment} },
});

const publicSubnet = new aws.ec2.Subnet("ai-public-subnet", {
    vpcId: vpc.id,
    cidrBlock: "10.0.1.0/24",
    availabilityZone: "us-east-1a",
    mapPublicIpOnLaunch: true,
    tags: { ...commonTags, Name: ai-public-subnet-${environment} },
});

const privateSubnet = new aws.ec2.Subnet("ai-private-subnet", {
    vpcId: vpc.id,
    cidrBlock: "10.0.2.0/24",
    availabilityZone: "us-east-1a",
    tags: { ...commonTags, Name: ai-private-subnet-${environment} },
});

const internetGateway = new aws.ec2.InternetGateway("ai-igw", {
    vpcId: vpc.id,
    tags: { ...commonTags, Name: ai-igw-${environment} },
});

const eip = new aws.ec2.Eip("nat-eip", {
    domain: "vpc",
    tags: { ...commonTags, Name: nat-eip-${environment} },
});

const natGateway = new aws.ec2.NatGateway("ai-nat", {
    subnetId: publicSubnet.id,
    allocationId: eip.id,
    tags: { ...commonTags, Name: ai-nat-${environment} },
});

const privateRouteTable = new aws.ec2.RouteTable("ai-private-rt", {
    vpcId: vpc.id,
    routes: [
        {
            cidrBlock: "0.0.0.0/0",
            natGatewayId: natGateway.id,
        },
    ],
    tags: { ...commonTags, Name: ai-private-rt-${environment} },
});

new aws.ec2.RouteTableAssociation("ai-private-rta", {
    subnetId: privateSubnet.id,
    routeTableId: privateRouteTable.id,
});

// ============================================
// SECURITY GROUPS
// ============================================
const albSecurityGroup = new aws.ec2.SecurityGroup("ai-alb-sg", {
    vpcId: vpc.id,
    description: "Security group for Application Load Balancer",
    ingress: [
        {
            protocol: "tcp",
            fromPort: 443,
            toPort: 443,
            cidrBlocks: ["0.0.0.0/0"],
            description: "HTTPS from anywhere",
        },
        {
            protocol: "tcp",
            fromPort: 80,
            toPort: 80,
            cidrBlocks: ["0.0.0.0/0"],
            description: "HTTP redirect to HTTPS",
        },
    ],
    egress: [
        {
            protocol: "-1",
            fromPort: 0,
            toPort: 0,
            cidrBlocks: ["0.0.0.0/0"],
        },
    ],
    tags: { ...commonTags, Name: ai-alb-sg-${environment} },
});

const ecsSecurityGroup = new aws.ec2.SecurityGroup("ai-ecs-sg", {
    vpcId: vpc.id,
    description: "Security group for ECS Fargate tasks",
    ingress: [
        {
            protocol: "tcp",
            fromPort: 8080,
            toPort: 8080,
            securityGroups: [albSecurityGroup.id],
            description: "Traffic from ALB",
        },
    ],
    egress: [
        {
            protocol: "-1",
            fromPort: 0,
            toPort: 0,
            cidrBlocks: ["0.0.0.0/0"],
        },
    ],
    tags: { ...commonTags, Name: ai-ecs-sg-${environment} },
});

// ============================================
// ECS CLUSTER & SERVICES
// ============================================
const ecsCluster = new aws.ecs.Cluster("ai-cluster", {
    name: ai-api-cluster-${environment},
    settings: [
        {
            name: "containerInsights",
            value: "enabled",
        },
    ],
    tags: { ...commonTags, Name: ai-cluster-${environment} },
});

const taskExecutionRole = new aws.iam.Role("ai-task-execution-role", {
    name: ai-task-execution-${environment},
    assumeRolePolicy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [
            {
                Effect: "Allow",
                Principal: { Service: ["ecs-tasks.amazonaws.com"] },
                Action: "sts:AssumeRole",
            },
        ],
    }),
});

new aws.iam.RolePolicyAttachment("ai-task-execution-policy", {
    role: taskExecutionRole.name,
    policyArn: "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy",
});

// Task Role cho phép gọi HolySheep AI API
const taskRole = new aws.iam.Role("ai-task-role", {
    name: ai-task-role-${environment},
    assumeRolePolicy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [
            {
                Effect: "Allow",
                Principal: { Service: ["ecs-tasks.amazonaws.com"] },
                Action: "sts:AssumeRole",
            },
        ],
    }),
});

new aws.iam.RolePolicy("ai-task-policy", {
    name: ai-task-policy-${environment},
    role: taskRole.name,
    policy: pulumi.output(holySheepConfig.apiKey).apply(() => JSON.stringify({
        Version: "2012-10-17",
        Statement: [
            {
                Effect: "Allow",
                Action: ["execute-api:Invoke"],
                Resource: ["arn:aws:execute-api:*:*:*"],
            },
            {
                Effect: "Allow",
                Action: ["secretsmanager:GetSecretValue"],
                Resource: [arn:aws:secretsmanager:*:*:secret:holySheepApiKey-*],
            },
        ],
    })),
});

// ============================================
// APPLICATION LOAD BALANCER
// ============================================
const alb = new awsx.lb.ApplicationLoadBalancer("ai-alb", {
    name: ai-alb-${environment},
    securityGroups: [albSecurityGroup.id],
    subnets: [publicSubnet.id],
    defaultTargetGroup: {
        port: 8080,
        protocol: "HTTP",
        healthCheck: {
            enabled: true,
            path: "/health",
            interval: 30,
            timeout: 5,
            healthyThreshold: 2,
            unhealthyThreshold: 3,
        },
    },
});

// ============================================
// DATABASE LAYER
// ============================================
const dbSubnetGroup = new aws.rds.SubnetGroup("ai-db-subnet-group", {
    name: ai-db-subnet-${environment},
    subnetIds: [privateSubnet.id],
    tags: { ...commonTags, Name: ai-db-subnet-${environment} },
});

const dbParameterGroup = new aws.rds.ParameterGroup("ai-db-params", {
    name: ai-db-params-${environment},
    family: "postgres16",
    parameters: {
        "max_connections": "200",
        "shared_buffers": (isProduction ? "512" : "128") + "MB",
        "effective_cache_size": (isProduction ? "1536" : "384") + "MB",
    },
    tags: { ...commonTags, Name: ai-db-params-${environment} },
});

const dbInstance = new aws.rds.Instance("ai-postgres", {
    identifier: ai-postgres-${environment},
    engine: "postgres",
    engineVersion: "16.2",
    instanceClass: isProduction ? "db.r7g.large" : "db.t4g.medium",
    allocatedStorage: isProduction ? 100 : 20,
    maxAllocatedStorage: isProduction ? 500 : 100,
    dbName: "aiplatform",
    username: config.require("dbUsername"),
    password: config.requireSecret("dbPassword"),
    dbSubnetGroupName: dbSubnetGroup.name,
    vpcSecurityGroupIds: [ecsSecurityGroup.id],
    parameterGroupName: dbParameterGroup.name,
    backupRetentionPeriod: isProduction ? 14 : 1,
    skipFinalSnapshot: !isProduction,
    deletionProtection: isProduction,
    publiclyAccessible: false,
    tags: { ...commonTags, Name: ai-postgres-${environment} },
});

// ============================================
// REDIS CACHE
// ============================================
const elasticacheSubnetGroup = new aws.elasticache.SubnetGroup("ai-redis-subnet", {
    name: ai-redis-subnet-${environment},
    subnetIds: [privateSubnet.id],
});

const redisCluster = new aws.elasticache.Cluster("ai-redis", {
    clusterId: ai-redis-${environment},
    engine: "redis",
    engineVersion: "7.1",
    nodeType: isProduction ? "cache.r7g.large" : "cache.t4g.micro",
    numCacheNodes: isProduction ? 2 : 1,
    parameterGroupName: "default.redis7",
    port: 6379,
    subnetGroupName: elasticacheSubnetGroup.name,
    securityGroupIds: [ecsSecurityGroup.id],
    tags: { ...commonTags, Name: ai-redis-${environment} },
});

// ============================================
// SECRETS MANAGER
// ============================================
const holySheepSecret = new aws.secretsmanager.Secret("holy-sheep-api-key", {
    name: holySheepApiKey-${environment},
    description: "HolySheep AI API Key for AI API Platform",
    recoveryWindowInDays: isProduction ? 30 : 7,
    tags: { ...commonTags, Name: holySheep-api-key-${environment} },
});

new aws.secretsmanager.SecretVersion("holy-sheep-api-key-value", {
    secretId: holySheepSecret.id,
    secretString: config.requireSecret("holySheepApiKeyRaw"),
});

// ============================================
// ECS TASK DEFINITION & SERVICE
// ============================================
const aiServiceTaskDefinition = new aws.ecs.TaskDefinition("ai-service", {
    family: ai-service-${environment},
    cpu: isProduction ? "1024" : "512",
    memory: isProduction ? "2048" : "1024",
    networkMode: "awsvpc",
    requiresCompatibilities: ["FARGATE"],
    executionRoleArn: taskExecutionRole.arn,
    taskRoleArn: taskRole.arn,
    containerDefinitions: pulumi.jsonStringify([
        {
            name: "ai-api-backend",
            image: "ghcr.io/myorg/ai-api-backend:latest",
            essential: true,
            portMappings: [{ containerPort: 8080, protocol: "tcp" }],
            environment: [
                { name: "NODE_ENV", value: environment },
                { name: "HOLYSHEEP_BASE_URL", value: holySheepConfig.baseUrl },
                { name: "DB_HOST", value: dbInstance.address },
                { name: "REDIS_HOST", value: redisCluster.cacheNodes[0].address },
                { name: "AWS_REGION", value: aws.getRegion().then(r => r.name) },
            ],
            secrets: [
                {
                    name: "HOLYSHEEP_API_KEY",
                    valueFrom: pulumi.interpolate${holySheepSecret.arn}:api_key::,
                },
                {
                    name: "DB_PASSWORD",
                    valueFrom: arn:aws:secretsmanager:*:*:secret:dbPassword-${environment}::,
                },
            ],
            logConfiguration: {
                logDriver: "awslogs",
                options: {
                    "awslogs-group": /ecs/ai-service-${environment},
                    "awslogs-region": aws.getRegion().then(r => r.name),
                    "awslogs-stream-prefix": "ecs",
                },
            },
            healthCheck: {
                command: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"],
                interval: 30,
                timeout: 5,
                retries: 3,
                startPeriod: 60,
            },
        },
    ]),
    tags: { ...commonTags, Name: ai-service-task-${environment} },
});

// CloudWatch Log Group
const ecsLogGroup = new aws.cloudwatch.LogGroup("ai-ecs-logs", {
    name: /ecs/ai-service-${environment},
    retentionInDays: isProduction ? 30 : 7,
    tags: { ...commonTags, Name: ai-ecs-logs-${environment} },
});

const aiService = new aws.ecs.Service("ai-service", {
    cluster: ecsCluster.id,
    name: ai-service-${environment},
    taskDefinition: aiServiceTaskDefinition.arn,
    desiredCount: isProduction ? 3 : 1,
    launchType: "FARGATE",
    healthCheckGracePeriodSeconds: 60,
    loadBalancers: [
        {
            targetGroupArn