Skip to content

필사 모드: Backstage 운영 실전 — TechDocs, 플러그인 개발, 프로덕션 체크리스트

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

들어가며 — 설치는 쉽고 운영은 어렵다

Backstage를 데모로 띄우는 데는 한나절이면 충분합니다. 그러나 6개월 뒤에도 살아 있는 Backstage, 즉 문서가 최신이고, 플러그인이 안정적으로 동작하며, 업그레이드가 밀리지 않고, 수백 명이 매일 쓰는 Backstage를 만드는 것은 전혀 다른 문제입니다. 1편에서 카탈로그를, 2편에서 Scaffolder를 다뤘다면, 이번 마지막 3편의 주제는 운영입니다. TechDocs로 문서를 코드처럼 관리하는 파이프라인, 플러그인 아키텍처와 커스텀 플러그인 개발, 그리고 프로덕션 운영의 전 영역(권한, 업그레이드, 성능, 관측, 보안, 조직 모델)을 체크리스트와 함께 정리합니다.

TechDocs — 문서를 코드처럼

TechDocs는 Backstage의 docs-as-code 솔루션입니다. 핵심 아이디어는 단순합니다. 문서를 코드 옆에 마크다운으로 두고, MkDocs로 빌드해서, 카탈로그 엔터티에 연결된 문서 사이트로 서빙하는 것입니다. 문서가 코드와 같은 저장소, 같은 PR, 같은 리뷰를 거치므로 "코드는 바뀌었는데 문서는 그대로"인 문제가 구조적으로 줄어듭니다.

저장소 쪽에 필요한 것은 두 가지입니다.

mkdocs.yml (저장소 루트)

site_name: payment-api

site_description: 결제 API 서비스 문서

nav:

- 소개: index.md

- 아키텍처: architecture.md

- 운영 가이드: runbook.md

- 온보딩: onboarding.md

plugins:

- techdocs-core

payment-api/

├── catalog-info.yaml # backstage.io/techdocs-ref: dir:. 어노테이션

├── mkdocs.yml

└── docs/

├── index.md

├── architecture.md

├── runbook.md

└── onboarding.md

빌드 전략 — 로컬 빌드 vs 외부 파이프라인

TechDocs 빌드에는 두 가지 모드가 있습니다.

| 전략 | 동작 | 적합한 상황 |

| --- | --- | --- |

| 로컬 빌드 (out-of-the-box) | Backstage 백엔드가 요청 시 직접 빌드 | 데모, 소규모, 초기 도입 |

| 외부 파이프라인 (recommended) | CI가 빌드해 오브젝트 스토리지에 업로드 | 프로덕션, 대규모 |

프로덕션 권장 구성은 외부 파이프라인입니다. Backstage 본체에서 빌드 부하를 떼어내고, 문서 갱신을 코드 머지 시점에 일어나게 만들기 때문입니다.

개발자 PR 머지

|

v

CI (GitHub Actions)

| techdocs-cli generate (MkDocs 빌드)

| techdocs-cli publish (S3 업로드)

v

S3 버킷 (techdocs-bucket)

^

| 읽기 전용

Backstage TechDocs Backend ---> 사용자 브라우저

CI 파이프라인 예제입니다.

.github/workflows/techdocs.yaml

name: techdocs

on:

push:

branches: [main]

paths: ['docs/**', 'mkdocs.yml']

jobs:

publish:

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v4

- uses: actions/setup-python@v5

with:

python-version: '3.12'

- uses: actions/setup-node@v4

with:

node-version: 20

- name: Install tooling

run: |

pip install mkdocs-techdocs-core

npm install -g @techdocs/cli

- name: Generate docs

run: techdocs-cli generate --no-docker

- name: Publish to S3

env:

AWS_ACCESS_KEY_ID: ${{ secrets.TECHDOCS_AWS_KEY }}

AWS_SECRET_ACCESS_KEY: ${{ secrets.TECHDOCS_AWS_SECRET }}

run: |

techdocs-cli publish \

--publisher-type awsS3 \

--storage-name techdocs-bucket \

--entity default/Component/payment-api

Backstage 쪽 설정은 다음과 같습니다.

app-config.yaml

techdocs:

builder: 'external' # 백엔드 빌드 비활성화

publisher:

type: 'awsS3'

awsS3:

bucketName: techdocs-bucket

region: ap-northeast-2

운영 팁 하나를 덧붙이면, 2편에서 다룬 Scaffolder 스켈레톤에 mkdocs.yml과 docs 디렉토리 골격을 포함시키세요. 새 서비스가 "문서 있는 상태"로 태어나게 만드는 것이 문서 문화를 만드는 가장 현실적인 방법입니다.

플러그인 아키텍처 — 모든 것이 플러그인이다

Backstage의 설계 철학은 "모든 기능은 플러그인"입니다. 카탈로그도, Scaffolder도, TechDocs도 전부 플러그인입니다. 구조를 이해하면 커스텀 개발이 쉬워집니다.

+---------------------- Backstage 모노레포 (yarn workspaces) ----------------------+

| |

| packages/app (프론트엔드 셸) packages/backend (백엔드 셸) |

| +--------------------------+ +-----------------------------+ |

| | React SPA | | Node.js | |

| | - 플러그인 페이지 라우팅 | HTTP | - 플러그인별 라우터 mount | |

| | - 엔터티 페이지 탭/카드 | <-------> | - DB/캐시/스케줄러 제공 | |

| +--------------------------+ +-----------------------------+ |

| |

| plugins/ |

| ├── my-plugin (프론트엔드 플러그인: React 컴포넌트) |

| ├── my-plugin-backend (백엔드 플러그인: REST API, DB 접근) |

| └── my-plugin-common (공유 타입, isomorphic 코드) |

+---------------------------------------------------------------------------------+

백엔드는 "새 백엔드 시스템(new backend system)"이 표준입니다. 의존성 주입 기반으로, 플러그인이 필요한 인프라 서비스(로거, DB, 스케줄러, 디스커버리)를 선언하면 프레임워크가 주입해 줍니다. 백엔드 진입점이 다음처럼 선언적으로 바뀐 것이 핵심입니다.

// packages/backend/src/index.ts (새 백엔드 시스템)

const backend = createBackend();

backend.add(import('@backstage/plugin-app-backend'));

backend.add(import('@backstage/plugin-catalog-backend'));

backend.add(import('@backstage/plugin-scaffolder-backend'));

backend.add(import('@backstage/plugin-techdocs-backend'));

backend.add(import('@backstage/plugin-auth-backend'));

backend.add(import('@backstage/plugin-permission-backend'));

// 사내 커스텀 플러그인

backend.add(import('@internal/plugin-deploy-board-backend'));

backend.start();

커스텀 플러그인 실전 — 사내 배포 현황판

"지금 어떤 서비스가 어떤 환경에 어떤 버전으로 떠 있는가"를 보여주는 배포 현황판을 만든다고 가정하고, 프론트/백엔드 골격을 보겠습니다. 플러그인 생성은 CLI로 시작합니다.

yarn new # 메뉴에서 plugin / backend-plugin 선택

백엔드 — 배포 데이터 API

// plugins/deploy-board-backend/src/plugin.ts

coreServices,

createBackendPlugin,

} from '@backstage/backend-plugin-api';

export const deployBoardPlugin = createBackendPlugin({

pluginId: 'deploy-board',

register(env) {

env.registerInit({

deps: {

logger: coreServices.logger,

httpRouter: coreServices.httpRouter,

database: coreServices.database,

},

async init({ logger, httpRouter, database }) {

httpRouter.use(await createRouter({ logger, database }));

},

});

},

});

// plugins/deploy-board-backend/src/router.ts

export async function createRouter(options: {

logger: any;

database: any;

}): Promise<Router> {

const router = Router();

router.use(express.json());

// 배포 이벤트 수집 (CD 파이프라인이 호출)

router.post('/deployments', async (req, res) => {

const { component, environment, version, status } = req.body;

options.logger.info(`deployment: ${component} ${version} -> ${environment}`);

// database 핸들로 knex 인스턴스를 얻어 저장

const knex = await options.database.getClient();

await knex('deployments').insert({

component,

environment,

version,

status,

deployed_at: new Date(),

});

res.status(201).json({ ok: true });

});

// 컴포넌트별 최신 배포 조회 (프론트엔드가 호출)

router.get('/deployments/:component', async (req, res) => {

const knex = await options.database.getClient();

const rows = await knex('deployments')

.where({ component: req.params.component })

.orderBy('deployed_at', 'desc')

.limit(20);

res.json(rows);

});

return router;

}

프론트엔드 — 엔터티 페이지 카드

// plugins/deploy-board/src/components/DeployBoardCard.tsx

export const DeployBoardCard = () => {

const { entity } = useEntity();

const fetchApi = useApi(fetchApiRef);

const discoveryApi = useApi(discoveryApiRef);

const { value, loading, error } = useAsync(async () => {

const baseUrl = await discoveryApi.getBaseUrl('deploy-board');

const res = await fetchApi.fetch(

`${baseUrl}/deployments/${entity.metadata.name}`,

);

return res.json();

}, [entity.metadata.name]);

if (error) return <InfoCard title="배포 현황">조회 실패</InfoCard>;

return (

isLoading={loading}

options={{ paging: false, search: false }}

columns={[

{ title: '환경', field: 'environment' },

{ title: '버전', field: 'version' },

{ title: '상태', field: 'status' },

{ title: '배포 시각', field: 'deployed_at' },

]}

data={value ?? []}

/>

);

};

이 카드를 엔터티 페이지(EntityPage)의 개요 탭에 끼워 넣으면, 카탈로그에서 서비스를 열 때마다 환경별 배포 상태가 보입니다. 카탈로그(1편) 위에 데이터를 얹는 전형적인 패턴입니다. CD 파이프라인 쪽은 배포 완료 시점에 위의 POST 엔드포인트를 호출하는 한 줄만 추가하면 됩니다.

주요 생태계 플러그인

직접 만들기 전에 생태계를 먼저 확인하세요. 실무에서 채택률이 높은 플러그인들입니다.

| 플러그인 | 제공 기능 | 연동 키 |

| --- | --- | --- |

| Kubernetes | 엔터티별 파드/디플로이먼트 상태 | kubernetes-id 어노테이션 |

| ArgoCD | 싱크 상태, 배포 이력 | argocd 앱 셀렉터 어노테이션 |

| SonarQube | 코드 품질 게이트, 커버리지 | sonarqube 프로젝트 키 |

| Grafana | 대시보드/알림 임베드 | grafana 대시보드 셀렉터 |

| PagerDuty | 온콜 표시, 인시던트 생성 | pagerduty 인테그레이션 키 |

| GitHub Actions | 워크플로 실행 상태 | project-slug 어노테이션 |

| Cost Insights | 클라우드 비용 가시화 | 비용 데이터 어댑터 구현 |

공통 패턴이 보일 겁니다. 전부 카탈로그 어노테이션으로 활성화됩니다. 1편에서 강조한 "어노테이션 거버넌스"가 여기서 빛을 발합니다. 어노테이션이 잘 채워진 카탈로그라면 플러그인 추가는 설정 몇 줄로 끝납니다.

권한 — Permission Framework

기본 Backstage는 로그인한 모든 사용자가 모든 것을 볼 수 있습니다. 조직이 커지면 "Scaffolder 템플릿 실행은 정직원만", "특정 시스템의 엔터티는 해당 본부만" 같은 요구가 나옵니다. permission framework는 이를 정책 코드로 해결합니다.

구조는 세 부분입니다. 플러그인이 권한 질의를 발행하고(예: catalog.entity.delete), 정책(policy)이 허용/거부를 결정하며, 결정은 사용자 신원과 조건(conditional decision)을 반영할 수 있습니다.

// packages/backend/src/extensions/permissionPolicy.ts

PermissionPolicy,

PolicyQuery,

PolicyQueryUser,

} from '@backstage/plugin-permission-node';

AuthorizeResult,

PolicyDecision,

isPermission,

} from '@backstage/plugin-permission-common';

class AcmePermissionPolicy implements PermissionPolicy {

async handle(

request: PolicyQuery,

user?: PolicyQueryUser,

): Promise<PolicyDecision> {

// 카탈로그 엔터티 삭제는 platform-team만 허용

if (isPermission(request.permission, catalogEntityDeletePermission)) {

const isPlatformTeam = user?.info.ownershipEntityRefs.includes(

'group:default/platform-team',

);

return {

result: isPlatformTeam ? AuthorizeResult.ALLOW : AuthorizeResult.DENY,

};

}

return { result: AuthorizeResult.ALLOW };

}

}

export const permissionModuleAcmePolicy = createBackendModule({

pluginId: 'permission',

moduleId: 'acme-policy',

register(reg) {

reg.registerInit({

deps: { policy: policyExtensionPoint },

async init({ policy }) {

policy.setPolicy(new AcmePermissionPolicy());

},

});

},

});

활성화는 app-config에서 합니다.

permission:

enabled: true

정책 설계의 원칙은 "기본 허용, 위험 동작만 제한"으로 시작하는 것입니다. 처음부터 촘촘한 거부 정책을 깔면 포털 채택 자체가 막힙니다.

프로덕션 운영 — 업그레이드, 모노레포, DB

릴리스 트레인과 업그레이드 전략

Backstage는 매월 메인라인 릴리스를 내고, 그 사이에 패치가 나옵니다. 업그레이드를 미루면 미룰수록 마이그레이션 비용이 비선형적으로 커지는 것이 실무의 공통된 경험입니다. 권장 운영 방식은 다음과 같습니다.

- **분기마다 최소 1회, 가능하면 매월 업그레이드 슬롯을 캘린더에 고정**합니다.

- 업그레이드는 전용 CLI로 수행합니다.

모든 @backstage 패키지를 목표 릴리스로 범프

yarn backstage-cli versions:bump

변경된 패키지 간 중복/충돌 점검

yarn backstage-cli versions:check

빌드와 테스트로 검증

yarn tsc && yarn build:all && yarn test:all

- 각 릴리스의 변경 사항은 업그레이드 헬퍼(Backstage Upgrade Helper)로 diff를 확인하면서 적용합니다. 특히 backend 시스템과 프론트엔드 시스템의 마이그레이션 가이드는 릴리스 노트와 함께 반드시 읽어야 합니다.

yarn 모노레포 관리

Backstage 앱은 yarn workspaces 모노레포입니다. 운영에서 자주 만나는 문제는 의존성 중복입니다. 같은 @backstage 패키지의 서로 다른 버전이 공존하면 타입 에러나 런타임 오작동이 발생할 수 있습니다. `yarn backstage-cli versions:check --fix`로 해소하고, 커스텀 플러그인의 @backstage 의존성은 peerDependency 성격으로 관리해 본체 버전을 따라가게 하는 것이 안전합니다.

DB 마이그레이션

각 백엔드 플러그인은 자기 스키마를 Knex 마이그레이션으로 관리하며, 부팅 시 자동 적용됩니다. 운영에서 중요한 것은 두 가지입니다. 첫째, 업그레이드 직후 첫 부팅은 마이그레이션 시간만큼 느릴 수 있으므로 readiness 프로브 타임아웃에 여유를 두세요. 둘째, 롤백 시나리오입니다. 스키마가 전진한 상태에서 구버전으로 되돌리면 깨질 수 있으므로, 업그레이드 전 DB 스냅샷을 표준 절차에 포함해야 합니다.

성능 — 카탈로그 대규모화와 캐싱

엔터티가 수천 개를 넘어가면 신경 쓸 지점들이 생깁니다.

- **프로세싱 루프 부하**: 모든 엔터티는 주기적으로 재처리됩니다. 엔터티 수가 늘면 DB와 CPU 부하가 함께 늘어납니다. 프로세서를 커스텀했다면 외부 API 호출 같은 느린 작업이 루프 안에 들어가지 않게 해야 합니다.

- **DB 튜닝**: PostgreSQL의 커넥션 풀 크기, 그리고 refresh_state 테이블 중심의 인덱스 동작을 모니터링하세요. 카탈로그 부하의 대부분은 DB에서 나타납니다.

- **검색 인덱싱**: 검색 플러그인의 인덱싱 주기를 엔터티 규모에 맞게 조정하고, 대규모라면 검색 백엔드를 PostgreSQL 검색에서 전용 엔진(예: OpenSearch/Elasticsearch)으로 전환합니다.

- **캐시 계층**: 캐시 스토어를 memory에서 Redis 같은 외부 스토어로 바꾸면 멀티 레플리카에서 캐시 적중률이 안정됩니다.

backend:

cache:

store: redis

connection: redis://backstage-redis:6379

- **수평 확장**: Backstage 백엔드는 무상태에 가깝게 설계되어 있어 레플리카 증설로 읽기 부하를 분산할 수 있습니다. 스케줄 작업은 DB 코디네이션으로 중복 실행이 방지됩니다.

관측 — 포털도 서비스다

Backstage 자체도 SLO를 가진 프로덕션 서비스로 취급해야 합니다.

- **메트릭**: 백엔드는 OpenTelemetry 계측을 지원합니다. HTTP 요청 지연/에러율, 카탈로그 프로세싱 큐 지연, Scaffolder 태스크 성공률을 핵심 지표로 수집해 Prometheus로 내보내세요.

- **로그**: 구조화 JSON 로그를 표준 수집 파이프라인(예: Loki, CloudWatch)으로 보냅니다. 특히 catalog 프로세싱 에러 로그는 "어떤 엔터티가 왜 갱신에 실패하는가"를 알려주는 일차 신호입니다.

- **대시보드와 알림**: 포털 가용성, 로그인 성공률, 카탈로그 신선도(마지막 성공 동기화 시각), TechDocs 빌드 실패율 정도면 초기 운영 대시보드로 충분합니다.

보안 — 플러그인 공급망과 시크릿

- **플러그인 공급망**: 커뮤니티 플러그인은 npm 패키지입니다. 도입 전 유지보수 활성도와 권한 요구 범위를 검토하고, 사내 npm 프록시/락파일 감사(yarn npm audit, socket 류 도구)를 CI에 포함하세요. 신뢰 경계 안으로 들어오는 코드라는 점을 잊으면 안 됩니다.

- **시크릿 관리**: app-config의 시크릿은 전부 환경 변수 참조로 두고, 실제 값은 Kubernetes Secret 또는 외부 시크릿 매니저(Vault, AWS Secrets Manager)에서 주입합니다. GitHub App 키 파일 같은 자격증명은 절대 이미지에 굽지 않습니다.

- **네트워크 경계**: Backstage 백엔드는 사내 시스템 곳곳에 접근 권한을 가진 허브가 됩니다. 아웃바운드 접근 대상을 NetworkPolicy로 명시하고, 토큰은 최소 권한으로 발급하세요.

- **인증 토큰 검증**: 백엔드 간 호출은 서비스 토큰으로 보호되며, 외부 노출 엔드포인트는 인증 미들웨어 뒤에 있는지 정기적으로 점검해야 합니다.

조직 운영 모델 — 플랫폼 팀과 기여 모델

Backstage 운영의 절반은 조직 설계입니다. 검증된 모델은 "중앙 플랫폼 팀 + 내부 오픈소스 기여"의 조합입니다.

- **플랫폼 팀**은 코어 운영(업그레이드, 인증, 카탈로그 거버넌스, 공통 플러그인)을 소유합니다. 2~4명의 전담으로 시작하는 경우가 많습니다.

- **도메인 팀**은 자기 도구의 플러그인/템플릿을 직접 기여합니다. 이때 기여 가이드라인(코드 리뷰 기준, 플러그인 품질 기준, 소유권 약속)을 문서화한 내부 오픈소스 모델이 필요합니다.

- **RFC 프로세스**: 포털에 큰 변화(새 코어 플러그인, 권한 정책 변경, 메타데이터 스키마 변경)를 도입할 때는 경량 RFC 문서로 의견을 수렴하는 절차를 두면, 플랫폼 팀의 독단도 현장의 무질서도 막을 수 있습니다.

도입 성숙도 로드맵

지금까지의 3편을 성숙도 모델로 묶으면 다음과 같습니다.

레벨 0 설치됨 데모 인스턴스, 수동 등록 엔터티 몇 개

레벨 1 카탈로그 가동 디스커버리 자동화, 소유권 모델, 인증 연동 [1편]

레벨 2 셀프서비스 골든 패스 템플릿, day-2 액션, 채택률 측정 [2편]

레벨 3 지식 허브 TechDocs 전면화, 생태계 플러그인, 검색 정착 [3편]

레벨 4 운영 성숙 권한 정책, SLO 운영, 기여 모델, 정기 업그레이드 [3편]

레벨 5 표준 플랫폼 포털 경유가 기본 경로, 지표 기반 지속 개선

레벨을 건너뛰려는 시도(카탈로그 없이 플러그인부터, 채택 없이 권한부터)가 실패의 주요 원인이라는 점을 다시 강조합니다.

프로덕션 체크리스트

**TechDocs**

- [ ] builder external + 오브젝트 스토리지 퍼블리셔 구성

- [ ] 문서 CI 파이프라인 (머지 시 자동 빌드/업로드)

- [ ] Scaffolder 스켈레톤에 mkdocs 골격 포함

**플러그인**

- [ ] 신규 요구는 생태계 플러그인 우선 검토

- [ ] 커스텀 플러그인은 frontend/backend/common 3패키지 구조

- [ ] 플러그인별 owner와 on-call 지정

**권한/보안**

- [ ] permission framework 활성화, 위험 동작 제한 정책

- [ ] 시크릿은 외부 주입, 이미지에 자격증명 없음

- [ ] 플러그인 의존성 감사가 CI에 포함

**운영**

- [ ] 월간/분기 업그레이드 슬롯 고정, versions:bump 절차 문서화

- [ ] 업그레이드 전 DB 스냅샷 표준 절차

- [ ] OpenTelemetry 메트릭 + 구조화 로그 수집

- [ ] 포털 SLO 정의 (가용성, 카탈로그 신선도)

- [ ] Redis 캐시 + 멀티 레플리카 구성

- [ ] 플랫폼 팀 소유권과 기여 가이드라인 문서화

- [ ] RFC 프로세스 운영

마치며

3편에 걸쳐 Backstage 기반 IDP의 뼈대를 살펴봤습니다. 1편의 카탈로그는 조직의 소프트웨어 현실을 데이터로 만들었고, 2편의 Scaffolder는 조직의 표준을 실행 가능한 코드로 만들었으며, 이번 3편은 그 위에 문서와 플러그인 생태계를 얹고 전체를 프로덕션 품질로 운영하는 방법을 다뤘습니다. 마지막으로 한 가지만 남기겠습니다. IDP는 도구 프로젝트가 아니라 제품입니다. 사용자(개발자)가 있고, 채택률이라는 지표가 있고, 로드맵과 운영 책임이 있어야 합니다. Backstage는 그 제품을 만들기 위한 가장 성숙한 오픈소스 토대입니다.

참고 자료

- [Backstage TechDocs 문서](https://backstage.io/docs/features/techdocs/)

- [TechDocs CI/CD 권장 아키텍처](https://backstage.io/docs/features/techdocs/architecture)

- [MkDocs 공식 문서](https://www.mkdocs.org/)

- [Backstage 플러그인 개발 가이드](https://backstage.io/docs/plugins/)

- [새 백엔드 시스템 문서](https://backstage.io/docs/backend-system/)

- [Permission Framework 문서](https://backstage.io/docs/permissions/overview)

- [Backstage 업그레이드 헬퍼](https://backstage.github.io/upgrade-helper/)

- [Backstage 릴리스 및 버전 정책](https://backstage.io/docs/releases/versioning-policy)

- [OpenTelemetry 공식 사이트](https://opentelemetry.io/)

- [CNCF Backstage 프로젝트 페이지](https://www.cncf.io/projects/backstage/)

현재 단락 (1/311)

Backstage를 데모로 띄우는 데는 한나절이면 충분합니다. 그러나 6개월 뒤에도 살아 있는 Backstage, 즉 문서가 최신이고, 플러그인이 안정적으로 동작하며, 업그레이드가...

작성 글자: 0원문 글자: 11,563작성 단락: 0/311