7.2 KiB
7.2 KiB
id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
| id | title | category | status | source_trust_level | verification_status | created_at | updated_at | tags | tech_stack | applied_in | aliases | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| devops-pulumi-iac | Pulumi — 코드로 IaC (TS / Python / Go) | Coding | draft | B | conceptual | 2026-05-09 | 2026-05-09 |
|
|
|
Pulumi
Terraform 의 코드 버전. TS / Python / Go / .NET 으로 IaC. AWS CDK 비슷 + multi-cloud. Loop / function / abstraction 자유.
📖 핵심 개념
- 코드 = infra 정의.
- State: Pulumi Cloud / S3 / 자체.
- Stack: 환경별 (dev / prod).
- Component: 재사용 unit.
💻 코드 패턴
시작
brew install pulumi
pulumi new typescript
# 또는 aws-typescript / azure-typescript / kubernetes-typescript
TS 예 — S3 + RDS
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;
명령
pulumi up # plan + apply
pulumi up --yes # 확인 없이
pulumi preview # plan only
pulumi destroy
pulumi stack ls
pulumi stack output dbEndpoint
Stack (환경별)
pulumi stack init dev
pulumi stack init staging
pulumi stack init prod
pulumi stack select prod
pulumi up
# Pulumi.prod.yaml
config:
aws:region: us-east-1
myapp:dbInstanceClass: db.r6g.large
myapp:replicaCount: 5
// Code
import * as pulumi from '@pulumi/pulumi';
const cfg = new pulumi.Config('myapp');
const instanceClass = cfg.require('dbInstanceClass');
const replicaCount = cfg.requireNumber('replicaCount');
Component (재사용)
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
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)
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
const apiKey = cfg.requireSecret('apiKey'); // encrypted in state
pulumi config set --secret myapp:apiKey sk-abc123
Import (existing)
pulumi import aws:s3/bucket:Bucket existing my-existing-bucket
→ 기존 cloud 자원 → Pulumi 안 가져오기.
Drift detection
pulumi refresh # cloud → state sync
pulumi up # state → cloud sync
CI
- 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
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)
# Component 를 npm package 로
yarn publish
// 다른 곳
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
// 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.