Pulumi Best Practices
Pulumi IaC Best Practices Project Structure State Management Testing Secrets Policy as Code CrossGuard CI/CD Production Deployment
| Practice | Level | Impact | Effort | Priority |
|---|---|---|---|---|
| Project Structure | Foundation | สูง — Maintainability | ต่ำ | P0 ทำทันที |
| State Management | Foundation | สูง — Reliability | ต่ำ | P0 ทำทันที |
| Secret Management | Security | สูง — Security | กลาง | P0 ทำทันที |
| Policy as Code | Governance | สูง — Compliance | กลาง | P1 สัปดาห์แรก |
| Testing | Quality | สูง — Confidence | สูง | P1 สัปดาห์แรก |
| CI/CD Integration | Automation | สูง — 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}")
เคล็ดลับ
- Component: สร้าง ComponentResource สำหรับทุก Module ที่ใช้ซ้ำ
- Preview: รัน pulumi preview ทุกครั้งก่อน up ตรวจ Changes
- Stack: แยก Stack ตาม Environment ใช้ Config แยกกัน
- Import: ใช้ pulumi import นำ Resource เดิมเข้า IaC ไม่ต้องสร้างใหม่
- Automation: ใช้ Automation API สำหรับ CI/CD ที่ซับซ้อน
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
