---
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<string>;
  
  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]]
