본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout

내비게이션

  • Tech
  • Projects
  • Books
  • About
  • Tags

카테고리

  • AI / ML
  • 웹 개발
  • 프로그래밍
  • 개발 도구

연결

  • GitHub
  • Email
  • RSS
© 2026 Kreath Archive. All rights reserved.Built with Next.js + MDX
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 4장: 서비스 카탈로그와 소프트웨어 템플릿
2026년 3월 23일·인프라·

4장: 서비스 카탈로그와 소프트웨어 템플릿

Backstage 소프트웨어 카탈로그의 엔티티 모델, catalog-info.yaml 스키마, 메타데이터 표준화, 그리고 Scaffolder를 활용한 프로젝트 자동 생성을 다룹니다.

15분1,096자7개 섹션
platform-engineeringdevopsinfrastructure
공유
platform-engineering4 / 10
12345678910
이전3장: Backstage로 개발자 포털 구축다음5장: Golden Path 설계와 구현

학습 목표

  • Backstage 소프트웨어 카탈로그의 엔티티 모델을 이해합니다.
  • catalog-info.yaml의 상세 스키마를 작성할 수 있습니다.
  • 메타데이터 표준화 전략을 수립할 수 있습니다.
  • **Scaffolder(스캐폴더)**를 활용하여 소프트웨어 템플릿을 작성할 수 있습니다.

엔티티 모델 심화

3장에서 Backstage 카탈로그의 엔티티 종류를 간략히 소개했습니다. 이번 장에서는 각 엔티티의 관계와 설계 패턴을 깊이 있게 다룹니다.

엔티티 계층 구조

Backstage의 엔티티들은 계층적 관계를 형성합니다.

Domain

**Domain(도메인)**은 비즈니스 영역을 나타냅니다. DDD(Domain-Driven Design)의 Bounded Context와 유사한 개념입니다.

domain-payment.yaml
yaml
apiVersion: backstage.io/v1alpha1
kind: Domain
metadata:
  name: payment
  description: "결제 및 정산 도메인"
  annotations:
    backstage.io/techdocs-ref: dir:.
  tags:
    - payment
    - fintech
spec:
  owner: group:default/payment-division

System

**System(시스템)**은 하나의 기능을 제공하기 위해 협력하는 컴포넌트들의 집합입니다.

system-checkout.yaml
yaml
apiVersion: backstage.io/v1alpha1
kind: System
metadata:
  name: checkout-system
  description: "결제 처리 시스템 - 주문, 결제, 영수증 발행"
  tags:
    - payment
    - checkout
spec:
  owner: group:default/checkout-team
  domain: payment

Component

**Component(컴포넌트)**는 카탈로그에서 가장 많이 사용되는 엔티티입니다. 서비스, 라이브러리, 웹사이트 등 다양한 소프트웨어 자산을 나타냅니다.

component-checkout-service.yaml
yaml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: checkout-service
  description: "결제 처리 핵심 서비스"
  annotations:
    github.com/project-slug: mycompany/checkout-service
    backstage.io/techdocs-ref: dir:.
    pagerduty.com/service-id: P1234567
    grafana/dashboard-selector: "checkout"
    sonarqube.org/project-key: mycompany_checkout-service
  tags:
    - java
    - spring-boot
    - grpc
  links:
    - url: https://grafana.mycompany.com/d/checkout
      title: "Grafana Dashboard"
      icon: dashboard
    - url: https://mycompany.atlassian.net/wiki/spaces/CHECKOUT
      title: "Confluence 문서"
      icon: docs
spec:
  type: service
  lifecycle: production
  owner: group:default/checkout-team
  system: checkout-system
  dependsOn:
    - resource:default/checkout-database
    - resource:default/payment-queue
    - component:default/user-service
  providesApis:
    - checkout-api
  consumesApis:
    - payment-gateway-api
    - notification-api

API

API 엔티티는 서비스 간 인터페이스를 정의합니다. OpenAPI, AsyncAPI, gRPC 등 다양한 형식을 지원합니다.

api-checkout.yaml
yaml
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
  name: checkout-api
  description: "결제 처리 REST API"
  tags:
    - rest
    - payment
spec:
  type: openapi
  lifecycle: production
  owner: group:default/checkout-team
  system: checkout-system
  definition:
    $text: ./openapi.yaml

Resource

Resource(리소스) 엔티티는 서비스가 의존하는 인프라 리소스를 나타냅니다.

resource-checkout-database.yaml
yaml
apiVersion: backstage.io/v1alpha1
kind: Resource
metadata:
  name: checkout-database
  description: "결제 데이터 저장용 PostgreSQL"
  annotations:
    mycompany.com/cloud-resource-id: "rds:ap-northeast-2:checkout-db"
  tags:
    - postgresql
    - rds
spec:
  type: database
  lifecycle: production
  owner: group:default/checkout-team
  system: checkout-system
  dependencyOf:
    - component:default/checkout-service
Tip

엔티티 간 관계(dependsOn, providesApis, consumesApis)를 정확하게 기술하면, Backstage가 자동으로 의존성 그래프를 생성합니다. 이는 장애 분석, 영향도 평가, 아키텍처 시각화에 매우 유용합니다.


메타데이터 표준화

카탈로그의 가치는 데이터의 일관성에서 나옵니다. 팀마다 다른 방식으로 메타데이터를 작성하면 카탈로그의 유용성이 급격히 떨어집니다.

네이밍 컨벤션

조직 전체에 적용할 네이밍 규칙을 정의합니다.

naming-conventions.yaml
yaml
conventions:
  component_name:
    pattern: "[a-z][a-z0-9-]*"
    examples:
      good: ["user-service", "payment-gateway", "auth-library"]
      bad: ["UserService", "payment_gateway", "auth lib"]
 
  system_name:
    pattern: "[a-z][a-z0-9-]*-system"
    examples:
      good: ["checkout-system", "user-management-system"]
      bad: ["checkout", "UserManagement"]
 
  api_name:
    pattern: "[a-z][a-z0-9-]*-api"
    examples:
      good: ["checkout-api", "user-api"]
      bad: ["checkoutAPI", "user_api"]
 
  tags:
    categories:
      language: ["java", "kotlin", "go", "python", "typescript"]
      framework: ["spring-boot", "gin", "fastapi", "nestjs"]
      protocol: ["rest", "grpc", "graphql", "async-api"]
      criticality: ["tier-1", "tier-2", "tier-3"]

필수 어노테이션

모든 Component 엔티티에 필수적으로 포함해야 할 어노테이션을 정의합니다.

required-annotations.yaml
yaml
required_annotations:
  all_components:
    - key: "github.com/project-slug"
      description: "GitHub 저장소 경로"
      example: "mycompany/checkout-service"
 
    - key: "backstage.io/techdocs-ref"
      description: "TechDocs 문서 위치"
      example: "dir:."
 
  production_services:
    - key: "pagerduty.com/service-id"
      description: "PagerDuty 서비스 ID"
      example: "P1234567"
 
    - key: "mycompany.com/tier"
      description: "서비스 중요도 등급"
      example: "tier-1"
 
    - key: "mycompany.com/slo-target"
      description: "SLO 목표 가용성"
      example: "99.9%"

자동 검증

메타데이터 표준을 자동으로 검증하는 CI 파이프라인을 구성합니다.

.github/workflows/catalog-validation.yaml
yaml
name: Catalog Validation
 
on:
  pull_request:
    paths:
      - "catalog-info.yaml"
 
jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - name: Validate catalog-info.yaml
        run: |
          npx @backstage/cli catalog-info validate catalog-info.yaml
 
      - name: Check required annotations
        run: |
          python scripts/check-annotations.py catalog-info.yaml
 
      - name: Check naming conventions
        run: |
          python scripts/check-naming.py catalog-info.yaml
Warning

메타데이터 표준화는 처음부터 엄격하게 적용하되, 기존 서비스의 마이그레이션에는 유예 기간을 두세요. 모든 서비스가 한 번에 표준을 충족하기 어려우므로, 분기별로 컴플라이언스 비율을 높여가는 전략이 효과적입니다.


자동 등록과 디스커버리

수백 개의 서비스를 수동으로 카탈로그에 등록하는 것은 현실적이지 않습니다. 자동 등록 메커니즘을 활용해야 합니다.

GitHub 디스커버리

GitHub Organization의 모든 저장소를 스캔하여 catalog-info.yaml이 있는 저장소를 자동으로 등록합니다.

app-config.yaml (GitHub Discovery)
yaml
catalog:
  providers:
    github:
      myCompanyOrg:
        organization: mycompany
        catalogPath: /catalog-info.yaml
        filters:
          branch: main
          repository: ".*"
        schedule:
          frequency:
            minutes: 30
          timeout:
            minutes: 3

Kubernetes 기반 디스커버리

Kubernetes 클러스터에서 실행 중인 워크로드를 자동으로 카탈로그에 등록하는 것도 가능합니다.

kubernetes-annotation.yaml
yaml
# Kubernetes Deployment에 Backstage 어노테이션 추가
apiVersion: apps/v1
kind: Deployment
metadata:
  name: checkout-service
  annotations:
    backstage.io/kubernetes-id: checkout-service
    backstage.io/kubernetes-namespace: checkout
    backstage.io/kubernetes-label-selector: "app=checkout-service"

소프트웨어 템플릿 (Scaffolder)

**Scaffolder(스캐폴더)**는 Backstage의 소프트웨어 템플릿 엔진입니다. 개발자가 몇 번의 클릭만으로 표준화된 프로젝트를 생성할 수 있도록 합니다.

템플릿 구조

템플릿 디렉토리 구조
text
templates/
  spring-boot-service/
    template.yaml          # 템플릿 정의
    skeleton/              # 프로젝트 스켈레톤
      catalog-info.yaml
      mkdocs.yml
      docs/
        index.md
      src/
        main/
          java/...
        test/
          java/...
      build.gradle
      Dockerfile
      .github/
        workflows/
          ci.yaml

템플릿 정의

template.yaml
yaml
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: spring-boot-service
  title: "Spring Boot 마이크로서비스"
  description: "표준 Spring Boot 마이크로서비스를 생성합니다. CI/CD, 모니터링, TechDocs가 사전 구성됩니다."
  tags:
    - java
    - spring-boot
    - recommended
spec:
  owner: group:default/platform-team
  type: service
 
  parameters:
    - title: "서비스 기본 정보"
      required:
        - serviceName
        - description
        - owner
        - system
      properties:
        serviceName:
          title: "서비스 이름"
          type: string
          description: "소문자, 하이픈 구분 (예: checkout-service)"
          pattern: "^[a-z][a-z0-9-]*$"
          ui:autofocus: true
        description:
          title: "서비스 설명"
          type: string
          description: "이 서비스가 하는 일을 간략히 설명하세요"
        owner:
          title: "소유 팀"
          type: string
          description: "서비스를 관리하는 팀"
          ui:field: OwnerPicker
          ui:options:
            catalogFilter:
              kind: Group
        system:
          title: "시스템"
          type: string
          description: "이 서비스가 속하는 시스템"
          ui:field: EntityPicker
          ui:options:
            catalogFilter:
              kind: System
 
    - title: "기술 옵션"
      properties:
        javaVersion:
          title: "Java 버전"
          type: string
          enum: ["21", "17"]
          default: "21"
        springBootVersion:
          title: "Spring Boot 버전"
          type: string
          enum: ["3.4.x", "3.3.x"]
          default: "3.4.x"
        database:
          title: "데이터베이스"
          type: string
          enum: ["postgresql", "mysql", "none"]
          default: "postgresql"
        messaging:
          title: "메시징"
          type: string
          enum: ["kafka", "rabbitmq", "none"]
          default: "none"
 
    - title: "인프라 옵션"
      properties:
        environment:
          title: "배포 환경"
          type: array
          items:
            type: string
            enum: ["dev", "staging", "production"]
          default: ["dev", "staging", "production"]
          uniqueItems: true
        tier:
          title: "서비스 등급"
          type: string
          enum: ["tier-1", "tier-2", "tier-3"]
          default: "tier-3"
          description: "tier-1: 핵심 서비스, tier-2: 중요 서비스, tier-3: 일반 서비스"
 
  steps:
    - id: fetch-skeleton
      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 }}
 
    - id: create-repo
      name: "GitHub 저장소 생성"
      action: publish:github
      input:
        repoUrl: "github.com?owner=mycompany&repo=${{ parameters.serviceName }}"
        description: ${{ parameters.description }}
        defaultBranch: main
        protectDefaultBranch: true
        repoVisibility: internal
 
    - id: register-catalog
      name: "카탈로그에 등록"
      action: catalog:register
      input:
        repoContentsUrl: ${{ steps['create-repo'].output.repoContentsUrl }}
        catalogInfoPath: /catalog-info.yaml
 
    - id: create-argocd-app
      name: "ArgoCD 앱 생성"
      action: argocd:create-resources
      input:
        appName: ${{ parameters.serviceName }}
        argoInstance: main
        namespace: ${{ parameters.serviceName }}
        repoUrl: ${{ steps['create-repo'].output.remoteUrl }}
        path: k8s/overlays/dev
 
  output:
    links:
      - title: "GitHub 저장소"
        url: ${{ steps['create-repo'].output.remoteUrl }}
      - title: "카탈로그 페이지"
        icon: catalog
        entityRef: ${{ steps['register-catalog'].output.entityRef }}

스켈레톤 파일의 템플릿 변수

스켈레톤 파일 내에서 Nunjucks 템플릿 문법을 사용하여 변수를 치환합니다.

skeleton/catalog-info.yaml
yaml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: ${{ values.serviceName }}
  description: "${{ values.description }}"
  annotations:
    github.com/project-slug: "mycompany/${{ values.serviceName }}"
    backstage.io/techdocs-ref: dir:.
    mycompany.com/tier: "${{ values.tier }}"
  tags:
    - java
    - spring-boot
spec:
  type: service
  lifecycle: production
  owner: ${{ values.owner }}
  system: ${{ values.system }}
Info

Scaffolder의 강점은 프로젝트 생성 시 코드만이 아니라 CI/CD 파이프라인, 모니터링 설정, 문서 템플릿, 카탈로그 등록까지 한 번에 수행한다는 점입니다. 이것이 바로 5장에서 다룰 Golden Path의 핵심입니다.


커스텀 Scaffolder 액션

기본 제공 액션 외에 조직 특화 액션을 직접 개발할 수 있습니다.

plugins/scaffolder-backend-module-mycompany/src/actions/createSlackChannel.ts
typescript
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
 
export function createSlackChannelAction() {
  return createTemplateAction<{
    channelName: string;
    description: string;
    teamMembers: string[];
  }>({
    id: 'mycompany:slack:create-channel',
    schema: {
      input: {
        required: ['channelName'],
        type: 'object',
        properties: {
          channelName: {
            type: 'string',
            title: 'Slack 채널 이름',
          },
          description: {
            type: 'string',
            title: '채널 설명',
          },
          teamMembers: {
            type: 'array',
            items: { type: 'string' },
            title: '초대할 팀원 이메일',
          },
        },
      },
    },
    async handler(ctx) {
      const { channelName, description, teamMembers } = ctx.input;
 
      ctx.logger.info(
        `Creating Slack channel: #${channelName}`,
      );
 
      // Slack API를 호출하여 채널 생성
      const response = await fetch('https://slack.com/api/conversations.create', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.SLACK_BOT_TOKEN}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          name: channelName,
          is_private: false,
        }),
      });
 
      const data = await response.json();
 
      if (!data.ok) {
        throw new Error(
          `Slack 채널 생성 실패: ${data.error}`,
        );
      }
 
      ctx.logger.info(
        `Slack channel created: ${data.channel.id}`,
      );
 
      ctx.output('channelId', data.channel.id);
      ctx.output('channelUrl', `https://mycompany.slack.com/archives/${data.channel.id}`);
    },
  });
}

이 커스텀 액션을 템플릿에서 사용할 수 있습니다.

template.yaml (커스텀 액션 사용)
yaml
steps:
  # ... 기존 스텝 ...
 
  - id: create-slack-channel
    name: "Slack 채널 생성"
    action: mycompany:slack:create-channel
    input:
      channelName: "svc-${{ parameters.serviceName }}"
      description: "${{ parameters.description }}"
      teamMembers: ${{ parameters.teamMembers }}

정리

이번 장에서는 Backstage 소프트웨어 카탈로그의 엔티티 모델과 소프트웨어 템플릿을 상세히 다루었습니다.

  • 카탈로그 엔티티는 Domain, System, Component, API, Resource의 계층 구조를 형성합니다.
  • catalog-info.yaml에는 일관된 네이밍 규칙과 필수 어노테이션을 적용해야 합니다.
  • GitHub Discovery를 통해 대규모 조직의 서비스를 자동으로 등록할 수 있습니다.
  • Scaffolder는 표준화된 프로젝트 생성을 자동화하며, 커스텀 액션으로 확장 가능합니다.

다음 장에서는 **Golden Path(골든 패스)**를 다룹니다. Scaffolder로 생성한 프로젝트가 어떻게 조직의 모범 사례를 자연스럽게 포함하게 되는지, 그리고 강제가 아닌 매력으로 80% 이상의 자발적 채택률을 달성하는 전략을 살펴보겠습니다.

이 글이 도움이 되셨나요?

관련 주제 더 보기

#platform-engineering#devops#infrastructure

관련 글

인프라

5장: Golden Path 설계와 구현

Golden Path의 정의와 철학, 옵셔널하되 매력적인 경로 설계, Terraform/Pulumi 기반 자동 인프라 프로비저닝, 그리고 80% 이상 자발적 채택률의 비결을 다룹니다.

2026년 3월 25일·19분
인프라

3장: Backstage로 개발자 포털 구축

Spotify 오픈소스이자 CNCF 졸업 프로젝트인 Backstage의 아키텍처, 설치, 소프트웨어 카탈로그, TechDocs, 플러그인 시스템을 실습합니다.

2026년 3월 21일·16분
인프라

6장: 셀프서비스 인프라

셀프서비스의 핵심 원칙, GitOps 기반 인프라 요청, Crossplane을 활용한 Kubernetes 네이티브 인프라 추상화, 그리고 승인 워크플로우를 다룹니다.

2026년 3월 27일·14분
이전 글3장: Backstage로 개발자 포털 구축
다음 글5장: Golden Path 설계와 구현

댓글

목차

약 15분 남음
  • 학습 목표
  • 엔티티 모델 심화
    • 엔티티 계층 구조
    • Domain
    • System
    • Component
    • API
    • Resource
  • 메타데이터 표준화
    • 네이밍 컨벤션
    • 필수 어노테이션
    • 자동 검증
  • 자동 등록과 디스커버리
    • GitHub 디스커버리
    • Kubernetes 기반 디스커버리
  • 소프트웨어 템플릿 (Scaffolder)
    • 템플릿 구조
    • 템플릿 정의
    • 스켈레톤 파일의 템플릿 변수
  • 커스텀 Scaffolder 액션
  • 정리