内部開発者プラットフォームIDP:Backstageでエンタープライズ開発者ポータルを構築 2026

DevOps运维

内部開発者プラットフォームIDP:Backstageでエンタープライズ開発者ポータルを構築

200のマイクロサービスを持つ会社に入社したばかり。初日、リーダーが「order-serviceをデプロイして」と言う。そしてGitリポジトリを探すのに3時間、CI/CD設定を見つけるのに2時間、APIドキュメントを探すのに1時間——コードを1行も書く前に1日が終わった。

これが内部開発者プラットフォーム(IDP)がない日常です。Spotifyがオープンソースした開発者ポータルフレームワーク「Backstage」は、プラットフォームエンジニアリングのデファクトスタンダードになりつつあります。本記事では5つのコアパターンから、エンタープライズグレードのIDPをゼロから構築します。

コア概念早見表

概念 説明 例え
IDP(Internal Developer Platform) 内部開発者プラットフォーム、開発者セルフサービスの統一入口 企業内の「App Store」
Backstage SpotifyオープンソースのIDPフレームワーク IDP界のKubernetes
Software Catalog ソフトウェアカタログ、全サービスのメタデータレジストリ サービスの「電話帳」
TechDocs Markdownベースの技術ドキュメントシステム エンジニアリング版Wiki
Software Template ソフトウェアテンプレート、ワンクリックで標準プロジェクト作成 スキャフォールドCLIのWeb版
Plugin Backstageプラグイン、機能拡張モジュール ブラウザ拡張機能

内部開発者プラットフォームの5つのペインポイント

  1. サービス発見の困難さ:数百のマイクロサービス、誰が担当か、どうデプロイするか、どこで監視するか分からない
  2. ドキュメントの散在:Confluence、GitLab Wiki、Notion……見つからない、理解できない
  3. 新プロジェクト立ち上げの遅さ:毎回ゼロからスキャフォールド構築、CI/CD、監視、ログ設定の繰り返し
  4. ツールチェーンの分断:Jenkins、ArgoCD、Grafana、Jira……頻繁なコンテキストスイッチで生産性低下
  5. コンプライアンス監査の困難:誰が何を変更したか、誰が権限を持っているか、サービス依存関係がカオス

パターン1:Backstageアーキテクチャとインストール

Backstageはプラグインベースのモノリスアーキテクチャを採用。コアはCatalog + TechDocs + Scaffold + Searchの4モジュール、プラグインでエコシステムを拡張。

# Backstageインストールと初期化
# 実行環境: Node.js 20.x+ / yarn 1.22+ / Git 2.40+
# 依存バージョン: @backstage/cli 1.32+ / @backstage/core 1.30+

# 1. Backstageアプリ作成
npx @backstage/create-app@latest --name my-idp
cd my-idp

# 2. プロジェクト構造
# my-idp/
# ├── app/                    # フロントエンドReactアプリ
# │   ├── src/
# │   │   ├── components/     # カスタムコンポーネント
# │   │   ├── plugins/        # フロントエンドプラグイン
# │   │   └── App.tsx         # アプリエントリポイント
# ├── packages/
# │   ├── backend/            # バックエンドNode.jsサービス
# │   └── app/                # フロントエンドビルドパッケージ
# ├── plugins/                # カスタムプラグインディレクトリ
# ├── catalog-info.yaml       # Catalogエンティティ定義
# └── app-config.yaml         # アプリ設定

# 3. 開発サーバー起動
yarn dev

# 4. プロダクションビルド
yarn build:backend
# app-config.yaml: Backstageコア設定
# 実行環境: Backstage 1.32+
app:
  title: エンタープライズ開発者ポータル
  baseUrl: http://localhost:3000

organization:
  name: MyCompany

backend:
  baseUrl: http://localhost:7007
  listen:
    port: 7007
  csp:
    connect-src: ["'self'", 'http:', 'https:']
  cors:
    origin: http://localhost:3000
    methods: [GET, HEAD, PATCH, POST, PUT, DELETE]
    credentials: true
  database:
    client: pg
    connection:
      host: ${POSTGRES_HOST}
      port: ${POSTGRES_PORT:-5432}
      user: ${POSTGRES_USER}
      password: ${POSTGRES_PASSWORD}
      database: backstage_plugin

integrations:
  github:
    - host: github.com
      token: ${GITHUB_TOKEN}
  gitlab:
    - host: gitlab.mycompany.com
      token: ${GITLAB_TOKEN}

auth:
  environment: development
  providers:
    github:
      development:
        clientId: ${GITHUB_CLIENT_ID}
        clientSecret: ${GITHUB_CLIENT_SECRET}
    oidc:
      production:
        metadataUrl: ${OIDC_METADATA_URL}
        clientId: ${OIDC_CLIENT_ID}
        clientSecret: ${OIDC_CLIENT_SECRET}

catalog:
  rules:
    - allow: [Component, System, API, Resource, Location, Template]
  locations:
    - type: url
      target: https://github.com/mycompany/catalog/blob/main/catalog-info.yaml
    - type: url
      target: https://gitlab.mycompany.com/platform/catalog/-/blob/main/all.yaml

techdocs:
  builder: 'local'
  generator:
    runIn: 'docker'
  publisher:
    type: 'local'

パターン2:ソフトウェアカタログCatalog

CatalogはBackstageの心臓——全サービスのメタデータレジストリ。YAML記述ファイルでエンティティを定義、サービスを自動発見・インデックス。

# catalog-info.yaml: サービスコンポーネント登録
# 実行環境: Backstage 1.32+ / Catalog API v1
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: order-service
  description: 注文コアサービス、注文作成・支払い・履行の全プロセスを処理
  annotations:
    backstage.io/source-location: url:https://github.com/mycompany/order-service
    backstage.io/techdocs-ref: dir:.
    github.com/project-slug: mycompany/order-service
    gitlab.com/project-id: "12345"
    jira.com/project-key: ORD
    grafana/dashboard-tag: order-service
    prometheus.io/alert: order-service-alerts
  tags:
    - java
    - spring-boot
    - microservice
    - tier-1
  links:
    - url: https://grafana.mycompany.com/d/order-service
      title: Grafanaモニタリング
      icon: dashboard
    - url: https://argocd.mycompany.com/applications/order-service
      title: ArgoCDデプロイ
      icon: cloud
    - url: https://order-service.mycompany.com/swagger
      title: APIドキュメント
      icon: api
spec:
  type: service
  lifecycle: production
  owner: team-order
  system: order-system
  dependsOn:
    - component:payment-service
    - component:inventory-service
    - resource:order-db
  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-order
  system: order-system
  definition:
    $text: https://raw.githubusercontent.com/mycompany/order-service/main/openapi.yaml
---
apiVersion: backstage.io/v1alpha1
kind: System
metadata:
  name: order-system
  description: 注文システム、注文・支払い・在庫などのコアサービスを含む
spec:
  owner: team-order
  domain: commerce
---
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
  name: team-order
  description: 注文チーム
spec:
  type: team
  parent: dept-commerce
  children: []
  members:
    - tanaka.taro
    - suzuki.hanako
    - sato.jiro
// TypeScript: カスタムCatalogエンティティプロセッサー
// 実行環境: Node.js 20.x+ / @backstage/plugin-catalog-node 1.12+
import {
  CatalogProcessor,
  CatalogProcessorEmit,
  processingResult,
} from '@backstage/plugin-catalog-node';
import { LocationSpec, Location } from '@backstage/plugin-catalog-common';
import { Entity } from '@backstage/catalog-model';

export class CustomServiceProcessor implements CatalogProcessor {
  getProcessorName(): string {
    return 'CustomServiceProcessor';
  }

  async validateLocationKind(location: Location): Promise<boolean> {
    return location.type === 'url';
  }

  async preProcessEntity(
    entity: Entity,
    location: LocationSpec,
    emit: CatalogProcessorEmit,
  ): Promise<Entity> {
    if (
      entity.kind === 'Component' &&
      entity.spec?.lifecycle === 'production'
    ) {
      const annotations = entity.metadata.annotations ?? {};
      annotations['compliance.mycompany.com/scan-required'] = 'true';
      annotations['compliance.mycompany.com/last-scan'] = new Date().toISOString();
      entity.metadata.annotations = annotations;
    }

    if (entity.kind === 'Component' && entity.spec?.dependsOn) {
      const deps = entity.spec.dependsOn as string[];
      for (const dep of deps) {
        emit(
          processingResult.relation({
            source: {
              name: entity.metadata.name,
              namespace: entity.metadata.namespace ?? 'default',
              kind: entity.kind,
            },
            target: {
              name: dep.replace('component:', ''),
              namespace: 'default',
              kind: 'Component',
            },
            type: 'dependsOn',
          }),
        );
      }
    }

    return entity;
  }
}

パターン3:技術ドキュメントTechDocs

TechDocsはMkDocs + Markdownベース、Docs-as-Codeの哲学——ドキュメントはコードと同じリポジトリライフサイクルで管理。

# mkdocs.yaml: TechDocs設定
# 実行環境: MkDocs 1.6+ / mkdocs-material 9.5+
site_name: Order Service 技術ドキュメント
site_description: 注文サービス完全技術ドキュメント

nav:
  - ホーム: index.md
  - クイックスタート: getting-started.md
  - アーキテクチャ設計:
    - システム概要: architecture/overview.md
    - データモデル: architecture/data-model.md
    - API設計: architecture/api-design.md
  - 運用マニュアル:
    - デプロイガイド: ops/deployment.md
    - 監視・アラート: ops/monitoring.md
    - トラブルシューティング: ops/troubleshooting.md
  - 開発規約:
    - コーディング規約: dev/coding-standards.md
    - Gitワークフロー: dev/git-workflow.md
    - テスト戦略: dev/testing.md

plugins:
  - techdocs-core:
      add_mermaid: true
      add_plantuml: true

markdown_extensions:
  - admonition
  - codehilite
  - footnotes
  - meta
  - toc:
      permalink: true
  - pymdownx.superfences:
      custom_fences:
        - name: mermaid
          class: mermaid
          format: !!python/name:pymdownx.superfences.fence_code_format
<!-- docs/index.md: TechDocsホーム -->
# Order Service

## 概要

Order Serviceは注文システムのコアマイクロサービスで、注文作成・支払い・履行の全プロセスを担当します。

## クイックリンク

| リソース | リンク |
|----------|--------|
| ソースコード | [GitHub](https://github.com/mycompany/order-service) |
| CI/CD | [Jenkins](https://jenkins.mycompany.com/job/order-service) |
| モニタリング | [Grafana](https://grafana.mycompany.com/d/order-service) |
| APIドキュメント | [Swagger](https://order-service.mycompany.com/swagger) |

## アーキテクチャ図

```mermaid
graph TB
    A[API Gateway] --> B[Order Service]
    B --> C[Payment Service]
    B --> D[Inventory Service]
    B --> E[(Order DB)]
    B --> F[Redis Cache]
    B --> G[Kafka]

主要メトリクス

メトリクス 目標 現在
P99レイテンシ <200ms 150ms
可用性 99.95% 99.97%
エラー率 <0.1% 0.05%

## パターン4:プラグイン開発

Backstageのプラグインシステムは中核的な競争力です。カスタムプラグインでIDPを企業ツールチェーンに真に適合させます。

```typescript
// TypeScript: Backstageカスタムプラグイン - サービスヘルスチェックパネル
// 実行環境: Node.js 20.x+ / @backstage/core-plugin-api 1.9+ / React 18.x

// --- フロントエンドプラグイン ---

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

export const serviceHealthPlugin = createPlugin({
  id: 'service-health',
  routes: {
    root: rootRouteRef,
  },
});

export const ServiceHealthPage = serviceHealthPlugin.provide(
  createRoutableExtension({
    name: 'ServiceHealthPage',
    component: () =>
      import('./components/ServiceHealthPanel').then(
        (m) => m.ServiceHealthPanel,
      ),
    mountPoint: rootRouteRef,
  }),
);

// plugins/service-health/src/components/ServiceHealthPanel.tsx
import React from 'react';
import { useEntity } from '@backstage/plugin-catalog-react';
import {
  Header,
  Page,
  Content,
  InfoCard,
  Progress,
  StatusOK,
  StatusError,
  StatusWarning,
} from '@backstage/core-components';
import { useApi } from '@backstage/core-plugin-api';
import { serviceHealthApiRef } from '../api';

interface HealthCheckResult {
  name: string;
  status: 'healthy' | 'degraded' | 'down';
  latency_ms: number;
  last_check: string;
  message: string;
}

export const ServiceHealthPanel: React.FC = () => {
  const { entity } = useEntity();
  const healthApi = useApi(serviceHealthApiRef);
  const [healthData, setHealthData] = React.useState<HealthCheckResult[]>([]);
  const [loading, setLoading] = React.useState(true);

  React.useEffect(() => {
    const serviceName = entity.metadata.name;
    healthApi
      .getServiceHealth(serviceName)
      .then((data) => {
        setHealthData(data.checks);
        setLoading(false);
      })
      .catch(() => setLoading(false));
  }, [entity, healthApi]);

  if (loading) return <Progress />;

  const StatusIcon = ({ status }: { status: string }) => {
    switch (status) {
      case 'healthy':
        return <StatusOK />;
      case 'degraded':
        return <StatusWarning />;
      case 'down':
        return <StatusError />;
      default:
        return null;
    }
  };

  return (
    <Page themeId="tool">
      <Header title={`${entity.metadata.name} ヘルスチェック`} />
      <Content>
        {healthData.map((check) => (
          <InfoCard key={check.name} title={check.name}>
            <StatusIcon status={check.status} />
            <p>ステータス: {check.status}</p>
            <p>レイテンシ: {check.latency_ms}ms</p>
            <p>最終チェック: {check.last_check}</p>
            <p>メッセージ: {check.message}</p>
          </InfoCard>
        ))}
      </Content>
    </Page>
  );
};
// TypeScript: Backstageバックエンドプラグイン
// 実行環境: Node.js 20.x+ / @backstage/backend-common 0.25+

// plugins/service-health-backend/src/plugin.ts
import {
  createBackendPlugin,
  coreServices,
} from '@backstage/backend-plugin-api';
import { createRouter } from './router';

export const serviceHealthBackendPlugin = createBackendPlugin({
  pluginId: 'service-health',
  register(env) {
    env.registerInit({
      deps: {
        logger: coreServices.logger,
        config: coreServices.rootConfig,
        httpRouter: coreServices.httpRouter,
        discovery: coreServices.discovery,
      },
      async init({ logger, config, httpRouter, discovery }) {
        const router = await createRouter({
          logger,
          config,
          discovery,
        });

        httpRouter.use(router);
        httpRouter.addAuthPolicy({
          path: '/health',
          allow: 'unauthenticated',
        });

        logger.info('Service Health plugin initialized');
      },
    });
  },
});

// plugins/service-health-backend/src/router.ts
import express, { Router } from 'express';
import { LoggerService, RootConfigService, DiscoveryService } from '@backstage/backend-plugin-api';

interface RouterOptions {
  logger: LoggerService;
  config: RootConfigService;
  discovery: DiscoveryService;
}

export async function createRouter(
  options: RouterOptions,
): Promise<Router> {
  const { logger, config } = options;
  const router = Router();

  router.get('/health', (_req, res) => {
    res.json({ status: 'ok' });
  });

  router.get('/services/:serviceName/health', async (req, res) => {
    const { serviceName } = req.params;

    try {
      const healthChecks = await fetchServiceHealth(config, serviceName);
      res.json({
        service: serviceName,
        checks: healthChecks,
        timestamp: new Date().toISOString(),
      });
    } catch (error) {
      logger.error(`Failed to fetch health for ${serviceName}: ${error}`);
      res.status(500).json({ error: 'Health check failed' });
    }
  });

  return router;
}

async function fetchServiceHealth(
  config: RootConfigService,
  serviceName: string,
): Promise<Array<{
  name: string;
  status: 'healthy' | 'degraded' | 'down';
  latency_ms: number;
  last_check: string;
  message: string;
}>> {
  const prometheusUrl = config.getString('monitoring.prometheus.url');

  return [
    {
      name: 'HTTPエンドポイント',
      status: 'healthy',
      latency_ms: 45,
      last_check: new Date().toISOString(),
      message: '全エンドポイント正常応答',
    },
    {
      name: 'データベース接続',
      status: 'healthy',
      latency_ms: 12,
      last_check: new Date().toISOString(),
      message: 'コネクションプール正常',
    },
    {
      name: 'Kafkaコンシューマー',
      status: 'degraded',
      latency_ms: 250,
      last_check: new Date().toISOString(),
      message: 'コンシューマーラグが閾値超過',
    },
  ];
}

パターン5:本番レベルIDPデプロイ

# docker-compose.yml: Backstage本番デプロイ
# 実行環境: Docker Compose v2.30+ / PostgreSQL 16
version: "3.8"

services:
  backstage:
    image: mycompany/backstage:1.32.0
    ports:
      - "7007:7007"
    environment:
      APP_CONFIG_app_baseUrl: https://idp.mycompany.com
      APP_CONFIG_backend_baseUrl: https://idp.mycompany.com
      APP_CONFIG_backend_database_client: pg
      APP_CONFIG_backend_database_connection_host: postgres
      APP_CONFIG_backend_database_connection_port: "5432"
      APP_CONFIG_backend_database_connection_user: ${POSTGRES_USER}
      APP_CONFIG_backend_database_connection_password: ${POSTGRES_PASSWORD}
      POSTGRES_HOST: postgres
      POSTGRES_PORT: "5432"
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      GITHUB_TOKEN: ${GITHUB_TOKEN}
      GITLAB_TOKEN: ${GITLAB_TOKEN}
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:7007/healthcheck"]
      interval: 30s
      timeout: 10s
      retries: 3

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: backstage_plugin
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

volumes:
  postgres_data:
# kubernetes/backstage-deployment.yaml: K8s本番デプロイ
# 実行環境: Kubernetes 1.30+ / Helm 3.15+
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backstage
  namespace: idp
  labels:
    app: backstage
spec:
  replicas: 3
  selector:
    matchLabels:
      app: backstage
  template:
    metadata:
      labels:
        app: backstage
    spec:
      containers:
        - name: backstage
          image: mycompany/backstage:1.32.0
          ports:
            - containerPort: 7007
          env:
            - name: APP_CONFIG_app_baseUrl
              value: "https://idp.mycompany.com"
            - name: APP_CONFIG_backend_baseUrl
              value: "https://idp.mycompany.com"
            - name: APP_CONFIG_backend_database_client
              value: "pg"
            - name: APP_CONFIG_backend_database_connection_host
              valueFrom:
                secretKeyRef:
                  name: backstage-db-secret
                  key: host
            - name: APP_CONFIG_backend_database_connection_password
              valueFrom:
                secretKeyRef:
                  name: backstage-db-secret
                  key: password
          resources:
            requests:
              cpu: "500m"
              memory: "512Mi"
            limits:
              cpu: "2000m"
              memory: "2Gi"
          livenessProbe:
            httpGet:
              path: /healthcheck
              port: 7007
            initialDelaySeconds: 30
            periodSeconds: 30
          readinessProbe:
            httpGet:
              path: /healthcheck
              port: 7007
            initialDelaySeconds: 10
            periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
  name: backstage
  namespace: idp
spec:
  selector:
    app: backstage
  ports:
    - port: 80
      targetPort: 7007
  type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: backstage
  namespace: idp
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  tls:
    - hosts:
        - idp.mycompany.com
      secretName: backstage-tls
  rules:
    - host: idp.mycompany.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: backstage
                port:
                  number: 80
# Helmデプロイ(推奨)
# 実行環境: Helm 3.15+ / Kubernetes 1.30+

# 1. Backstage Helmリポジトリ追加
helm repo add backstage https://backstage.github.io/charts
helm repo update

# 2. values.yamlオーバーライド設定
cat > backstage-values.yaml << 'EOF'
backstage:
  image:
    repository: mycompany/backstage
    tag: 1.32.0
  extraAppConfig:
    - configMapRef: backstage-config
  extraEnvVarsSecrets:
    - backstage-secrets

ingress:
  enabled: true
  host: idp.mycompany.com
  tls:
    enabled: true
    secretName: backstage-tls

postgresql:
  enabled: true
  primary:
    persistence:
      size: 20Gi
  auth:
    existingSecret: backstage-db-secret
EOF

# 3. デプロイ
helm install backstage backstage/backstage \
  -n idp \
  --create-namespace \
  -f backstage-values.yaml

# 4. 検証
kubectl get pods -n idp
kubectl port-forward svc/backstage 7007:80 -n idp

よくある落とし穴:5つの罠

罠1:Catalogエンティティ登録後に表示されない

❌ 間違い:ローカルでcatalog-info.yamlを作成するだけでLocationを登録していない
✅ 正解:app-config.yamlにlocationsを追加、またはUIでLocationを登録

罠2:TechDocsビルド失敗

❌ 間違い:ホストマシンで直接MkDocsを実行、依存関係不足でビルド失敗
✅ 正解:techdocs.generator.runIn: 'docker'を設定、コンテナ化ビルドを使用

罠3:GitHub/GitLab統合Tokenの権限不足

# ❌ 間違い:Tokenにrepo:read権限しかない
# ✅ 正解:Tokenには以下の権限が必要
# GitHub: repo, read:org, read:user
# GitLab: api, read_repository, read_api

罠4:プラグイン開発時のホットリロードが効かない

# ❌ 間違い:プラグインコード変更後に開発サーバーを再起動していない
# ✅ 正解:yarn devが実行中であることを確認、BackstageはHMRホットリロードをサポート
# HMRが効かない場合、キャッシュをクリアして再起動:
yarn cache clean
yarn dev

罠5:本番環境のデータベース接続プール枯渇

# app-config.yaml: 接続プール設定
backend:
  database:
    client: pg
    connection:
      host: postgres
      port: 5432
      user: ${POSTGRES_USER}
      password: ${POSTGRES_PASSWORD}
      pool:
        min: 5
        max: 20
        idleTimeoutMillis: 30000
        connectionTimeoutMillis: 5000

エラートラブルシューティング表

エラーメッセージ 原因 解決策
Failed to fetch catalog entities GitHub/GitLab Tokenが無効または権限不足 Token権限を確認、repoとread:orgを確保
ECONNREFUSED 127.0.0.1:5432 PostgreSQLが未起動または接続設定エラー データベースサービスと接続パラメータを確認
TechDocs generation failed MkDocsビルド失敗、依存関係不足 runIn: 'docker'を設定、または不足Pythonパッケージをインストール
Plugin not found: xxx プラグインがバックエンドに登録されていない packages/backend/src/index.tsでプラグインをimportして登録
Unauthorized: Invalid token OIDC/OAuth設定エラー clientId/clientSecretとコールバックURLを確認
CORS error フロントエンド/バックエンドのドメイン不一致 backend.cors.originをフロントエンドURLに合わせて設定
Location not found catalog-info.yamlのURLにアクセスできない URLの到達性とToken権限を確認
Out of memory Node.jsヒープメモリ不足 NODE_OPTIONS=--max-old-space-size=4096を設定
Schema validation failed catalog-info.yamlのフォーマットエラー backstage-cli catalog-validateで検証
Template execution timeout Software Templateの実行タイムアウト テンプレート内のHTTPリクエストを確認、タイムアウト設定を増加

高度な最適化:5つの本番レベルテクニック

テクニック1:Catalog自動発見

// TypeScript: GitLab自動発見プロセッサー
// 実行環境: @backstage/plugin-catalog-backend-module-gitlab 0.8+
import { GitLabDiscoveryProcessor } from '@backstage/plugin-catalog-backend-module-gitlab';

const config = {
  catalog: {
    processors: [
      GitLabDiscoveryProcessor.fromConfig(env.config, {
        logger: env.logger,
        schedule: {
          frequency: { minutes: 30 },
          timeout: { minutes: 3 },
          initialDelay: { seconds: 15 },
        },
      }),
    ],
  },
};

テクニック2:RBAC権限制御

# app-config.yaml: RBAC設定
# 実行環境: @backstage/plugin-permission-backend 0.6+
permission:
  enabled: true
  rbac:
    policies:
      - name: 'role:default/developer'
        permissions:
          - 'catalog.entity.read'
          - 'techdocs.read'
          - 'scaffolder.template.execute'
      - name: 'role:default/admin'
        permissions:
          - 'catalog.entity.create'
          - 'catalog.entity.delete'
          - 'catalog.entity.update'
          - 'techdocs.write'
          - 'scaffolder.template.create'
    policyFile: ./rbac-policy.csv

テクニック3:検索強化

# app-config.yaml: 検索エンジン設定
# 実行環境: @backstage/plugin-search-backend-module-elasticsearch 1.6+
search:
  collators:
    techdocs:
      schedule:
        frequency: { minutes: 10 }
        timeout: { minutes: 5 }
        initialDelay: { seconds: 30 }
  elasticsearch:
    url: ${ELASTICSEARCH_URL}
    auth:
      username: ${ELASTICSEARCH_USER}
      password: ${ELASTICSEARCH_PASSWORD}
    indexPrefix: backstage_search_

テクニック4:マルチクラスタArgoCD統合

# app-config.yaml: ArgoCDマルチクラスタ統合
# 実行環境: @backstage/plugin-argocd 2.5+
argocd:
  appLocatorMethods:
    - type: 'config'
      instances:
        - name: production
          url: https://argocd.mycompany.com
          token: ${ARGOCD_TOKEN_PROD}
        - name: staging
          url: https://argocd-staging.mycompany.com
          token: ${ARGOCD_TOKEN_STAGING}

テクニック5:Golden Pathテンプレート

# templates/java-spring-boot/template.yaml: Golden Pathスキャフォールドテンプレート
# 実行環境: @backstage/plugin-scaffolder 1.25+
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: java-spring-boot-service
  title: Java Spring Bootマイクロサービステンプレート
  description: CI/CD、監視、ドキュメント付きの標準化Spring Bootマイクロサービスを作成
spec:
  owner: platform-team
  type: service
  parameters:
    - title: サービス基本情報
      required:
        - name
        - owner
      properties:
        name:
          title: サービス名
          type: string
          pattern: '^[a-z][a-z0-9-]*$'
          description: 小文字始まり、小文字・数字・ハイフンのみ
        owner:
          title: 担当チーム
          type: string
          ui:field: OwnerPicker
          ui:options:
            allowedKinds:
              - Group
        description:
          title: サービス説明
          type: string
          description: サービス機能の1行説明
        database:
          title: データベースタイプ
          type: string
          default: postgresql
          enum:
            - postgresql
            - mysql
            - mongodb
            - none
    - title: CI/CD設定
      properties:
        ciProvider:
          title: CIツール
          type: string
          default: github-actions
          enum:
            - github-actions
            - gitlab-ci
            - jenkins
        deployTarget:
          title: デプロイ先
          type: string
          default: kubernetes
          enum:
            - kubernetes
            - ecs
            - cloud-run

  steps:
    - id: fetch-template
      name: テンプレートコード取得
      action: fetch:template
      input:
        url: ./skeleton
        values:
          name: ${{ parameters.name }}
          owner: ${{ parameters.owner }}
          description: ${{ parameters.description }}
          database: ${{ parameters.database }}
          ciProvider: ${{ parameters.ciProvider }}
          deployTarget: ${{ parameters.deployTarget }}

    - id: publish
      name: Gitリポジトリに公開
      action: publish:github
      input:
        allowedHosts: ['github.com']
        description: ${{ parameters.description }}
        repoUrl: github.com?owner=mycompany&repo=${{ parameters.name }}
        defaultBranch: main
        protectDefaultBranch: true

    - id: register
      name: Catalogに登録
      action: catalog:register
      input:
        repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }}
        catalogInfoPath: '/catalog-info.yaml'

  output:
    links:
      - title: リポジトリURL
        url: ${{ steps.publish.output.remoteUrl }}
      - title: Catalogで開く
        url: https://idp.mycompany.com/catalog/default/component/${{ parameters.name }}

IDPプラットフォーム比較分析

次元 Backstage Port Humanitec
オープンソース ✅ Apache 2.0 ❌ 商用SaaS ❌ 商用SaaS
自己ホスト ✅ 完全制御 ❌ クラウドホスト ❌ クラウドホスト
プラグインエコシステム 200+コミュニティプラグイン 組み込み統合 組み込み統合
学習曲線 中高(TypeScript必要) 低(ビジュアル設定) 中(プラットフォームエンジニアリング概念)
カスタマイズ 非常に高い(コードレベル) 中(API拡張) 中(Score定義)
ソフトウェアカタログ ✅ コア機能 ✅ コア機能 ✅ コア機能
TechDocs ✅ ネイティブサポート ❌ 統合必要 ❌ 統合必要
コスト エンジニアリングリソース $50-200/ユーザー/月 $100-500/ユーザー/月
適用規模 50-5000+開発者 10-500開発者 100-5000+開発者
代表的な顧客 Spotify、American Airlines Lyft、Palo Alto BMW、Qonto

まとめ

Backstageは2026年に内部開発者プラットフォームを構築する最適な選択肢——オープンソース、自己ホスト可能、豊富なプラグインエコシステム。ただし注意点も:

  • 小規模チーム(<50人):PortなどのSaaSソリューションを優先検討、Backstageの保守コストが見合わない可能性
  • 中規模チーム(50-500人):Backstageが最適、2-3人のエンジニアで保守可能
  • 大規模チーム(500+人):Backstage + Golden Pathテンプレート + 自動発見で、完全なプラットフォームエンジニアリング体系を構築

成功の鍵:まずCatalogとTechDocsを導入、段階的にプラグインを追加、最後にGolden Pathテンプレートを構築。一気に完璧を目指さないこと。

オンラインツールおすすめ

  • /ja/json/format - JSONフォーマッター、CatalogエンティティYAML→JSON変換
  • /ja/dev/curl-to-code - cURL→コード変換、Backstage API呼び出しの迅速生成
  • /ja/encode/hash - ハッシュ計算、サービス一意識別子の生成
  • /ja/text/diff - テキスト差分、catalog-info.yaml変更の比較

ブラウザローカルツールを無料で試す →

#内部开发者平台#Backstage#IDP#开发者门户#平台工程#2026#DevOps运维