CBA — Backstage 인증 어소시에이트 · 소프트웨어 템플릿과 TechDocs · 이론
스캐폴더는 왜 액션의 조합인가 — 그리고 TechDocs 가 mkdocs 를 쓰는 이유
한 줄 요약
Backstage 의 소프트웨어 템플릿은 폼(parameters) → 작업 목록(steps) → 결과(output) 라는 세 층으로 되어 있고, 각 step 은 재사용 가능한 액션(action) 을 호출합니다. 이 조립식 구조 덕분에 조직마다 다른 골든 패스를 같은 부품으로 만들 수 있습니다.
왜 이게 필요했나
"새 서비스 만들기"를 자동화하는 첫 시도는 보통 셸 스크립트입니다. 그런데 그 스크립트가 하는 일을 나열해 보면 이렇습니다.
1. 입력을 받는다 (이름, 소유 팀, 언어, 배포 환경)
2. 입력이 규칙에 맞는지 검증한다 (이름이 DNS 규칙에 맞는가)
3. 템플릿 저장소에서 뼈대를 가져와 값을 치환한다
4. 새 Git 저장소를 만들고 푸시한다
5. CI 를 설정한다
6. 카탈로그에 등록한다
7. 결과 링크를 사람에게 보여 준다
스크립트로 만들면 2 번(검증)은 대충 되고, 4~6 번은 토큰이 필요해 개발자 로컬에 자격증명을 두게 되고, 7 번은 없습니다. 그리고 다른 언어용 스크립트를 만들 때 1~2, 4~7 을 통째로 복사하게 됩니다.
스캐폴더는 이 구조를 분해합니다. 입력 정의는 JSON Schema 로, 각 작업은 액션 으로, 결과는 output 으로 나눕니다. 그러면 "Node 서비스 템플릿"과 "Python 서비스 템플릿"이 1 번 뼈대 가져오기만 다르고 나머지는 같은 액션을 씁니다.
어떻게 동작하나
Template 매니페스트의 세 층
apiVersion: scaffolder.backstage.io/v1beta3kind: Templatemetadata: name: node-service title: Node.js 서비스spec: owner: group:team-platform type: service parameters: # ← 사용자에게 보여 줄 폼 (JSON Schema) - title: 기본 정보 required: [name, owner] properties: name: type: string pattern: '^[a-z0-9-]+$' owner: type: string ui:field: OwnerPicker steps: # ← 실제로 하는 일 - id: fetch name: 뼈대 가져오기 action: fetch:template - id: publish name: 저장소 만들기 action: publish:github - id: register name: 카탈로그 등록 action: catalog:register output: # ← 끝나고 보여 줄 것 links: - title: Repositoryparameters 는 JSON Schema 입니다. type, required, pattern, enum 이 그대로 동작하므로 잘못된 입력이 폼 단계에서 막힙니다. 여기에 Backstage 고유의 ui: 접두 키가 붙어 위젯을 지정합니다 — OwnerPicker(카탈로그의 Group 목록에서 고르기), RepoUrlPicker(호스트/조직/저장소명 조합), EntityPicker 같은 것들입니다. 이 위젯들이 중요한 이유는 자유 텍스트를 없애기 때문입니다. 소유자를 손으로 타이핑하게 두면 오타 난 소유자가 카탈로그에 들어갑니다.
steps 의 각 항목은 id, name, action, input 을 갖습니다. 대표 액션은 이렇습니다.
| 액션 | 하는 일 |
| --- | --- |
| fetch:template | 스켈레톤 디렉터리를 가져와 변수를 치환해 작업 공간에 푼다 |
| fetch:plain | 치환 없이 파일을 그대로 가져온다 |
| publish:github / publish:gitlab | 새 저장소를 만들고 작업 공간 내용을 푸시한다 |
| catalog:register | 생성된 catalog-info.yaml 을 카탈로그에 등록한다 |
| fs:rename, fs:delete | 작업 공간 파일 조작 |
액션 사이의 값 전달은 템플릿 표현식으로 합니다. 폼 입력은 parameters 로, 앞 단계 결과는 steps.<id>.output.<필드> 로 참조합니다. publish:github 의 출력에는 remoteUrl 과 repoContentsUrl 이 있고, catalog:register 는 등록된 엔티티의 참조를 돌려줍니다.
output 은 작업이 끝난 뒤 사용자에게 보여 줄 링크와 텍스트입니다. 사소해 보이지만 개발자 경험에서 큰 몫을 합니다 — 5 초 뒤에 "만들어졌습니다"만 뜨고 어디로 가야 할지 안 알려 주면 사람들은 다시 검색을 시작합니다.
여기서 자격증명이 어디 있는가 가 중요합니다. publish:github 을 실행하는 것은 사용자의 브라우저가 아니라 Backstage 백엔드 입니다. 토큰은 서버에 있고, 사용자는 그 토큰을 볼 수 없습니다. 이것이 "포털을 통한 셀프서비스"가 "모두에게 토큰 나눠 주기"보다 안전한 이유입니다.
TechDocs 와 docs-as-code
TechDocs 는 mkdocs 를 씁니다. 왜 하필 mkdocs 인가 하면 — 문서 소스가 그냥 마크다운 파일이고, 설정이 mkdocs.yml 한 개이며, 결과가 정적 파일이라 어디에나 올릴 수 있기 때문입니다. 무거운 문서 플랫폼을 도입하지 않고도 docs-as-code 가 성립합니다.
동작 흐름은 이렇습니다.
1. 서비스 저장소에 mkdocs.yml 과 docs/index.md 를 둔다.
2. 엔티티에 애너테이션 backstage.io/techdocs-ref: dir:. 를 단다 — "이 엔티티의 문서는 이 저장소의 같은 디렉터리에 있다"는 뜻이다.
3. 빌드된 정적 결과가 스토리지에 저장되고, 포털의 Docs 탭에서 렌더된다.
빌드 시점은 두 가지 전략이 있습니다. 로컬 빌드(포털이 요청 시 빌드)는 설정이 쉽지만 느리고 대규모에 부적합합니다. 외부 빌드(CI 가 빌드해 오브젝트 스토리지에 올림)가 운영 권장 방식입니다. CBA 에서 이 구분을 묻습니다.
docs-as-code 의 핵심 가치는 반복해서 말할 만합니다. 문서가 코드와 같은 저장소, 같은 PR, 같은 리뷰를 거치면 어긋날 확률이 크게 줄어듭니다. 별도 위키에 사는 문서는 3 개월이면 틀려지고, 틀린 문서는 없는 문서보다 나쁩니다.
현장에서 만나는 모습
저자의 홈랩에서 스캐폴딩이 필요한 이유는 사고 목록이 증명합니다. Gateway API 는 CRD v1.6.1 이 필요했고 v1.2 로는 tlsroutes 와 referencegrants 가 v1 이 아니라 Cilium 게이트웨이 컨트롤러가 기동을 거부했습니다. KubeVirt 는 containerDisk 경로에 결함이 있어 DataVolume(PVC) 경로로 우회해야 했습니다. GPU Operator 는 containerd 런타임 설정에서 사고가 났습니다.
이 지식들은 한 번 밟으면 다시 밟을 이유가 없는 함정 입니다. 사람 머릿속에 남으면 다음 사람이 똑같이 밟고, 템플릿의 스켈레톤과 문서로 굳히면 아무도 밟지 않습니다. 스캐폴더의 가치가 "타이핑을 줄여 주는 것"이라고 생각하면 과소평가입니다. 진짜 가치는 검증된 경로를 기본값으로 만드는 것 입니다.
또 하나. 이 클러스터는 kubeadm init 때 --control-plane-endpoint 를 VIP 나 DNS 가 아니라 첫 노드의 물리 IP 로 넣었습니다. 나중에 컨트롤 플레인을 3 대로 늘려도 그 값 때문에 첫 노드가 죽으면 API 접근이 끊깁니다. 저자는 "처음부터 다시 한다면 간접 주소를 넣었을 것"이라고 적었습니다. 초기 선택이 나중에 바꾸기 몹시 고통스러운 값 이 있다는 뜻이고, 그런 값이야말로 템플릿의 기본값에 박아 두어야 할 것들입니다.
다음 실습에서 할 것
/root/cba-template/ 에 Template 매니페스트를 작성합니다 — parameters 의 JSON Schema, steps 의 액션과 입력, output 의 링크까지. 이어서 mkdocs.yml 과 docs/index.md, 그리고 스켈레톤의 catalog-info.yaml 에 techdocs 애너테이션을 답니다. 마지막으로 이 템플릿이 만들어 낼 매니페스트를 실제 클러스터에 적용해 결과를 검증합니다.