Skip to content
Published on

Platform Engineering과 Backstage로 Internal Developer Platform 구축 실전 가이드

Authors
  • Name
    Twitter
Platform Engineering Backstage

들어가며 - 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가지

  1. Software Catalog: 조직의 모든 소프트웨어 자산(서비스, 라이브러리, 파이프라인, 인프라 등)을 YAML 기반 메타데이터로 등록하고 검색합니다. 서비스 간 의존성, 소유권, API 명세까지 한눈에 파악할 수 있습니다.

  2. Software Templates (Scaffolder): Golden Path를 코드화합니다. 개발자가 UI에서 몇 가지 파라미터만 입력하면, 조직 표준에 맞는 프로젝트가 자동으로 생성되고, Git 저장소 생성, CI/CD 파이프라인 설정, K8s 네임스페이스 프로비저닝까지 일괄 수행됩니다.

  3. TechDocs: docs-as-code 방식으로 MkDocs 기반의 기술 문서를 서비스 저장소에서 자동 빌드하고 Backstage UI에서 렌더링합니다. 문서가 코드와 함께 관리되므로 최신 상태가 유지됩니다.

  4. 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-kubernetesPod 상태, 로그, 이벤트 실시간 조회ServiceAccount, RBAC 설정
@backstage/plugin-techdocsdocs-as-code 기반 문서 렌더링MkDocs 설정, S3/GCS 퍼블리셔
@roadiehq/backstage-plugin-github-insightsPR 현황, 기여자 통계GitHub App 토큰
@backstage/plugin-catalog-importUI에서 catalog-info.yaml 등록카탈로그 규칙
@backstage-community/plugin-cost-insights클라우드 비용 가시화비용 API 연동
@pagerduty/backstage-plugin온콜 현황, 인시던트 목록PagerDuty API 키
@backstage/plugin-scaffolder-backend-module-githubGitHub 저장소 자동 생성GitHub App 권한

IDP 도구 비교

Backstage 외에도 여러 IDP 도구가 시장에 존재합니다. 조직의 규모, 기술 수준, 예산에 따라 적합한 도구가 다릅니다.

항목BackstagePortCortexOpsLevel
라이선스Apache 2.0 (오픈소스)SaaS (무료 티어 있음)SaaSSaaS
호스팅셀프호스팅클라우드 관리형클라우드 관리형클라우드 관리형
커스터마이징매우 높음 (플러그인 개발)중간 (위젯/블루프린트)낮음중간
초기 설정 비용높음 (전담 팀 필요)낮음낮음낮음
운영 부담높음 (업그레이드, 보안)없음 (SaaS)없음없음
Service CatalogYAML 기반, 강력UI 기반, 직관적자동 디스커버리자동 디스커버리
Scaffolding내장 (Scaffolder)셀프서비스 액션제한적서비스 템플릿
기술 문서TechDocs 내장외부 연동외부 연동외부 연동
Scorecards플러그인으로 추가내장핵심 기능내장
적합 조직대규모, 기술력 높은 팀중소규모, 빠른 도입서비스 성숙도 중심중간 규모
가격 (50명 기준)무료 (인프라/인건비 별도)~$2,000/월~$5,000/월~$3,000/월

선택 기준 요약: 전담 플랫폼 엔지니어링 팀이 있고 높은 커스터마이징이 필요하면 Backstage, 빠른 도입과 낮은 운영 부담을 원하면 Port, 서비스 성숙도 측정과 표준 준수에 집중하고 싶다면 Cortex가 적합합니다.

운영 시 주의사항

Adoption 메트릭 추적

IDP를 구축하는 것보다 개발자들이 실제로 사용하게 만드는 것이 더 어렵습니다. 다음 메트릭을 추적하여 채택 현황을 측정하세요:

메트릭측정 방법목표
카탈로그 커버리지등록된 서비스 수 / 전체 서비스 수95% 이상
템플릿 사용률템플릿으로 생성된 서비스 / 전체 신규 서비스80% 이상
DAU/WAUBackstage 일간/주간 활성 사용자개발자의 60% 이상
온보딩 시간신규 서비스 생성 ~ 첫 배포까지 소요 시간1시간 이내
MTTR 개선장애 발생 ~ 담당팀 인지까지 시간기존 대비 50% 감소
TechDocs 최신성30일 내 업데이트된 문서 비율70% 이상

피해야 할 안티패턴

  1. Big Bang 출시: 모든 기능을 한 번에 출시하려 하면 실패합니다. MVP로 시작하세요. 첫 번째 단계는 Software Catalog만으로 충분합니다. 카탈로그에 조직의 모든 서비스를 등록하고, 소유권과 의존성을 시각화하는 것만으로도 큰 가치를 제공합니다.

  2. 플랫폼 = 포탈 착각: UI만 예쁘게 만들어놓고 뒤에 자동화가 없으면 개발자들은 금방 떠납니다. "클릭 한 번으로 K8s 네임스페이스와 CI/CD가 설정되는" 실질적 자동화가 핵심입니다.

  3. 개발자 피드백 무시: 플랫폼은 제품입니다. 개발자 설문, 사용 데이터 분석, 정기적 피드백 세션 없이 플랫폼 팀이 "좋을 것 같은" 기능만 만들면 채택률이 떨어집니다.

  4. Backstage 버전 업그레이드 방치: Backstage는 릴리스 주기가 빠릅니다(월 1~2회). 6개월 이상 업그레이드를 미루면 마이그레이션이 극도로 어려워집니다. backstage-cli versions:bump 명령을 정기적으로 실행하세요.

  5. 강제 채택: 관리 도구로 전락하면 개발자의 반감을 삽니다. 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 호출 실패

복구 절차:

  1. Backstage 로그 확인: kubectl logs -l app=backstage -c backstage --tail=100
  2. 토큰 유효성 검증: curl -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/user
  3. YAML 검증: npx @backstage/cli catalog-info validate catalog-info.yaml
  4. 수동 새로고침: Backstage UI의 해당 엔티티 페이지에서 "Refresh" 버튼 클릭
  5. 캐시 초기화가 필요한 경우: Backstage Pod 재시작

사례 2: Scaffolder 템플릿 실행 중 GitHub 저장소 생성 실패

증상: 템플릿 실행 시 "publish:github" 단계에서 RequestError: HttpError: Resource not accessible by integration 에러

원인 분석:

  • GitHub App의 Repository: Administration 권한 누락
  • 조직의 저장소 생성 정책(repository creation policy)에 의해 차단
  • 이미 동일 이름의 저장소가 존재

복구 절차:

  1. GitHub App 권한 확인 및 업데이트
  2. 조직 설정에서 App이 저장소 생성을 허용하는지 확인
  3. 실패한 태스크의 로그를 Backstage UI에서 확인 (Scaffolder 태스크 로그 페이지)
  4. 부분적으로 생성된 리소스(빈 저장소 등)가 있다면 수동 정리 후 재실행

사례 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 버킷 권한 문제 (외부 퍼블리셔 사용 시)

복구 절차:

  1. 저장소에 mkdocs.ymldocs/index.md가 존재하는지 확인
  2. 로컬에서 npx @techdocs/cli generate 명령으로 빌드 테스트
  3. 외부 퍼블리셔 사용 시 IAM 권한 확인
  4. techdocs.builderlocal로 변경하여 Backstage가 직접 빌드하도록 설정 변경 (소규모 환경)

사례 5: Backstage 업그레이드 후 플러그인 호환성 깨짐

증상: 업그레이드 후 특정 플러그인 페이지에서 화이트 스크린 또는 런타임 에러

원인 분석:

  • Backstage 코어 패키지와 플러그인 간 버전 불일치
  • API가 deprecated된 후 제거됨
  • 새로운 Backstage 시스템(New Backend System) 마이그레이션 필요

복구 절차:

  1. 즉시 이전 버전으로 롤백 (Helm rollback 또는 이전 이미지 태그로 배포)
  2. 브라우저 콘솔에서 에러 메시지 확인
  3. 문제 플러그인의 GitHub 이슈 검색
  4. 플러그인을 최신 호환 버전으로 업데이트하거나, 임시로 비활성화
  5. 스테이징에서 검증 후 다시 프로덕션 배포

체크리스트

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 어노테이션 설정 (클러스터 내 워크로드 매핑)

참고자료