SiamCafe.net Blog
Technology

Pulumi IaC Best Practices ที่ต้องรู้

pulumi iac best practices ทตองร
Pulumi IaC Best Practices ที่ต้องรู้ | SiamCafe Blog
2026-05-20· อ. บอม — SiamCafe.net· 8,205 คำ

Pulumi Best Practices

Pulumi IaC Best Practices Project Structure State Management Testing Secrets Policy as Code CrossGuard CI/CD Production Deployment

PracticeLevelImpactEffortPriority
Project StructureFoundationสูง — Maintainabilityต่ำP0 ทำทันที
State ManagementFoundationสูง — Reliabilityต่ำP0 ทำทันที
Secret ManagementSecurityสูง — SecurityกลางP0 ทำทันที
Policy as CodeGovernanceสูง — ComplianceกลางP1 สัปดาห์แรก
TestingQualityสูง — ConfidenceสูงP1 สัปดาห์แรก
CI/CD IntegrationAutomationสูง — VelocityกลางP1 สัปดาห์แรก

Project Structure

# === Pulumi Project Structure ===

# infrastructure/
# ├── Pulumi.yaml              # Project definition
# ├── Pulumi.dev.yaml          # Dev stack config
# ├── Pulumi.staging.yaml      # Staging stack config
# ├── Pulumi.prod.yaml         # Production stack config
# ├── __main__.py              # Entry point
# ├── config.py                # Config loader
# ├── components/
# │   ├── network.py           # VPC, Subnets, SG
# │   ├── compute.py           # EC2, ECS, Lambda
# │   ├── database.py          # RDS, DynamoDB
# │   ├── monitoring.py        # CloudWatch, Alerts
# │   └── cdn.py               # CloudFront, S3
# └── tests/
#     ├── test_network.py
#     ├── test_compute.py
#     └── test_policy.py

# __main__.py
# import pulumi
# from config import get_config
# from components.network import NetworkStack
# from components.compute import ComputeStack
# from components.database import DatabaseStack
#
# config = get_config()
# network = NetworkStack("network", config)
# database = DatabaseStack("database", config, network)
# compute = ComputeStack("compute", config, network, database)
#
# pulumi.export("vpc_id", network.vpc.id)
# pulumi.export("api_url", compute.api_url)

# ComponentResource Example
# class NetworkStack(pulumi.ComponentResource):
#     def __init__(self, name, config, opts=None):
#         super().__init__("custom:infra:Network", name, None, opts)
#
#         self.vpc = aws.ec2.Vpc(f"{name}-vpc",
#             cidr_block=config["vpc_cidr"],
#             enable_dns_hostnames=True,
#             tags={"Name": f"{name}-vpc", "Environment": config["env"]},
#             opts=pulumi.ResourceOptions(parent=self))
#
#         self.register_outputs({"vpc_id": self.vpc.id})

from dataclasses import dataclass

@dataclass
class StackConfig:
    stack: str
    env: str
    instance_type: str
    min_size: int
    max_size: int
    db_instance: str
    multi_az: bool

configs = [
    StackConfig("dev", "development", "t3.small", 1, 2, "db.t3.micro", False),
    StackConfig("staging", "staging", "t3.medium", 2, 4, "db.t3.small", False),
    StackConfig("prod", "production", "t3.large", 3, 10, "db.r6g.large", True),
]

print("=== Stack Configurations ===")
for c in configs:
    print(f"  [{c.stack}] Env: {c.env}")
    print(f"    Instance: {c.instance_type} | Scale: {c.min_size}-{c.max_size}")
    print(f"    DB: {c.db_instance} | Multi-AZ: {c.multi_az}")

Testing Infrastructure

# === Pulumi Testing ===

# Unit Test with Mocks (pytest)
# import pulumi
# import pytest
#
# class MyMocks(pulumi.runtime.Mocks):
#     def new_resource(self, args):
#         return [args.name + "_id", args.inputs]
#     def call(self, args):
#         return {}
#
# pulumi.runtime.set_mocks(MyMocks())
#
# from components.network import NetworkStack
#
# @pulumi.runtime.test
# def test_vpc_has_tags():
#     def check_tags(args):
#         tags = args[0]
#         assert "Environment" in tags
#         assert "Name" in tags
#
#     config = {"vpc_cidr": "10.0.0.0/16", "env": "test"}
#     network = NetworkStack("test", config)
#     pulumi.Output.all(network.vpc.tags).apply(check_tags)
#
# @pulumi.runtime.test
# def test_vpc_cidr():
#     def check_cidr(cidr):
#         assert cidr == "10.0.0.0/16"
#
#     config = {"vpc_cidr": "10.0.0.0/16", "env": "test"}
#     network = NetworkStack("test", config)
#     network.vpc.cidr_block.apply(check_cidr)

# Policy as Code (CrossGuard)
# from pulumi_policy import PolicyPack, ResourceValidationPolicy
#
# def no_public_s3(args, report_violation):
#     if args.resource_type == "aws:s3/bucket:Bucket":
#         acl = args.props.get("acl")
#         if acl == "public-read" or acl == "public-read-write":
#             report_violation("S3 buckets must not be public")
#
# def require_encryption(args, report_violation):
#     if args.resource_type == "aws:s3/bucket:Bucket":
#         encryption = args.props.get("serverSideEncryptionConfiguration")
#         if not encryption:
#             report_violation("S3 buckets must have encryption enabled")
#
# PolicyPack("security-policies", policies=[
#     ResourceValidationPolicy(name="no-public-s3", validate=no_public_s3),
#     ResourceValidationPolicy(name="require-encryption", validate=require_encryption),
# ])

@dataclass
class TestType:
    test: str
    tool: str
    what: str
    when: str
    example: str

tests = [
    TestType("Unit Test", "pytest + Mocks", "Resource properties, tags, config",
        "Every PR", "VPC has correct CIDR, tags present"),
    TestType("Property Test", "CrossGuard", "Security policies, compliance",
        "Every preview/up", "No public S3, encryption required"),
    TestType("Integration Test", "pytest + real provider", "Full stack deployment",
        "Nightly", "Deploy to test account, verify connectivity"),
    TestType("Preview Test", "pulumi preview", "Change detection, drift",
        "Every PR", "No unexpected destroys, correct changes"),
    TestType("Cost Test", "Infracost + Pulumi", "Cost estimation before deploy",
        "Every PR", "Monthly cost < $5000 for staging"),
]

print("\n=== Testing Strategy ===")
for t in tests:
    print(f"  [{t.test}] Tool: {t.tool}")
    print(f"    What: {t.what}")
    print(f"    When: {t.when}")
    print(f"    Example: {t.example}")

CI/CD and Operations

# === GitHub Actions CI/CD ===

# .github/workflows/pulumi.yml
# name: Pulumi IaC
# on:
#   pull_request:
#     paths: ['infrastructure/**']
#   push:
#     branches: [main]
#     paths: ['infrastructure/**']
#
# jobs:
#   preview:
#     if: github.event_name == 'pull_request'
#     runs-on: ubuntu-latest
#     steps:
#       - uses: actions/checkout@v4
#       - uses: actions/setup-python@v5
#         with: { python-version: '3.11' }
#       - run: pip install -r infrastructure/requirements.txt
#       - uses: pulumi/actions@v5
#         with:
#           command: preview
#           stack-name: dev
#           work-dir: infrastructure
#           comment-on-pr: true
#         env:
#           PULUMI_ACCESS_TOKEN: }
#
#   deploy:
#     if: github.ref == 'refs/heads/main'
#     runs-on: ubuntu-latest
#     steps:
#       - uses: actions/checkout@v4
#       - uses: pulumi/actions@v5
#         with:
#           command: up
#           stack-name: prod
#           work-dir: infrastructure
#         env:
#           PULUMI_ACCESS_TOKEN: }

@dataclass
class BestPractice:
    category: str
    practice: str
    why: str
    how: str

practices = [
    BestPractice("Structure", "Use ComponentResource for modules",
        "Reusable, testable, organized",
        "class MyComponent(pulumi.ComponentResource)"),
    BestPractice("State", "Use remote backend (Pulumi Cloud/S3)",
        "Team collaboration, backup, locking",
        "pulumi login s3://my-state-bucket"),
    BestPractice("Secrets", "Never hardcode secrets",
        "Security compliance",
        "pulumi config set --secret dbPassword"),
    BestPractice("Tags", "Tag all resources consistently",
        "Cost tracking, ownership, compliance",
        "Default tags via transformations"),
    BestPractice("Naming", "Use consistent naming convention",
        "Readability, searchability",
        "{project}-{env}-{component}-{resource}"),
    BestPractice("Review", "Preview before every deploy",
        "Prevent accidents, catch drift",
        "pulumi preview in CI, comment on PR"),
    BestPractice("Policy", "Enforce policies with CrossGuard",
        "Compliance, security guardrails",
        "PolicyPack with required rules"),
    BestPractice("Import", "Import existing resources",
        "Migrate to IaC without recreating",
        "pulumi import aws:ec2/instance:Instance myvm i-xxx"),
]

print("Best Practices:")
for p in practices:
    print(f"  [{p.category}] {p.practice}")
    print(f"    Why: {p.why}")
    print(f"    How: {p.how}")

เคล็ดลับ

Pulumi คืออะไร

IaC Platform Python TypeScript Go C# Java AWS Azure GCP Kubernetes State Management Policy CrossGuard Automation API Open Source Free Tier

Project Structure ควรเป็นอย่างไร

Stack dev staging prod ComponentResource Reusable Module StackReference Config Pulumi.yaml Monorepo Secret Encryption Layer Network Compute Database

Testing ทำอย่างไร

Unit Test pytest Mocks Property CrossGuard Integration Preview Drift Cost Infracost CI PR Nightly Security Compliance

Secrets จัดการอย่างไร

pulumi config set --secret Encrypted Passphrase KMS AWS Secrets Manager Azure Key Vault GCP Secret Manager ESC secretProvider State Encryption

สรุป

Pulumi IaC Best Practices Project Structure ComponentResource State Management Testing CrossGuard Policy Secrets CI/CD GitHub Actions Production Deployment

📖 บทความที่เกี่ยวข้อง

Pulumi IaC สำหรับมือใหม่ Step by Stepอ่านบทความ → Docker Multi-stage Build Best Practices ที่ต้องรู้อ่านบทความ → Pulumi IaC API Gateway Patternอ่านบทความ → Pulumi IaC Cloud Migration Strategyอ่านบทความ → Pulumi IaC Metric Collectionอ่านบทความ →

📚 ดูบทความทั้งหมด →