티스토리 뷰

작성일: 2025-11-02
카테고리: Platform Architecture, API Gateway, Kubernetes
난이도: 고급


TL;DR

  • 문제: REST API 개발과 배포가 복잡하고, AI 에이전트와의 통합이 어려웠습니다.
  • 해결: CloudFunction 기반 API 개발 + Kubernetes 자동 배포 + MCP Tool 통합을 하나의 플랫폼으로 제공했습니다.
  • 핵심:
    • Hot Reload: MongoDB Change Stream으로 1초 이내 배포
    • State Machine: Phase 기반 리소스 생명주기 관리
    • Multi-tenant DB: Gateway별 독립 MongoDB 데이터베이스
    • Environment 분리: dev/staging/prod 환경 자동 생성
    • AI 통합: CloudFunction을 MCP Tool로 변환하여 AI 에이전트가 직접 호출
  • 결과: 코드 작성부터 배포, AI 통합까지 웹 콘솔 하나로 완결되는 통합 API Platform 완성

들어가며

imprun.dev"API 개발부터 AI 통합까지, 모든 것을 하나로" 제공하는 Kubernetes 기반 API Platform입니다.

개발자는 웹 브라우저에서 CloudFunction 코드를 작성하고, 버튼 클릭 한 번으로 REST API를 배포하며, AI 에이전트가 이 API를 Tool로 사용할 수 있게 만들 수 있습니다.

graph LR
    Dev["코드 작성<br/>(Web Console)"]
    Deploy["1초 배포<br/>(Hot Reload)"]
    API["REST API<br/>(Gateway)"]
    AI["AI 통합<br/>(MCP Tool)"]

    Dev --> Deploy
    Deploy --> API
    API --> AI

    style Dev stroke:#2563eb,stroke-width:2px
    style Deploy stroke:#ea580c,stroke-width:2px
    style API stroke:#16a34a,stroke-width:2px
    style AI stroke:#7c3aed,stroke-width:2px

이 글에서 다룰 내용:

  • imprun Platform의 전체 아키텍처
  • Hot Reload 배포 메커니즘
  • State Machine 기반 리소스 관리
  • Multi-tenant Database 설계
  • AI Gateway와 MCP Tool 통합

1. Platform 개요

1.1 imprun이 해결하는 문제

기존 REST API 개발의 불편함:

  • 로컬 개발 환경 구축 (Node.js, DB, Redis 설치)
  • Git 커밋/푸시 → CI/CD 파이프라인 대기 (5~10분)
  • Kubernetes 배포 설정 작성 (Deployment, Service, Ingress)
  • 환경별(dev/staging/prod) 설정 관리
  • AI 에이전트와 통합하려면 별도 서버 필요

imprun의 접근:

  • ✅ 웹 브라우저가 IDE (Monaco Editor)
  • ✅ 코드 저장 즉시 배포 (1초 이내, Hot Reload)
  • ✅ Kubernetes 리소스 자동 생성
  • ✅ 3개 환경(dev/staging/prod) 자동 구성
  • ✅ CloudFunction → MCP Tool 자동 변환

1.2 핵심 구성 요소

graph TB
    subgraph "Web Console"
        Console["Next.js 15 Web Console"]
        Monaco["Monaco Editor"]
    end

    subgraph "API Server"
        ApiServer["NestJS API Server"]
        MongoDB["System DB (MongoDB)"]
        Redis["Session Store (Redis)"]
    end

    subgraph "Kubernetes Cluster"
        subgraph "Gateway Runtime"
            Runtime1["Runtime Pod (dev)"]
            Runtime2["Runtime Pod (staging)"]
            Runtime3["Runtime Pod (prod)"]
        end

        GatewayDB["Gateway DB (MongoDB)"]
        APISIX["APISIX Ingress"]
    end

    subgraph "AI Gateway"
        AIGateway["AI Gateway Service"]
        MCPProxy["MCP Proxy"]
    end

    Console --> ApiServer
    Monaco --> ApiServer
    ApiServer --> MongoDB
    ApiServer --> Redis
    ApiServer -->|Kubernetes API| Runtime1
    ApiServer -->|Kubernetes API| Runtime2
    ApiServer -->|Kubernetes API| Runtime3

    Runtime1 -.->|Change Stream| GatewayDB
    Runtime2 -.->|Change Stream| GatewayDB
    Runtime3 -.->|Change Stream| GatewayDB

    APISIX -->|Route| Runtime1
    APISIX -->|Route| Runtime2
    APISIX -->|Route| Runtime3

    AIGateway --> MCPProxy
    MCPProxy -->|HTTP Call| APISIX

    style Console stroke:#2563eb,stroke-width:2px
    style ApiServer stroke:#16a34a,stroke-width:2px
    style Runtime1 stroke:#ea580c,stroke-width:2px
    style AIGateway stroke:#7c3aed,stroke-width:2px

계층별 역할:

  1. Web Console: CloudFunction 작성 및 배포 UI
  2. API Server: Platform 전체 관리 (Gateway, Function, User, Auth)
  3. Kubernetes Cluster: Runtime Pod 실행 및 트래픽 라우팅
  4. AI Gateway: LLM 통합 및 MCP Tool 오케스트레이션

2. 핵심 아키텍처 패턴

2.1 Hot Reload: MongoDB Change Stream

전통적인 배포 방식:

코드 변경 → Git Push → CI 빌드 → 이미지 빌드 → Kubernetes 배포 → Pod 재시작 (5~10분)

imprun의 Hot Reload:

코드 저장 → MongoDB 저장 → Change Stream 감지 → VM 재로드 (1초 이내)

구현 세부사항:

// 1. FunctionService: 사용자가 Function 배포
async publish(gatewayId: string, functionId: string, environment: string) {
  const func = await this.findOne(gatewayId, functionId)

  // Gateway 전용 Database의 __published_functions 컬렉션에 저장
  const gatewayDb = this.getGatewayDb(gatewayId)

  await gatewayDb.collection('__published_functions').updateOne(
    { name: func.name, environment },
    {
      $set: {
        name: func.name,
        code: func.code,
        environment,
        publishedAt: new Date()
      }
    },
    { upsert: true }
  )

  // ✨ Change Stream이 자동으로 Runtime Pod에 전파
}
// 2. Runtime Pod: Change Stream 감지 및 Hot Reload
const changeStream = db.collection('__published_functions').watch()

changeStream.on('change', async (change) => {
  if (change.operationType === 'insert' || change.operationType === 'update') {
    const func = change.fullDocument

    // VM Context 재생성 (Cold Start 없음!)
    this.vmContexts.set(func.name, this.createVMContext(func.code))

    console.log(`✅ Hot Reload: ${func.name} (${Date.now() - func.publishedAt.getTime()}ms)`)
  }
})

Hot Reload의 장점:

  • 빠른 배포: 1초 이내 코드 반영
  • 무중단: Pod 재시작 없이 VM Context만 재생성
  • 비용 절감: 이미지 빌드/푸시 불필요
  • 개발 경험: 즉각적인 피드백으로 생산성 향상

2.2 State Machine: Phase 기반 리소스 관리

API Gateway 생성 시 여러 Kubernetes 리소스(Namespace, Deployment, Service, Ingress, Database)를 순차적으로 프로비저닝해야 합니다. 이를 State Machine + Background Task 패턴으로 안정적으로 관리합니다.

Gateway Phase Lifecycle:

Creating → Created → Starting → Started
   ↓          ↓         ↓          ↓
Deleting ← ← ← ← ← ← ← ← ← ← Deleted

Phase별 작업:

Phase 작업 내용 완료 조건
Creating Namespace, Database, Environment 3개 생성 모든 Environment가 Created 상태
Created Environment별 Runtime Pod 배포 모든 Pod가 Ready 상태
Starting APISIX Route 생성, Health Check 모든 Pod 응답 확인
Started 정상 운영 중 -

Background Task 구현:

@Injectable()
export class ApiGatewayTaskService {
  @Cron('*/1 * * * * *')  // 매초 실행
  async tick() {
    await this.handleCreatingPhase()
    await this.handleCreatedPhase()
    await this.handleStartingPhase()
  }

  async handleCreatingPhase() {
    // Optimistic Lock: 처리 중이지 않은 Gateway만 선택
    const gateway = await this.db.findOneAndUpdate(
      {
        phase: 'Creating',
        lockedAt: { $lt: new Date(Date.now() - 60000) }  // 60초 이상 지난 것만
      },
      { $set: { lockedAt: new Date() } },
      { sort: { lockedAt: 1, updatedAt: 1 }, returnDocument: 'after' }
    )

    if (!gateway) return

    try {
      // 1. Namespace 생성 (멱등성 보장)
      await this.k8sService.createNamespace(gateway.gatewayId)

      // 2. Environment 3개 생성 (dev/staging/prod)
      for (const envName of ['dev', 'staging', 'prod']) {
        await this.environmentService.create({
          gatewayId: gateway.gatewayId,
          name: envName,
          domain: `${gateway.gatewayId}-${envName}.api.imprun.dev`,
          phase: 'Creating'
        })
      }

      // 3. Gateway Database 생성
      await this.databaseService.createGatewayDatabase(gateway.gatewayId)

      // 4. 모든 Environment가 Created 상태인지 확인
      const allCreated = await this.environmentService.checkAllCreated(gateway.gatewayId)

      if (allCreated) {
        // Phase 전환: Creating → Created
        await this.db.updateOne(
          { gatewayId: gateway.gatewayId },
          {
            $set: {
              phase: 'Created',
              lockedAt: null
            }
          }
        )
      }
    } finally {
      // Lock 해제
      await this.db.updateOne(
        { gatewayId: gateway.gatewayId },
        { $set: { lockedAt: new Date(0) } }
      )
    }
  }
}

State Machine의 장점:

  • 명확한 상태: 각 Gateway의 현재 상태를 명확히 파악
  • 멱등성: 실패 시 재시도해도 안전
  • 복구 가능: Lock timeout으로 자동 복구
  • 확장 가능: 새로운 Phase 추가 용이

2.3 Optimistic Locking: 동시성 제어

여러 Background Task 인스턴스가 동시에 실행될 때, 같은 Gateway를 중복 처리하지 않도록 lockedAt 필드를 사용한 낙관적 락을 구현했습니다.

Optimistic Lock 패턴:

// 1. Lock 획득 시도 (findOneAndUpdate로 원자적 수행)
const gateway = await db.findOneAndUpdate(
  {
    phase: 'Creating',
    lockedAt: { $lt: new Date(Date.now() - 60000) }  // 60초 이상 지난 것만
  },
  { $set: { lockedAt: new Date() } },  // 현재 시각으로 Lock
  { sort: { lockedAt: 1, updatedAt: 1 }, returnDocument: 'after' }
)

// 2. Lock 획득 실패 시 (다른 Task가 처리 중)
if (!gateway) return

// 3. 작업 수행
try {
  await processGateway(gateway)
} finally {
  // 4. Lock 해제 (INIT_TIME = new Date(0)으로 설정)
  await db.updateOne(
    { gatewayId: gateway.gatewayId },
    { $set: { lockedAt: new Date(0) } }
  )
}

Lock Timeout 전략:

  • 정상 처리: 작업 완료 후 lockedAt = INIT_TIME 설정
  • 실패/타임아웃: 60초 경과 시 다른 Task가 재시도
  • 멱등성 보장: 각 단계는 중복 실행되어도 안전하게 설계

3. Multi-tenant Database 설계

3.1 Database 분리 전략

imprunSystem DatabaseGateway Database를 분리하여 데이터 격리와 성능을 보장합니다.

graph LR
    subgraph "System Database (sys_db)"
        User["User"]
        ApiGateway["ApiGateway"]
        Environment["Environment"]
        Function["Function (코드)"]
        Application["Application"]
        ApplicationGrant["ApplicationGrant"]
    end

    subgraph "Gateway Database (gatewayid_hometax)"
        Published["__published_functions"]
        UserData["users (사용자 정의)"]
        TaxData["taxes (사용자 정의)"]
    end

    subgraph "Gateway Database (gatewayid_shopping)"
        Published2["__published_functions"]
        Product["products (사용자 정의)"]
        Order["orders (사용자 정의)"]
    end

    ApiServer["API Server"] --> User
    ApiServer --> ApiGateway
    ApiServer --> Environment
    ApiServer --> Function

    Runtime1["Runtime Pod (hometax-dev)"] -.->|Change Stream| Published
    Runtime1 -.->|"imp.db()"| UserData
    Runtime1 -.->|"imp.db()"| TaxData

    Runtime2["Runtime Pod (shopping-prod)"] -.->|Change Stream| Published2
    Runtime2 -.->|"imp.db()"| Product
    Runtime2 -.->|"imp.db()"| Order

    style ApiServer stroke:#16a34a,stroke-width:2px
    style Runtime1 stroke:#ea580c,stroke-width:2px
    style Runtime2 stroke:#ea580c,stroke-width:2px

System Database (sys_db):

  • Platform 관리 데이터
  • User, ApiGateway, Environment, Function, Application, ApplicationGrant
  • API Server만 접근

Gateway Database (gatewayid_{gatewayId}):

  • Gateway별 독립 데이터베이스
  • __published_functions: 배포된 CloudFunction (Change Stream 대상)
  • 사용자 정의 컬렉션: CloudFunction에서 자유롭게 생성
  • Runtime Pod만 접근 (imp.db() API)

3.2 Database ACL

CloudFunction에서는 imp.db() API를 통해 Gateway Database에 접근하며, ACL 규칙으로 권한을 제어합니다.

// CloudFunction 코드 예시
export default async function(req, res) {
  // imp.db()는 현재 Gateway의 Database에만 접근
  const users = await imp.db().collection('users').find({}).toArray()

  // ❌ 다른 Gateway의 Database 접근 불가
  // ❌ System Database 접근 불가

  return res.json({ users })
}

ACL 규칙:

  • ✅ 현재 Gateway의 Database만 접근 가능
  • ✅ 모든 컬렉션 읽기/쓰기 허용
  • __published_functions 컬렉션 수정 금지
  • ❌ 다른 Gateway Database 접근 금지
  • ❌ System Database 접근 금지

4. Environment 아키텍처

4.1 dev/staging/prod 자동 구성

API Gateway 생성 시 3개 Environment가 자동으로 생성되며, 각각 독립적인 Runtime Pod와 도메인을 가집니다.

graph TB
    Gateway["API Gateway: hometax"]

    subgraph "Environment: dev"
        EnvDev["Environment (dev)"]
        PodDev["Runtime Pod"]
        DomainDev["hometax.dev.api.imprun.dev"]
    end

    subgraph "Environment: staging"
        EnvStaging["Environment (staging)"]
        PodStaging["Runtime Pod"]
        DomainStaging["hometax.staging.api.imprun.dev"]
    end

    subgraph "Environment: prod"
        EnvProd["Environment (prod)"]
        PodProd["Runtime Pod"]
        DomainProd["hometax.prod.api.imprun.dev"]
    end

    Gateway --> EnvDev
    Gateway --> EnvStaging
    Gateway --> EnvProd

    EnvDev --> PodDev
    EnvStaging --> PodStaging
    EnvProd --> PodProd

    PodDev --> DomainDev
    PodStaging --> DomainStaging
    PodProd --> DomainProd

    style Gateway stroke:#2563eb,stroke-width:2px
    style EnvDev stroke:#16a34a,stroke-width:2px
    style EnvStaging stroke:#ea580c,stroke-width:2px
    style EnvProd stroke:#dc2626,stroke-width:2px

Environment Entity 구조:

{
  _id: ObjectId,
  gatewayId: "hometax",
  name: "dev" | "staging" | "prod",
  domain: "hometax.dev.api.imprun.dev",
  customDomain?: "api.hometax.com",
  authType: "key-auth" | "none",
  plugins: [...],  // APISIX Plugins
  state: "active" | "inactive",
  phase: "Creating" | "Created",
  createdAt: Date,
  updatedAt: Date
}

4.2 Promotion Pipeline

CloudFunction을 dev → staging → prod로 순차적으로 승격할 수 있습니다.

// Promotion API
POST /api-gateways/:gatewayId/functions/:functionId/promote
{
  from: "dev",
  to: "staging"
}

// 구현
async promote(gatewayId: string, functionId: string, from: string, to: string) {
  // 1. 소스 Environment에서 Function 조회
  const func = await this.findOnePublished(gatewayId, functionId, from)

  if (!func) {
    throw new NotFoundException(`Function not found in ${from}`)
  }

  // 2. 타겟 Environment에 배포
  await this.publish(gatewayId, functionId, to)

  // 3. 배포 히스토리 기록
  await this.deploymentHistoryService.create({
    gatewayId,
    functionId,
    environment: to,
    version: func.version,
    promotedFrom: from
  })
}

5. AI Gateway 통합

5.1 MCP (Model Context Protocol) Tool

CloudFunction을 AI 에이전트가 호출할 수 있는 MCP Tool로 변환합니다.

graph LR
    User["사용자"]
    AIGateway["AI Gateway"]
    LLM["LLM (Claude, GPT)"]
    MCPProxy["MCP Proxy"]
    Function["CloudFunction"]

    User -->|"세금 계산해줘"| AIGateway
    AIGateway -->|Chat Request| LLM
    LLM -->|Tool Call| MCPProxy
    MCPProxy -->|HTTP POST| Function
    Function -->|Result| MCPProxy
    MCPProxy -->|Result| LLM
    LLM -->|Final Answer| AIGateway
    AIGateway -->|Response| User

    style User stroke:#2563eb,stroke-width:2px
    style LLM stroke:#7c3aed,stroke-width:2px
    style Function stroke:#16a34a,stroke-width:2px

MCP Tool 등록:

// 1. CloudFunction 작성
export default async function calculateTax(req, res) {
  const { income, deductions } = req.body
  const tax = (income - deductions) * 0.22
  return res.json({ tax })
}

// 2. MCP Tool로 등록
POST /ai-gateway/mcp-tools
{
  "name": "calculate_tax",
  "description": "소득세를 계산합니다",
  "functionUrl": "https://hometax.prod.api.imprun.dev/calculateTax",
  "inputSchema": {
    "type": "object",
    "properties": {
      "income": { "type": "number" },
      "deductions": { "type": "number" }
    },
    "required": ["income", "deductions"]
  }
}

// 3. AI Chat에서 사용
POST /ai-gateway/chat
{
  "messages": [
    { "role": "user", "content": "연봉 5000만원, 공제 1000만원일 때 세금 계산해줘" }
  ]
}

// LLM이 자동으로 calculate_tax Tool 호출 → 결과 반환

5.2 Multi-LLM Support

AI Gateway는 여러 LLM Provider를 지원하며, Adaptor 패턴으로 통일된 인터페이스를 제공합니다.

// Adaptor Interface
interface IAdaptor {
  chatCompletion(params: ChatCompletionParams): Promise<ChatCompletionResponse>
}

// OpenAI Adaptor
export class OpenAIAdaptor implements IAdaptor {
  async chatCompletion(params) {
    const response = await this.openai.chat.completions.create({
      model: params.model,
      messages: params.messages,
      tools: params.tools
    })
    return this.normalize(response)
  }
}

// Anthropic Adaptor
export class AnthropicAdaptor implements IAdaptor {
  async chatCompletion(params) {
    const response = await this.anthropic.messages.create({
      model: params.model,
      messages: params.messages,
      tools: params.tools
    })
    return this.normalize(response)
  }
}

// Factory Pattern
export class AdaptorFactory {
  create(provider: string): IAdaptor {
    switch (provider) {
      case 'openai': return new OpenAIAdaptor()
      case 'anthropic': return new AnthropicAdaptor()
      case 'local': return new LocalAdaptor()
      default: throw new Error(`Unknown provider: ${provider}`)
    }
  }
}

6. Application-Grant 인증 아키텍처

6.1 3-Layer 구조

외부 Consumer가 API Gateway를 사용하려면 Application을 생성하고, Grant를 통해 특정 Environment에 접근 권한을 부여받습니다.

graph TB
    subgraph "Layer 1: Application (Business)"
        App["Application<br/>(모바일 앱)"]
    end

    subgraph "Layer 2: ApplicationGrant (Permission)"
        Grant1["Grant<br/>(hometax-dev)"]
        Grant2["Grant<br/>(shopping-prod)"]
    end

    subgraph "Layer 3: Environment (Infrastructure)"
        Env1["Environment<br/>(hometax-dev)"]
        Env2["Environment<br/>(shopping-prod)"]
    end

    App --> Grant1
    App --> Grant2
    Grant1 --> Env1
    Grant2 --> Env2

    style App stroke:#2563eb,stroke-width:2px
    style Grant1 stroke:#16a34a,stroke-width:2px
    style Grant2 stroke:#16a34a,stroke-width:2px
    style Env1 stroke:#ea580c,stroke-width:2px
    style Env2 stroke:#ea580c,stroke-width:2px

핵심 원칙:

  • Application은 Gateway 독립적: 여러 Gateway/Environment 접근 가능
  • Grant는 N:M 관계: 하나의 Application이 여러 Gateway/Environment에 Grant 보유
  • API Key는 Grant당 1개: AES-256 암호화, 평문은 생성 시 1회만 노출

6.2 API Key 보안

// 1. API Key 생성 (ApplicationGrantService)
async create(dto: CreateApplicationGrantDto) {
  const apiKey = this.generateApiKey()  // 32바이트 랜덤
  const encryptedKey = this.encrypt(apiKey)  // AES-256-CBC

  const grant = await this.db.insert({
    applicationId: dto.applicationId,
    gatewayId: dto.gatewayId,
    environment: dto.environment,
    credentialId: `${dto.gatewayId}-${dto.environment}-${dto.appId}`,
    credentialSecret: encryptedKey,
    active: true
  })

  // ✅ 평문 API Key는 생성 시 1회만 반환
  return {
    ...grant,
    apiKey  // 이후 다시 조회 불가
  }
}

// 2. APISIX Consumer 생성 (자동)
await this.apisixService.createOrUpdateConsumer(
  region,
  namespace,
  environment,
  {
    username: grant.credentialId,
    plugins: {
      'key-auth': {
        key: apiKey  // APISIX에는 평문 전달
      }
    }
  }
)

7. 전체 Request Flow

사용자 요청이 CloudFunction까지 도달하는 전체 흐름을 살펴보겠습니다.

sequenceDiagram
    participant Client as Client
    participant APISIX as APISIX Ingress
    participant Runtime as Runtime Pod
    participant GatewayDB as Gateway DB
    participant VM as VM Context

    Client->>APISIX: GET /calculateTax?income=5000
    Note over APISIX: API Key 인증 (key-auth plugin)

    APISIX->>Runtime: Forward Request
    Note over Runtime: 1. Change Stream으로 최신 코드 로드됨

    Runtime->>GatewayDB: Check __published_functions
    GatewayDB-->>Runtime: Function Code

    Runtime->>VM: Execute Function in VM Context
    Note over VM: imp.db() 사용 가능

    VM->>GatewayDB: imp.db().collection('users').find()
    GatewayDB-->>VM: Query Result

    VM-->>Runtime: Function Result
    Runtime-->>APISIX: Response
    APISIX-->>Client: Response

각 단계 설명:

  1. Client → APISIX: API Key 포함하여 요청
  2. APISIX 인증: key-auth Plugin으로 Consumer 확인
  3. APISIX → Runtime: 인증 성공 시 Runtime Pod로 라우팅
  4. Runtime: Change Stream으로 최신 코드 로드 완료 (Hot Reload)
  5. VM Context: Function 실행 (Sandbox 환경)
  6. imp.db(): Gateway Database 접근 (ACL 적용)
  7. Response: 결과 반환

8. 성능 및 확장성

8.1 Hot Reload 성능

측정 결과:

  • MongoDB 저장 → Change Stream 감지: 평균 50ms
  • VM Context 재생성: 평균 20ms
  • 총 배포 시간: 70ms ~ 1초

전통적인 배포 대비:

  • 이미지 빌드/푸시: 3~5분 → 제거
  • Pod 재시작: 30초~1분 → 제거
  • Health Check: 10초 → 제거

8.2 Multi-tenant 확장

Gateway별 격리:

  • 독립 Database → 데이터 격리 및 성능 보장
  • 독립 Runtime Pod → 리소스 격리 (CPU, Memory)
  • 독립 도메인 → 트래픽 분산

Kubernetes HPA:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: hometax-dev-runtime
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: hometax-dev-runtime
  minReplicas: 1
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70

8.3 Background Task 독립 실행

각 TaskService는 독립적으로 Cron 실행되므로, 여러 Gateway를 동시에 프로비저닝할 수 있습니다.

// ApiGatewayTaskService
@Cron('*/1 * * * * *')
async tick() {
  // Fire-and-Forget 패턴 (에러만 로깅)
  this.handleCreatingPhase().catch((err) => this.logger.error(err))
  this.handleDeletingPhase().catch((err) => this.logger.error(err))
}

// EnvironmentTaskService
@Cron('*/1 * * * * *')
async tick() {
  this.handleCreatingPhase().catch((err) => this.logger.error(err))
  this.handleDeletingPhase().catch((err) => this.logger.error(err))
}

// InstanceTaskService
@Cron('*/5 * * * * *')
async tick() {
  this.handleStartingPhase().catch((err) => this.logger.error(err))
  this.handleStoppingPhase().catch((err) => this.logger.error(err))
}

장점:

  • 각 TaskService가 독립적으로 동작
  • 하나의 Phase 실패가 다른 Phase에 영향 없음
  • Optimistic Lock으로 동시성 제어

9. 실전 예시: 홈택스 API 구축

전체 아키텍처를 활용하여 홈택스 API를 구축하는 과정을 살펴보겠습니다.

9.1 API Gateway 생성

# Web Console에서 생성
POST /api-gateways
{
  "name": "홈택스 API",
  "regionId": "64a7b2c3...",
  "bundle": {
    "runtime": "nodejs20",
    "memory": "512Mi",
    "cpu": "500m"
  }
}

# Response
{
  "gatewayId": "hometax",
  "phase": "Creating",
  "environments": []
}

# Background Task가 자동으로:
# 1. Namespace 생성
# 2. dev/staging/prod Environment 3개 생성
# 3. Gateway Database 생성
# 4. Runtime Pod 3개 배포
# → 약 30초 후 phase: "Started"

9.2 CloudFunction 작성 및 배포

// Web Console Monaco Editor에서 작성
export default async function calculateTax(req, res) {
  const { income, deductions } = req.body

  // Validation
  if (!income || !deductions) {
    return res.status(400).json({ error: 'Missing required fields' })
  }

  // 세금 계산
  const taxableIncome = income - deductions
  const tax = taxableIncome * 0.22

  // Database 저장
  await imp.db().collection('tax_calculations').insertOne({
    income,
    deductions,
    tax,
    calculatedAt: new Date()
  })

  return res.json({ tax })
}
# dev 환경에 배포
POST /api-gateways/hometax/functions/calculateTax/publish
{ "environment": "dev" }

# ✅ 1초 이내에 배포 완료 (Hot Reload)
# 호출: GET https://hometax.dev.api.imprun.dev/calculateTax?income=5000&deductions=1000

9.3 Application 생성 및 API Key 발급

# 1. Application 생성
POST /applications
{
  "name": "모바일 앱",
  "description": "홈택스 모바일 앱",
  "organization": "국세청"
}

# 2. Grant 생성 (dev 환경 접근 권한)
POST /application-grants
{
  "applicationId": "64a7b2c3...",
  "gatewayId": "hometax",
  "environment": "dev"
}

# Response
{
  "grantId": "64a7b2c4...",
  "credentialId": "hometax-dev-app123",
  "apiKey": "sk_live_abc123def456...",  # ✅ 평문은 이번 1회만 노출
  "credentialReference": {
    "apisixConsumerId": "hometax-dev-app123"
  }
}

9.4 API 호출 (Client)

# API Key로 인증하여 호출
curl -X POST https://hometax.dev.api.imprun.dev/calculateTax \
  -H "apikey: sk_live_abc123def456..." \
  -H "Content-Type: application/json" \
  -d '{
    "income": 50000000,
    "deductions": 10000000
  }'

# Response
{
  "tax": 8800000
}

9.5 Promotion (dev → prod)

# dev 테스트 완료 후 staging으로 배포
POST /api-gateways/hometax/functions/dev%2FcalculateTax/deploy-to-stage
{
  "targetStage": "staging"
}

# Response: staging/calculateTax 생성 (source.semver 상속)
# History: "Deployed from dev to staging (version: 1.0.0)"

# staging 테스트 완료 후 prod로 배포
POST /api-gateways/hometax/functions/staging%2FcalculateTax/deploy-to-stage
{
  "targetStage": "prod"
}

# Response: prod/calculateTax 생성
# History: "Deployed from staging to prod (version: 1.0.0)"

# ✅ 각 배포 1초 이내 완료 (Hot Reload)
# ✅ Promotion Pipeline 활성화 시 stage 순서 자동 검증

9.6 AI 통합 (MCP Tool)

# 1. MCP Tool 등록
POST /ai-gateway/mcp-tools
{
  "name": "calculate_tax",
  "description": "소득세를 계산합니다",
  "functionUrl": "https://hometax.prod.api.imprun.dev/calculateTax",
  "inputSchema": {
    "type": "object",
    "properties": {
      "income": { "type": "number", "description": "연간 소득" },
      "deductions": { "type": "number", "description": "공제액" }
    },
    "required": ["income", "deductions"]
  }
}

# 2. AI Chat에서 사용
POST /ai-gateway/chat
{
  "messages": [
    { "role": "user", "content": "연봉 5천만원, 공제 1천만원일 때 세금 계산해줘" }
  ]
}

# AI가 자동으로 calculate_tax Tool 호출 → 결과 생성
# Response:
{
  "choices": [
    {
      "message": {
        "role": "assistant",
        "content": "연봉 5천만원에서 공제 1천만원을 제외하면 과세 대상 소득은 4천만원입니다. 세율 22%를 적용하면 세금은 8,800,000원입니다."
      }
    }
  ]
}

10. 개발 경험 및 배운 점

10.1 기술적 도전

Hot Reload 구현의 어려움:

  • 초기에는 WebSocket으로 코드를 전송했으나, 연결 안정성 문제 발생
  • MongoDB Change Stream으로 전환하여 안정성과 순서 보장 확보
  • VM Context 재생성 시 메모리 누수 방지 (명시적 가비지 컬렉션)

State Machine 복잡도:

  • Phase 전환 조건이 복잡하여 초기 구현 시 무한 루프 발생
  • Optimistic Lock으로 동시성 문제 해결
  • 멱등성을 보장하는 코드 작성이 핵심

Multi-tenant Database 성능:

  • Gateway가 1000개 이상으로 증가 시 MongoDB 연결 풀 부족
  • Connection Pooling 전략 개선 (Gateway당 최소 연결 수 제한)
  • Database Sharding 도입 고려 중

10.2 아키텍처 선택의 trade-off

Hot Reload vs. 이미지 빌드:

  • 장점: 빠른 배포, 개발 경험 향상
  • 단점: 코드 버전 관리 어려움 (Git과 분리)
  • 해결: Deployment History 기록, Git 통합 계획

State Machine vs. Event-Driven:

  • 장점: 명확한 상태, 복구 용이
  • 단점: Polling 방식으로 리소스 사용량 증가
  • 해결: Cron 주기 최적화, Phase별 독립 처리

Multi-tenant DB vs. Shared DB:

  • 장점: 완전한 데이터 격리, 성능 보장
  • 단점: Database 수 증가로 관리 복잡도 증가
  • 해결: 자동화된 Database 생성/삭제, Monitoring 강화

11. 향후 계획

11.1 단기 계획 (1~3개월)

Enhanced Observability:

  • CloudFunction 실행 로그 수집 (Loki)
  • Metrics 수집 (Prometheus)
  • Distributed Tracing (Jaeger)

Testing Infrastructure:

  • E2E 테스트 자동화
  • Performance 테스트 (K6)
  • Chaos Engineering (실패 시나리오 테스트)

11.2 장기 계획 (6개월~1년)

Secret Management:

  • Kubernetes Secret 통합
  • Vault 도입 검토
  • API Key Rotation

Performance Optimization:

  • Function Cold Start 최소화
  • Database Query 최적화
  • CDN 통합

Developer Experience:

  • CLI 도구 개발
  • Local Development 환경 개선
  • VSCode Extension

마치며

imprun Platform은 Hot Reload, State Machine, Multi-tenant Database, AI 통합이라는 4가지 핵심 아키텍처 패턴으로 구축되었습니다.

핵심 가치:

  • 빠른 배포: 1초 이내 코드 반영으로 개발 생산성 극대화
  • 안정적 운영: State Machine과 Optimistic Lock으로 복구 가능한 시스템
  • 완전한 격리: Gateway별 독립 Database와 Runtime으로 Multi-tenant 환경 보장
  • AI 통합: CloudFunction을 MCP Tool로 변환하여 AI 에이전트 활용

선택한 이유:

  • Event-Driven 대신 State Machine을 선택한 이유는 명확한 상태 관리와 복구 가능성
  • 이미지 빌드 대신 Hot Reload를 선택한 이유는 개발 경험 향상
  • Shared Database 대신 Multi-tenant Database를 선택한 이유는 완전한 데이터 격리

피한 이유:

  • Saga Pattern은 현재 아키텍처에서 과도한 복잡도 추가
  • Event-Driven은 디버깅이 어렵고 순서 보장이 어려움
  • Blue-Green Deployment는 Hot Reload로 무중단 배포 가능

달성한 결과:

  • 배포 시간: 5~10분 → 1초 이내 (99% 감소)
  • Gateway 생성 시간: 수동 30분 → 자동 30초 (98% 감소)
  • 개발자 만족도: Hot Reload로 즉각적인 피드백
  • AI 통합: CloudFunction → MCP Tool 자동 변환

관련 블로그:

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/02   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
글 보관함