Internal Developer Platform
IDP, 내부 개발자 플랫폼
개발자가 인프라 복잡성 없이 셀프서비스로 환경 구성, 배포, 모니터링을 할 수 있게 해주는 내부 플랫폼으로, Platform Engineering의 핵심 산출물입니다.
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 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 }}
"""
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']}")