Skip to content
Published on

Platform EngineeringとBackstageによるInternal Developer Platform構築実践ガイド

Authors
  • Name
    Twitter
Platform Engineering Backstage

はじめに - Platform Engineeringが従来のDevOpsに取って代わる理由

「You build it, you run it」というDevOpsの原則は開発者に自律性を与えましたが、同時に膨大な認知負荷(Cognitive Load)をもたらしました。開発者がKubernetesマニフェストを直接作成し、CI/CDパイプラインを設定し、監視ダッシュボードを構成し、インフラプロビジョニングまで担当する状況が生まれたのです。Gartnerは2026年までにソフトウェアエンジニアリング組織の80%がPlatform Engineeringチームを組織すると予測しており、実際にこの流れは現実になっています。

Platform Engineeringは、セルフサービス機能を備えたInternal Developer Platform(IDP)を構築して開発者の認知負荷を軽減し、組織全体のソフトウェアデリバリー速度を向上させるディシプリンです。DevOpsが「文化と実践」に焦点を当てたのに対し、Platform Engineeringは「プロダクトとしてのプラットフォーム」に焦点を当てます。開発者はIDPの顧客であり、プラットフォームチームはこのプロダクトを開発・運用するチームです。

本記事では、CNCF IncubatingプロジェクトであるBackstageを基盤としたIDP構築の全プロセスを解説します。Software Catalog構成、Golden Path Template設計、プラグイン開発、そして運用時に直面する失敗事例と復旧手順まで、実践中心にまとめました。

Internal Developer Platformの概念と構成要素

IDPとは何か

Internal Developer Platform(IDP)は、開発者がインフラや運用の複雑さを気にせずコードに集中できるよう、セルフサービスインターフェースを通じてインフラプロビジョニング、デプロイ、監視、ドキュメントなどを統合提供するプラットフォームです。

IDPの主要な構成要素は以下の通りです:

  • Service Catalog:組織内のすべてのサービス、API、リソース、チームのメタデータを一元管理
  • Self-Service Portal:開発者がインフラ要求なしに直接環境をプロビジョニング
  • Golden Path Template:組織標準に準拠したプロジェクトスキャフォールディングテンプレート
  • Documentation Hub:サービスごとの技術ドキュメントをコードリポジトリと連携して自動レンダリング
  • Integration Layer:CI/CD、監視、インシデント管理ツールとの連携

IDP vs Developer Portal

IDPとDeveloper Portalはしばしば混同されますが、厳密には区別されます。Developer PortalはIDPのフロントエンドレイヤーです。IDPは、Portalの背後にある自動化層、インフラ抽象化、ポリシーエンジン、ワークフローオーケストレーションまで含むより広い概念です。

┌──────────────────────────────────────────────────────┐
Developer Portal (UI)│   ┌───────────┐ ┌───────────┐ ┌───────────────────┐  │
│   │  Catalog  │ │  Scaffolder│TechDocs         │  │
│   └───────────┘ └───────────┘ └───────────────────┘  │
├──────────────────────────────────────────────────────┤
Internal Developer Platform│   ┌───────────┐ ┌───────────┐ ┌───────────────────┐  │
│   │ IaC Engine│CI/CD     │ │ Policy Engine     │  │
 (Terraform│  (GitHub (OPA/Kyverno)     │  │
│   │  Crossplane│Actions) │ │                   │  │
│   └───────────┘ └───────────┘ └───────────────────┘  │
│   ┌───────────┐ ┌───────────┐ ┌───────────────────┐  │
│   │ K8s       │ │ Observ-   │ │ Secret Mgmt       │  │
│   │ Clusters  │ │ ability   │  (Vault)           │  │
│   └───────────┘ └───────────┘ └───────────────────┘  │
└──────────────────────────────────────────────────────┘

Backstageアーキテクチャとコア機能

Backstageとは

Backstageは、Spotifyが社内で使用していたDeveloper Portalを2020年にオープンソースとして公開したプロジェクトです。2024年にCNCF Incubatingプロジェクトに昇格し、2026年現在、コミュニティで最も活発に利用されているIDPフレームワークです。

4つのコア機能

  1. Software Catalog:組織のすべてのソフトウェア資産(サービス、ライブラリ、パイプライン、インフラなど)をYAMLベースのメタデータで登録・検索します。サービス間の依存関係、所有権、API仕様を一目で把握できます。

  2. Software Templates(Scaffolder):Golden Pathをコード化します。開発者がUIでいくつかのパラメータを入力するだけで、組織標準に準拠したプロジェクトが自動生成され、Gitリポジトリ作成、CI/CDパイプライン設定、K8sネームスペースプロビジョニングまで一括で実行されます。

  3. TechDocs:docs-as-code方式でMkDocsベースの技術ドキュメントをサービスリポジトリから自動ビルドし、Backstage UIでレンダリングします。ドキュメントがコードと一緒に管理されるため、常に最新の状態が維持されます。

  4. Plugins:Backstageの拡張性の核心です。200以上のコミュニティプラグインが存在し、GitHub、GitLab、PagerDuty、Datadog、ArgoCD、Kubernetesなどと連携できます。独自のプラグインを開発することも可能です。

アーキテクチャ構造

┌─────────────────────────────────────────────────┐
Backstage App (React)│  ┌─────────┐ ┌──────────┐ ┌─────────────────┐   │
│  │ Catalog │ │ Scaffolder│TechDocs        │   │
│  │ Plugin  │ │ Plugin   │ │ Plugin          │   │
│  └─────────┘ └──────────┘ └─────────────────┘   │
│  ┌─────────┐ ┌──────────┐ ┌─────────────────┐   │
│  │ K8s     │ │ CI/CD    │ │ Custom Plugins  │   │
│  │ Plugin  │ │ Plugin   │ │                 │   │
│  └─────────┘ └──────────┘ └─────────────────┘   │
├─────────────────────────────────────────────────┤
Backstage Backend (Node.js)│  ┌──────────┐ ┌───────────┐ ┌────────────────┐  │
│  │ Catalog  │ │ Scaffolder│Auth Provider  │  │
│  │ Backend  │ │ Backend (GitHub/Okta)  │  │
│  └──────────┘ └───────────┘ └────────────────┘  │
├─────────────────────────────────────────────────┤
Database (PostgreSQL) / Search└─────────────────────────────────────────────────┘

Backstageは**フロントエンド(React SPA)バックエンド(Node.js)**に分離された構造です。フロントエンドプラグインはReactコンポーネントとして、バックエンドプラグインはExpressルーターの形で実装されます。データストアにはPostgreSQL(本番)またはSQLite(開発用)を使用します。

Backstageインストールと初期設定

プロジェクト作成

Backstageアプリを作成する最初のステップです。Node.js 18以上とYarn Classic(1.x)が必要です。

# Backstageアプリの作成
npx @backstage/create-app@latest

# プロジェクトディレクトリ構造
# my-backstage-app/
# ├── app-config.yaml           # メイン設定ファイル
# ├── app-config.production.yaml # 本番オーバーライド
# ├── catalog-info.yaml         # Backstage自体のカタログエントリ
# ├── packages/
# │   ├── app/                  # フロントエンド(React)
# │   └── backend/              # バックエンド(Node.js)
# ├── plugins/                  # カスタムプラグイン
# └── package.json

# ローカル開発サーバーの起動
cd my-backstage-app
yarn dev

コア設定 - app-config.yaml

app-config.yamlはBackstageの中央設定ファイルです。データベース、認証、カタログソース、統合(Integration)などを定義します。

# app-config.yaml
app:
  title: 'ACME Developer Platform'
  baseUrl: http://localhost:3000

organization:
  name: 'ACME Corp'

backend:
  baseUrl: http://localhost:7007
  listen:
    port: 7007
  database:
    client: pg
    connection:
      host: ${POSTGRES_HOST}
      port: ${POSTGRES_PORT}
      user: ${POSTGRES_USER}
      password: ${POSTGRES_PASSWORD}

# GitHub統合設定
integrations:
  github:
    - host: github.com
      token: ${GITHUB_TOKEN}

# 認証プロバイダー設定
auth:
  environment: production
  providers:
    github:
      production:
        clientId: ${AUTH_GITHUB_CLIENT_ID}
        clientSecret: ${AUTH_GITHUB_CLIENT_SECRET}

# カタログソース登録
catalog:
  import:
    entityFilename: catalog-info.yaml
    pullRequestBranchName: backstage-integration
  rules:
    - allow: [Component, System, API, Resource, Location, Template, Group, User]
  locations:
    # 組織全体のGitHubリポジトリ自動探索
    - type: github-discovery
      target: https://github.com/acme-corp/*/blob/main/catalog-info.yaml
    # テンプレート登録
    - type: file
      target: ../../templates/all-templates.yaml
    # 組織構造の同期
    - type: github-org
      target: https://github.com/acme-corp

注意GITHUB_TOKENにはreporead:orgread:user権限が必要です。Fine-grained Tokenを使用する場合、対象リポジトリに対するContents、Metadata読み取り権限を明示的に付与する必要があります。トークン権限が不足するとカタログ登録時にNotFoundErrorが発生します。

本番デプロイ設定

# app-config.production.yaml
app:
  baseUrl: https://developer.acme.com

backend:
  baseUrl: https://developer-api.acme.com
  cors:
    origin: https://developer.acme.com

# TechDocsを外部ストレージに設定
techdocs:
  builder: 'external'
  generator:
    runIn: 'local'
  publisher:
    type: 'awsS3'
    awsS3:
      bucketName: 'acme-techdocs'
      region: 'ap-northeast-2'

Software Catalog構成

catalog-info.yamlの作成

すべてのソフトウェアエンティティはcatalog-info.yamlファイルを通じてBackstageに登録されます。このファイルはサービスリポジトリのルートに配置され、サービスのメタデータ、所有権、依存関係、API仕様を定義します。

# catalog-info.yaml - マイクロサービス登録の例
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: order-service
  title: '注文サービス'
  description: '注文作成、決済処理、在庫引き当てを担当するコアドメインサービス'
  annotations:
    # GitHub連携
    github.com/project-slug: acme-corp/order-service
    # CI/CD連携
    backstage.io/techdocs-ref: dir:.
    github.com/workflows: build-and-deploy.yaml
    # Kubernetes連携
    backstage.io/kubernetes-id: order-service
    backstage.io/kubernetes-namespace: order
    # PagerDutyインシデント連携
    pagerduty.com/service-id: PXXXXXX
    # Datadogダッシュボード
    datadoghq.com/dashboard-url: https://app.datadoghq.com/dashboard/xxx
  tags:
    - java
    - spring-boot
    - grpc
  links:
    - url: https://grafana.acme.com/d/order-svc
      title: 'Grafana Dashboard'
      icon: dashboard
    - url: https://wiki.acme.com/order-domain
      title: 'Domain Wiki'
      icon: docs
spec:
  type: service
  lifecycle: production
  owner: team-order
  system: ecommerce-platform
  providesApis:
    - order-api
  consumesApis:
    - inventory-api
    - payment-api
  dependsOn:
    - resource:order-database
    - resource:order-redis
---
# API仕様の登録
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
  name: order-api
  description: '注文処理gRPC API'
spec:
  type: grpc
  lifecycle: production
  owner: team-order
  system: ecommerce-platform
  definition:
    $text: ./proto/order.proto
---
# データベースリソースの登録
apiVersion: backstage.io/v1alpha1
kind: Resource
metadata:
  name: order-database
  description: '注文サービスPostgreSQLデータベース'
spec:
  type: database
  owner: team-order
  system: ecommerce-platform

エンティティ関係構造

Backstageのエンティティモデルは階層構造を持ちます:

エンティティKind役割
Domainビジネスドメイン領域commerce、logistics
System関連コンポーネントの論理グループecommerce-platform
Component個別ソフトウェア単位(サービス、ライブラリ、ウェブサイト)order-service
APIコンポーネントが提供するインターフェースorder-api(gRPC/REST/GraphQL)
Resourceインフラリソースorder-database、order-redis
Groupチーム・組織単位team-order
User個別ユーザーjane.doe

この関係構造により、「このサービスは誰が所有し、どのAPIを提供し、どのインフラに依存し、障害時に誰に連絡すべきか」を即座に把握できます。サービスが数百に増えるマイクロサービス環境では、この可視性が決定的に重要です。

Golden Path Templateの作成

Golden Pathは、組織が推奨する「正しい始め方」をコード化したものです。開発者が新しいサービスを開始する際に、CI/CD、テスト、監視、セキュリティ設定がデフォルトで組み込まれたプロジェクトを自動生成できます。

Scaffolder Templateの作成

# templates/spring-boot-service/template.yaml
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: spring-boot-grpc-service
  title: 'Spring Boot gRPCマイクロサービス'
  description: |
    Spring Boot 3.x + gRPCベースのマイクロサービスプロジェクトを作成します。
    含まれるもの:Dockerfile、Helm Chart、GitHub Actions CI/CD、
    Prometheusメトリクス、Health Check、catalog-info.yaml
  tags:
    - java
    - spring-boot
    - grpc
    - recommended
spec:
  owner: platform-team
  type: service

  # ユーザー入力パラメータの定義
  parameters:
    - title: 'サービス基本情報'
      required:
        - serviceName
        - owner
        - system
      properties:
        serviceName:
          title: 'サービス名'
          type: string
          description: '英小文字とハイフンのみ使用(例:order-service)'
          pattern: '^[a-z][a-z0-9-]*$'
          ui:autofocus: true
        description:
          title: 'サービス説明'
          type: string
        owner:
          title: '所有チーム'
          type: string
          ui:field: OwnerPicker
          ui:options:
            catalogFilter:
              kind: Group
        system:
          title: '所属システム'
          type: string
          ui:field: EntityPicker
          ui:options:
            catalogFilter:
              kind: System

    - title: '技術オプション'
      properties:
        javaVersion:
          title: 'Javaバージョン'
          type: string
          enum: ['17', '21']
          default: '21'
        database:
          title: 'データベース'
          type: string
          enum: ['postgresql', 'mysql', 'none']
          default: 'postgresql'
        enableKafka:
          title: 'Kafka連携'
          type: boolean
          default: false

    - title: 'インフラ設定'
      properties:
        namespace:
          title: 'K8sネームスペース'
          type: string
          default: 'default'
        cluster:
          title: 'デプロイクラスター'
          type: string
          enum: ['dev', 'staging', 'production']
          default: 'dev'

  # 実行ステップの定義
  steps:
    # 1. テンプレートからプロジェクトコードを生成
    - id: fetch-template
      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 }}
          enableKafka: ${{ parameters.enableKafka }}
          namespace: ${{ parameters.namespace }}

    # 2. GitHubリポジトリの作成
    - id: publish-github
      name: 'GitHubリポジトリ作成'
      action: publish:github
      input:
        allowedHosts: ['github.com']
        repoUrl: github.com?owner=acme-corp&repo=${{ parameters.serviceName }}
        description: ${{ parameters.description }}
        defaultBranch: main
        protectDefaultBranch: true
        requireCodeOwnerReviews: true
        repoVisibility: internal

    # 3. Backstageカタログに登録
    - id: register-catalog
      name: 'カタログ登録'
      action: catalog:register
      input:
        repoContentsUrl: ${{ steps['publish-github'].output.repoContentsUrl }}
        catalogInfoPath: /catalog-info.yaml

    # 4. ArgoCD Application作成
    - id: create-argocd-app
      name: 'ArgoCDデプロイ設定'
      action: argocd:create-resources
      input:
        appName: ${{ parameters.serviceName }}
        argoInstance: main
        namespace: ${{ parameters.namespace }}
        repoUrl: ${{ steps['publish-github'].output.remoteUrl }}
        path: deploy/helm

  # 完了後の案内
  output:
    links:
      - title: 'GitHubリポジトリ'
        url: ${{ steps['publish-github'].output.remoteUrl }}
      - title: 'カタログで確認'
        icon: catalog
        entityRef: ${{ steps['register-catalog'].output.entityRef }}

このテンプレート1つで、開発者はUIからサービス名といくつかのオプションを選択するだけで、GitHubリポジトリ作成からCI/CDパイプライン設定、K8sデプロイ、カタログ登録まで5分以内に完了します。従来平均3日かかっていた新規サービスのオンボーディングが劇的に短縮されます。

プラグイン開発と統合

カスタムプラグインの作成

Backstage CLIを使用してフロントエンドまたはバックエンドプラグインのスキャフォールディングを行えます。

# フロントエンドプラグインの作成
cd my-backstage-app
yarn new --select plugin

# バックエンドプラグインの作成
yarn new --select backend-plugin

# 生成されたプラグイン構造
# plugins/
# └── my-custom-plugin/
#     ├── src/
#     │   ├── components/
#     │   │   └── ExampleComponent/
#     │   ├── plugin.ts          # プラグイン定義
#     │   ├── routes.ts          # ルーティング設定
#     │   └── index.ts
#     ├── dev/                   # 独立開発環境
#     │   └── index.tsx
#     └── package.json

フロントエンドプラグインの実装例

チーム別サービスヘルス状況をダッシュボードで表示するプラグインの例です。

// plugins/team-health-dashboard/src/plugin.ts
import { createPlugin, createRoutableExtension } from '@backstage/core-plugin-api'

export const teamHealthDashboardPlugin = createPlugin({
  id: 'team-health-dashboard',
  routes: {
    root: rootRouteRef,
  },
})

export const TeamHealthDashboardPage = teamHealthDashboardPlugin.provide(
  createRoutableExtension({
    name: 'TeamHealthDashboardPage',
    component: () => import('./components/DashboardPage').then((m) => m.DashboardPage),
    mountPoint: rootRouteRef,
  })
)
// plugins/team-health-dashboard/src/components/DashboardPage.tsx
import React, { useEffect, useState } from 'react';
import {
  Table,
  TableColumn,
  StatusOK,
  StatusError,
  StatusWarning,
  Progress,
  ResponseErrorPanel,
} from '@backstage/core-components';
import { useApi, configApiRef } from '@backstage/core-plugin-api';
import { catalogApiRef } from '@backstage/plugin-catalog-react';

interface ServiceHealth {
  name: string;
  owner: string;
  status: 'healthy' | 'degraded' | 'down';
  uptime: number;
  lastIncident: string;
  errorRate: number;
}

const columns: TableColumn<ServiceHealth>[] = [
  { title: 'サービス', field: 'name' },
  { title: '所有チーム', field: 'owner' },
  {
    title: 'ステータス',
    field: 'status',
    render: (row: ServiceHealth) => {
      switch (row.status) {
        case 'healthy':
          return <StatusOK>正常</StatusOK>;
        case 'degraded':
          return <StatusWarning>パフォーマンス低下</StatusWarning>;
        case 'down':
          return <StatusError>障害</StatusError>;
        default:
          return null;
      }
    },
  },
  { title: 'Uptime (%)', field: 'uptime', type: 'numeric' },
  { title: 'エラー率 (%)', field: 'errorRate', type: 'numeric' },
  { title: '最後のインシデント', field: 'lastIncident' },
];

export const DashboardPage = () => {
  const catalogApi = useApi(catalogApiRef);
  const [services, setServices] = useState<ServiceHealth[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error>();

  useEffect(() => {
    const fetchData = async () => {
      try {
        const entities = await catalogApi.getEntities({
          filter: { kind: 'Component', 'spec.type': 'service' },
        });

        // 各サービスのヘルスデータをPrometheus/Datadog APIから取得
        const healthData = await Promise.all(
          entities.items.map(async entity => {
            const metrics = await fetchServiceMetrics(
              entity.metadata.name,
            );
            return {
              name: entity.metadata.name,
              owner:
                entity.spec?.owner?.toString() ?? 'unknown',
              status: metrics.status,
              uptime: metrics.uptime,
              lastIncident: metrics.lastIncident,
              errorRate: metrics.errorRate,
            };
          }),
        );

        setServices(healthData);
      } catch (e) {
        setError(e as Error);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [catalogApi]);

  if (loading) return <Progress />;
  if (error) return <ResponseErrorPanel error={error} />;

  return (
    <Table
      title="サービスヘルスダッシュボード"
      columns={columns}
      data={services}
      options={{
        sorting: true,
        paging: true,
        pageSize: 20,
        search: true,
      }}
    />
  );
};

主要プラグイン統合一覧

本番IDPで頻繁に統合されるプラグインとその役割:

プラグイン用途設定ポイント
@backstage/plugin-kubernetesPod状態、ログ、イベントのリアルタイム表示ServiceAccount、RBAC設定
@backstage/plugin-techdocsdocs-as-codeベースのドキュメントレンダリングMkDocs設定、S3/GCSパブリッシャー
@roadiehq/backstage-plugin-github-insightsPR状況、コントリビューター統計GitHub Appトークン
@backstage/plugin-catalog-importUIからcatalog-info.yamlを登録カタログルール
@backstage-community/plugin-cost-insightsクラウドコスト可視化コストAPI連携
@pagerduty/backstage-pluginオンコール状況、インシデント一覧PagerDuty APIキー
@backstage/plugin-scaffolder-backend-module-githubGitHubリポジトリ自動作成GitHub App権限

IDPツール比較

Backstage以外にも複数のIDPツールが市場に存在します。組織の規模、技術力、予算によって適切なツールは異なります。

項目BackstagePortCortexOpsLevel
ライセンスApache 2.0(オープンソース)SaaS(無料ティアあり)SaaSSaaS
ホスティングセルフホスティングクラウドマネージドクラウドマネージドクラウドマネージド
カスタマイズ性非常に高い(プラグイン開発)中程度(ウィジェット/ブループリント)低い中程度
初期設定コスト高い(専任チーム必要)低い低い低い
運用負担高い(アップグレード、セキュリティ)なし(SaaS)なしなし
Service CatalogYAMLベース、強力UIベース、直感的自動ディスカバリー自動ディスカバリー
スキャフォールディング内蔵(Scaffolder)セルフサービスアクション限定的サービステンプレート
技術ドキュメントTechDocs内蔵外部連携外部連携外部連携
スコアカードプラグインで追加内蔵コア機能内蔵
適合組織大規模、技術力の高いチーム中小規模、迅速な導入サービス成熟度重視中規模
価格(50名基準)無料(インフラ/人件費別途)~$2,000/月~$5,000/月~$3,000/月

選択基準のまとめ:専任のPlatform Engineeringチームがあり高いカスタマイズが必要ならBackstage、迅速な導入と低い運用負担を求めるならPort、サービス成熟度の測定と標準準拠に注力したいならCortexが適しています。

運用時の注意事項

導入メトリクスの追跡

IDPを構築するよりも開発者に実際に使ってもらうことの方が難しいです。以下のメトリクスを追跡して導入状況を測定しましょう:

メトリクス測定方法目標
カタログカバレッジ登録済みサービス数 / 全サービス数95%以上
テンプレート使用率テンプレートで作成されたサービス / 全新規サービス80%以上
DAU/WAUBackstage日次/週次アクティブユーザー開発者の60%以上
オンボーディング時間新規サービス作成〜初回デプロイまでの所要時間1時間以内
MTTR改善障害発生〜担当チーム認知までの時間既存比50%削減
TechDocs最新性30日以内に更新されたドキュメントの割合70%以上

避けるべきアンチパターン

  1. ビッグバンリリース:すべての機能を一度にリリースしようとすると失敗します。MVPから始めましょう。最初のステップはSoftware Catalogだけで十分です。カタログに組織のすべてのサービスを登録し、所有権と依存関係を可視化するだけでも大きな価値を提供します。

  2. プラットフォーム=ポータルの勘違い:UIだけきれいに作って裏に自動化がなければ、開発者はすぐに離れます。「ワンクリックでK8sネームスペースとCI/CDが設定される」実質的な自動化が核心です。

  3. 開発者フィードバックの無視:プラットフォームはプロダクトです。開発者アンケート、使用データ分析、定期的なフィードバックセッションなしにプラットフォームチームが「良さそうな」機能だけを作ると導入率が下がります。

  4. Backstageバージョンアップグレードの放置:Backstageはリリースサイクルが速いです(月1〜2回)。6ヶ月以上アップグレードを先延ばしにすると、マイグレーションが極めて困難になります。backstage-cli versions:bumpコマンドを定期的に実行しましょう。

  5. 強制的な導入:管理ツールに成り下がると開発者の反感を買います。IDPは開発者体験(DX)を向上させるツールであるべきです。「これを使わなければ処分」ではなく、「これを使えば3日かかっていたことが5分で終わる」がメッセージであるべきです。

Backstageアップグレード手順

# 1. 現在のバージョン確認
yarn backstage-cli info

# 2. バージョンバンプ(依存関係の自動更新)
yarn backstage-cli versions:bump

# 3. 変更内容の確認
git diff

# 4. 型チェック
yarn tsc

# 5. テスト実行
yarn test

# 6. ビルド確認
yarn build

# 7. マイグレーションガイドの確認
# https://backstage.io/docs/getting-started/keeping-backstage-updated

# 推奨:CIで自動的にアップグレードPRを生成
# .github/workflows/backstage-upgrade.yaml
# 毎週月曜日に自動実行してバージョンバンプ後にPRを作成

警告versions:bump実行前に必ず現在の変更内容をコミットしてください。アップグレード時にpackage.jsonyarn.lock、そして時にはコードマイグレーションが必要なbreaking changeが含まれることがあります。ステージング環境で先に検証してから本番に適用することを推奨します。

失敗事例と復旧手順

事例1:カタログエンティティの同期失敗

症状:GitHubにcatalog-info.yamlが存在するがBackstage UIに表示されない

原因分析

  • GitHubトークンの期限切れまたは権限不足(最も一般的)
  • YAML構文エラー(インデント、不正なkind値)
  • catalog.rulesで該当Kindがallowリストにない
  • ネットワーク問題によるGitHub API呼び出し失敗

復旧手順

  1. Backstageログの確認:kubectl logs -l app=backstage -c backstage --tail=100
  2. トークン有効性の検証:curl -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/user
  3. YAML検証:npx @backstage/cli catalog-info validate catalog-info.yaml
  4. 手動リフレッシュ:Backstage UIの該当エンティティページで「Refresh」ボタンをクリック
  5. キャッシュリセットが必要な場合:Backstage Podの再起動

事例2:ScaffolderテンプレートGitHubリポジトリ作成中の失敗

症状:テンプレート実行時に「publish:github」ステップでRequestError: HttpError: Resource not accessible by integrationエラー

原因分析

  • GitHub AppのRepository: Administration権限の欠如
  • 組織のリポジトリ作成ポリシーによるブロック
  • 同名のリポジトリが既に存在

復旧手順

  1. GitHub App権限の確認と更新
  2. 組織設定でAppがリポジトリ作成を許可されているか確認
  3. 失敗したタスクのログをBackstage UIで確認(Scaffolderタスクログページ)
  4. 部分的に作成されたリソース(空のリポジトリなど)があれば手動でクリーンアップ後に再実行

事例3:PostgreSQL接続プール枯渇

症状:Backstageの応答速度が急激に低下、断続的なConnectionTimeoutError

原因分析

  • カタログエンティティが数千に増加してDB接続が不足
  • デフォルトの接続プールサイズ(10個)がトラフィックに対応できない

復旧手順と予防

# app-config.yaml - 接続プールチューニング
backend:
  database:
    client: pg
    connection:
      host: ${POSTGRES_HOST}
      port: ${POSTGRES_PORT}
      user: ${POSTGRES_USER}
      password: ${POSTGRES_PASSWORD}
    pool:
      min: 5
      max: 30
      acquireTimeoutMillis: 60000
      idleTimeoutMillis: 30000

事例4:TechDocsビルド失敗

症状:サービスページでTechDocsタブが空またはビルドエラー表示

原因分析

  • リポジトリにmkdocs.ymlファイルがない
  • Python依存関係(mkdocs-techdocs-core)のインストール失敗
  • S3バケット権限の問題(外部パブリッシャー使用時)

復旧手順

  1. リポジトリにmkdocs.ymldocs/index.mdが存在するか確認
  2. ローカルでnpx @techdocs/cli generateコマンドによるビルドテスト
  3. 外部パブリッシャー使用時のIAM権限確認
  4. techdocs.builderlocalに変更してBackstageが直接ビルドするよう設定変更(小規模環境)

事例5:Backstageアップグレード後のプラグイン互換性の破損

症状:アップグレード後、特定のプラグインページでホワイトスクリーンまたはランタイムエラー

原因分析

  • Backstageコアパッケージとプラグイン間のバージョン不一致
  • APIがdeprecatedされた後に削除された
  • 新しいBackstageシステム(New Backend System)へのマイグレーションが必要

復旧手順

  1. 直ちに前のバージョンにロールバック(Helm rollbackまたは以前のイメージタグでデプロイ)
  2. ブラウザコンソールでエラーメッセージを確認
  3. 問題プラグインのGitHub issueを検索
  4. プラグインを最新の互換バージョンに更新するか、一時的に無効化
  5. ステージングで検証後に再度本番デプロイ

チェックリスト

IDP構築準備チェックリスト

  • Platform Engineering専任チーム組成(最低2〜3名)
  • 開発者ペインポイントサーベイの実施(認知負荷が最も大きい領域の把握)
  • IDPの最初のMVP範囲の決定(推奨:Software Catalogから開始)
  • Backstage vs SaaSツールの比較評価完了
  • PostgreSQLインスタンスのプロビジョニング(本番)
  • GitHub AppまたはOAuth Appの作成と権限設定
  • SSOプロバイダー連携計画(Okta、Azure AD、Googleなど)

Backstage運用チェックリスト

  • app-config.production.yaml分離とシークレット管理(Vault、K8s Secret)
  • HTTPS/TLS設定(IngressまたはロードバランサーV)
  • データベースバックアップスケジュール設定
  • 監視構成(Backstageメトリクス + インフラメトリクス)
  • 週次バージョンアップグレードワークフロー自動化
  • カタログエンティティ検証CI(PRでcatalog-info.yamlの有効性検査)
  • 開発者オンボーディングガイドドキュメント作成
  • 四半期ごとの開発者満足度調査計画

カタログ登録チェックリスト(サービス別)

  • catalog-info.yamlの作成とリポジトリルートへの配置
  • metadata.nameが組織ネーミング規則に準拠しているか確認
  • spec.ownerが正しいチームグループに設定されているか
  • spec.systemが正しいシステムに所属しているか
  • API仕様(OpenAPI、gRPC、AsyncAPI)の登録
  • annotationsにCI/CD、監視、インシデントツール連携情報を追加
  • TechDocs設定(mkdocs.yml + docs/ディレクトリ)
  • Kubernetesアノテーション設定(クラスター内ワークロードマッピング)

参考資料