- Authors
- Name

- Play 1:IDPが必要な時期を見極める
- Play 2:プラットフォームチームの構成と役割定義
- Play 3:技術スタックの選択
- Play 4:Backstageセットアップとソフトウェアカタログ
- Play 5:サービステンプレート(スキャフォールディング)
- Play 6:TechDocsでドキュメントを統合
- Play 8:プラットフォーム成功指標の測定
- Play 9:トラブルシューティング
- Play 10:IDPロードマップの段階的実装
- クイズ
- References
Play 1:IDPが必要な時期を見極める
Internal Developer Platform(IDP)は、開発者がインフラ申請なしにセルフサービスでサービスの作成、デプロイ、モニタリングを行える内部プラットフォームである。2026年のGartner調査によると、エンタープライズの80%がプラットフォームエンジニアリングに投資している。
しかし、すべての組織にIDPが必要なわけではない。以下の条件のうち3つ以上に該当する場合、IDP導入を検討すべき時期である。
導入シグナルチェックリスト:
- サービス数が20を超えている
- 新規サービスの作成に1週間以上かかる
- チームごとにCI/CDパイプラインが異なり、標準がない
- オンボーディングに2週間以上かかる(開発環境構築、権限申請など)
- インフラチームが反復的なJiraチケット処理に80%以上の時間を費やしている
- デプロイ後の障害時、ロールバック手順がチームごとに異なるか、ドキュメント化されていない
- コスト帰属(cost attribution)ができない
3つ未満の場合は、IDPよりも簡単なシェルスクリプト自動化やGitHub Actions標準テンプレートで十分である。
Play 2:プラットフォームチームの構成と役割定義
IDPは製品である。インフラエンジニア数名がサイドプロジェクトとして作るのではなく、専任チームがプロダクトマインドセットで運営すべきである。
チーム構成(開発者50〜200名の組織基準)
| 役割 | 人数 | 主な責務 |
|---|---|---|
| Platform PM | 1名 | 開発者の要件収集、ロードマップ管理、採用率追跡 |
| Platform Engineer | 2〜3名 | インフラ抽象化、API/UI開発、ゴールデンパス設計 |
| SRE / DevOps | 1〜2名 | モニタリングパイプライン、オンコール、インシデント対応自動化 |
| Developer Advocate | 0.5名(兼任) | ドキュメンテーション、オンボーディングガイド、社内研修 |
核心原則:
- プラットフォームチームの顧客は社内開発者である。 NPS(Net Promoter Score)を四半期ごとに測定する。
- 採用は強制ではなく魅力で。 ゴールデンパスに従えば30分で完了するが、従わなければ2週間かかるようにする。
- フィードバックループは2週間以内に。 開発者が要望した機能は、2週間以内に最低限の回答(実装計画または却下理由)を受け取れるようにする。
Play 3:技術スタックの選択
Backstage vs 自社構築 vs SaaS 比較
| 基準 | Backstage(オープンソース) | 自社構築 | SaaS(Port/Cortexなど) |
|---|---|---|---|
| 初期コスト | 中(3〜6ヶ月で構築) | 高(6〜12ヶ月) | 低(即時開始) |
| カスタマイズ性 | 高(プラグインエコシステム) | 最高 | 限定的 |
| メンテナンス負荷 | 高(アップグレード、セキュリティパッチ) | 非常に高い | なし(ベンダー責任) |
| 組織規模適合性 | 100名以上 | 500名以上 | 50〜300名 |
| ベンダーロックイン | なし | なし | 高い |
| プラグイン/統合 | 2000以上のプラグイン | 必要なもののみ | ベンダー提供範囲 |
推奨戦略:100名以下の組織であればSaaSから始める。100〜500名であればBackstageを導入するが、RoadieのようなマネージドBackstageも検討する。500名以上であれば専任チームがBackstageをカスタマイズして運用する。
Play 4:Backstageセットアップとソフトウェアカタログ
Backstageのインストール
# Backstage CLIで新規プロジェクトを作成
npx @backstage/create-app@latest --skip-install
# 生成されるディレクトリ構造
my-backstage/
├── app-config.yaml # コア設定ファイル
├── app-config.production.yaml
├── packages/
│ ├── app/ # フロントエンド(React)
│ └── backend/ # バックエンド(Node.js)
├── plugins/ # カスタムプラグイン
├── catalog-info.yaml # このプロジェクト自体のカタログ登録
└── package.json
# 依存関係のインストールと実行
cd my-backstage
yarn install
yarn dev
# http://localhost:3000 でアクセス
app-config.yaml の主要設定
# app-config.yaml
app:
title: 'MyOrg Developer Platform'
baseUrl: http://localhost:3000
organization:
name: 'MyOrg'
backend:
baseUrl: http://localhost: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}
# ソフトウェアカタログ設定
catalog:
import:
entityFilename: catalog-info.yaml
rules:
- allow: [Component, System, API, Resource, Location, Group, User]
locations:
# 組織のすべてのリポジトリからcatalog-info.yamlを自動探索
- type: github-discovery
target: https://github.com/my-org/*/blob/main/catalog-info.yaml
# 手動登録
- type: file
target: ./catalog-entities/all-systems.yaml
# 認証(GitHub OAuth)
auth:
environment: development
providers:
github:
development:
clientId: ${GITHUB_OAUTH_CLIENT_ID}
clientSecret: ${GITHUB_OAUTH_CLIENT_SECRET}
サービスカタログ登録標準
各サービスリポジトリのルートに catalog-info.yaml を配置する。
# catalog-info.yaml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: order-service
description: '注文処理マイクロサービス'
annotations:
github.com/project-slug: my-org/order-service
backstage.io/techdocs-ref: dir:.
datadoghq.com/dashboard-url: https://app.datadoghq.com/dashboard/abc-123
pagerduty.com/service-id: PXXXXXX
argocd/app-name: order-service-prod
tags:
- java
- spring-boot
- tier-1
links:
- url: https://order.internal.example.com
title: 本番URL
- url: https://grafana.internal.example.com/d/order-service
title: Grafanaダッシュボード
spec:
type: service
lifecycle: production
owner: team-commerce
system: commerce-platform
dependsOn:
- component:payment-service
- resource:orders-database
providesApis:
- order-api
consumesApis:
- payment-api
- inventory-api
---
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
name: order-api
description: '注文REST API'
spec:
type: openapi
lifecycle: production
owner: team-commerce
definition:
$text: ./docs/openapi.yaml
Play 5:サービステンプレート(スキャフォールディング)
BackstageのSoftware Templatesは、新規サービスを標準化された構造で作成する機能である。30分以内にCI/CD、モニタリング、カタログ登録まで完了したサービスを構築できる。
# templates/spring-boot-service/template.yaml
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: spring-boot-service
title: 'Spring Boot マイクロサービス'
description: 'CI/CD、モニタリング、カタログが自動設定されたSpring Bootサービスを作成します'
tags:
- java
- spring-boot
- recommended
spec:
owner: team-platform
type: service
parameters:
- title: サービス情報
required:
- serviceName
- ownerTeam
- tier
properties:
serviceName:
title: サービス名
type: string
pattern: '^[a-z][a-z0-9-]*$'
description: '小文字、数字、ハイフンのみ許可'
ownerTeam:
title: オーナーチーム
type: string
ui:field: OwnerPicker
ui:options:
catalogFilter:
kind: Group
tier:
title: サービスティア
type: string
enum: ['tier-1', 'tier-2', 'tier-3']
enumNames: ['Tier 1 (99.99% SLO)', 'Tier 2 (99.9% SLO)', 'Tier 3 (99% SLO)']
javaVersion:
title: Javaバージョン
type: string
default: '21'
enum: ['17', '21']
- title: インフラ設定
properties:
database:
title: データベース
type: string
default: 'postgresql'
enum: ['postgresql', 'mysql', 'none']
messageQueue:
title: メッセージキュー
type: string
default: 'none'
enum: ['kafka', 'rabbitmq', 'none']
replicaCount:
title: デフォルトレプリカ数
type: integer
default: 3
minimum: 1
maximum: 20
steps:
# 1. テンプレートからリポジトリを生成
- id: fetch-template
name: テンプレートコード生成
action: fetch:template
input:
url: ./skeleton
values:
serviceName: ${{ parameters.serviceName }}
ownerTeam: ${{ parameters.ownerTeam }}
tier: ${{ parameters.tier }}
javaVersion: ${{ parameters.javaVersion }}
database: ${{ parameters.database }}
replicaCount: ${{ parameters.replicaCount }}
# 2. GitHubリポジトリを作成
- id: publish
name: GitHubリポジトリ作成
action: publish:github
input:
allowedHosts: ['github.com']
repoUrl: github.com?owner=my-org&repo=${{ parameters.serviceName }}
defaultBranch: main
repoVisibility: internal
protectDefaultBranch: true
requireCodeOwnerReviews: true
# 3. ArgoCDアプリケーションを登録
- id: register-argocd
name: ArgoCDアプリケーション登録
action: argocd:create-resources
input:
appName: ${{ parameters.serviceName }}-prod
argoInstance: main
namespace: ${{ parameters.serviceName }}
repoUrl: https://github.com/my-org/${{ parameters.serviceName }}
path: k8s/overlays/production
# 4. Backstageカタログに登録
- id: register-catalog
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-catalog'].output.entityRef }}
Play 6:TechDocsでドキュメントを統合
分散したドキュメントをBackstage TechDocsに統合すれば、サービスカタログから直接技術ドキュメントを確認できる。
# mkdocs.yml(各サービスリポジトリのルート)
site_name: order-service
nav:
- Home: index.md
- Architecture: architecture.md
- API Reference: api.md
- Runbook: runbook.md
- ADR:
- adr/001-database-choice.md
- adr/002-event-schema.md
plugins:
- techdocs-core
<!-- docs/runbook.md -->
# Order Service 運用ランブック
## 障害対応
### 注文処理遅延(P95 > 500ms)
1. Grafanaダッシュボードを確認:[リンク]
2. DBコネクションプールの状態を確認:
```bash
kubectl exec -it deploy/order-service -- curl localhost:8080/actuator/metrics/hikaricp.connections.active
```
- コネクションプールが飽和している場合:
kubectl scale deploy/order-service --replicas=6 - DBスロークエリを確認:
SELECT pid, now() - query_start AS duration, query FROM pg_stat_activity WHERE state = 'active' AND now() - query_start > interval '5s';
注文作成失敗(HTTP 500)
- エラーログを確認:
kubectl logs deploy/order-service --tail=100 | grep ERROR - エラーコード別の対応:
ORDER-001:決済サービス接続失敗 -> payment-serviceの状態を確認ORDER-002:在庫不足 -> inventory-serviceの同期を確認ORDER-003:DBデッドロック -> トランザクション分離レベルを確認
## Play 7:セルフサービスインフラプロビジョニング
開発者がBackstage UIからデータベース、メッセージキュー、キャッシュなどを直接プロビジョニングできるようにする。実際のインフラ作成はTerraform + GitOpsで処理する。
```yaml
# templates/provision-postgresql/template.yaml
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: provision-postgresql
title: 'PostgreSQLデータベースプロビジョニング'
description: 'RDS PostgreSQLインスタンスをセルフサービスで作成します'
spec:
owner: team-platform
type: resource
parameters:
- title: データベース設定
required:
- dbName
- environment
- instanceClass
properties:
dbName:
title: DB名
type: string
pattern: '^[a-z][a-z0-9_]*$'
environment:
title: 環境
type: string
enum: ['dev', 'staging', 'production']
instanceClass:
title: インスタンスサイズ
type: string
default: 'db.r7g.large'
enum:
- 'db.t4g.medium'
- 'db.r7g.large'
- 'db.r7g.xlarge'
- 'db.r7g.2xlarge'
enumNames:
- 'Small (2 vCPU, 4GB) - dev/staging'
- 'Medium (2 vCPU, 16GB) - production'
- 'Large (4 vCPU, 32GB) - production'
- 'XLarge (8 vCPU, 64GB) - high traffic'
storageGb:
title: ストレージ(GB)
type: integer
default: 100
minimum: 20
maximum: 16000
multiAz:
title: Multi-AZデプロイメント
type: boolean
default: false
steps:
- id: create-terraform-pr
name: Terraform PR作成
action: publish:github:pull-request
input:
repoUrl: github.com?owner=my-org&repo=infrastructure
branchName: provision-db-${{ parameters.dbName }}
title: 'DBプロビジョニング: ${{ parameters.dbName }} (${{ parameters.environment }})'
description: |
自動生成されたDBプロビジョニングリクエストです。
- DB名: ${{ parameters.dbName }}
- 環境: ${{ parameters.environment }}
- インスタンス: ${{ parameters.instanceClass }}
- ストレージ: ${{ parameters.storageGb }}GB
- Multi-AZ: ${{ parameters.multiAz }}
targetPath: terraform/rds/${{ parameters.environment }}/${{ parameters.dbName }}
sourcePath: ./terraform-template
Play 8:プラットフォーム成功指標の測定
IDPの成果を測定しなければ、投資対効果を証明できない。以下の指標を四半期ごとに追跡する。
主要業績評価指標(KPI)
# platform_metrics.py - プラットフォームKPIダッシュボードデータ収集
import requests
from datetime import datetime, timedelta
class PlatformMetrics:
def __init__(self, github_token: str, backstage_url: str):
self.github = github_token
self.backstage = backstage_url
def service_creation_lead_time(self) -> dict:
"""新規サービス作成所要時間(目標:30分以内)"""
# Backstage scaffolderログから抽出
response = requests.get(
f"{self.backstage}/api/scaffolder/v2/tasks",
params={"createdAfter": (datetime.now() - timedelta(days=90)).isoformat()}
)
tasks = response.json()["items"]
lead_times = []
for task in tasks:
if task["status"] == "completed":
start = datetime.fromisoformat(task["createdAt"])
end = datetime.fromisoformat(task["completedAt"])
lead_times.append((end - start).total_seconds() / 60)
return {
"median_minutes": sorted(lead_times)[len(lead_times) // 2],
"p95_minutes": sorted(lead_times)[int(len(lead_times) * 0.95)],
"total_services_created": len(lead_times),
}
def golden_path_adoption_rate(self) -> dict:
"""ゴールデンパス採用率(目標:80%以上)"""
# GitHub APIからreusable workflowの使用状況を照会
repos = requests.get(
"https://api.github.com/orgs/my-org/repos",
headers={"Authorization": f"token {self.github}"},
params={"per_page": 100, "type": "internal"}
).json()
using_golden_path = 0
total_active = 0
for repo in repos:
if repo["archived"]:
continue
total_active += 1
# CIワークフローでゴールデンパスの参照を確認
workflows = requests.get(
f"https://api.github.com/repos/my-org/{repo['name']}/actions/workflows",
headers={"Authorization": f"token {self.github}"}
).json()
for wf in workflows.get("workflows", []):
if "golden" in wf.get("path", "").lower():
using_golden_path += 1
break
return {
"adoption_rate": using_golden_path / max(total_active, 1),
"using_golden_path": using_golden_path,
"total_active_repos": total_active,
}
def developer_nps(self) -> dict:
"""開発者満足度NPS(目標:30以上)"""
# 四半期サーベイ結果(Google Forms / Typeformなど)
# API連携するか、手動入力
return {
"nps_score": 42,
"promoters_pct": 55,
"detractors_pct": 13,
"response_rate": 0.72,
"top_complaints": [
"ビルド時間が遅い",
"ログ検索UIが不便",
"権限申請の自動化が不十分",
]
}
KPI目標値
| 指標 | 悪い | 普通 | 良い | 目標 |
|---|---|---|---|---|
| サービス作成時間 | 1週間以上 | 1〜3日 | 1時間 | 30分 |
| ゴールデンパス採用率 | 30%未満 | 30〜60% | 60〜80% | 80%以上 |
| 開発者NPS | 0未満 | 0〜20 | 20〜40 | 40以上 |
| オンボーディング時間 | 2週間以上 | 1〜2週間 | 2〜5日 | 1日 |
| インフラチケット数/月 | 50以上 | 20〜50 | 5〜20 | 5未満 |
Play 9:トラブルシューティング
問題1:Backstageカタログ同期の遅延
WARN: Entity refresh for component:order-service took 45s (threshold: 10s)
原因:GitHub discoveryが数百のリポジトリをスキャンし、APIレートリミットに達している。
# 解決策:スキャン範囲の制限 + キャッシュ設定
catalog:
providers:
github:
myOrg:
organization: 'my-org'
catalogPath: '/catalog-info.yaml'
filters:
repository: '^(?!archived-).*$' # archived-プレフィックスのリポジトリを除外
topic:
include: ['backstage-enabled'] # トピックベースのフィルタリング
schedule:
frequency: { minutes: 30 } # 30分間隔(デフォルトは5分)
timeout: { minutes: 5 }
問題2:Software Template実行失敗 — GitHub権限
Error: Resource not accessible by integration
HttpError: 403 - Resource not accessible by integration
原因:GitHub Appの権限が不足しているか、作成しようとしているリポジトリのorganizationへのアクセス権限がない。
# GitHub App権限の確認
# Settings > Developer settings > GitHub Apps > [アプリ名] > Permissions
# 必要な権限:
# - Repository: Administration (Read & Write)
# - Repository: Contents (Read & Write)
# - Organization: Members (Read)
# またはPersonal Access Token(PAT)使用時に必要なscope:
# repo, workflow, admin:org
問題3:TechDocsビルド失敗
mkdocs build failed: No module named 'techdocs_core'
# 解決策:TechDocsビルド環境にプラグインをインストール
pip install mkdocs-techdocs-core
# Dockerビルドを使用する場合
docker run --rm -v $(pwd):/content \
spotify/techdocs:latest \
build --site-dir /content/site
# app-config.yamlでビルド方式を設定
techdocs:
builder: 'external' # CIでビルド
publisher:
type: 'awsS3'
awsS3:
bucketName: 'my-org-techdocs'
region: 'ap-northeast-2'
問題4:プラットフォーム採用率が上がらない
これは技術的な問題ではなく、組織の問題である。
解決戦略:
- チャンピオンチームをまず確保する:アーリーアダプター2〜3チームを選定し、そのチームの成功事例を社内に共有する。
- 摩擦を取り除く:ゴールデンパスに従わない場合の苦痛(手動デプロイに2週間、手動モニタリング設定)を維持しつつ、ゴールデンパスの利便性を最大化する。
- 強制しない:毎月「Platform Day」を開催し、デモとフィードバックセッションを実施する。
- 指標で証明する:「ゴールデンパスを使用しているチームのデプロイ頻度は3倍高い」といったデータを共有する。
Play 10:IDPロードマップの段階的実装
すべてを一度に構築しようとすると失敗する。3段階に分けて段階的に構築する。
Phase 1(1〜3ヶ月):基礎
- ソフトウェアカタログの構築(すべてのサービス、チーム、APIを登録)
- CIゴールデンパスの標準化(GitHub Actions reusable workflow)
- サービス作成テンプレート1〜2個
Phase 2(4〜6ヶ月):拡張
- CDゴールデンパス(Argo Rolloutsカナリアデプロイメント)
- TechDocs統合(ランブック、ADR)
- セルフサービスインフラプロビジョニング(DB、キャッシュ)
- コストタギングとダッシュボード
Phase 3(7〜12ヶ月):成熟
- セキュリティポリシーの自動適用(OPA/Kyverno)
- DORAメトリクスの自動収集とダッシュボード
- 内部マーケットプレイス(共有ライブラリ、プラグイン)
- 開発環境のワンクリックプロビジョニング
クイズ
Q1. IDP導入が時期尚早な組織の特徴は?
正解:||サービス数が20未満で、インフラチームの反復チケット処理比率が低く、新規サービス作成が1週間以内に可能な組織である。このような場合、IDPよりもシンプルなスクリプト自動化や標準CI/CDテンプレートで十分である。||
Q2. プラットフォームチームを構成する際にPlatform PMが必要な理由は?
正解:||IDPは内部製品であるため、顧客(開発者)の要件収集、優先順位の決定、採用率の測定が必要である。エンジニアのみで構成すると技術中心に偏り、開発者が実際に必要とする機能ではなく、技術的に興味深い機能を作ることになる。||
Q3. BackstageのSoftware Catalogにおいて、catalog-info.yamlのdependsOnフィールドが重要な理由は?
正解:||サービス間の依存関係を明示することで、障害の影響範囲を即座に把握できる。order-serviceがpayment-serviceにdependsOnしている場合、payment-serviceの障害時にorder-serviceも影響を受けることがカタログから直接確認できる。||
Q4. Software TemplateにArgoCDアプリ登録ステップを含める理由は?
正解:||サービス作成と同時にGitOpsベースのデプロイパイプラインが自動構成されるため、開発者がコードをpushすると即座にデプロイが開始される。このステップがなければ、開発者は別途ArgoCDの設定を要求するチケットを作成しなければならず、これがオンボーディング時間を延ばす主要な原因となる。||
Q5. プラットフォーム採用率が50%以下の場合、強制ではなく魅力で高める方法は?
正解:||ゴールデンパスに従わない場合の不便さ(手動デプロイに2週間、手動モニタリング設定)を維持しつつ、ゴールデンパスの利便性(30分以内のサービス作成、自動デプロイ、自動モニタリング)を最大化する。チャンピオンチームの成功事例を共有し、指標で効果を証明する。||
Q6. IDP構築を3段階に分けるべき理由は?
正解:||すべての機能を一度に構築すると12ヶ月以上かかり、ROIを証明する前にプロジェクトが中止される可能性がある。Phase 1(3ヶ月)でカタログとCI標準化により素早く価値を示し、その成果を基にPhase 2、3への投資を確保する。||
Q7. 開発者NPSを測定する際の注意点は?
正解:||回答率が70%以上でなければ意味のある指標にならない。また、NPSスコアだけを見るのではなく、detractor(非推奨者)の具体的な不満事項を分析する必要がある。top_complaintsを四半期ごとに追跡して改善状況を確認し、改善された項目を公開的に知らせることでフィードバックループを閉じるべきである。||