スキャフォルダはなぜアクションの組み合わせなのか — そしてTechDocsがmkdocsを使う理由
一言でいうと
Backstageのソフトウェアテンプレートは、フォーム(parameters) → 作業リスト(steps) → 結果(output)という3つの層でできていて、各stepは、再利用可能なアクション(action)を呼び出します。この組み立て式の構造のおかげで、組織ごとに違うゴールデンパスを、同じ部品で作れます。
なぜ必要なのか
「新しいサービスを作る」ことを自動化する最初の試みは、たいていシェルスクリプトです。ところが、そのスクリプトがやっていることを並べてみると、こうなります。
- 入力を受け取る(名前、担当チーム、言語、デプロイ環境)
- 入力がルールに合っているかを検証する(名前がDNSの規則に合っているか)
- テンプレートのリポジトリから骨組みを取得し、値を置換する
- 新しいGitリポジトリを作成してプッシュする
- CIを設定する
- カタログに登録する
- 結果のリンクを人に見せる
スクリプトで作ると、2つ目(検証)はいい加減になり、4–6つ目はトークンが必要なので、開発者のローカルに認証情報を置くことになり、7つ目はありません。そして、別の言語用のスクリプトを作るときは、1–2つ目と4–7つ目を丸ごとコピーすることになります。
スキャフォルダーは、この構造を分解します。入力の定義はJSON Schemaで、各作業はアクションで、結果はoutputで分けます。そうすれば、「Nodeサービスのテンプレート」と「Pythonサービスのテンプレート」は、1つ目の骨組みの取得だけが違い、残りは同じアクションを使います。
どう動くのか
Templateマニフェストの3つの層
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
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: Repository
parametersは、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.<필드>(プレースホルダーはステップのidとフィールドです)で参照します。publish:githubの出力にはremoteUrlとrepoContentsUrlがあり、catalog:registerは、登録されたエンティティの参照を返します。
outputは、作業が終わったあとに、ユーザーに見せるリンクとテキストです。些細に見えますが、開発者体験では大きな役割を果たします。5秒後に「作成されました」としか表示されず、どこへ行けばよいかを教えてくれなければ、人々は再び検索を始めます。
ここで、認証情報がどこにあるかが重要です。publish:githubを実行するのは、ユーザーのブラウザーではなく、Backstageバックエンドです。トークンはサーバーにあり、ユーザーはそのトークンを見られません。これが、「ポータルを通じたセルフサービス」が、「全員にトークンを配る」よりも安全な理由です。
TechDocsとdocs-as-code
TechDocsは、mkdocsを使います。なぜよりによってmkdocsなのかというと、ドキュメントのソースがただのMarkdownファイルで、設定がmkdocs.yml1つで、結果が静的ファイルなので、どこにでも置けるからです。重いドキュメントプラットフォームを導入しなくても、docs-as-codeが成り立ちます。
動作の流れは、こうです。
- サービスのリポジトリに、
mkdocs.ymlとdocs/index.mdを置きます。 - エンティティにアノテーション
backstage.io/techdocs-ref: dir:.を付けます。「このエンティティのドキュメントは、このリポジトリの同じディレクトリにある」という意味です。 - ビルドされた静的な結果がストレージに保存され、ポータルのDocsタブでレンダリングされます。
ビルドの時点には、2つの戦略があります。ローカルビルド(ポータルがリクエスト時にビルド)は設定が簡単ですが、遅く、大規模には不向きです。外部ビルド(CIがビルドして、オブジェクトストレージにアップロード)が、運用で推奨される方式です。CBAでは、この区別が問われます。
docs-as-codeの核心となる価値は、繰り返し言うに値します。ドキュメントがコードと同じリポジトリ、同じPR、同じレビューを経れば、ずれる確率が大きく下がります。別のWikiにあるドキュメントは、3か月で間違ったものになり、間違ったドキュメントは、ないドキュメントよりも悪いものです。
現場での姿
著者のホームラボでスキャフォルディングが必要な理由は、事故の一覧が証明しています。Gateway APIにはCRD v1.6.1が必要で、v1.2ではtlsroutesとreferencegrantsがv1ではなく、Cilium Gatewayコントローラーが起動を拒否しました。KubeVirtはcontainerDiskのパスに欠陥があり、DataVolume(PVC)のパスに回避する必要がありました。GPU Operatorは、containerdのランタイム設定で事故が起きました。
これらの知識は、一度踏んだら、二度と踏む理由のない落とし穴です。人の頭の中に残れば、次の人がまったく同じように踏み、テンプレートのスケルトンとドキュメントとして固めれば、誰も踏みません。スキャフォルダーの価値が「タイピングを減らしてくれること」だと考えると、過小評価です。本当の価値は、検証された経路をデフォルトにすることです。
もう1つあります。このクラスターは、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のアノテーションを付けます。最後に、このテンプレートが作り出すマニフェストを、実際のクラスターに適用して、結果を検証します。