Skip to content
Published on

DevOps 内部開発者プラットフォーム プレイブック 2026

Authors
  • Name
    Twitter
DevOps 内部開発者プラットフォーム プレイブック 2026

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 PM1名開発者の要件収集、ロードマップ管理、採用率追跡
Platform Engineer2〜3名インフラ抽象化、API/UI開発、ゴールデンパス設計
SRE / DevOps1〜2名モニタリングパイプライン、オンコール、インシデント対応自動化
Developer Advocate0.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
   ```
  1. コネクションプールが飽和している場合:
    kubectl scale deploy/order-service --replicas=6
    
  2. DBスロークエリを確認:
    SELECT pid, now() - query_start AS duration, query
    FROM pg_stat_activity
    WHERE state = 'active' AND now() - query_start > interval '5s';
    

注文作成失敗(HTTP 500)

  1. エラーログを確認:
    kubectl logs deploy/order-service --tail=100 | grep ERROR
    
  2. エラーコード別の対応:
    • 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%以上
開発者NPS0未満0〜2020〜4040以上
オンボーディング時間2週間以上1〜2週間2〜5日1日
インフラチケット数/月50以上20〜505〜205未満

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:プラットフォーム採用率が上がらない

これは技術的な問題ではなく、組織の問題である。

解決戦略:

  1. チャンピオンチームをまず確保する:アーリーアダプター2〜3チームを選定し、そのチームの成功事例を社内に共有する。
  2. 摩擦を取り除く:ゴールデンパスに従わない場合の苦痛(手動デプロイに2週間、手動モニタリング設定)を維持しつつ、ゴールデンパスの利便性を最大化する。
  3. 強制しない:毎月「Platform Day」を開催し、デモとフィードバックセッションを実施する。
  4. 指標で証明する:「ゴールデンパスを使用しているチームのデプロイ頻度は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を四半期ごとに追跡して改善状況を確認し、改善された項目を公開的に知らせることでフィードバックループを閉じるべきである。||

References