- Published on
Platform Engineering과 Backstage로 Internal Developer Platform 구축 실전 가이드
- Authors
- Name
- 들어가며 - Platform Engineering이 전통적 DevOps를 대체하는 이유
- Internal Developer Platform 개념과 구성 요소
- Backstage 아키텍처와 핵심 기능
- Backstage 설치와 초기 설정
- Software Catalog 구성
- Golden Path Template 만들기
- 플러그인 개발과 통합
- IDP 도구 비교
- 운영 시 주의사항
- 실패 사례와 복구 절차
- 체크리스트
- 참고자료

들어가며 - Platform Engineering이 전통적 DevOps를 대체하는 이유
"You build it, you run it"이라는 DevOps 원칙은 개발자에게 자율성을 주었지만, 동시에 엄청난 인지 부하(Cognitive Load)를 안겼습니다. 개발자가 Kubernetes 매니페스트를 직접 작성하고, CI/CD 파이프라인을 설정하고, 모니터링 대시보드를 구성하고, 인프라 프로비저닝까지 담당해야 하는 상황이 벌어진 것입니다. Gartner는 2026년까지 소프트웨어 엔지니어링 조직의 80%가 Platform Engineering 팀을 구성할 것으로 전망했고, 실제로 이 흐름은 현실이 되고 있습니다.
Platform Engineering은 셀프서비스 역량을 갖춘 Internal Developer Platform(IDP)을 구축하여 개발자의 인지 부하를 줄이고, 조직 전체의 소프트웨어 딜리버리 속도를 높이는 규율(discipline)입니다. DevOps가 "문화와 실천"에 초점을 맞추었다면, Platform Engineering은 "제품으로서의 플랫폼"에 초점을 맞춥니다. 개발자는 IDP의 고객이고, 플랫폼 팀은 이 제품을 개발하고 운영하는 팀입니다.
이 글에서는 CNCF Incubating 프로젝트인 Backstage를 기반으로 IDP를 구축하는 전 과정을 다룹니다. Software Catalog 구성, Golden Path Template 설계, 플러그인 개발, 그리고 운영 시 마주치는 실패 사례와 복구 절차까지 실전 중심으로 정리했습니다.
Internal Developer Platform 개념과 구성 요소
IDP란 무엇인가
Internal Developer Platform(IDP)은 개발자가 인프라와 운영 복잡성을 신경 쓰지 않고 코드에 집중할 수 있도록, 셀프서비스 인터페이스를 통해 인프라 프로비저닝, 배포, 모니터링, 문서화 등을 통합 제공하는 플랫폼입니다.
IDP의 핵심 구성 요소는 다음과 같습니다:
- Service Catalog: 조직 내 모든 서비스, API, 리소스, 팀의 메타데이터를 한곳에서 관리
- Self-Service Portal: 개발자가 인프라 요청 없이 직접 환경을 프로비저닝
- Golden Path Template: 조직 표준에 맞는 프로젝트 스캐폴딩 템플릿
- Documentation Hub: 서비스별 기술 문서를 코드 저장소와 연동하여 자동 렌더링
- Integration Layer: CI/CD, 모니터링, 인시던트 관리 도구와의 연동
IDP vs Developer Portal
IDP와 Developer Portal은 종종 혼용되지만 엄밀히 구분됩니다. Developer Portal은 IDP의 프론트엔드 레이어입니다. IDP는 Portal 뒤에 있는 자동화 계층, 인프라 추상화, 정책 엔진, 워크플로우 오케스트레이션까지 포함하는 더 넓은 개념입니다.
┌──────────────────────────────────────────────────────┐
│ Developer Portal (UI) │
│ ┌───────────┐ ┌───────────┐ ┌───────────────────┐ │
│ │ Catalog │ │ Scaffolder│ │ TechDocs │ │
│ └───────────┘ └───────────┘ └───────────────────┘ │
├──────────────────────────────────────────────────────┤
│ Internal Developer Platform │
│ ┌───────────┐ ┌───────────┐ ┌───────────────────┐ │
│ │ IaC Engine│ │ CI/CD │ │ Policy Engine │ │
│ │ (Terraform│ │ (GitHub │ │ (OPA/Kyverno) │ │
│ │ Crossplane│ │ Actions) │ │ │ │
│ └───────────┘ └───────────┘ └───────────────────┘ │
│ ┌───────────┐ ┌───────────┐ ┌───────────────────┐ │
│ │ K8s │ │ Observ- │ │ Secret Mgmt │ │
│ │ Clusters │ │ ability │ │ (Vault) │ │
│ └───────────┘ └───────────┘ └───────────────────┘ │
└──────────────────────────────────────────────────────┘
Backstage 아키텍처와 핵심 기능
Backstage란
Backstage는 Spotify가 내부에서 사용하던 Developer Portal을 2020년에 오픈소스로 공개한 프로젝트입니다. 2024년 CNCF Incubating 프로젝트로 승격되었으며, 2026년 현재 커뮤니티에서 가장 활발하게 사용되는 IDP 프레임워크입니다.
핵심 기능 4가지
Software Catalog: 조직의 모든 소프트웨어 자산(서비스, 라이브러리, 파이프라인, 인프라 등)을 YAML 기반 메타데이터로 등록하고 검색합니다. 서비스 간 의존성, 소유권, API 명세까지 한눈에 파악할 수 있습니다.
Software Templates (Scaffolder): Golden Path를 코드화합니다. 개발자가 UI에서 몇 가지 파라미터만 입력하면, 조직 표준에 맞는 프로젝트가 자동으로 생성되고, Git 저장소 생성, CI/CD 파이프라인 설정, K8s 네임스페이스 프로비저닝까지 일괄 수행됩니다.
TechDocs: docs-as-code 방식으로 MkDocs 기반의 기술 문서를 서비스 저장소에서 자동 빌드하고 Backstage UI에서 렌더링합니다. 문서가 코드와 함께 관리되므로 최신 상태가 유지됩니다.
Plugins: Backstage의 확장성 핵심입니다. 200개 이상의 커뮤니티 플러그인이 존재하며, GitHub, GitLab, PagerDuty, Datadog, ArgoCD, Kubernetes 등과 연동됩니다. 직접 플러그인을 개발할 수도 있습니다.
아키텍처 구조
┌─────────────────────────────────────────────────┐
│ Backstage App (React) │
│ ┌─────────┐ ┌──────────┐ ┌─────────────────┐ │
│ │ Catalog │ │ Scaffolder│ │ TechDocs │ │
│ │ Plugin │ │ Plugin │ │ Plugin │ │
│ └─────────┘ └──────────┘ └─────────────────┘ │
│ ┌─────────┐ ┌──────────┐ ┌─────────────────┐ │
│ │ K8s │ │ CI/CD │ │ Custom Plugins │ │
│ │ Plugin │ │ Plugin │ │ │ │
│ └─────────┘ └──────────┘ └─────────────────┘ │
├─────────────────────────────────────────────────┤
│ Backstage Backend (Node.js) │
│ ┌──────────┐ ┌───────────┐ ┌────────────────┐ │
│ │ Catalog │ │ Scaffolder│ │ Auth Provider │ │
│ │ Backend │ │ Backend │ │ (GitHub/Okta) │ │
│ └──────────┘ └───────────┘ └────────────────┘ │
├─────────────────────────────────────────────────┤
│ Database (PostgreSQL) / Search │
└─────────────────────────────────────────────────┘
Backstage는 **프론트엔드(React SPA)**와 **백엔드(Node.js)**로 분리된 구조입니다. 프론트엔드 플러그인은 React 컴포넌트로, 백엔드 플러그인은 Express 라우터 형태로 구현됩니다. 데이터 저장소로는 PostgreSQL(프로덕션)이나 SQLite(개발용)를 사용합니다.
Backstage 설치와 초기 설정
프로젝트 생성
Backstage 앱을 생성하는 첫 번째 단계입니다. Node.js 18 이상과 Yarn Classic(1.x)이 필요합니다.
# Backstage 앱 생성
npx @backstage/create-app@latest
# 프로젝트 디렉토리 구조
# my-backstage-app/
# ├── app-config.yaml # 메인 설정 파일
# ├── app-config.production.yaml # 프로덕션 오버라이드
# ├── catalog-info.yaml # Backstage 자체의 카탈로그 엔트리
# ├── packages/
# │ ├── app/ # 프론트엔드 (React)
# │ └── backend/ # 백엔드 (Node.js)
# ├── plugins/ # 커스텀 플러그인
# └── package.json
# 로컬 개발 서버 시작
cd my-backstage-app
yarn dev
핵심 설정 - app-config.yaml
app-config.yaml은 Backstage의 중앙 설정 파일입니다. 데이터베이스, 인증, 카탈로그 소스, 통합(Integration) 등을 정의합니다.
# app-config.yaml
app:
title: 'ACME Developer Platform'
baseUrl: http://localhost:3000
organization:
name: 'ACME Corp'
backend:
baseUrl: http://localhost:7007
listen:
port: 7007
database:
client: pg
connection:
host: ${POSTGRES_HOST}
port: ${POSTGRES_PORT}
user: ${POSTGRES_USER}
password: ${POSTGRES_PASSWORD}
# GitHub 통합 설정
integrations:
github:
- host: github.com
token: ${GITHUB_TOKEN}
# 인증 프로바이더 설정
auth:
environment: production
providers:
github:
production:
clientId: ${AUTH_GITHUB_CLIENT_ID}
clientSecret: ${AUTH_GITHUB_CLIENT_SECRET}
# 카탈로그 소스 등록
catalog:
import:
entityFilename: catalog-info.yaml
pullRequestBranchName: backstage-integration
rules:
- allow: [Component, System, API, Resource, Location, Template, Group, User]
locations:
# 조직 전체 GitHub 저장소 자동 탐색
- type: github-discovery
target: https://github.com/acme-corp/*/blob/main/catalog-info.yaml
# 템플릿 등록
- type: file
target: ../../templates/all-templates.yaml
# 조직 구조 동기화
- type: github-org
target: https://github.com/acme-corp
주의:
GITHUB_TOKEN에는repo,read:org,read:user권한이 필요합니다. Fine-grained Token을 사용할 경우, 대상 저장소에 대한 Contents, Metadata 읽기 권한을 명시적으로 부여해야 합니다. 토큰 권한이 부족하면 카탈로그 등록 시NotFoundError가 발생합니다.
프로덕션 배포 설정
# app-config.production.yaml
app:
baseUrl: https://developer.acme.com
backend:
baseUrl: https://developer-api.acme.com
cors:
origin: https://developer.acme.com
# TechDocs를 외부 스토리지로 설정
techdocs:
builder: 'external'
generator:
runIn: 'local'
publisher:
type: 'awsS3'
awsS3:
bucketName: 'acme-techdocs'
region: 'ap-northeast-2'
Software Catalog 구성
catalog-info.yaml 작성
모든 소프트웨어 엔티티는 catalog-info.yaml 파일을 통해 Backstage에 등록됩니다. 이 파일은 서비스 저장소의 루트에 위치하며, 서비스의 메타데이터, 소유권, 의존성, API 명세를 정의합니다.
# catalog-info.yaml - 마이크로서비스 등록 예시
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: order-service
title: '주문 서비스'
description: '주문 생성, 결제 처리, 재고 차감을 담당하는 핵심 도메인 서비스'
annotations:
# GitHub 연동
github.com/project-slug: acme-corp/order-service
# CI/CD 연동
backstage.io/techdocs-ref: dir:.
github.com/workflows: build-and-deploy.yaml
# Kubernetes 연동
backstage.io/kubernetes-id: order-service
backstage.io/kubernetes-namespace: order
# PagerDuty 인시던트 연동
pagerduty.com/service-id: PXXXXXX
# Datadog 대시보드
datadoghq.com/dashboard-url: https://app.datadoghq.com/dashboard/xxx
tags:
- java
- spring-boot
- grpc
links:
- url: https://grafana.acme.com/d/order-svc
title: 'Grafana Dashboard'
icon: dashboard
- url: https://wiki.acme.com/order-domain
title: 'Domain Wiki'
icon: docs
spec:
type: service
lifecycle: production
owner: team-order
system: ecommerce-platform
providesApis:
- order-api
consumesApis:
- inventory-api
- payment-api
dependsOn:
- resource:order-database
- resource:order-redis
---
# API 명세 등록
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
name: order-api
description: '주문 처리 gRPC API'
spec:
type: grpc
lifecycle: production
owner: team-order
system: ecommerce-platform
definition:
$text: ./proto/order.proto
---
# 데이터베이스 리소스 등록
apiVersion: backstage.io/v1alpha1
kind: Resource
metadata:
name: order-database
description: '주문 서비스 PostgreSQL 데이터베이스'
spec:
type: database
owner: team-order
system: ecommerce-platform
엔티티 관계 구조
Backstage의 엔티티 모델은 계층 구조를 갖습니다:
| 엔티티 Kind | 역할 | 예시 |
|---|---|---|
| Domain | 비즈니스 도메인 영역 | commerce, logistics |
| System | 관련 컴포넌트의 논리적 그룹 | ecommerce-platform |
| Component | 개별 소프트웨어 단위 (서비스, 라이브러리, 웹사이트) | order-service |
| API | 컴포넌트가 제공하는 인터페이스 | order-api (gRPC/REST/GraphQL) |
| Resource | 인프라 리소스 | order-database, order-redis |
| Group | 팀/조직 단위 | team-order |
| User | 개별 사용자 | jane.doe |
이 관계 구조를 통해 "이 서비스를 누가 소유하고, 어떤 API를 제공하며, 어떤 인프라에 의존하고, 장애 시 누구에게 연락해야 하는지"를 즉시 파악할 수 있습니다. 서비스가 수백 개로 늘어나는 마이크로서비스 환경에서 이런 가시성은 결정적입니다.
Golden Path Template 만들기
Golden Path는 조직이 권장하는 "올바른 시작 방법"을 코드화한 것입니다. 개발자가 새 서비스를 시작할 때 CI/CD, 테스트, 모니터링, 보안 설정이 기본으로 내장된 프로젝트를 자동 생성할 수 있습니다.
Scaffolder Template 작성
# templates/spring-boot-service/template.yaml
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: spring-boot-grpc-service
title: 'Spring Boot gRPC 마이크로서비스'
description: |
Spring Boot 3.x + gRPC 기반 마이크로서비스 프로젝트를 생성합니다.
포함 항목: Dockerfile, Helm Chart, GitHub Actions CI/CD,
Prometheus 메트릭, Health Check, catalog-info.yaml
tags:
- java
- spring-boot
- grpc
- recommended
spec:
owner: platform-team
type: service
# 사용자 입력 파라미터 정의
parameters:
- title: '서비스 기본 정보'
required:
- serviceName
- owner
- system
properties:
serviceName:
title: '서비스 이름'
type: string
description: '영문 소문자, 하이픈만 사용 (예: order-service)'
pattern: '^[a-z][a-z0-9-]*$'
ui:autofocus: true
description:
title: '서비스 설명'
type: string
owner:
title: '소유 팀'
type: string
ui:field: OwnerPicker
ui:options:
catalogFilter:
kind: Group
system:
title: '소속 시스템'
type: string
ui:field: EntityPicker
ui:options:
catalogFilter:
kind: System
- title: '기술 옵션'
properties:
javaVersion:
title: 'Java 버전'
type: string
enum: ['17', '21']
default: '21'
database:
title: '데이터베이스'
type: string
enum: ['postgresql', 'mysql', 'none']
default: 'postgresql'
enableKafka:
title: 'Kafka 연동'
type: boolean
default: false
- title: '인프라 설정'
properties:
namespace:
title: 'K8s 네임스페이스'
type: string
default: 'default'
cluster:
title: '배포 클러스터'
type: string
enum: ['dev', 'staging', 'production']
default: 'dev'
# 실행 단계 정의
steps:
# 1. 템플릿에서 프로젝트 코드 생성
- id: fetch-template
name: '프로젝트 코드 생성'
action: fetch:template
input:
url: ./skeleton
values:
serviceName: ${{ parameters.serviceName }}
description: ${{ parameters.description }}
owner: ${{ parameters.owner }}
system: ${{ parameters.system }}
javaVersion: ${{ parameters.javaVersion }}
database: ${{ parameters.database }}
enableKafka: ${{ parameters.enableKafka }}
namespace: ${{ parameters.namespace }}
# 2. GitHub 저장소 생성
- id: publish-github
name: 'GitHub 저장소 생성'
action: publish:github
input:
allowedHosts: ['github.com']
repoUrl: github.com?owner=acme-corp&repo=${{ parameters.serviceName }}
description: ${{ parameters.description }}
defaultBranch: main
protectDefaultBranch: true
requireCodeOwnerReviews: true
repoVisibility: internal
# 3. Backstage 카탈로그에 등록
- id: register-catalog
name: '카탈로그 등록'
action: catalog:register
input:
repoContentsUrl: ${{ steps['publish-github'].output.repoContentsUrl }}
catalogInfoPath: /catalog-info.yaml
# 4. ArgoCD Application 생성
- id: create-argocd-app
name: 'ArgoCD 배포 설정'
action: argocd:create-resources
input:
appName: ${{ parameters.serviceName }}
argoInstance: main
namespace: ${{ parameters.namespace }}
repoUrl: ${{ steps['publish-github'].output.remoteUrl }}
path: deploy/helm
# 완료 후 안내
output:
links:
- title: 'GitHub 저장소'
url: ${{ steps['publish-github'].output.remoteUrl }}
- title: '카탈로그에서 보기'
icon: catalog
entityRef: ${{ steps['register-catalog'].output.entityRef }}
이 템플릿 하나로 개발자는 UI에서 서비스 이름과 몇 가지 옵션만 선택하면, GitHub 저장소 생성부터 CI/CD 파이프라인 설정, K8s 배포, 카탈로그 등록까지 5분 안에 완료됩니다. 기존에 평균 3일이 소요되던 신규 서비스 온보딩이 획기적으로 단축됩니다.
플러그인 개발과 통합
커스텀 플러그인 생성
Backstage CLI를 통해 프론트엔드 또는 백엔드 플러그인 스캐폴딩을 수행할 수 있습니다.
# 프론트엔드 플러그인 생성
cd my-backstage-app
yarn new --select plugin
# 백엔드 플러그인 생성
yarn new --select backend-plugin
# 생성된 플러그인 구조
# plugins/
# └── my-custom-plugin/
# ├── src/
# │ ├── components/
# │ │ └── ExampleComponent/
# │ ├── plugin.ts # 플러그인 정의
# │ ├── routes.ts # 라우팅 설정
# │ └── index.ts
# ├── dev/ # 독립 개발 환경
# │ └── index.tsx
# └── package.json
프론트엔드 플러그인 구현 예시
팀별 서비스 헬스 현황을 대시보드로 보여주는 플러그인 예시입니다.
// plugins/team-health-dashboard/src/plugin.ts
import { createPlugin, createRoutableExtension } from '@backstage/core-plugin-api'
export const teamHealthDashboardPlugin = createPlugin({
id: 'team-health-dashboard',
routes: {
root: rootRouteRef,
},
})
export const TeamHealthDashboardPage = teamHealthDashboardPlugin.provide(
createRoutableExtension({
name: 'TeamHealthDashboardPage',
component: () => import('./components/DashboardPage').then((m) => m.DashboardPage),
mountPoint: rootRouteRef,
})
)
// plugins/team-health-dashboard/src/components/DashboardPage.tsx
import React, { useEffect, useState } from 'react';
import {
Table,
TableColumn,
StatusOK,
StatusError,
StatusWarning,
Progress,
ResponseErrorPanel,
} from '@backstage/core-components';
import { useApi, configApiRef } from '@backstage/core-plugin-api';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
interface ServiceHealth {
name: string;
owner: string;
status: 'healthy' | 'degraded' | 'down';
uptime: number;
lastIncident: string;
errorRate: number;
}
const columns: TableColumn<ServiceHealth>[] = [
{ title: '서비스', field: 'name' },
{ title: '소유팀', field: 'owner' },
{
title: '상태',
field: 'status',
render: (row: ServiceHealth) => {
switch (row.status) {
case 'healthy':
return <StatusOK>정상</StatusOK>;
case 'degraded':
return <StatusWarning>성능 저하</StatusWarning>;
case 'down':
return <StatusError>장애</StatusError>;
default:
return null;
}
},
},
{ title: 'Uptime (%)', field: 'uptime', type: 'numeric' },
{ title: '에러율 (%)', field: 'errorRate', type: 'numeric' },
{ title: '마지막 인시던트', field: 'lastIncident' },
];
export const DashboardPage = () => {
const catalogApi = useApi(catalogApiRef);
const [services, setServices] = useState<ServiceHealth[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error>();
useEffect(() => {
const fetchData = async () => {
try {
const entities = await catalogApi.getEntities({
filter: { kind: 'Component', 'spec.type': 'service' },
});
// 각 서비스의 헬스 데이터를 Prometheus/Datadog API에서 가져오기
const healthData = await Promise.all(
entities.items.map(async entity => {
const metrics = await fetchServiceMetrics(
entity.metadata.name,
);
return {
name: entity.metadata.name,
owner:
entity.spec?.owner?.toString() ?? 'unknown',
status: metrics.status,
uptime: metrics.uptime,
lastIncident: metrics.lastIncident,
errorRate: metrics.errorRate,
};
}),
);
setServices(healthData);
} catch (e) {
setError(e as Error);
} finally {
setLoading(false);
}
};
fetchData();
}, [catalogApi]);
if (loading) return <Progress />;
if (error) return <ResponseErrorPanel error={error} />;
return (
<Table
title="서비스 헬스 대시보드"
columns={columns}
data={services}
options={{
sorting: true,
paging: true,
pageSize: 20,
search: true,
}}
/>
);
};
주요 플러그인 통합 목록
프로덕션 IDP에서 자주 통합하는 플러그인과 역할입니다:
| 플러그인 | 용도 | 설정 포인트 |
|---|---|---|
@backstage/plugin-kubernetes | Pod 상태, 로그, 이벤트 실시간 조회 | ServiceAccount, RBAC 설정 |
@backstage/plugin-techdocs | docs-as-code 기반 문서 렌더링 | MkDocs 설정, S3/GCS 퍼블리셔 |
@roadiehq/backstage-plugin-github-insights | PR 현황, 기여자 통계 | GitHub App 토큰 |
@backstage/plugin-catalog-import | UI에서 catalog-info.yaml 등록 | 카탈로그 규칙 |
@backstage-community/plugin-cost-insights | 클라우드 비용 가시화 | 비용 API 연동 |
@pagerduty/backstage-plugin | 온콜 현황, 인시던트 목록 | PagerDuty API 키 |
@backstage/plugin-scaffolder-backend-module-github | GitHub 저장소 자동 생성 | GitHub App 권한 |
IDP 도구 비교
Backstage 외에도 여러 IDP 도구가 시장에 존재합니다. 조직의 규모, 기술 수준, 예산에 따라 적합한 도구가 다릅니다.
| 항목 | Backstage | Port | Cortex | OpsLevel |
|---|---|---|---|---|
| 라이선스 | Apache 2.0 (오픈소스) | SaaS (무료 티어 있음) | SaaS | SaaS |
| 호스팅 | 셀프호스팅 | 클라우드 관리형 | 클라우드 관리형 | 클라우드 관리형 |
| 커스터마이징 | 매우 높음 (플러그인 개발) | 중간 (위젯/블루프린트) | 낮음 | 중간 |
| 초기 설정 비용 | 높음 (전담 팀 필요) | 낮음 | 낮음 | 낮음 |
| 운영 부담 | 높음 (업그레이드, 보안) | 없음 (SaaS) | 없음 | 없음 |
| Service Catalog | YAML 기반, 강력 | UI 기반, 직관적 | 자동 디스커버리 | 자동 디스커버리 |
| Scaffolding | 내장 (Scaffolder) | 셀프서비스 액션 | 제한적 | 서비스 템플릿 |
| 기술 문서 | TechDocs 내장 | 외부 연동 | 외부 연동 | 외부 연동 |
| Scorecards | 플러그인으로 추가 | 내장 | 핵심 기능 | 내장 |
| 적합 조직 | 대규모, 기술력 높은 팀 | 중소규모, 빠른 도입 | 서비스 성숙도 중심 | 중간 규모 |
| 가격 (50명 기준) | 무료 (인프라/인건비 별도) | ~$2,000/월 | ~$5,000/월 | ~$3,000/월 |
선택 기준 요약: 전담 플랫폼 엔지니어링 팀이 있고 높은 커스터마이징이 필요하면 Backstage, 빠른 도입과 낮은 운영 부담을 원하면 Port, 서비스 성숙도 측정과 표준 준수에 집중하고 싶다면 Cortex가 적합합니다.
운영 시 주의사항
Adoption 메트릭 추적
IDP를 구축하는 것보다 개발자들이 실제로 사용하게 만드는 것이 더 어렵습니다. 다음 메트릭을 추적하여 채택 현황을 측정하세요:
| 메트릭 | 측정 방법 | 목표 |
|---|---|---|
| 카탈로그 커버리지 | 등록된 서비스 수 / 전체 서비스 수 | 95% 이상 |
| 템플릿 사용률 | 템플릿으로 생성된 서비스 / 전체 신규 서비스 | 80% 이상 |
| DAU/WAU | Backstage 일간/주간 활성 사용자 | 개발자의 60% 이상 |
| 온보딩 시간 | 신규 서비스 생성 ~ 첫 배포까지 소요 시간 | 1시간 이내 |
| MTTR 개선 | 장애 발생 ~ 담당팀 인지까지 시간 | 기존 대비 50% 감소 |
| TechDocs 최신성 | 30일 내 업데이트된 문서 비율 | 70% 이상 |
피해야 할 안티패턴
Big Bang 출시: 모든 기능을 한 번에 출시하려 하면 실패합니다. MVP로 시작하세요. 첫 번째 단계는 Software Catalog만으로 충분합니다. 카탈로그에 조직의 모든 서비스를 등록하고, 소유권과 의존성을 시각화하는 것만으로도 큰 가치를 제공합니다.
플랫폼 = 포탈 착각: UI만 예쁘게 만들어놓고 뒤에 자동화가 없으면 개발자들은 금방 떠납니다. "클릭 한 번으로 K8s 네임스페이스와 CI/CD가 설정되는" 실질적 자동화가 핵심입니다.
개발자 피드백 무시: 플랫폼은 제품입니다. 개발자 설문, 사용 데이터 분석, 정기적 피드백 세션 없이 플랫폼 팀이 "좋을 것 같은" 기능만 만들면 채택률이 떨어집니다.
Backstage 버전 업그레이드 방치: Backstage는 릴리스 주기가 빠릅니다(월 1~2회). 6개월 이상 업그레이드를 미루면 마이그레이션이 극도로 어려워집니다.
backstage-cli versions:bump명령을 정기적으로 실행하세요.강제 채택: 관리 도구로 전락하면 개발자의 반감을 삽니다. IDP는 개발자 경험(DX)을 개선하는 도구여야 합니다. "이걸 안 쓰면 징계"가 아니라, "이걸 쓰면 3일 걸리던 게 5분이 된다"가 메시지여야 합니다.
Backstage 업그레이드 절차
# 1. 현재 버전 확인
yarn backstage-cli info
# 2. 버전 범프 (의존성 자동 업데이트)
yarn backstage-cli versions:bump
# 3. 변경사항 확인
git diff
# 4. 타입 체크
yarn tsc
# 5. 테스트 실행
yarn test
# 6. 빌드 확인
yarn build
# 7. 마이그레이션 가이드 확인
# https://backstage.io/docs/getting-started/keeping-backstage-updated
# 권장: CI에서 자동으로 업그레이드 PR 생성
# .github/workflows/backstage-upgrade.yaml
# 매주 월요일 자동 실행하여 버전 범프 후 PR 생성
경고:
versions:bump실행 전 반드시 현재 변경사항을 커밋하세요. 업그레이드 시package.json,yarn.lock, 그리고 때로는 코드 마이그레이션이 필요한 breaking change가 포함될 수 있습니다. 스테이징 환경에서 먼저 검증한 후 프로덕션에 적용하는 것을 권장합니다.
실패 사례와 복구 절차
사례 1: 카탈로그 엔티티 동기화 실패
증상: GitHub에 catalog-info.yaml이 존재하지만 Backstage UI에 표시되지 않음
원인 분석:
- GitHub 토큰 만료 또는 권한 부족 (가장 흔함)
- YAML 구문 오류 (들여쓰기, 잘못된
kind값) catalog.rules에서 해당 Kind가allow목록에 없음- 네트워크 문제로 GitHub API 호출 실패
복구 절차:
- Backstage 로그 확인:
kubectl logs -l app=backstage -c backstage --tail=100 - 토큰 유효성 검증:
curl -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/user - YAML 검증:
npx @backstage/cli catalog-info validate catalog-info.yaml - 수동 새로고침: Backstage UI의 해당 엔티티 페이지에서 "Refresh" 버튼 클릭
- 캐시 초기화가 필요한 경우: Backstage Pod 재시작
사례 2: Scaffolder 템플릿 실행 중 GitHub 저장소 생성 실패
증상: 템플릿 실행 시 "publish:github" 단계에서 RequestError: HttpError: Resource not accessible by integration 에러
원인 분석:
- GitHub App의
Repository: Administration권한 누락 - 조직의 저장소 생성 정책(repository creation policy)에 의해 차단
- 이미 동일 이름의 저장소가 존재
복구 절차:
- GitHub App 권한 확인 및 업데이트
- 조직 설정에서 App이 저장소 생성을 허용하는지 확인
- 실패한 태스크의 로그를 Backstage UI에서 확인 (Scaffolder 태스크 로그 페이지)
- 부분적으로 생성된 리소스(빈 저장소 등)가 있다면 수동 정리 후 재실행
사례 3: PostgreSQL 연결 풀 고갈
증상: Backstage 응답 속도 급격히 저하, 간헐적 ConnectionTimeoutError
원인 분석:
- 카탈로그 엔티티가 수천 개로 증가하면서 DB 연결이 부족
- 기본 연결 풀 크기(10개)가 트래픽을 감당하지 못함
복구 절차 및 예방:
# app-config.yaml - 연결 풀 튜닝
backend:
database:
client: pg
connection:
host: ${POSTGRES_HOST}
port: ${POSTGRES_PORT}
user: ${POSTGRES_USER}
password: ${POSTGRES_PASSWORD}
pool:
min: 5
max: 30
acquireTimeoutMillis: 60000
idleTimeoutMillis: 30000
사례 4: TechDocs 빌드 실패
증상: 서비스 페이지에서 TechDocs 탭이 비어 있거나 빌드 에러 표시
원인 분석:
mkdocs.yml파일이 저장소에 없음- Python 의존성(mkdocs-techdocs-core) 설치 실패
- S3 버킷 권한 문제 (외부 퍼블리셔 사용 시)
복구 절차:
- 저장소에
mkdocs.yml과docs/index.md가 존재하는지 확인 - 로컬에서
npx @techdocs/cli generate명령으로 빌드 테스트 - 외부 퍼블리셔 사용 시 IAM 권한 확인
techdocs.builder를local로 변경하여 Backstage가 직접 빌드하도록 설정 변경 (소규모 환경)
사례 5: Backstage 업그레이드 후 플러그인 호환성 깨짐
증상: 업그레이드 후 특정 플러그인 페이지에서 화이트 스크린 또는 런타임 에러
원인 분석:
- Backstage 코어 패키지와 플러그인 간 버전 불일치
- API가 deprecated된 후 제거됨
- 새로운 Backstage 시스템(New Backend System) 마이그레이션 필요
복구 절차:
- 즉시 이전 버전으로 롤백 (Helm rollback 또는 이전 이미지 태그로 배포)
- 브라우저 콘솔에서 에러 메시지 확인
- 문제 플러그인의 GitHub 이슈 검색
- 플러그인을 최신 호환 버전으로 업데이트하거나, 임시로 비활성화
- 스테이징에서 검증 후 다시 프로덕션 배포
체크리스트
IDP 구축 준비 체크리스트
- 플랫폼 엔지니어링 전담 팀 구성 (최소 2~3명)
- 개발자 페인포인트 서베이 실시 (인지 부하가 가장 큰 영역 파악)
- IDP의 첫 번째 MVP 범위 결정 (권장: Software Catalog부터 시작)
- Backstage vs SaaS 도구 비교 평가 완료
- PostgreSQL 인스턴스 프로비저닝 (프로덕션)
- GitHub App 또는 OAuth App 생성 및 권한 설정
- SSO 프로바이더 연동 계획 (Okta, Azure AD, Google 등)
Backstage 운영 체크리스트
-
app-config.production.yaml분리 및 시크릿 관리 (Vault, K8s Secret) - HTTPS/TLS 설정 (Ingress 또는 로드밸런서)
- 데이터베이스 백업 스케줄 설정
- 모니터링 구성 (Backstage 메트릭 + 인프라 메트릭)
- 주간 버전 업그레이드 워크플로우 자동화
- 카탈로그 엔티티 검증 CI (PR에서 catalog-info.yaml 유효성 검사)
- 개발자 온보딩 가이드 문서 작성
- 분기별 개발자 만족도 조사 계획
카탈로그 등록 체크리스트 (서비스별)
-
catalog-info.yaml작성 및 저장소 루트에 배치 -
metadata.name조직 네이밍 규칙 준수 확인 -
spec.owner올바른 팀 그룹으로 설정 -
spec.system올바른 시스템에 소속 - API 명세(OpenAPI, gRPC, AsyncAPI) 등록
-
annotations에 CI/CD, 모니터링, 인시던트 도구 연동 정보 추가 - TechDocs 설정 (
mkdocs.yml+docs/디렉토리) - Kubernetes 어노테이션 설정 (클러스터 내 워크로드 매핑)
참고자료
- Backstage 공식 문서 - What is Backstage?
- Platform Engineering - How to Set Up an Internal Developer Platform
- GitGuardian - Platform Engineering: Building Your Developer Portal with Backstage Part 1
- Growin Blog - Platform Engineering 2026
- The New Stack - In 2026, AI Is Merging with Platform Engineering: Are You Ready?
- CNCF - Backstage Project Page
- Spotify Engineering - How We Use Backstage at Spotify