--- id: devops-pulumi-iac title: Pulumi — 코드로 IaC (TS / Python / Go) category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [devops, pulumi, iac, vibe-coding] tech_stack: { language: "TS / Python / Go", applicable_to: ["DevOps"] } applied_in: [] aliases: [Pulumi, IaC, CDK, AWS CDK, infrastructure as code, programmatic IaC] --- # Pulumi > Terraform 의 코드 버전. **TS / Python / Go / .NET 으로 IaC**. AWS CDK 비슷 + multi-cloud. Loop / function / abstraction 자유. ## 📖 핵심 개념 - 코드 = infra 정의. - State: Pulumi Cloud / S3 / 자체. - Stack: 환경별 (dev / prod). - Component: 재사용 unit. ## 💻 코드 패턴 ### 시작 ```bash brew install pulumi pulumi new typescript # 또는 aws-typescript / azure-typescript / kubernetes-typescript ``` ### TS 예 — S3 + RDS ```ts import * as aws from '@pulumi/aws'; import * as random from '@pulumi/random'; // S3 const bucket = new aws.s3.BucketV2('my-bucket', { bucket: 'my-bucket', tags: { Env: 'prod' }, }); new aws.s3.BucketServerSideEncryptionConfigurationV2('encrypt', { bucket: bucket.id, rules: [{ applyServerSideEncryptionByDefault: { sseAlgorithm: 'AES256' } }], }); // RDS const password = new random.RandomPassword('db-password', { length: 32 }); const db = new aws.rds.Instance('app-db', { engine: 'postgres', engineVersion: '16', instanceClass: 'db.t4g.micro', allocatedStorage: 20, storageEncrypted: true, username: 'app', password: password.result, skipFinalSnapshot: false, finalSnapshotIdentifier: 'app-db-final', deletionProtection: true, }); export const dbEndpoint = db.endpoint; export const bucketName = bucket.id; ``` ### 명령 ```bash pulumi up # plan + apply pulumi up --yes # 확인 없이 pulumi preview # plan only pulumi destroy pulumi stack ls pulumi stack output dbEndpoint ``` ### Stack (환경별) ```bash pulumi stack init dev pulumi stack init staging pulumi stack init prod pulumi stack select prod pulumi up ``` ```yaml # Pulumi.prod.yaml config: aws:region: us-east-1 myapp:dbInstanceClass: db.r6g.large myapp:replicaCount: 5 ``` ```ts // Code import * as pulumi from '@pulumi/pulumi'; const cfg = new pulumi.Config('myapp'); const instanceClass = cfg.require('dbInstanceClass'); const replicaCount = cfg.requireNumber('replicaCount'); ``` ### Component (재사용) ```ts class AppDatabase extends pulumi.ComponentResource { public readonly endpoint: pulumi.Output; constructor(name: string, args: AppDatabaseArgs, opts?: pulumi.ComponentResourceOptions) { super('myco:db:AppDatabase', name, args, opts); const password = new random.RandomPassword(`${name}-pw`, { length: 32 }, { parent: this }); const db = new aws.rds.Instance(`${name}-rds`, { engine: 'postgres', instanceClass: args.instanceClass, // ... }, { parent: this }); new aws.secretsmanager.Secret(`${name}-secret`, { ... }, { parent: this }); this.endpoint = db.endpoint; this.registerOutputs({ endpoint: this.endpoint }); } } interface AppDatabaseArgs { instanceClass: string; } // 사용 const ordersDb = new AppDatabase('orders', { instanceClass: 'db.r6g.large' }); const inventoryDb = new AppDatabase('inventory', { instanceClass: 'db.t4g.medium' }); ``` → 재사용 + grouping. ### Multi-cloud ```ts import * as aws from '@pulumi/aws'; import * as gcp from '@pulumi/gcp'; import * as k8s from '@pulumi/kubernetes'; // AWS const bucket = new aws.s3.BucketV2('logs'); // GCP const gcsBucket = new gcp.storage.Bucket('analytics', { location: 'US' }); // K8s const ns = new k8s.core.v1.Namespace('app', { metadata: { name: 'app' } }); ``` → 한 program 안 multi-cloud. ### Output (async value) ```ts const url = pulumi.interpolate`https://${db.endpoint}:5432/${dbName}`; // 또는 const connection = pulumi.all([db.endpoint, password.result]).apply(([endpoint, pw]) => `postgresql://app:${pw}@${endpoint}:5432/app` ); ``` → Pulumi 의 lazy / async value handling. ### Secret ```ts const apiKey = cfg.requireSecret('apiKey'); // encrypted in state ``` ```bash pulumi config set --secret myapp:apiKey sk-abc123 ``` ### Import (existing) ```bash pulumi import aws:s3/bucket:Bucket existing my-existing-bucket ``` → 기존 cloud 자원 → Pulumi 안 가져오기. ### Drift detection ```bash pulumi refresh # cloud → state sync pulumi up # state → cloud sync ``` ### CI ```yaml - uses: pulumi/actions@v5 with: command: up stack-name: prod cloud-url: s3://my-state-bucket ``` ### vs Terraform ``` Pulumi: + Real programming language (TS / Python) + Loop / function / abstraction + Test (Jest) + Type-safe (TS) - Smaller community - Newer Terraform: + HCL (declarative, simpler 작은 case) + Largest community + Module marketplace - HCL 의 한계 (loop, complex logic) ``` → 큰 / 복잡 = Pulumi. 단순 / 표준 = Terraform. ### Test ```ts import { describe, it } from '@jest/globals'; import * as pulumi from '@pulumi/pulumi'; pulumi.runtime.setMocks({ newResource: (args) => ({ id: `${args.name}-id`, state: { ...args.inputs, id: `${args.name}-id` }, }), call: () => ({}), }); describe('infrastructure', () => { it('creates encrypted bucket', async () => { const infra = await import('./index'); const bucket = infra.bucket; const sseConfig = await new Promise((resolve) => bucket.serverSideEncryptionConfiguration.apply(resolve)); expect(sseConfig).toBeDefined(); }); }); ``` → 일반 unit test 처럼. ### Component packages (sharing) ```bash # Component 를 npm package 로 yarn publish ``` ```ts // 다른 곳 import { AppDatabase } from '@myco/pulumi-components'; const db = new AppDatabase('orders', {...}); ``` ### Crossplane vs Pulumi ``` Crossplane: K8s 안 cloud manage. Pulumi: Code 로 cloud manage. → K8s native = Crossplane. Code-first = Pulumi. ``` ### AWS CDK ```ts // CDK = AWS only Pulumi-like import { Stack } from 'aws-cdk-lib'; import { Bucket } from 'aws-cdk-lib/aws-s3'; class MyStack extends Stack { constructor(...) { super(...); new Bucket(this, 'MyBucket'); } } ``` → AWS only. AWS deeply 통합. ### Best practices ``` 1. State = remote (Pulumi Cloud / S3). 2. Stack 별 환경 분리. 3. Component 로 재사용. 4. Test (mock). 5. CI 자동. 6. Secret 명시 (encrypted state). 7. Import existing 가능. 8. Drift 정기 detect. ``` ## 🤔 의사결정 기준 | 상황 | 추천 | |---|---| | Code-first IaC | Pulumi | | AWS only | Pulumi 또는 CDK | | 일반 / 표준 | Terraform / OpenTofu | | K8s 중심 | Crossplane | | Multi-cloud | Pulumi | | TS team | Pulumi (자연) | | Module marketplace | Terraform | ## ❌ 안티패턴 - **State local file**: 잃으면 disaster. Remote. - **Stack mix (dev / prod 한 곳)**: 분리. - **Secret plain config**: requireSecret. - **Drift 무 detect**: 콘솔 변경 → 다음 up 가 덮음. - **Component 없이 copy-paste**: 코드 폭발. - **Test 없이 prod**: 위험. - **Outputs share 안 함**: 다른 stack 가 reference 못 함. ## 🤖 LLM 활용 힌트 - TS = type-safe IaC. - Component 적극. - Stack per env. - Pulumi Cloud free tier. ## 🔗 관련 문서 - [[DevOps_Terraform_Patterns]] - [[DevOps_Crossplane_Tekton]] - [[DevOps_ArgoCD_GitOps]]