エンティティと関係 — 所有権がカタログの心臓である理由
一言でいうと
カタログは、サービスの一覧ではなく関係のグラフです。エンティティの種類を暗記するよりも、各種類がどんな質問に答えるために存在するのかを理解するほうが、試験にも実務にも役立ちます。
なぜ必要なのか
「サービスの一覧」だけでは、次のような質問に答えられません。
- このAPIをなくしたら何が壊れるか。→ 依存関係が必要
- このチームが解散したら、誰が引き継ぐか。→ 所有権が必要
- 決済ドメイン全体の状態は。→ まとまり(System/Domain)が必要
- このデータベースは誰のものか。→ Resourceという種類が必要
そのため、Backstageはエンティティを複数の種類に分け、その間の関係を保存します。
どう動くのか
エンティティの8つの種類
| kind | 答える質問 | 代表的なフィールド |
|---|---|---|
| Component | 私たちが作ってデプロイするソフトウェアの断片 | spec.type(service/website/library)、spec.lifecycle、spec.owner、spec.system |
| API | コンポーネントが公開または利用するインターフェース | spec.type(openapi/asyncapi/graphql/grpc)、spec.definition |
| Resource | コンポーネントが必要とするインフラ | spec.type(database/s3-bucket/queue) |
| System | 一緒に動作するエンティティのまとまり | spec.owner、spec.domain |
| Domain | 複数のシステムを包括する事業領域 | spec.owner |
| Group | チーム・組織の単位 | spec.type(team)、spec.children、spec.profile |
| User | 人 | spec.memberOf |
| Location | ほかのエンティティがある場所を指す道しるべ | spec.type、spec.targets |
ここに、Template(スキャフォルダー用)が加わります。そして、spec.lifecycleは自由な文字列ですが、慣例的にexperimental / production / deprecatedを使います。廃止予定のサービスを一覧からふるい落とすのに使われます。
関係(relations)は計算されるもの
重要なポイントです。catalog-info.yamlに書くのは、spec.owner、spec.system、spec.providesApisのような宣言で、カタログがそれを読み取って、双方向の関係を計算して保存します。
spec.owner: group:team-checkout → ownedBy / ownerOf
spec.system: commerce → partOf / hasPart
spec.providesApis: [checkout-api] → providesApi / apiProvidedBy
spec.consumesApis: [payments-api] → consumesApi / apiConsumedBy
spec.dependsOn: [resource:checkout-db] → dependsOn / dependencyOf
そのため、providesApisを片側だけに書いても、APIエンティティのページに「このAPIを提供するコンポーネント」が表示されます。反対方向を手で書く必要はありません。試験で「関係を両方のファイルにすべて書く必要があるか」と問われたら、答えはいいえです。
エンティティ参照の形式
関係を書くとき、ほかのエンティティを指す文字列の形式が決まっています。
[<kind>:][<namespace>/]<name>
3つの部分のうち、kindとnamespaceは省略でき、省略すると、コンテキストに応じたデフォルト値が適用されます。namespaceのデフォルト値はdefaultです。
| 書いたもの | 解釈 |
|---|---|
team-checkout |
コンテキストのデフォルトのkind + defaultネームスペース |
group:team-checkout |
group:default/team-checkout |
group:payments/team-checkout |
ネームスペースまで明示 |
spec.ownerのようなフィールドは、デフォルトのkindが決まっているので、team-checkoutとだけ書いても動作しますが、明示的にgroup:を付けるほうが、レビューでははるかに安全です。人が読むときに、それがチームか個人かがすぐにわかるからです。
なぜcatalog-info.yamlはコードの隣に置くのか
カタログを埋める方式は、3つあります。
- 静的登録: Backstageの設定ファイルにURLのリストを書きます。小規模なら使えますが、増えると管理できなくなります。
- Locationエンティティ: 道しるべのエンティティが、ほかのファイルを指します。階層的にまとめるときに便利です。
- ディスカバリー(discovery): 組織のリポジトリを走査して
catalog-info.yamlを見つけ、自動で登録します。実務での答えです。
3つ目が成り立つには、ファイルがコードと同じリポジトリになければなりません。この配置がもたらす効果は大きいです。
- サービスを作った人がオーナーを書きます。あとから別の人が推測することがありません。
- コードレビューを一緒に経ます。所有権の変更がPRとして記録されます。
- リポジトリをアーカイブすると、エンティティも一緒に消えます。幽霊エントリが残りません。
- サービスの構造が変わると、同じコミットでカタログも変わります。
逆に、カタログを中央のリポジトリ1か所にまとめておくと、そのファイルを直す動機のある人が誰もいなくなります。カタログが腐る、最もよくある経路です。
所有権が心臓部である理由
カタログで、たった1つだけ正確でなければならないとしたら、それはオーナーです。
- 障害が起きたとき、誰をページするかを決めます。
- 脆弱性が見つかったとき、誰にチケットを送るかを決めます。
- コストを誰に帰属させるかを決めます。
- 廃止の判断を誰が下すかを決めます。
そのため、オーナーは人(User)ではなく、チーム(Group)でなければなりません。人は退職し、チームは引き継がれます。試験でよく出るポイントであり、実務でカタログが崩れる最初の原因でもあります。
現場での姿
著者のホームラボで、Kubernetesのワークロードにapp.kubernetes.io/name、app.kubernetes.io/part-ofのような標準ラベルを付ける習慣は、このカタログの考え方とまったく同じものです。Helmチャートのベストプラクティスでも、app.kubernetes.io/name、instance、version、component、part-of、managed-byの6つを標準として付けるよう勧めています。
app.kubernetes.io/name→ カタログのComponent名app.kubernetes.io/part-of→ カタログのSystemapp.kubernetes.io/component→ Componentのspec.typeに当たる役割
つまり、同じ所有権・所属の情報を、クラスターのラベルとカタログのエンティティの2か所に一貫して表現することが、実務の姿です。そして、この2つをつなぐのが、BackstageのKubernetesプラグインです。エンティティにアノテーションbackstage.io/kubernetes-idを付けておくと、その値と同じラベルを持つワークロードをクラスターから探して、エンティティページに表示します。
ホームラボのGPUノードに付けたgpu.homelab/tier=xlargeのような、意味に基づくラベルも、同じ精神です。物理的な事実を、人が判断に使える語彙に翻訳することです。カタログがシステム・ドメインでサービスをまとめる理由と、変わりません。
次のラボですること
/root/cba-catalog/に、Component、API、Resource、System、Domain、Group、User、Locationのエンティティファイルを自分で書きます。そのあと、同じ所有権の情報を実際のクラスターのラベルで表現してkubectlで確認し、最後にすべてのエンティティの参照文字列を、正規化された形式で取り出してみます。