🔧 DevOps

Internal Developer Platform

IDP, 내부 개발자 플랫폼

개발자가 인프라 복잡성 없이 셀프서비스로 환경 구성, 배포, 모니터링을 할 수 있게 해주는 내부 플랫폼으로, Platform Engineering의 핵심 산출물입니다.

📖 상세 설명

Internal Developer Platform(IDP)은 조직 내 개발자들이 인프라 전문 지식 없이도 애플리케이션을 개발, 배포, 운영할 수 있도록 셀프서비스 기능을 제공하는 내부 플랫폼입니다. Gartner는 2026년까지 80%의 소프트웨어 엔지니어링 조직이 플랫폼 팀을 구축할 것으로 전망하며, IDP는 이러한 Platform Engineering의 핵심 결과물입니다.

IDP의 핵심 가치는 개발자 경험(Developer Experience, DX)의 개선입니다. 개발자는 Kubernetes, Terraform, 네트워킹 등 인프라 세부사항을 이해할 필요 없이, 추상화된 인터페이스를 통해 "Python 서비스 생성", "데이터베이스 프로비저닝", "스테이징 환경 배포" 같은 작업을 수행합니다. 이는 인지 부하(Cognitive Load)를 줄이고 개발자가 비즈니스 로직에 집중할 수 있게 합니다.

IDP의 주요 구성요소로는 서비스 카탈로그(조직 내 모든 서비스, API, 인프라 자원 목록), 셀프서비스 포털(환경 생성, 배포, 롤백 등 자동화된 워크플로우), 골든 패스(검증된 기술 스택과 템플릿), 관찰성 통합(로깅, 메트릭, 트레이싱 대시보드) 등이 있습니다. Spotify의 Backstage는 대표적인 오픈소스 IDP 프레임워크입니다.

IDP 구축 시 흔한 실수는 처음부터 완벽한 플랫폼을 만들려는 것입니다. 성공적인 IDP는 MVP로 시작해 개발자 피드백을 기반으로 점진적으로 확장합니다. 또한 플랫폼 팀과 개발 팀 간의 긴밀한 협업이 필수적이며, "플랫폼을 제품처럼" 관리하는 Product Thinking 접근이 중요합니다.

💻 코드 예제

Backstage 서비스 템플릿 예제 (YAML)

# Backstage Software Template - 새 마이크로서비스 생성
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: python-fastapi-service
  title: Python FastAPI 마이크로서비스
  description: FastAPI 기반 마이크로서비스를 생성하고 Kubernetes에 배포합니다
  tags:
    - python
    - fastapi
    - kubernetes
spec:
  owner: platform-team
  type: service

  # 개발자 입력 파라미터
  parameters:
    - title: 서비스 정보
      required:
        - serviceName
        - team
      properties:
        serviceName:
          title: 서비스 이름
          type: string
          description: 영문 소문자, 하이픈만 사용
          pattern: '^[a-z][a-z0-9-]*$'
        team:
          title: 담당 팀
          type: string
          ui:field: OwnerPicker
          ui:options:
            allowedKinds:
              - Group
        description:
          title: 서비스 설명
          type: string

    - title: 인프라 옵션
      properties:
        database:
          title: 데이터베이스
          type: string
          enum:
            - none
            - postgresql
            - mongodb
          default: none
        cache:
          title: 캐시
          type: string
          enum:
            - none
            - redis
          default: none
        environment:
          title: 초기 환경
          type: array
          items:
            type: string
            enum:
              - development
              - staging
              - production
          default:
            - development

  # 자동화 단계
  steps:
    # 1. 템플릿에서 코드 생성
    - id: fetch-template
      name: 프로젝트 템플릿 가져오기
      action: fetch:template
      input:
        url: ./skeleton
        values:
          serviceName: ${{ parameters.serviceName }}
          team: ${{ parameters.team }}
          description: ${{ parameters.description }}
          database: ${{ parameters.database }}

    # 2. GitHub 저장소 생성
    - id: create-repo
      name: GitHub 저장소 생성
      action: publish:github
      input:
        repoUrl: github.com?owner=my-org&repo=${{ parameters.serviceName }}
        description: ${{ parameters.description }}
        defaultBranch: main
        protectDefaultBranch: true

    # 3. Kubernetes 리소스 생성
    - id: create-k8s-resources
      name: Kubernetes 네임스페이스 및 리소스 생성
      action: kubernetes:apply
      input:
        manifest:
          apiVersion: v1
          kind: Namespace
          metadata:
            name: ${{ parameters.serviceName }}
            labels:
              team: ${{ parameters.team }}

    # 4. ArgoCD 애플리케이션 등록
    - id: register-argocd
      name: ArgoCD 앱 등록
      action: argocd:create-app
      input:
        appName: ${{ parameters.serviceName }}
        repoUrl: ${{ steps['create-repo'].output.remoteUrl }}
        path: k8s/
        targetNamespace: ${{ parameters.serviceName }}

    # 5. 데이터베이스 프로비저닝 (선택한 경우)
    - id: provision-database
      name: 데이터베이스 프로비저닝
      if: ${{ parameters.database != 'none' }}
      action: crossplane:create
      input:
        apiVersion: database.example.com/v1alpha1
        kind: ${{ parameters.database == 'postgresql' && 'PostgreSQLInstance' || 'MongoDBInstance' }}
        metadata:
          name: ${{ parameters.serviceName }}-db
        spec:
          size: small
          version: latest

    # 6. Backstage 카탈로그에 등록
    - id: register-catalog
      name: 서비스 카탈로그 등록
      action: catalog:register
      input:
        repoContentsUrl: ${{ steps['create-repo'].output.repoContentsUrl }}
        catalogInfoPath: /catalog-info.yaml

  # 완료 후 출력
  output:
    links:
      - title: GitHub 저장소
        url: ${{ steps['create-repo'].output.remoteUrl }}
      - title: ArgoCD 대시보드
        url: https://argocd.example.com/applications/${{ parameters.serviceName }}
      - title: 서비스 카탈로그
        url: ${{ steps['register-catalog'].output.entityRef }}

Python으로 IDP API 클라이언트 구현

"""
IDP(Internal Developer Platform) 셀프서비스 API 클라이언트
개발자가 프로그래매틱하게 리소스를 생성/관리할 수 있는 인터페이스
"""
import httpx
from dataclasses import dataclass
from typing import Optional, List
from enum import Enum

class DatabaseType(Enum):
    POSTGRESQL = "postgresql"
    MONGODB = "mongodb"
    REDIS = "redis"

class EnvironmentType(Enum):
    DEVELOPMENT = "development"
    STAGING = "staging"
    PRODUCTION = "production"

@dataclass
class ServiceConfig:
    name: str
    team: str
    description: str
    language: str = "python"
    database: Optional[DatabaseType] = None
    cache: bool = False
    environments: List[EnvironmentType] = None

    def __post_init__(self):
        if self.environments is None:
            self.environments = [EnvironmentType.DEVELOPMENT]

class IDPClient:
    """IDP 셀프서비스 API 클라이언트"""

    def __init__(self, base_url: str, api_token: str):
        self.base_url = base_url
        self.client = httpx.Client(
            base_url=base_url,
            headers={"Authorization": f"Bearer {api_token}"},
            timeout=30.0
        )

    def create_service(self, config: ServiceConfig) -> dict:
        """새 마이크로서비스 생성 (저장소, K8s 리소스, CI/CD 포함)"""
        payload = {
            "templateName": f"{config.language}-service-template",
            "parameters": {
                "serviceName": config.name,
                "team": config.team,
                "description": config.description,
                "database": config.database.value if config.database else "none",
                "cache": "redis" if config.cache else "none",
                "environments": [env.value for env in config.environments]
            }
        }

        response = self.client.post("/api/scaffolder/v1/tasks", json=payload)
        response.raise_for_status()

        task = response.json()
        print(f"서비스 생성 작업 시작: {task['id']}")
        return task

    def get_task_status(self, task_id: str) -> dict:
        """서비스 생성 작업 상태 조회"""
        response = self.client.get(f"/api/scaffolder/v1/tasks/{task_id}")
        response.raise_for_status()
        return response.json()

    def list_services(self, team: Optional[str] = None) -> List[dict]:
        """서비스 카탈로그 조회"""
        params = {"filter": "kind=Component"}
        if team:
            params["filter"] += f",metadata.annotations.team={team}"

        response = self.client.get("/api/catalog/entities", params=params)
        response.raise_for_status()
        return response.json()

    def deploy_service(
        self,
        service_name: str,
        environment: EnvironmentType,
        version: str
    ) -> dict:
        """서비스를 특정 환경에 배포"""
        payload = {
            "serviceName": service_name,
            "environment": environment.value,
            "version": version,
            "strategy": "rolling"  # or "blue-green", "canary"
        }

        response = self.client.post("/api/deployments", json=payload)
        response.raise_for_status()
        return response.json()

    def get_service_metrics(self, service_name: str) -> dict:
        """서비스 메트릭 조회 (관찰성 통합)"""
        response = self.client.get(
            f"/api/observability/services/{service_name}/metrics"
        )
        response.raise_for_status()
        return response.json()

    def provision_database(
        self,
        service_name: str,
        db_type: DatabaseType,
        size: str = "small"
    ) -> dict:
        """데이터베이스 셀프서비스 프로비저닝"""
        payload = {
            "serviceName": service_name,
            "databaseType": db_type.value,
            "size": size,  # small, medium, large
            "backup": True,
            "encryption": True
        }

        response = self.client.post("/api/databases", json=payload)
        response.raise_for_status()
        return response.json()


# 사용 예시
if __name__ == "__main__":
    # IDP 클라이언트 초기화
    idp = IDPClient(
        base_url="https://developer-portal.example.com",
        api_token="your-api-token"
    )

    # 새 서비스 생성
    config = ServiceConfig(
        name="order-service",
        team="commerce-team",
        description="주문 처리 마이크로서비스",
        language="python",
        database=DatabaseType.POSTGRESQL,
        cache=True,
        environments=[
            EnvironmentType.DEVELOPMENT,
            EnvironmentType.STAGING
        ]
    )

    task = idp.create_service(config)
    print(f"서비스 생성 중... Task ID: {task['id']}")

    # 작업 상태 확인
    status = idp.get_task_status(task["id"])
    print(f"상태: {status['status']}")

    # 팀의 모든 서비스 조회
    services = idp.list_services(team="commerce-team")
    for svc in services:
        print(f"- {svc['metadata']['name']}: {svc['spec']['lifecycle']}")

🗣️ 실무에서 이렇게 말해요

플랫폼 엔지니어 "개발팀에서 새 서비스 만들 때마다 DevOps 티켓 열고 기다리는 게 병목이에요. IDP 도입해서 셀프서비스로 전환하면 어떨까요?"
CTO "Backstage 같은 거 말하는 건가요? 우리 규모에서 ROI가 나올까요?"
플랫폼 엔지니어 "작게 시작할 수 있어요. 먼저 서비스 카탈로그와 기본 템플릿부터 만들고, 개발자 피드백 받으면서 점진적으로 확장하면 됩니다. 온보딩 시간만 절반으로 줄여도 효과 있어요."
면접관 "Platform Engineering과 DevOps의 차이점이 뭐라고 생각하세요?"
지원자 "DevOps는 개발과 운영의 협업 문화이고, Platform Engineering은 그 협업을 가능하게 하는 내부 플랫폼(IDP)을 제품처럼 만드는 것입니다. 개발자를 내부 고객으로 보고, 그들의 인지 부하를 줄이는 셀프서비스 인터페이스를 제공해요. 결국 목표는 같지만, Platform Engineering은 더 체계적인 접근 방식이라고 봅니다."
리뷰어 "이 Backstage 템플릿에서 데이터베이스 프로비저닝이 동기식으로 되어 있는데, 시간이 오래 걸리면 타임아웃 나지 않을까요?"
작성자 "좋은 지적이에요. Crossplane Claim 생성만 하고, 실제 Ready 상태 확인은 비동기로 처리하도록 수정하겠습니다. 개발자에게는 '프로비저닝 중' 상태를 보여주고 완료 시 Slack 알림 보내는 방식으로요."

⚠️ 주의사항

  • 과도한 추상화 지양: IDP가 모든 것을 숨기면 개발자가 문제 해결 능력을 잃습니다. 필요시 하위 레이어에 접근할 수 있는 "탈출구(Escape Hatch)"를 제공하세요.
  • 개발자 피드백 수집: IDP를 제품처럼 관리하세요. 정기적인 사용자 인터뷰, 만족도 조사, 사용 메트릭 분석을 통해 실제 필요를 반영해야 합니다.
  • 점진적 구축: 처음부터 완벽한 플랫폼을 만들려 하지 마세요. 가장 큰 병목점을 해결하는 MVP로 시작하고, 빠른 피드백 루프를 통해 발전시키세요.

📚 더 배우기