팀에서 AI API 인프라를 확장하면서 겪은 문제였다. 새로운 AI 모델을 배포할 때마다 수동으로 endpoint를 설정하고, rate limit을 구성하고,密钥 관리를 수동으로 처리하니 배포 시간이 30분씩 소요됐다. 특히深夜突발 상황에서는配置文件 실수로 인해 API 호출이 실패하는 문제가 반복됐다.

결국 Pulumi를 도입해 인프라를 코드화한 뒤, 동일한 작업을 3분으로 단축했다. 이 글에서는 HolySheep AI 게이트웨이와 Pulumi를 결합한 AI API 인프라 관리 방법을 설명한다.

Pulumi IaC란 무엇인가

Pulumi는 TypeScript, Python, Go, C# 등 프로그래밍 언어로 인프라를 정의할 수 있는 IaC 도구이다. Terraform과 달리 선언적 설정 파일이 아닌 실제 코드를 사용하므로:

프로젝트 설정

# Pulumi 프로젝트 생성
$ pulumi new typescript

필요한 패키지 설치

$ npm install @pulumi/pulumi @pulumi/aws @types/node

HolySheep AI SDK 설치

$ npm install @holy-sheep/ai-sdk

HolySheep AI 게이트웨이 기본 설정

HolySheep AI는 글로벌 AI API 게이트웨이로, 단일 API 키로 여러 모델을 통합 관리할 수 있다. 먼저 기본 연결 설정을 구성한다.

import * as pulumi from "@pulumi/pulumi";

const config = new pulumi.Config();

// HolySheep AI API 키 (환경변수 또는 Pulumi 설정에서 관리)
const holySheepApiKey = config.requireSecret("holySheepApiKey");

// 모델별 엔드포인트 매핑
const modelEndpoints: Record<string, string> = {
  "gpt-4.1": "/chat/completions",
  "claude-sonnet-4": "/v1/messages",
  "gemini-2.5-flash": "/v1beta/models/gemini-2.5-flash:generateContent",
  "deepseek-v3": "/chat/completions",
};

// 각 모델별 rate limit 정책 정의
const rateLimitPolicies = [
  { model: "gpt-4.1", requestsPerMinute: 500, tokensPerMinute: 150000 },
  { model: "claude-sonnet-4", requestsPerMinute: 300, tokensPerMinute: 100000 },
  { model: "gemini-2.5-flash", requestsPerMinute: 1000, tokensPerMinute: 500000 },
  { model: "deepseek-v3", requestsPerMinute: 600, tokensPerMinute: 200000 },
];

// API Gateway 리소스 생성
const apiGateway = new aws.apigateway.RestApi("holy-sheep-gateway", {
  name: "holy-sheep-ai-gateway",
  description: "HolySheep AI API Gateway managed by Pulumi",
});

// 각 모델별 리소스 및 메소드 설정
rateLimitPolicies.forEach((policy, index) => {
  const resource = new aws.apigateway.Resource(model-${policy.model}, {
    restApi: apiGateway.id,
    parentId: apiGateway.rootResourceId,
    pathPart: policy.model.replace(".", "-"),
  });

  // API Gateway 메소드 및 통합 설정
  const method = new aws.apigateway.Method(method-${policy.model}, {
    restApi: apiGateway.id,
    resourceId: resource.id,
    httpMethod: "POST",
    authorization: "NONE",
  });

  // Lambda 통합 (AI API 라우팅용)
  const integration = new aws.apigateway.Integration(integration-${policy.model}, {
    restApi: apiGateway.id,
    resourceId: resource.id,
    httpMethod: method.httpMethod,
    integrationHttpMethod: "POST",
    type: "AWS_PROXY",
    uri: arn:aws:lambda:us-east-1:123456789:function:ai-router-${index},
  });

  // Usage Plan 생성
  const usagePlan = new aws.apigateway.UsagePlan(usage-plan-${policy.model}, {
    name: ${policy.model}-plan,
    apiStages: [{
      apiId: apiGateway.id,
      stage: "prod",
    }],
    quotaSettings: {
      limit: policy.requestsPerMinute * 60,
      period: "HOUR",
    },
    throttleSettings: {
      burstLimit: policy.requestsPerMinute,
      rateLimit: policy.requestsPerMinute / 60,
    },
  });
});

export const apiGatewayUrl = apiGateway.id.apply(id => 
  https://${id}.execute-api.us-east-1.amazonaws.com/prod
);
export const gatewayArn = apiGateway.arn;

다중 환경 구성 관리

개발, 스테이징, 프로덕션 환경별 인프라를 하나의 Pulumi 스택으로 관리하는 방법을 보여준다.

import * as pulumi from "@pulumi/pulumi";

interface EnvironmentConfig {
  region: string;
  instanceType: string;
  minCapacity: number;
  maxCapacity: number;
  enableCaching: boolean;
  cacheTtl: number;
}

const environments: Record<string, EnvironmentConfig> = {
  dev: {
    region: "us-east-1",
    instanceType: "t3.medium",
    minCapacity: 1,
    maxCapacity: 5,
    enableCaching: false,
    cacheTtl: 300,
  },
  staging: {
    region: "us-east-1",
    instanceType: "t3.large",
    minCapacity: 2,
    maxCapacity: 10,
    enableCaching: true,
    cacheTtl: 600,
  },
  prod: {
    region: "us-east-1",
    instanceType: "t3.xlarge",
    minCapacity: 5,
    maxCapacity: 50,
    enableCaching: true,
    cacheTtl: 3600,
  },
};

const stackName = pulumi.getStack();
const envConfig = environments[stackName] || environments.dev;

class AIInfrastructureStack {
  private vpc: aws.ec2.Vpc;
  private cluster: aws.ecs.Cluster;
  private loadBalancer: aws.alb.LoadBalancer;

  constructor() {
    this.vpc = this.createVpc();
    this.cluster = this.createEcsCluster();
    this.loadBalancer = this.createLoadBalancer();
    this.createAutoScalingGroup();
  }

  private createVpc(): aws.ec2.Vpc {
    return new aws.ec2.Vpc("ai-api-vpc", {
      cidrBlock: "10.0.0.0/16",
      enableDnsHostnames: true,
      enableDnsSupport: true,
      tags: {
        Name: ai-api-vpc-${stackName},
        Environment: stackName,
      },
    });
  }

  private createEcsCluster(): aws.ecs.Cluster {
    return new aws.ecs.Cluster("ai-api-cluster", {
      name: ai-api-${stackName},
      settings: [{
        name: "containerInsights",
        value: "enabled",
      }],
    });
  }

  private createLoadBalancer(): aws.alb.LoadBalancer {
    const subnetIds = this.vpc.publicSubnets;

    return new aws.alb.LoadBalancer("ai-api-lb", {
      name: ai-api-${stackName},
      internal: false,
      loadBalancerType: "application",
      subnets: subnetIds,
      securityGroups: [],
    });
  }

  private createAutoScalingGroup(): aws.autoscaling.Group {
    const asg = new aws.autoscaling.Group("ai-api-asg", {
      vpcZoneIdentifiers: this.vpc.privateSubnets,
      desiredCapacity: envConfig.minCapacity,
      minSize: envConfig.minCapacity,
      maxSize: envConfig.maxCapacity,
      launchTemplate: {
        id: "lt-xxxxxxxx",
        version: "$Latest",
      },
      tags: [{
        key: "Name",
        value: ai-api-instance-${stackName},
        propagateAtLaunch: true,
      }],
    });

    // Auto Scaling 정책 설정
    const cpuScalingTarget = new aws.autoscaling.TargetTrackingPolicy("cpu-scaling", {
      targetGroupArn: this.loadBalancer.defaultTargetGroupArn,
      estimatedInstanceWarmup: pulumi.interpolate${60},
      targetValue: 70,
      predefinedMetricSpecification: {
        predefinedMetricType: "ALBRequestCountPerTarget",
        resourceLabel: pulumi.interpolate${this.loadBalancer.arnSuffix}/targetgroup/${this.loadBalancer.name}/,
      },
    });

    return asg;
  }
}

const infrastructure = new AIInfrastructureStack();

export const clusterArn = infrastructure.cluster.arn;
export const loadBalancerDns = infrastructure.loadBalancer.dnsName;

AI API 요청 라우팅 Lambda 함수

Pulumi로 Lambda 함수를 생성하고 HolySheep AI로 요청을 라우팅하는 설정이다.

import * as aws from "@pulumi/aws";

const holySheepApiKey = new aws.secretsmanager.Secret("holySheepApiKey", {
  name: "holy-sheep-api-key",
  recoveryWindowInDays: 7,
});

new aws.secretsmanager.SecretVersion("holySheepApiKeyVersion", {
  secretId: holySheepApiKey.id,
  secretString: process.env.HOLYSHEEP_API_KEY || "",
});

// Lambda 실행 역할
const lambdaRole = new aws.iam.Role("aiRouterRole", {
  name: "ai-router-lambda-role",
  assumeRolePolicy: JSON.stringify({
    Version: "2012-10-17",
    Statement: [{
      Action: "sts:AssumeRole",
      Effect: "Allow",
      Principal: {
        Service: "lambda.amazonaws.com",
      },
    }],
  }),
});

const lambdaPolicy = new aws.iam.RolePolicy("aiRouterPolicy", {
  role: lambdaRole.id,
  policy: JSON.stringify({
    Version: "2012-10-17",
    Statement: [
      {
        Effect: "Allow",
        Action: [
          "secretsmanager:GetSecretValue",
        ],
        Resource: holySheepApiKey.arn,
      },
      {
        Effect: "Allow",
        Action: [
          "logs:CreateLogGroup",
          "logs:CreateLogStream",
          "logs:PutLogEvents",
        ],
        Resource: "arn:aws:logs:*:*:*",
      },
    ],
  }),
});

// AI 라우팅 Lambda 함수
const aiRouterFunction = new aws.lambda.Function("aiRouter", {
  functionName: "ai-api-router",
  runtime: "nodejs18.x",
  role: lambdaRole.arn,
  handler: "index.handler",
  sourceCodeHash: "PLACEHOLDER_ZIP_HASH",
  filename: "ai-router.zip",
  environment: {
    variables: {
      HOLYSHEEP_BASE_URL: "https://api.holysheep.ai/v1",
      HOLYSHEEP_API_KEY_REF: holySheepApiKey.arn,
      LOG_LEVEL: stackName === "prod" ? "info" : "debug",
    },
  },
  timeout: 30,
  memorySize: 512,
  layers: [],
});

// CloudWatch 로그 그룹
const logGroup = new aws.cloudwatch.LogGroup("aiRouterLogs", {
  name: /aws/lambda/${aiRouterFunction.functionName},
  retentionInDays: stackName === "prod" ? 30 : 7,
});

export const lambdaArn = aiRouterFunction.arn;
export const logGroupName = logGroup.name;

Lambda 핸들러 코드:

// ai-router/index.ts
const https = require("https");

const HOLYSHEEP_BASE_URL = process.env.HOLYSHEEP_BASE_URL;
const API_KEY = process.env.API_KEY;

interface AICallRequest {
  model: string;
  messages: Array<{role: string; content: string}>;
  temperature?: number;
  max_tokens?: number;
}

interface AICallResponse {
  id: string;
  model: string;
  choices: Array<{
    message: { role: string; content: string };
    finish_reason: string;
  }>;
  usage: {
    prompt_tokens: number;
    completion_tokens: number;
    total_tokens: number;
  };
}

async function callHolySheepAI(request: AICallRequest): Promise<AICallResponse> {
  return new Promise((resolve, reject) => {
    const url = new URL(${HOLYSHEEP_BASE_URL}/chat/completions);
    
    const options = {
      hostname: url.hostname,
      port: 443,
      path: url.pathname,
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Authorization": Bearer ${API_KEY},
        "X-Model-Routing": request.model,
      },
    };

    const req = https.request(options, (res) => {
      let data = "";
      res.on("data", (chunk) => data += chunk);
      res.on("end", () => {
        if (res.statusCode >= 400) {
          reject(new Error(HolySheep API Error: ${res.statusCode} - ${data}));
          return;
        }
        resolve(JSON.parse(data));
      });
    });

    req.on("error", (error) => {
      reject(new Error(Connection Error: ${error.message}));
    });

    req.write(JSON.stringify(request));
    req.end();
  });
}

exports.handler = async (event: any): Promise<any> => {
  console.log("Received request:", JSON.stringify(event));

  try {
    const body = JSON.parse(event.body);
    const response = await callHolySheepAI({
      model: body.model || "gpt-4.1",
      messages: body.messages,
      temperature: body.temperature || 0.7,
      max_tokens: body.max_tokens || 2048,
    });

    return {
      statusCode: 200,
      headers: {
        "Content-Type": "application/json",
        "Access-Control-Allow-Origin": "*",
      },
      body: JSON.stringify(response),
    };
  } catch (error) {
    console.error("Error processing request:", error);
    
    return {
      statusCode: error instanceof Error ? 
        (error.message.includes("401") ? 401 : 
         error.message.includes("timeout") ? 504 : 500) : 500,
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        error: error instanceof Error ? error.message : "Internal Server Error",
        timestamp: new Date().toISOString(),
      }),
    };
  }
};

모니터링 및 알림 설정

import * as aws from "@pulumi/aws";

// CloudWatch 대시보드 생성
const dashboard = new aws.cloudwatch.Dashboard("aiApiDashboard", {
  dashboardName: "ai-api-monitoring",
  dashboardBody: JSON.stringify({
    widgets: [
      {
        type: "metric",
        properties: {
          metrics: [
            ["AWS/Lambda", "Invocations", "FunctionName", aiRouterFunction.functionName],
            [".", "Errors", ".", "."],
            [".", "Duration", ".", "."],
          ],
          period: 300,
          stat: "Average",
          region: "us-east-1",
          title: "Lambda Metrics",
        },
      },
      {
        type: "metric",
        properties: {
          metrics: [
            ["AWS/ApiGateway", "Count", "ApiName", apiGateway.name],
            [".", "Latency", ".", "."],
          ],
          period: 60,
          stat: "Average",
          region: "us-east-1",
          title: "API Gateway Metrics",
        },
      },
    ],
  }),
});

// SNS 토픽 생성 (알림용)
const alertTopic = new aws.sns.Topic("aiApiAlerts", {
  name: "ai-api-alerts",
  displayName: "AI API Alerts",
});

// ErrorRate 높을 때 알림
const errorRateAlarm = new aws.cloudwatch.MetricAlarm("errorRateAlarm", {
  name: "ai-api-high-error-rate",
  comparisonOperator: "GreaterThanThreshold",
  evaluationPeriods: 2,
  metricName: "Errors",
  namespace: "AWS/Lambda",
  period: 300,
  statistic: "Sum",
  threshold: 10,
  alarmDescription: "AI API Lambda function error rate is too high",
  insufficientDataActions: [],
  okActions: [alertTopic.arn],
  alarmActions: [alertTopic.arn],
  dimensions: {
    FunctionName: aiRouterFunction.functionName,
  },
});

// 지연 시간 알림
const latencyAlarm = new aws.cloudwatch.MetricAlarm("latencyAlarm", {
  name: "ai-api-high-latency",
  comparisonOperator: "GreaterThanThreshold",
  evaluationPeriods: 3,
  metricName: "Duration",
  namespace: "AWS/Lambda",
  period: 60,
  statistic: "Average",
  threshold: 3000, // 3초
  alarmDescription: "AI API response latency is too high",
  insufficientDataActions: [],
  okActions: [alertTopic.arn],
  alarmActions: [alertTopic.arn],
  dimensions: {
    FunctionName: aiRouterFunction.functionName,
  },
});

export const dashboardUrl = dashboard.dashboardUrl;
export const alertTopicArn = alertTopic.arn;

자주 발생하는 오류와 해결책

1. ConnectionError: timeout - HolySheep AI 연결 실패

HolySheep AI 게이트웨이 연결 시 timeout 오류가 발생하는 경우, 기본적으로 30초 제한이 적용된다. 대량 요청 시 이 제한을 초과할 수 있다.

// 잘못된 설정 (timeout 기본값 사용)
const response = await fetch(${HOLYSHEEP_BASE_URL}/chat/completions, {
  method: "POST",
  headers: {
    "Authorization": Bearer ${API_KEY},
    "Content-Type": "application/json",
  },
  body: JSON.stringify(request),
});

// 해결책: AbortController로 timeout 설정
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 60000); // 60초

try {
  const response = await fetch(${HOLYSHEEP_BASE_URL}/chat/completions, {
    method: "POST",
    headers: {
      "Authorization": Bearer ${API_KEY},
      "Content-Type": "application/json",
    },
    body: JSON.stringify(request),
    signal: controller.signal,
  });
  
  if (!response.ok) {
    throw new Error(HTTP Error: ${response.status});
  }
  
  const data = await response.json();
  clearTimeout(timeoutId);
  return data;
} catch (error) {
  if (error instanceof Error && error.name === "AbortError") {
    throw new Error("ConnectionError: timeout - HolySheep AI 응답 시간 초과");
  }
  throw error;
}

2. 401 Unauthorized - API 키 인증 실패

HolySheep AI API 키가 만료되었거나 잘못된 환경변수에서 로드될 때 발생한다. Pulumi secrets를 통한 안전한 관리 방법을 사용해야 한다.

// 잘못된 설정: 평문으로 API 키 저장
const API_KEY = "sk-xxxxx-xxxxxxxx"; // 절대 이렇게 하지 마세요

// 해결책 1: Pulumi secrets 사용
const config = new pulumi.Config();
const apiKey = config.requireSecret("holySheepApiKey"); // pulumi config set --secret holySheepApiKey "sk-xxxxx"

// 해결책 2: AWS Secrets Manager에서 동적 로드
import * as aws from "@pulumi/aws";

async function getSecretValue(secretArn: string): Promise<string> {
  const { SecretString } = await aws.secretsmanager.getSecretVersion({
    secretId: secretArn,
  });
  return SecretString || "";
}

// Lambda 함수에서 사용
const apiKey = await getSecretValue(holySheepApiKey.arn);

// 해결책 3: 환경변수에서 로드 (로컬 개발용)
const apiKey = process.env.HOLYSHEEP_API_KEY;
if (!apiKey) {
  throw new Error("401 Unauthorized: HOLYSHEEP_API_KEY 환경변수가 설정되지 않았습니다");
}

// 요청 헤더에 올바른 형식으로 포함
headers: {
  "Authorization": Bearer ${apiKey},
  "X-API-Key": apiKey, // HolySheep AI가 추가 인증으로 사용하는 경우
}

3. RateLimitExceeded - 요청 한도 초과

API 요청이 HolySheep AI의 rate limit에 도달하면 429 에러가 반환된다. 재시도 로직과 exponential backoff를 구현해야 한다.

// retry 로직이 포함된 HolySheep AI 호출 함수
async function callWithRetry(
  request: AICallRequest,
  maxRetries: number = 3,
  baseDelay: number = 1000
): Promise<AICallResponse> {
  let lastError: Error | null = null;
  
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await fetch(${HOLYSHEEP_BASE_URL}/chat/completions, {
        method: "POST",
        headers: {
          "Authorization": Bearer ${API_KEY},
          "Content-Type": "application/json",
        },
        body: JSON.stringify(request),
      });

      if (response.status === 429) {
        // Rate limit 초과 - Retry-After 헤더 확인
        const retryAfter = response.headers.get("Retry-After");
        const delay = retryAfter ? 
          parseInt(retryAfter) * 1000 : 
          baseDelay * Math.pow(2, attempt);
        
        console.log(`RateLimitExceeded: ${delay}ms 후 재시