內部開發者平台IDP:Backstage搭建企業級開發者門戶 2026

DevOps运维

內部開發者平台IDP:Backstage搭建企業級開發者門戶

你剛入職一家有200個微服務的公司。第一天,領導說「去部署一下order-service」。然後你花了3小時找Git倉庫、2小時找CI/CD設定、1小時找API文件——還沒開始寫程式碼,一天就過去了。

這就是沒有內部開發者平台(IDP)的日常。Backstage,Spotify開源的開發者門戶框架,正在成為平台工程的事實標準。本文從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、飛書文件……找不到也看不懂
  3. 新專案啟動慢:每次從零搭腳手架,CI/CD、監控、日誌配置重複勞動
  4. 工具鏈割裂:Jenkins、ArgoCD、Grafana、Jira……來回切換效率低下
  5. 合規審計難:誰改了什麼、誰有權限、服務依賴關係一鍋粥

模式一:Backstage架構與安裝

Backstage採用外掛化單體架構(Plugin Monolith),核心是Catalog + TechDocs + Scaffold + Search四大模組,透過外掛擴展生態。

# 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'

模式二:軟體目錄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:
    - zhang.san
    - li.si
    - wang.wu
// 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;
  }
}

模式三:技術文件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%

## 模式四:外掛開發

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: '消費延遲超過閾值',
    },
  ];
}

模式五:生產級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部署Backstage(推薦方式)
# 執行環境: 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 外掛未註冊到backend 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: 建立標準化的Spring Boot微服務,包含CI/CD、監控、文件
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: 一句話描述服務功能
        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: ${{ 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模板。不要試圖一步到位。

線上工具推薦

本站提供瀏覽器本地工具,免註冊即可試用 →

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