본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout

내비게이션

  • Tech
  • Projects
  • Books
  • About
  • Tags

카테고리

  • AI / ML
  • 웹 개발
  • 프로그래밍
  • 개발 도구

연결

  • GitHub
  • Email
  • RSS
© 2026 Kreath Archive. All rights reserved.Built with Next.js + MDX
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 7장: 플랫폼 API 설계
2026년 3월 29일·인프라·

7장: 플랫폼 API 설계

Platform as a Product 관점에서의 API 계층 설계, 추상화 수준 결정, 내부 API 버전닝, 인증과 인가, 감사 로깅, 그리고 CLI 도구 제공까지 다룹니다.

14분1,005자9개 섹션
platform-engineeringdevopsinfrastructure
공유
platform-engineering7 / 10
12345678910
이전6장: 셀프서비스 인프라다음8장: 비용 가시성과 FinOps 통합

학습 목표

  • Platform as a Product(제품으로서의 플랫폼) 관점을 이해합니다.
  • 플랫폼 API 계층을 설계하고 추상화 수준을 결정할 수 있습니다.
  • 내부 API의 버전닝, 인증/인가, 감사 로깅 전략을 수립할 수 있습니다.
  • 개발자를 위한 CLI 도구를 설계할 수 있습니다.

Platform as a Product

플랫폼을 성공적으로 운영하려면 내부 제품으로 취급해야 합니다. 개발자는 고객이고, 플랫폼 팀은 제품 팀입니다.

제품 관점의 핵심 요소

요소외부 제품플랫폼 (내부 제품)
고객외부 사용자내부 개발자
제품 관리자Product ManagerPlatform Product Manager
성공 지표MAU, 매출도입률, 개발자 만족도
피드백사용자 리뷰개발자 설문, NPS
문서API 문서플랫폼 가이드, Runbook
지원고객 지원팀Slack 채널, Office Hour

API가 왜 중요한가

Backstage 포털은 개발자 경험의 "프론트엔드"입니다. 하지만 플랫폼의 진정한 가치는 API 계층에 있습니다. 잘 설계된 플랫폼 API는 다음을 가능하게 합니다.

  • 포털 외에도 CLI, 스크립트, CI/CD에서 플랫폼 기능 활용
  • 자동화 파이프라인과의 통합
  • 플랫폼 기능의 프로그래매틱한 테스트
  • 서드파티 도구와의 연동

API 계층 설계

계층별 추상화

플랫폼 API는 세 가지 추상화 수준으로 설계합니다.

API 추상화 계층
text
Level 3: 워크플로우 API (최고 수준 추상화)
  - "새 마이크로서비스 생성" (한 번의 호출로 모든 것 완료)
  - "서비스를 production에 배포" (복잡한 과정을 한 번에)
 
Level 2: 리소스 API (중간 수준 추상화)
  - "PostgreSQL 데이터베이스 생성"
  - "Redis 캐시 생성"
  - "네임스페이스 생성"
 
Level 1: 기반 API (최저 수준 추상화)
  - "Terraform 워크스페이스 실행"
  - "Kubernetes 매니페스트 적용"
  - "ArgoCD 앱 동기화"

대부분의 개발자는 Level 3(워크플로우 API)를 사용합니다. 고급 사용자나 자동화 파이프라인은 Level 2(리소스 API)를 사용합니다. Level 1은 플랫폼 팀 내부에서만 사용합니다.

RESTful API 설계

platform-api-openapi.yaml
yaml
openapi: 3.1.0
info:
  title: "Platform API"
  version: "1.0.0"
  description: "내부 개발자 플랫폼 API"
 
paths:
  /api/v1/services:
    post:
      summary: "새 서비스 생성"
      description: "Golden Path 기반으로 새 마이크로서비스를 생성합니다."
      tags: [services]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [name, template, owner]
              properties:
                name:
                  type: string
                  pattern: "^[a-z][a-z0-9-]*$"
                  example: "checkout-service"
                template:
                  type: string
                  enum: ["spring-boot", "go-grpc", "fastapi", "nextjs"]
                owner:
                  type: string
                  example: "team-checkout"
                system:
                  type: string
                  example: "checkout-system"
                options:
                  type: object
                  properties:
                    database:
                      type: string
                      enum: ["postgresql", "mysql", "none"]
                    messaging:
                      type: string
                      enum: ["kafka", "rabbitmq", "none"]
      responses:
        "202":
          description: "서비스 생성이 시작되었습니다."
          content:
            application/json:
              schema:
                type: object
                properties:
                  taskId:
                    type: string
                    example: "task-abc123"
                  status:
                    type: string
                    example: "provisioning"
                  estimatedTime:
                    type: string
                    example: "5-10 minutes"
 
  /api/v1/services/{name}:
    get:
      summary: "서비스 상세 조회"
      tags: [services]
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: "서비스 상세 정보"
 
  /api/v1/infrastructure/databases:
    post:
      summary: "데이터베이스 프로비저닝"
      tags: [infrastructure]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [name, engine, size]
              properties:
                name:
                  type: string
                engine:
                  type: string
                  enum: ["postgresql", "mysql"]
                size:
                  type: string
                  enum: ["small", "medium", "large"]
                environment:
                  type: string
                  enum: ["dev", "staging", "production"]
      responses:
        "202":
          description: "데이터베이스 프로비저닝이 시작되었습니다."
 
  /api/v1/costs/{service}:
    get:
      summary: "서비스별 비용 조회"
      tags: [costs]
      parameters:
        - name: service
          in: path
          required: true
          schema:
            type: string
        - name: period
          in: query
          schema:
            type: string
            enum: ["7d", "30d", "90d"]
            default: "30d"
      responses:
        "200":
          description: "비용 데이터"
Tip

플랫폼 API는 비동기 패턴을 적극 활용해야 합니다. 인프라 프로비저닝은 수 분에서 수십 분이 걸릴 수 있으므로, 즉각 응답(202 Accepted)과 함께 작업 상태를 조회할 수 있는 엔드포인트를 제공합니다.


API 구현

서비스 생성 API 구현 예시

src/api/routes/services.ts
typescript
import { Router, Request, Response } from 'express';
import { z } from 'zod';
import { ScaffolderClient } from '../clients/scaffolder';
import { AuditLogger } from '../audit/logger';
import { CostEstimator } from '../costs/estimator';
 
const CreateServiceSchema = z.object({
  name: z.string().regex(/^[a-z][a-z0-9-]*$/),
  template: z.enum(['spring-boot', 'go-grpc', 'fastapi', 'nextjs']),
  owner: z.string(),
  system: z.string().optional(),
  options: z.object({
    database: z.enum(['postgresql', 'mysql', 'none']).default('none'),
    messaging: z.enum(['kafka', 'rabbitmq', 'none']).default('none'),
  }).optional(),
});
 
const router = Router();
 
router.post('/api/v1/services', async (req: Request, res: Response) => {
  // 입력 검증
  const parsed = CreateServiceSchema.safeParse(req.body);
  if (!parsed.success) {
    return res.status(400).json({
      error: 'Validation failed',
      details: parsed.error.issues,
    });
  }
 
  const input = parsed.data;
  const userId = req.user?.id;
 
  // 비용 추정
  const costEstimate = await CostEstimator.estimate({
    template: input.template,
    database: input.options?.database,
    messaging: input.options?.messaging,
  });
 
  // Scaffolder 작업 실행
  const task = await ScaffolderClient.createTask({
    templateRef: `template:default/${input.template}-service`,
    values: {
      serviceName: input.name,
      owner: input.owner,
      system: input.system,
      database: input.options?.database,
      messaging: input.options?.messaging,
    },
    createdBy: userId,
  });
 
  // 감사 로그 기록
  await AuditLogger.log({
    action: 'service.create',
    actor: userId,
    resource: input.name,
    details: {
      template: input.template,
      options: input.options,
      estimatedMonthlyCost: costEstimate.monthly,
    },
  });
 
  return res.status(202).json({
    taskId: task.id,
    status: 'provisioning',
    estimatedTime: '5-10 minutes',
    estimatedMonthlyCost: costEstimate.monthly,
    statusUrl: `/api/v1/tasks/${task.id}`,
  });
});
 
export { router as servicesRouter };

인증과 인가

인증 전략

내부 API의 인증은 조직의 SSO(Single Sign-On) 시스템과 통합합니다.

src/middleware/auth.ts
typescript
import { Request, Response, NextFunction } from 'express';
import { verifyToken } from '../auth/oidc';
 
interface PlatformUser {
  id: string;
  email: string;
  groups: string[];
  roles: string[];
}
 
declare global {
  namespace Express {
    interface Request {
      user?: PlatformUser;
    }
  }
}
 
export async function authMiddleware(
  req: Request,
  res: Response,
  next: NextFunction,
) {
  const token = req.headers.authorization?.replace('Bearer ', '');
 
  if (!token) {
    return res.status(401).json({ error: 'Authentication required' });
  }
 
  try {
    const payload = await verifyToken(token);
    req.user = {
      id: payload.sub,
      email: payload.email,
      groups: payload.groups || [],
      roles: payload.roles || [],
    };
    next();
  } catch (error) {
    return res.status(401).json({ error: 'Invalid token' });
  }
}

인가 정책

RBAC(Role-Based Access Control) 기반의 인가 정책을 적용합니다.

authorization-policy.yaml
yaml
roles:
  developer:
    permissions:
      - action: "service.create"
        conditions:
          owner: "self-team"
      - action: "infrastructure.request"
        conditions:
          environment: ["dev", "staging"]
          estimatedCost:
            lessThan: 200
      - action: "service.read"
      - action: "cost.read"
        conditions:
          scope: "own-team"
 
  team-lead:
    inherits: developer
    permissions:
      - action: "infrastructure.request"
        conditions:
          environment: ["dev", "staging", "production"]
          estimatedCost:
            lessThan: 1000
      - action: "infrastructure.approve"
        conditions:
          team: "own-team"
      - action: "cost.read"
        conditions:
          scope: "own-team"
 
  platform-admin:
    permissions:
      - action: "*"

감사 로깅

모든 플랫폼 API 호출은 감사 로그에 기록되어야 합니다.

src/audit/logger.ts
typescript
interface AuditEvent {
  action: string;
  actor: string | undefined;
  resource: string;
  details: Record<string, unknown>;
  result?: 'success' | 'failure';
  reason?: string;
}
 
export class AuditLogger {
  static async log(event: AuditEvent): Promise<void> {
    const auditRecord = {
      timestamp: new Date().toISOString(),
      ...event,
      metadata: {
        apiVersion: 'v1',
        source: 'platform-api',
      },
    };
 
    // 구조화된 로그로 출력
    console.log(JSON.stringify(auditRecord));
 
    // 영구 저장소에 기록 (예: Elasticsearch)
    await AuditStore.write(auditRecord);
  }
}

감사 로그에 기록할 주요 이벤트는 다음과 같습니다.

이벤트설명위험도
service.create새 서비스 생성낮음
service.delete서비스 삭제높음
infrastructure.request인프라 요청중간
infrastructure.approve인프라 승인중간
policy.override정책 예외 적용높음
rbac.change권한 변경높음

CLI 도구

개발자 경험을 완성하려면 포털과 함께 CLI 도구를 제공해야 합니다. 터미널에서 작업하는 개발자에게 CLI는 가장 자연스러운 인터페이스입니다.

CLI 설계 원칙

CLI 사용 예시
bash
# 새 서비스 생성
platform create service checkout-service \
  --template spring-boot \
  --owner team-checkout \
  --database postgresql
 
# 서비스 목록 조회
platform list services --team team-checkout
 
# 서비스 상태 확인
platform status checkout-service
 
# 데이터베이스 프로비저닝
platform create database checkout-db \
  --engine postgresql \
  --size medium \
  --environment dev
 
# 비용 조회
platform cost checkout-service --period 30d
 
# 환경 전환
platform env switch staging
 
# 로그 조회
platform logs checkout-service --tail 100

CLI 구현 (Go)

cmd/platform/main.go
go
package main
 
import (
	"fmt"
	"os"
 
	"github.com/spf13/cobra"
)
 
var rootCmd = &cobra.Command{
	Use:   "platform",
	Short: "내부 개발자 플랫폼 CLI",
	Long:  "IDP의 모든 기능을 커맨드라인에서 사용할 수 있습니다.",
}
 
var createServiceCmd = &cobra.Command{
	Use:   "service [name]",
	Short: "새 서비스 생성",
	Args:  cobra.ExactArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		name := args[0]
		template, _ := cmd.Flags().GetString("template")
		owner, _ := cmd.Flags().GetString("owner")
		database, _ := cmd.Flags().GetString("database")
 
		fmt.Printf("Creating service: %s\n", name)
		fmt.Printf("  Template: %s\n", template)
		fmt.Printf("  Owner: %s\n", owner)
		fmt.Printf("  Database: %s\n", database)
 
		// Platform API 호출
		client := NewPlatformClient()
		result, err := client.CreateService(CreateServiceInput{
			Name:     name,
			Template: template,
			Owner:    owner,
			Options: ServiceOptions{
				Database: database,
			},
		})
		if err != nil {
			return fmt.Errorf("service creation failed: %w", err)
		}
 
		fmt.Printf("\nService creation started!\n")
		fmt.Printf("  Task ID: %s\n", result.TaskID)
		fmt.Printf("  Status: %s\n", result.StatusURL)
 
		return nil
	},
}
 
func init() {
	createCmd := &cobra.Command{
		Use:   "create",
		Short: "리소스 생성",
	}
 
	createServiceCmd.Flags().String("template", "spring-boot", "서비스 템플릿")
	createServiceCmd.Flags().String("owner", "", "소유 팀")
	createServiceCmd.Flags().String("database", "none", "데이터베이스 유형")
	_ = createServiceCmd.MarkFlagRequired("owner")
 
	createCmd.AddCommand(createServiceCmd)
	rootCmd.AddCommand(createCmd)
}
 
func main() {
	if err := rootCmd.Execute(); err != nil {
		os.Exit(1)
	}
}
Info

CLI 도구는 자동 완성(Auto-completion)을 지원해야 합니다. Cobra(Go)나 oclif(TypeScript) 같은 CLI 프레임워크는 Bash, Zsh, Fish 셸의 자동 완성을 기본 지원합니다.


API 버전닝

내부 API라 해도 버전 관리는 필수입니다. 플랫폼 API의 소비자가 여러 팀에 걸쳐 있기 때문에, 하위 호환성을 깨는 변경은 큰 영향을 미칩니다.

버전닝 전략

URL 기반 버전닝
text
/api/v1/services        # 현재 안정 버전
/api/v2/services        # 차기 버전 (베타)
/api/v1/infrastructure  # 현재 안정 버전

버전 수명 주기:

단계설명기간
Alpha내부 테스트용, 언제든 변경 가능자유
Beta외부 팀 테스트, 큰 변경 사전 공지1-2개월
Stable프로덕션 사용, 하위 호환성 보장최소 6개월
Deprecated신규 사용 중단 권고3개월 유예
Removed완전 제거-

정리

이번 장에서는 플랫폼 API의 설계와 구현을 다루었습니다.

  • 플랫폼을 내부 제품으로 취급하고, API를 핵심 인터페이스로 설계해야 합니다.
  • API는 워크플로우, 리소스, 기반의 세 가지 추상화 수준으로 계층화합니다.
  • RBAC 기반 인가와 감사 로깅으로 보안과 추적성을 확보합니다.
  • CLI 도구는 터미널에서 작업하는 개발자에게 최적의 인터페이스입니다.
  • 내부 API도 버전닝과 하위 호환성 관리가 필수입니다.

다음 장에서는 비용 가시성과 FinOps 통합을 다룹니다. 개발자에게 비용을 보여주는 방법, Backstage 비용 대시보드, 태그 기반 비용 할당, 그리고 AI 기반 비용 최적화까지 살펴보겠습니다.

이 글이 도움이 되셨나요?

관련 주제 더 보기

#platform-engineering#devops#infrastructure

관련 글

인프라

8장: 비용 가시성과 FinOps 통합

FinOps 원칙과 플랫폼 통합, Backstage 비용 대시보드, 태그 기반 비용 할당, 리소스 생성 시점의 비용 예측, 그리고 AI 기반 비용 최적화를 다룹니다.

2026년 3월 31일·16분
인프라

6장: 셀프서비스 인프라

셀프서비스의 핵심 원칙, GitOps 기반 인프라 요청, Crossplane을 활용한 Kubernetes 네이티브 인프라 추상화, 그리고 승인 워크플로우를 다룹니다.

2026년 3월 27일·14분
인프라

9장: 조직 확장과 플랫폼 팀 운영

플랫폼 팀의 구조와 Team Topologies 적용, 채택률 측정과 개선, 개발자 만족도(NPS), 이해관계자 관리, 그리고 플랫폼 성숙도 모델을 다룹니다.

2026년 4월 2일·22분
이전 글6장: 셀프서비스 인프라
다음 글8장: 비용 가시성과 FinOps 통합

댓글

목차

약 14분 남음
  • 학습 목표
  • Platform as a Product
    • 제품 관점의 핵심 요소
    • API가 왜 중요한가
  • API 계층 설계
    • 계층별 추상화
    • RESTful API 설계
  • API 구현
    • 서비스 생성 API 구현 예시
  • 인증과 인가
    • 인증 전략
    • 인가 정책
  • 감사 로깅
  • CLI 도구
    • CLI 설계 원칙
    • CLI 구현 (Go)
  • API 버전닝
    • 버전닝 전략
  • 정리