🔧DevOps

Pulumi

프로그래밍 언어로 인프라 정의하는 IaC 도구. TypeScript, Python 지원.

상세 설명

Pulumi는 2017년 설립된 회사가 개발한 Infrastructure as Code(IaC) 플랫폼으로, TypeScript, Python, Go, C#, Java 등 범용 프로그래밍 언어로 클라우드 인프라를 정의합니다. HCL 같은 도메인 특화 언어(DSL) 대신 익숙한 언어를 사용할 수 있어 개발자 친화적입니다.

핵심 장점은 프로그래밍 언어의 모든 기능(조건문, 반복문, 함수, 클래스, 패키지 관리)을 인프라 코드에 그대로 활용할 수 있다는 점입니다. IDE의 자동완성, 타입 체킹, 리팩토링 도구도 그대로 사용 가능합니다.

Pulumi는 AWS, Azure, GCP, Kubernetes 등 150개 이상의 클라우드 프로바이더를 지원합니다. 상태(State)는 Pulumi Cloud(SaaS), S3, Azure Blob 등에 저장하며, Terraform 상태 파일을 마이그레이션할 수도 있습니다.

실무에서는 복잡한 인프라 패턴을 재사용 가능한 컴포넌트로 추상화하거나, 단위 테스트로 인프라 코드를 검증하는 데 유용합니다. Terraform과 비교했을 때 러닝 커브가 낮고(기존 언어 사용), 테스트가 쉽다는 장점이 있습니다.

코드 예제

// TypeScript로 AWS 인프라 정의
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";

// 설정 값 로드
const config = new pulumi.Config();
const instanceType = config.get("instanceType") || "t3.micro";
const desiredCapacity = config.getNumber("desiredCapacity") || 2;

// VPC 생성 (자동으로 서브넷, NAT 게이트웨이 등 구성)
const vpc = new awsx.ec2.Vpc("main-vpc", {
    cidrBlock: "10.0.0.0/16",
    numberOfAvailabilityZones: 2,
    natGateways: {
        strategy: "Single",
    },
    tags: {
        Name: "production-vpc",
        Environment: "production",
    },
});

// ECS 클러스터
const cluster = new aws.ecs.Cluster("app-cluster", {
    settings: [{
        name: "containerInsights",
        value: "enabled",
    }],
});

// Application Load Balancer
const alb = new awsx.lb.ApplicationLoadBalancer("app-alb", {
    subnetIds: vpc.publicSubnetIds,
    securityGroups: [],
});

// ECS Fargate 서비스
const service = new awsx.ecs.FargateService("app-service", {
    cluster: cluster.arn,
    desiredCount: desiredCapacity,
    taskDefinitionArgs: {
        container: {
            name: "app",
            image: "nginx:latest",
            cpu: 256,
            memory: 512,
            essential: true,
            portMappings: [{
                containerPort: 80,
                targetGroup: alb.defaultTargetGroup,
            }],
            environment: [
                { name: "NODE_ENV", value: "production" },
            ],
            logConfiguration: {
                logDriver: "awslogs",
                options: {
                    "awslogs-group": "/ecs/app",
                    "awslogs-region": aws.getRegion().then(r => r.name),
                    "awslogs-stream-prefix": "ecs",
                },
            },
        },
    },
    networkConfiguration: {
        subnets: vpc.privateSubnetIds,
        securityGroups: [],
        assignPublicIp: false,
    },
});

// RDS PostgreSQL
const dbSubnetGroup = new aws.rds.SubnetGroup("db-subnet", {
    subnetIds: vpc.privateSubnetIds,
});

const db = new aws.rds.Instance("app-db", {
    engine: "postgres",
    engineVersion: "15.4",
    instanceClass: "db.t3.micro",
    allocatedStorage: 20,
    dbName: "appdb",
    username: "admin",
    password: config.requireSecret("dbPassword"),
    dbSubnetGroupName: dbSubnetGroup.name,
    skipFinalSnapshot: true,
    multiAz: true,
});

// 출력값 내보내기
export const vpcId = vpc.vpcId;
export const albDns = alb.loadBalancer.dnsName;
export const dbEndpoint = db.endpoint;

// 단위 테스트 예시 (Jest)
/*
import * as pulumi from "@pulumi/pulumi";
import "jest";

pulumi.runtime.setMocks({
    newResource: (args) => ({ id: `${args.name}-id`, state: args.inputs }),
    call: (args) => args.inputs,
});

describe("Infrastructure", () => {
    let infra: typeof import("./index");

    beforeAll(async () => {
        infra = await import("./index");
    });

    test("VPC should have correct CIDR", async () => {
        const cidr = await infra.vpcCidr;
        expect(cidr).toBe("10.0.0.0/16");
    });
});
*/

실무에서 이렇게 말해요

시니어: "Terraform HCL로 반복 패턴 만드는 게 한계가 있는데, Pulumi로 마이그레이션 검토해볼까요?"

주니어: "좋아요. TypeScript로 작성하면 타입 체킹도 되고, 우리 팀 모두 TS 익숙하니까 러닝 커브도 낮을 것 같아요. 테스트 코드 작성도 쉬워지고요."

면접관: "Pulumi와 Terraform의 차이점은 뭔가요?"

지원자: "Terraform은 HCL이라는 선언적 DSL을 사용하고, Pulumi는 TypeScript, Python 같은 범용 언어를 사용합니다. Pulumi는 조건문, 반복문이 자연스럽고 단위 테스트가 쉬운 반면, Terraform은 생태계가 더 크고 성숙합니다."

리뷰어: "이 DB 비밀번호가 평문으로 들어가 있네요. config.requireSecret() 사용해서 암호화해야 해요."

개발자: "맞아요, pulumi config set --secret dbPassword로 설정하고 코드에서 requireSecret()로 가져오도록 수정하겠습니다."

주의사항

더 배우기