Skip to content

필사 모드: Backstage Scaffolder — 골든 패스를 코드로 만드는 법

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

들어가며 — 새 서비스 하나 만드는 데 2주가 걸리는 조직

신규 마이크로서비스 하나를 띄우는 데 실제로 필요한 작업을 나열해 보겠습니다. 저장소 생성, 보일러플레이트 코드, CI 파이프라인, Dockerfile, Kubernetes 매니페스트 또는 Helm 차트, 모니터링 대시보드, 알림 규칙, 로깅 설정, 시크릿 관리 연동, 카탈로그 등록. 각 단계마다 "지난번에 만든 거 복사"가 일어나고, 복사 원본이 팀마다 달라서 서비스 간 표준 편차는 시간이 갈수록 커집니다. 어떤 조직에서는 이 과정에 2주가 걸리고, 그 2주의 대부분은 위키를 뒤지고 다른 팀에 질문하는 시간입니다.

골든 패스(Golden Path)는 이 문제에 대한 답입니다. Spotify가 처음 쓴 이 용어는 "조직이 지지하는, 마찰이 최소화된 검증된 길"을 뜻합니다. 중요한 것은 강제가 아니라 유인이라는 점입니다. 골든 패스는 울타리가 아니라 포장도로입니다. 벗어날 수는 있지만, 포장도로가 충분히 빠르고 편하면 굳이 벗어날 이유가 없어집니다.

Backstage Scaffolder(Software Templates)는 골든 패스를 실행 가능한 코드로 만드는 도구입니다. 이번 글에서는 template.yaml의 구조부터 커스텀 액션 개발, 거버넌스와 측정까지, Scaffolder를 운영 수준으로 다루는 데 필요한 내용을 정리합니다. 1편에서 다룬 카탈로그가 전제라는 점을 기억해 주세요. Scaffolder의 결과물은 항상 카탈로그로 흘러들어갑니다.

Scaffolder의 동작 구조

Scaffolder는 백엔드 플러그인이며, 템플릿 실행은 태스크(task) 단위로 처리됩니다.

개발자 Scaffolder UI Scaffolder Backend

| | |

|--- 템플릿 선택 ---------->| |

|--- 폼 입력 (parameters)->| |

| |--- 태스크 생성 ----------->|

| | |

| | [steps 순차 실행] |

| | 1. fetch:template |

| | 2. publish:github |

| | 3. catalog:register |

| | |

|<-- 실시간 로그 스트림 ------|<-- 단계별 로그 ------------|

|<-- output 링크 ----------|<-- 완료 ------------------|

| | |

v v v

새 저장소 + CI + 카탈로그 등록 완료 (수 분 이내)

템플릿 자체도 카탈로그 엔터티(kind: Template)입니다. 즉 템플릿도 owner가 있고, 디스커버리로 자동 등록되며, 카탈로그 거버넌스의 대상이 됩니다.

template.yaml 해부 — parameters, steps, output

전체 구조를 갖춘 실전 템플릿을 먼저 보고 부분별로 해부하겠습니다.

apiVersion: scaffolder.backstage.io/v1beta3

kind: Template

metadata:

name: springboot-service

title: Spring Boot 마이크로서비스

description: 조직 표준이 내장된 Spring Boot 서비스를 생성합니다

tags:

- java

- spring-boot

- recommended

spec:

owner: group:default/platform-team

type: service

parameters:

- title: 서비스 기본 정보

required:

- name

- description

- owner

properties:

name:

title: 서비스 이름

type: string

pattern: '^[a-z0-9-]+$'

maxLength: 40

description: 소문자, 숫자, 하이픈만 허용

ui:autofocus: true

description:

title: 설명

type: string

owner:

title: 소유 팀

type: string

ui:field: OwnerPicker

ui:options:

catalogFilter:

kind: Group

spec.type: team

- title: 기술 옵션

properties:

javaVersion:

title: Java 버전

type: string

default: '21'

enum: ['17', '21']

enableKafka:

title: Kafka 컨슈머 포함

type: boolean

default: false

steps:

- id: fetch

name: 스켈레톤 렌더링

action: fetch:template

input:

url: ./skeleton

values:

name: ${{ parameters.name }}

description: ${{ parameters.description }}

owner: ${{ parameters.owner }}

javaVersion: ${{ parameters.javaVersion }}

enableKafka: ${{ parameters.enableKafka }}

- id: publish

name: GitHub 저장소 생성

action: publish:github

input:

repoUrl: github.com?owner=acme-corp&repo=${{ parameters.name }}

defaultBranch: main

repoVisibility: internal

protectDefaultBranch: true

requireCodeOwnerReviews: true

- id: register

name: 카탈로그 등록

action: catalog:register

input:

repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }}

catalogInfoPath: /catalog-info.yaml

output:

links:

- title: 저장소 바로가기

url: ${{ steps['publish'].output.remoteUrl }}

- title: 카탈로그에서 보기

icon: catalog

entityRef: ${{ steps['register'].output.entityRef }}

**parameters**는 JSON Schema 기반의 폼 정의입니다. 배열의 각 항목이 위저드의 한 페이지가 되고, `pattern`, `enum`, `maxLength` 같은 JSON Schema 검증이 클라이언트와 서버 양쪽에서 동작합니다. `ui:` 접두사 필드는 react-jsonschema-form 기반의 UI 확장으로, `ui:field: OwnerPicker`는 카탈로그의 Group 엔터티를 검색하는 전용 위젯을 띄워 줍니다.

**steps**는 순차 실행되는 액션 목록입니다. 각 step의 출력은 이후 step에서 참조할 수 있습니다. 표현식 문법은 코드 블록의 예제처럼 달러 기호와 이중 중괄호를 쓰는 Nunjucks 기반 문법입니다.

**output**은 실행 완료 화면에 노출할 링크들입니다. 새 저장소와 카탈로그 페이지로 바로 이동할 수 있게 하는 것이 관례입니다.

빌트인 액션 — 조합의 빌딩 블록

자주 쓰는 빌트인 액션을 정리하면 다음과 같습니다.

| 액션 | 역할 | 비고 |

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

| fetch:template | 스켈레톤을 가져와 Nunjucks로 렌더링 | 골든 패스의 본체 |

| fetch:plain | 렌더링 없이 파일 복사 | 바이너리, 그대로 쓸 파일 |

| publish:github | 저장소 생성 + 코드 푸시 | 브랜치 보호 옵션 포함 |

| publish:github:pull-request | 기존 저장소에 PR 생성 | 기존 코드에 표준 주입 시 |

| catalog:register | 카탈로그에 엔터티 등록 | 마지막 단계 관례 |

| catalog:write | catalog-info.yaml 파일 생성 | fetch 결과물에 추가 |

| fs:rename / fs:delete | 작업 디렉토리 파일 조작 | 조건부 파일 정리 |

| debug:log | 디버그 출력 | 템플릿 개발 시 |

설치된 인스턴스에서 사용 가능한 전체 액션 목록과 입력 스키마는 포털의 `/create/actions` 경로에서 확인할 수 있습니다. 템플릿을 작성하기 전에 이 페이지를 먼저 보는 습관이 시행착오를 크게 줄여 줍니다.

GitLab, Azure DevOps, Bitbucket용 publish 액션도 각각의 모듈로 제공되므로 VCS가 GitHub이 아니어도 동일한 패턴을 적용할 수 있습니다.

Nunjucks 템플릿팅 — skeleton 디렉토리

`fetch:template` 액션은 skeleton 디렉토리의 모든 텍스트 파일을 Nunjucks 템플릿 엔진으로 렌더링합니다. 디렉토리 구조 예시입니다.

templates/springboot-service/

├── template.yaml

└── skeleton/

├── catalog-info.yaml

├── README.md

├── Dockerfile

├── .github/

│ └── workflows/

│ └── ci.yaml

├── k8s/

│ ├── deployment.yaml

│ └── service.yaml

└── src/main/java/...

skeleton 안의 파일에는 치환 변수를 사용합니다. 예를 들어 skeleton의 catalog-info.yaml은 다음과 같이 작성합니다.

skeleton/catalog-info.yaml (Nunjucks 템플릿)

apiVersion: backstage.io/v1alpha1

kind: Component

metadata:

name: ${{ values.name }}

description: ${{ values.description | dump }}

annotations:

github.com/project-slug: acme-corp/${{ values.name }}

backstage.io/techdocs-ref: dir:.

spec:

type: service

lifecycle: experimental

owner: ${{ values.owner }}

조건 분기와 반복도 가능합니다. Kafka 옵션에 따라 의존성을 넣고 빼는 build.gradle 조각입니다.

// skeleton/build.gradle (Nunjucks 조건 분기)

dependencies {

implementation 'org.springframework.boot:spring-boot-starter-web'

implementation 'org.springframework.boot:spring-boot-starter-actuator'

{%- if values.enableKafka %}

implementation 'org.springframework.kafka:spring-kafka'

{%- endif %}

}

파일 이름과 디렉토리 이름에도 동일한 치환 문법을 쓸 수 있어서, 패키지 경로처럼 서비스 이름이 경로에 들어가는 구조도 표현됩니다. 한 가지 함정이 있습니다. GitHub Actions 워크플로 파일처럼 **스켈레톤 자체에 이중 중괄호 문법이 들어 있는 파일**은 Nunjucks가 잘못 해석할 수 있습니다. 이때는 raw 블록으로 감싸거나, `fetch:template`의 `copyWithoutTemplating` 옵션으로 해당 경로를 렌더링에서 제외합니다.

- id: fetch

action: fetch:template

input:

url: ./skeleton

copyWithoutTemplating:

- .github/workflows/*.yaml # 액션 워크플로는 치환 없이 복사

values:

name: ${{ parameters.name }}

커스텀 액션 개발 — Jira 티켓 자동 생성 예제

빌트인 액션으로 부족할 때는 TypeScript로 커스텀 액션을 만듭니다. 서비스 생성 시 Jira에 추적 티켓을 만드는 액션 예제입니다.

// plugins/scaffolder-backend-module-acme/src/actions/createJiraTicket.ts

export const createJiraTicketAction = () => {

return createTemplateAction<{

projectKey: string;

summary: string;

description: string;

}>({

id: 'acme:jira:create-ticket',

description: '신규 서비스 추적용 Jira 티켓을 생성합니다',

schema: {

input: {

type: 'object',

required: ['projectKey', 'summary'],

properties: {

projectKey: { type: 'string', title: 'Jira 프로젝트 키' },

summary: { type: 'string', title: '티켓 제목' },

description: { type: 'string', title: '티켓 본문' },

},

},

output: {

type: 'object',

properties: {

ticketUrl: { type: 'string' },

},

},

},

async handler(ctx) {

const { projectKey, summary, description } = ctx.input;

ctx.logger.info(`Creating Jira ticket in project ${projectKey}`);

const response = await fetch('https://jira.acme.io/rest/api/2/issue', {

method: 'POST',

headers: {

'Content-Type': 'application/json',

Authorization: `Bearer ${process.env.JIRA_TOKEN}`,

},

body: JSON.stringify({

fields: {

project: { key: projectKey },

summary,

description,

issuetype: { name: 'Task' },

},

}),

});

if (!response.ok) {

throw new Error(`Jira API error: ${response.status}`);

}

const issue = await response.json();

ctx.output('ticketUrl', `https://jira.acme.io/browse/${issue.key}`);

},

});

};

새 백엔드 시스템에서는 모듈로 등록합니다.

// plugins/scaffolder-backend-module-acme/src/module.ts

export const scaffolderModuleAcme = createBackendModule({

pluginId: 'scaffolder',

moduleId: 'acme-actions',

register(reg) {

reg.registerInit({

deps: { scaffolder: scaffolderActionsExtensionPoint },

async init({ scaffolder }) {

scaffolder.addActions(createJiraTicketAction());

},

});

},

});

이제 템플릿 steps에서 `action: acme:jira:create-ticket`으로 호출할 수 있습니다. 커스텀 액션은 조직 고유의 골든 패스를 만드는 핵심 수단입니다. 사내 시크릿 관리 시스템 등록, 내부 DNS 신청, 비용 태그 부여 같은 "우리 회사에만 있는 절차"가 전부 액션 후보입니다.

입력 검증과 UI 커스터마이징

폼 품질은 템플릿 채택률에 직접적인 영향을 줍니다. 세 가지 수준의 장치가 있습니다.

**1) JSON Schema 검증.** `pattern`, `minLength`, `enum`은 기본입니다. 서비스 이름처럼 전역 유일해야 하는 값은 정규식만으로 부족하므로, 커스텀 필드 확장에서 카탈로그 API를 조회해 중복을 사전 차단하는 방식이 좋습니다.

**2) 빌트인 커스텀 필드.** `OwnerPicker`(그룹 선택), `EntityPicker`(엔터티 선택), `RepoUrlPicker`(저장소 위치 선택)가 가장 많이 쓰입니다. RepoUrlPicker는 allowedOwners로 조직을 고정해 실수를 방지할 수 있습니다.

repoUrl:

title: 저장소 위치

type: string

ui:field: RepoUrlPicker

ui:options:

allowedHosts:

- github.com

allowedOwners:

- acme-corp

**3) 직접 만드는 커스텀 필드 확장.** 프론트엔드에서 React 컴포넌트와 검증 함수를 등록하면 폼 필드를 완전히 커스터마이징할 수 있습니다. 예를 들어 사내 비용 센터 코드를 ERP API에서 검색해 선택하게 하는 필드 같은 것입니다.

조직 표준 주입 — 스켈레톤이 곧 정책이다

골든 패스의 가치는 템플릿 엔진이 아니라 스켈레톤의 내용물에서 나옵니다. 새 서비스가 태어나는 순간 이미 갖추고 있어야 할 것들을 스켈레톤에 박아 넣으세요.

- **CI 파이프라인**: 빌드, 테스트, 정적 분석(SonarQube), 컨테이너 이미지 스캔, SBOM 생성까지 포함된 워크플로

- **관측 가능성**: 메트릭 엔드포인트 노출, 구조화 로깅 설정, 표준 대시보드 자동 생성(대시보드 프로비저닝 코드 포함)

- **보안 기본값**: 시크릿은 외부 시크릿 매니저 참조로만, 컨테이너 non-root 실행, NetworkPolicy 기본 포함

- **운영 표준**: readiness/liveness 프로브, 리소스 requests/limits, PodDisruptionBudget, 표준 라벨

- **문서와 카탈로그**: TechDocs 골격(mkdocs.yml + docs 디렉토리), catalog-info.yaml

이렇게 하면 "표준을 지켜라"라고 말할 필요가 없어집니다. 표준을 지키는 것이 가장 쉬운 길이 되기 때문입니다. 반대로 말하면, 스켈레톤이 부실한 템플릿은 골든 패스가 아니라 그냥 저장소 생성기일 뿐입니다.

템플릿 테스트, 버전 관리, dry-run

템플릿도 소프트웨어이므로 테스트와 버전 관리가 필요합니다.

**dry-run**: Backstage UI의 템플릿 에디터(`/create/edit`)에서 템플릿을 불러와 실제 저장소 생성 없이 렌더링 결과를 미리 볼 수 있습니다. 폼 입력에 따른 산출물 디렉토리를 확인하는 가장 빠른 루프입니다.

**CI에서의 검증**: 템플릿 저장소의 PR 파이프라인에서 최소한 다음을 자동화합니다.

.github/workflows/template-ci.yaml

name: template-ci

on:

pull_request:

jobs:

validate:

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v4

- name: template.yaml 스키마 검증

run: npx @roadiehq/backstage-entity-validator validate template.yaml

- name: 스켈레톤 렌더링 스모크 테스트

run: |

샘플 값으로 스켈레톤을 렌더링한 뒤

산출물에 대해 빌드가 통과하는지 확인

./scripts/render-and-build.sh sample-values.json

렌더링된 산출물이 실제로 빌드되고 테스트가 통과하는지까지 확인하는 것이 중요합니다. "템플릿으로 만든 서비스가 첫 빌드부터 깨지는" 경험은 골든 패스 신뢰도를 한 번에 무너뜨립니다.

**버전 관리**: 템플릿 저장소에 시맨틱 버전 태그를 붙이고, 프로덕션 Backstage는 태그/릴리스 기준으로 템플릿을 등록하는 방식이 안전합니다. 템플릿 변경이 기존 생성물에 소급 적용되지 않는다는 점도 명심해야 합니다. 이미 만들어진 서비스에 표준 변경을 전파하려면 `publish:github:pull-request` 액션 기반의 일괄 PR 캠페인이나 별도 자동화가 필요합니다.

거버넌스 — 누가 템플릿을 만드는가

템플릿 소유 모델은 두 가지 극단 사이에서 균형을 잡아야 합니다.

| 모델 | 장점 | 단점 |

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

| 플랫폼 팀 독점 | 품질 일관성, 표준 통제 | 병목, 현장 요구 반영 지연 |

| 전면 개방 | 빠른 확산, 현장 밀착 | 품질 편차, 중복 템플릿 난립 |

권장하는 중간 지점은 "개방 + 심사" 모델입니다. 누구나 템플릿을 제안할 수 있지만, 공식 카탈로그에 `recommended` 태그를 달고 노출되려면 플랫폼 팀의 리뷰를 거칩니다. 리뷰 기준을 체크리스트로 공개하세요. 스켈레톤 표준 포함 여부, CI 검증 존재 여부, owner 그룹 지정, 문서화 수준 같은 항목입니다. 그리고 템플릿마다 owner를 강제해서(템플릿도 카탈로그 엔터티이므로 1편의 거버넌스가 그대로 적용됩니다) 방치된 템플릿이 익명으로 남지 않게 합니다.

골든 패스 측정 — 채택률과 리드타임

측정하지 않는 골든 패스는 개선할 수 없습니다. 핵심 지표 두 가지를 추적하세요.

**채택률(adoption rate)**: 일정 기간 내 신규 생성 서비스 중 템플릿 경유 비율입니다. Scaffolder 태스크 이력과 카탈로그의 신규 Component 등록을 대조하면 계산할 수 있습니다. 채택률이 낮다면 템플릿이 현장 요구와 어긋나 있다는 신호입니다.

**신규 서비스 리드타임**: "만들기로 결정"부터 "프로덕션 첫 배포"까지의 시간입니다. 템플릿 도입 전 베이스라인을 측정해 두면 효과를 정량적으로 보고할 수 있습니다. 2주가 2시간이 되는 사례가 실제로 드물지 않습니다.

보조 지표로는 템플릿 실행 실패율(태스크 로그 기준), 템플릿별 사용 분포(안 쓰이는 템플릿 식별), 생성 후 30일 시점의 표준 준수율(생성된 서비스가 스켈레톤의 CI/관측 설정을 유지하고 있는지)이 유용합니다.

안티패턴 — 이렇게 만들면 안 쓴다

**과도한 옵션.** 파라미터가 20개씩 되는 템플릿은 "선택의 마찰"을 그대로 옮겨 놓은 것입니다. 골든 패스의 본질은 결정을 대신해 주는 것입니다. 옵션이 많아질수록 결정 부담이 사용자에게 돌아갑니다. 옵션이 5개를 넘으면 템플릿을 쪼개야 한다는 신호로 받아들이세요. "Java 서비스 템플릿에 프레임워크 선택 옵션 3개"보다 "Spring Boot 템플릿"과 "Quarkus 템플릿" 두 개가 낫습니다.

**방치된 템플릿.** 6개월간 업데이트가 없는 템플릿은 이미 조직 표준과 어긋나 있을 가능성이 높습니다. 낡은 의존성, 폐기된 CI 문법, 바뀐 보안 정책. 템플릿으로 만든 서비스가 생성 직후부터 보안 스캔에 걸리는 순간 신뢰는 끝납니다. 템플릿 저장소에도 의존성 자동 업데이트 봇을 붙이고, 분기마다 스켈레톤 산출물 빌드를 재검증하는 정기 파이프라인을 두세요.

**일회성 사고.** 템플릿을 "생성 시점에 한 번 쓰는 도구"로만 보는 관점입니다. 성숙한 조직은 day-2 작업에도 Scaffolder를 씁니다. 기존 서비스에 모니터링 추가, 라이브러리 마이그레이션 PR 생성, 인프라 리소스 신청 같은 작업을 템플릿화하면 포털이 "서비스 생성기"에서 "셀프서비스 허브"로 진화합니다.

**문서 없는 템플릿.** 템플릿이 무엇을 만들고 무엇을 만들지 않는지, 생성 후 해야 할 일이 무엇인지 설명이 없으면 사용자는 실행을 망설입니다. 템플릿 description과 README에 산출물 목록과 생성 후 단계를 명시하세요.

체크리스트

- [ ] 템플릿은 별도 저장소에서 버전 관리 (태그 기반 등록)

- [ ] template.yaml 스키마 검증이 PR 파이프라인에 존재

- [ ] 스켈레톤 렌더링 산출물의 빌드/테스트 스모크 테스트 자동화

- [ ] 스켈레톤에 CI, 관측, 보안, 운영 표준이 모두 내장

- [ ] catalog-info.yaml과 TechDocs 골격이 산출물에 포함

- [ ] 파라미터 5개 이하, 합리적 기본값 제공

- [ ] OwnerPicker / RepoUrlPicker로 입력 실수 차단

- [ ] 워크플로 파일 등 이중 중괄호 포함 파일은 copyWithoutTemplating 처리

- [ ] 템플릿마다 owner 그룹 지정, 리뷰 프로세스 문서화

- [ ] 채택률과 신규 서비스 리드타임 측정 체계 구축

- [ ] 분기별 템플릿 재검증(의존성, 보안 정책 정합성) 파이프라인

- [ ] day-2 작업 템플릿 로드맵 수립

마치며

Scaffolder는 기술적으로는 단순한 도구입니다. 폼을 그리고, 파일을 렌더링하고, 저장소를 만들고, 카탈로그에 등록합니다. 그러나 조직적으로는 강력한 지렛대입니다. 조직의 표준이 문서가 아니라 실행 가능한 코드가 되고, 새 서비스의 첫날 품질이 조직의 최고 수준으로 평준화됩니다. 핵심은 세 가지입니다. 스켈레톤에 표준을 아낌없이 담을 것, 템플릿을 소프트웨어처럼 테스트하고 버전 관리할 것, 채택률을 측정해 골든 패스가 실제로 걸어지는 길인지 확인할 것.

다음 3편에서는 TechDocs와 플러그인 개발, 그리고 Backstage를 프로덕션에서 운영하기 위한 전반적인 체크리스트를 다룹니다.

참고 자료

- [Backstage Software Templates 문서](https://backstage.io/docs/features/software-templates/)

- [템플릿 작성 가이드 (Writing Templates)](https://backstage.io/docs/features/software-templates/writing-templates)

- [빌트인 액션 목록 (Builtin Actions)](https://backstage.io/docs/features/software-templates/builtin-actions)

- [커스텀 액션 작성 가이드](https://backstage.io/docs/features/software-templates/writing-custom-actions)

- [커스텀 필드 확장 가이드](https://backstage.io/docs/features/software-templates/writing-custom-field-extensions)

- [Input Examples (파라미터 폼 예제)](https://backstage.io/docs/features/software-templates/input-examples)

- [Nunjucks 템플릿 엔진 공식 문서](https://mozilla.github.io/nunjucks/)

- [JSON Schema 공식 사이트](https://json-schema.org/)

- [Spotify 엔지니어링 — Golden Path 소개 글](https://engineering.atspotify.com/2020/08/how-we-use-golden-paths-to-solve-fragmentation-in-our-software-ecosystem/)

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

현재 단락 (1/310)

신규 마이크로서비스 하나를 띄우는 데 실제로 필요한 작업을 나열해 보겠습니다. 저장소 생성, 보일러플레이트 코드, CI 파이프라인, Dockerfile, Kubernetes 매니페...

작성 글자: 0원문 글자: 11,148작성 단락: 0/310