[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,323 @@
|
||||
---
|
||||
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]]
|
||||
Reference in New Issue
Block a user