9148c358d0
Topic_Agent/Topic_Blog/Topics/Topics_Biz/Topics_Meeting/Topics_Rag의 마크다운 지식 문서를 Topic_General/Topic_Programming/Topic_Graphic/Topic_Business 4개 카테고리로 재분류. - 중복 제거: frontmatter의 status:duplicate/merged + duplicate_of/redirect_to 필드로 자기 자신을 중복으로 선언한 리다이렉트 stub 1032개 제거, 완전 동일 내용 파일 472개 제거, 동일 파일명·다른 내용 충돌 시 더 큰(완전한) 버전만 유지(162개 제거) — 총 1639개 중복 제거. - 분류: 폴더 단위로 명확한 항목(AI_and_ML/Coding/Architecture 등 → Programming, Comfyui/Visual_Effects → Graphic, Topics_Biz/Topics_Meeting/사업 등 → Business, Poetic_Blog_Writing/창의성/Game_Design 등 → General)은 폴더 우선순위로, 나머지 혼재 폴더(Topic_Agent/Topic_Blog/Topics 루트/Thinking & Reasoning/Other/UI_UX_Assets)는 title/tags 키워드 스코어링으로 파일 단위 분류(불명확한 경우 General로 폴백). 원본 폴더명은 "From_*" 서브폴더로 보존해 추적 가능성 유지. - 최종 배치: Programming 2784 / General 1608 / Graphic 285 / Business 249 = 4926개 문서. - 에이전트 운영 상태(.astra/.agent/.obsidian/sessions/memory/_company/docs/lessons/_shared/src)는 지식 콘텐츠가 아니므로 재분류 대상에서 제외하고 원위치 유지. - Topics/Topic_email(상위 보호 폴더 Topic_email과 파일명 100% 중복) 삭제 — 보호 폴더 자체는 미변경. - 완전히 비게 된 Topic_Agent/Topic_Blog/Topics_Biz/Topics_Rag 폴더 제거.
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.