Skip to content

Backstage:Internal Developer Platform 框架

支持版本: Backstage 1.35+, Kubernetes 1.31+, EKS 最后更新: June 22, 2026

目录


概述

什么是 Internal Developer Platform (IDP)?

Internal Developer Platform (IDP) 是位于开发者与底层基础设施之间的自助服务层,为部署应用、预置资源和管理服务提供标准化工作流。正如 Platform Engineering Overview 中所述,IDP 通过抽象运维复杂性,让开发者能够专注于编写代码。

IDP 通常提供:

  • Service Catalog:所有服务、API 和基础设施组件的集中式注册表
  • 自助服务工作流:用于创建新服务、数据库和环境的模板化流程
  • 文档中心:集中化、可发现的技术文档
  • 可见性:跨组织实时查看部署、成本、所有权和健康状况

为什么选择 Backstage?

Backstage 是一个用于构建 Internal Developer Platform 的开源框架,最初由 Spotify 创建,现在是 CNCF Incubating 项目。Spotify 构建 Backstage 是为了管理数百个工程团队中的 2,000 多个 microservices(微服务)。在 2020 年开源后,Backstage 已成为 Kubernetes 生态系统中采用最广泛的 IDP 框架。

选择 Backstage 的主要原因:

  1. 开源且可扩展:采用 MIT 许可证,并拥有由 200+ 社区 plugins 组成的活跃 plugin 生态系统
  2. CNCF 支持:Incubating 项目,具备强治理能力,确保长期可持续性
  3. Plugin 架构:一切皆 plugin,无需 fork 即可高度定制
  4. Software Catalog:集中式所有权注册表,可建模你的整个技术生态系统
  5. Software Templates:Golden Path 脚手架,从第一天起强制执行组织标准
  6. TechDocs:docs-like-code 方法,将文档与其描述的代码放在一起
  7. 活跃社区:超过 900 位贡献者,并被 American Airlines、Netflix、Zalando 和 HP 等公司采用

IDP 平台对比

CriteriaBackstagePortCortexHumanitecOpsLevel
许可证开源 (MIT)商业(免费层)商业商业商业
托管方式自托管SaaSSaaSSaaS + AgentSaaS
定制能力不受限制(plugin system)API + Blueprints有限中等中等
Kubernetes Native强(plugins)基于 API有限强(Score)基于 API
设置复杂度高(DIY)
社区生态系统200+ plugins不断增长的市场有限不断增长有限
成本仅基础设施每用户/月每用户/月每用户/月每用户/月

注意:与 SaaS 替代方案相比,Backstage 需要更多初始投入来完成设置,但它提供无可比拟的灵活性且没有许可成本。对于已经投入 Kubernetes 和 GitOps 的组织,Backstage 可以自然地集成到现有生态系统中。


学习目标

完成本文档后,你将能够:

  1. 解释 Backstage 作为 IDP 框架在 Kubernetes 平台工程技术栈中的作用
  2. 使用 Helm 在 Amazon EKS 上部署 Backstage,并配置 RDS PostgreSQL 和 ALB ingress
  3. 配置 Software Catalog,从 GitHub repositories 自动发现服务
  4. 创建 Software Templates (Golden Paths),用于生成包含 CI/CD 和基础设施的 microservices 脚手架
  5. Backstage 与 Kubernetes plugin 集成,以在 EKS 上实现实时 workload 可见性
  6. 设置 使用 S3 storage 的 TechDocs,用于集中式文档
  7. 安装并配置 ArgoCD、Kubecost 以及其他 Kubernetes 生态系统工具的 plugins
  8. 多团队环境实现 RBAC 和治理策略
  9. 高可用、备份和升级策略在生产环境中运行 Backstage

Backstage 架构

高层架构

核心概念

Backstage 围绕四个基础支柱构建:

  1. Software Catalog:组织中所有软件的集中式、自动更新的清单。它为每个组件跟踪所有权、依赖关系、API、文档链接和运维元数据。

  2. Software Templates:可复用的脚手架定义(Golden Paths),用于创建包含全部组织标准的新项目、服务或基础设施 -- CI/CD pipelines、监控、安全策略和文档结构。

  3. TechDocs:一种 docs-like-code 解决方案,可将 Markdown 文档(通过 MkDocs)直接渲染到 Backstage portal 中。文档与代码位于同一 repository,确保其保持最新。

  4. Search:统一搜索平台,会索引 catalog、TechDocs 和任何其他数据源,为发现工程组织中的任何内容提供单一搜索栏。

Plugin 架构

Backstage 遵循“一切皆 plugin”的理念。即使 Software Catalog 这样的核心功能也是以 plugins 形式实现的。该架构提供:

  • Frontend Plugins:用于渲染 UI 元素(页面、卡片、标签页)的 React components
  • Backend Plugins:提供 APIs、处理数据处理并与外部系统集成的 Node.js modules
  • Plugin 隔离:每个 plugin 都有自己的数据库 schema、API routes 和配置
  • 可组合性:Plugins 可以通过定义良好的 extension points 依赖并扩展其他 plugins
┌─────────────────────────────────────────────────────────────────┐
│                     Backstage App Shell                         │
├─────────────────────────────────────────────────────────────────┤
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────────┐   │
│  │ Catalog  │  │Templates │  │ TechDocs │  │   Search     │   │
│  │ Plugin   │  │ Plugin   │  │ Plugin   │  │   Plugin     │   │
│  └──────────┘  └──────────┘  └──────────┘  └──────────────┘   │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────────┐   │
│  │   K8s    │  │  ArgoCD  │  │ Kubecost │  │   Custom     │   │
│  │ Plugin   │  │  Plugin  │  │  Plugin  │  │   Plugins    │   │
│  └──────────┘  └──────────┘  └──────────┘  └──────────────┘   │
├─────────────────────────────────────────────────────────────────┤
│                    Plugin API / Extension Points                 │
├─────────────────────────────────────────────────────────────────┤
│                    Backend Services (Node.js)                    │
├─────────────────────────────────────────────────────────────────┤
│              PostgreSQL          │           S3 / Cache          │
└─────────────────────────────────────────────────────────────────┘

一个 plugin 通常包含:

ComponentLocationPurpose
Frontend Pluginplugins/<name>/React components、routes、API clients
Backend Pluginplugins/<name>-backend/Express routers、database access、external API calls
Commonplugins/<name>-common/Shared types、constants、API definitions
Nodeplugins/<name>-node/Shared backend utilities、extension points

EKS 部署

前提条件

在 EKS 上部署 Backstage 之前,请确保以下资源可用:

ResourcePurposeNotes
EKS ClusterBackstage runtimeKubernetes 1.31+
Amazon RDS (PostgreSQL)持久化存储PostgreSQL 15+, db.r6g.large 或更大
Amazon ECRContainer image registryBackstage images 的私有 repository
AWS ALBIngress controller已安装 AWS Load Balancer Controller
Amazon S3TechDocs storage用于生成文档的 Bucket
AWS Certificate ManagerTLS certificate用于 ALB 上的 HTTPS
Amazon Cognito or OktaOIDC authentication用户登录的 identity provider
GitHub App or TokenSource code integration用于 catalog discovery 和 template scaffolding

步骤 1:创建 Backstage Application

首先在本地生成一个新的 Backstage application 脚手架:

bash
# Ensure Node.js 20+ and Yarn are installed
node --version   # v20.x or higher
yarn --version   # 4.x (Berry)

# Create a new Backstage app
npx @backstage/create-app@latest --skip-install
# When prompted, enter the app name: my-backstage-app

cd my-backstage-app

# Install dependencies
yarn install

生成的项目结构:

my-backstage-app/
├── app-config.yaml              # Main configuration
├── app-config.production.yaml   # Production overrides
├── catalog-info.yaml            # Backstage's own catalog entry
├── package.json
├── packages/
│   ├── app/                     # Frontend (React)
│   │   ├── src/
│   │   └── package.json
│   └── backend/                 # Backend (Node.js)
│       ├── src/
│       └── package.json
├── plugins/                     # Custom plugins
└── yarn.lock

步骤 2:将 Application 容器化

为生产部署创建一个 multi-stage Dockerfile:

dockerfile
# Stage 1: Build the frontend and backend
FROM node:20-bookworm-slim AS build

WORKDIR /app

# Copy dependency files
COPY package.json yarn.lock .yarnrc.yml ./
COPY .yarn ./.yarn
COPY packages/app/package.json ./packages/app/
COPY packages/backend/package.json ./packages/backend/
COPY plugins/ ./plugins/

# Install all dependencies
RUN yarn install --immutable

# Copy the rest of the source
COPY . .

# Build the app
RUN yarn tsc
RUN yarn build:backend --config ../../app-config.yaml

# Stage 2: Production image
FROM node:20-bookworm-slim

# Install runtime dependencies for TechDocs
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
      python3 python3-pip python3-venv git curl && \
    python3 -m pip install --break-system-packages \
      mkdocs-techdocs-core==1.4.* && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

# Create a non-root user
RUN useradd -m -u 1000 backstage
USER backstage
WORKDIR /app

# Copy the built backend bundle
COPY --from=build --chown=backstage:backstage /app/packages/backend/dist ./packages/backend/dist
COPY --from=build --chown=backstage:backstage /app/node_modules ./node_modules
COPY --from=build --chown=backstage:backstage /app/package.json ./

# Copy configuration files
COPY --chown=backstage:backstage app-config.yaml app-config.production.yaml ./

# Environment variables
ENV NODE_ENV=production

# Health check
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
  CMD curl -f http://localhost:7007/healthcheck || exit 1

EXPOSE 7007

CMD ["node", "packages/backend/dist", "--config", "app-config.yaml", "--config", "app-config.production.yaml"]

构建 image 并推送到 ECR:

bash
# Authenticate to ECR
aws ecr get-login-password --region ap-northeast-2 | \
  docker login --username AWS --password-stdin \
  111122223333.dkr.ecr.ap-northeast-2.amazonaws.com

# Build and push
docker build -t backstage:latest .
docker tag backstage:latest \
  111122223333.dkr.ecr.ap-northeast-2.amazonaws.com/backstage:v1.35.0
docker push \
  111122223333.dkr.ecr.ap-northeast-2.amazonaws.com/backstage:v1.35.0

步骤 3:使用 Helm 部署

添加 Backstage Helm chart repository 并创建 values 文件:

bash
helm repo add backstage https://backstage.github.io/charts
helm repo update

创建一个完整的 values.yaml

yaml
# backstage-values.yaml
backstage:
  image:
    registry: 111122223333.dkr.ecr.ap-northeast-2.amazonaws.com
    repository: backstage
    tag: v1.35.0
    pullPolicy: IfNotPresent

  replicas: 2

  resources:
    requests:
      memory: 512Mi
      cpu: 250m
    limits:
      memory: 1Gi
      cpu: 1000m

  extraEnvVarsSecrets:
    - backstage-secrets

  appConfig:
    app:
      title: "My Company Developer Portal"
      baseUrl: https://backstage.example.com
    backend:
      baseUrl: https://backstage.example.com
      listen:
        port: 7007
      cors:
        origin: https://backstage.example.com
        methods: [GET, HEAD, PATCH, POST, PUT, DELETE]
        credentials: true
      database:
        client: pg
        connection:
          host: ${POSTGRES_HOST}
          port: ${POSTGRES_PORT}
          user: ${POSTGRES_USER}
          password: ${POSTGRES_PASSWORD}
          database: backstage
          ssl:
            require: true
            rejectUnauthorized: true

  podAnnotations:
    prometheus.io/scrape: "true"
    prometheus.io/port: "7007"
    prometheus.io/path: "/metrics"

  serviceAccount:
    create: true
    name: backstage
    annotations:
      eks.amazonaws.com/role-arn: arn:aws:iam::111122223333:role/backstage-irsa-role

ingress:
  enabled: true
  className: alb
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]'
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:ap-northeast-2:111122223333:certificate/abcd-1234-efgh
    alb.ingress.kubernetes.io/ssl-policy: ELBSecurityPolicy-TLS13-1-2-2021-06
    alb.ingress.kubernetes.io/healthcheck-path: /healthcheck
    alb.ingress.kubernetes.io/group.name: backstage
  hosts:
    - host: backstage.example.com
      paths:
        - path: /
          pathType: Prefix

postgresql:
  enabled: false  # Using external RDS

serviceAccount:
  create: true
  name: backstage

部署到 EKS:

bash
# Create the namespace
kubectl create namespace backstage

# Create the secrets (referencing values from AWS Secrets Manager or SSM)
kubectl create secret generic backstage-secrets \
  --namespace backstage \
  --from-literal=POSTGRES_HOST=backstage-db.cluster-xxxxxxx.ap-northeast-2.rds.amazonaws.com \
  --from-literal=POSTGRES_PORT=5432 \
  --from-literal=POSTGRES_USER=backstage \
  --from-literal=POSTGRES_PASSWORD='<secure-password>' \
  --from-literal=GITHUB_TOKEN='ghp_xxxxxxxxxxxxxxxxxxxx' \
  --from-literal=AUTH_OIDC_CLIENT_ID='<client-id>' \
  --from-literal=AUTH_OIDC_CLIENT_SECRET='<client-secret>'

# Install the chart
helm install backstage backstage/backstage \
  --namespace backstage \
  --values backstage-values.yaml \
  --wait --timeout 10m

步骤 4:ALB Ingress 配置

如果你希望独立于 Helm chart 管理 Ingress resource,可以显式创建它:

yaml
# backstage-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: backstage
  namespace: backstage
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]'
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:ap-northeast-2:111122223333:certificate/abcd-1234-efgh
    alb.ingress.kubernetes.io/ssl-policy: ELBSecurityPolicy-TLS13-1-2-2021-06
    alb.ingress.kubernetes.io/healthcheck-path: /healthcheck
    alb.ingress.kubernetes.io/healthcheck-interval-seconds: "15"
    alb.ingress.kubernetes.io/healthy-threshold-count: "2"
    alb.ingress.kubernetes.io/unhealthy-threshold-count: "3"
    alb.ingress.kubernetes.io/group.name: backstage
    alb.ingress.kubernetes.io/tags: Environment=production,Team=platform
    alb.ingress.kubernetes.io/load-balancer-attributes: >-
      idle_timeout.timeout_seconds=120,
      routing.http.drop_invalid_header_fields.enabled=true
spec:
  ingressClassName: alb
  rules:
    - host: backstage.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: backstage
                port:
                  number: 7007
bash
kubectl apply -f backstage-ingress.yaml

步骤 5:通过 RDS 使用 PostgreSQL

数据库连接在 app-config.production.yaml 中配置。以下示例展示了包含连接池和 SSL 的完整数据库部分:

yaml
# app-config.production.yaml
backend:
  database:
    client: pg
    connection:
      host: ${POSTGRES_HOST}
      port: ${POSTGRES_PORT}
      user: ${POSTGRES_USER}
      password: ${POSTGRES_PASSWORD}
      database: backstage
      ssl:
        require: true
        rejectUnauthorized: true
    knexConfig:
      pool:
        min: 3
        max: 12
        acquireTimeoutMillis: 60000
        idleTimeoutMillis: 30000
    plugin:
      catalog:
        connection:
          database: backstage_plugin_catalog
      scaffolder:
        connection:
          database: backstage_plugin_scaffolder
      auth:
        connection:
          database: backstage_plugin_auth
      search:
        connection:
          database: backstage_plugin_search

注意:Backstage 支持按 plugin 进行数据库隔离。每个 plugin 都可以在同一个 PostgreSQL instance 中使用独立数据库,从而提升安全性并简化备份和迁移。

步骤 6:OIDC Authentication 设置

Backstage 支持多个 authentication providers。下面是 Amazon Cognito 和 Okta 的配置。

Amazon Cognito 配置

yaml
# app-config.production.yaml
auth:
  environment: production
  providers:
    oidc:
      production:
        metadataUrl: https://cognito-idp.ap-northeast-2.amazonaws.com/<user-pool-id>/.well-known/openid-configuration
        clientId: ${AUTH_OIDC_CLIENT_ID}
        clientSecret: ${AUTH_OIDC_CLIENT_SECRET}
        authorizationUrl: https://<domain>.auth.ap-northeast-2.amazoncognito.com/oauth2/authorize
        tokenUrl: https://<domain>.auth.ap-northeast-2.amazoncognito.com/oauth2/token
        scope: openid profile email
        prompt: auto
  session:
    secret: ${AUTH_SESSION_SECRET}

Okta 配置

yaml
# app-config.production.yaml
auth:
  environment: production
  providers:
    okta:
      production:
        clientId: ${AUTH_OKTA_CLIENT_ID}
        clientSecret: ${AUTH_OKTA_CLIENT_SECRET}
        audience: https://dev-123456.okta.com
        authServerId: default
        idp: ${AUTH_OKTA_IDP_ID}
  session:
    secret: ${AUTH_SESSION_SECRET}

Sign-In Resolver 配置

在 backend plugin 代码中,配置 OIDC identities 如何映射到 Backstage users:

typescript
// packages/backend/src/plugins/auth.ts
import { createBackendModule } from '@backstage/backend-plugin-api';
import {
  authProvidersExtensionPoint,
  createOAuthProviderFactory,
} from '@backstage/plugin-auth-node';
import { oidcAuthenticator } from '@backstage/plugin-auth-backend-module-oidc-provider';

export const authModuleOidc = createBackendModule({
  pluginId: 'auth',
  moduleId: 'oidc',
  register(reg) {
    reg.registerInit({
      deps: { providers: authProvidersExtensionPoint },
      async init({ providers }) {
        providers.registerProvider({
          providerId: 'oidc',
          factory: createOAuthProviderFactory({
            authenticator: oidcAuthenticator,
            async signInResolver({ result }, ctx) {
              const email = result.userinfo.email;
              if (!email) {
                throw new Error('OIDC login did not provide an email');
              }
              return ctx.signInWithCatalogUser({
                filter: { 'spec.profile.email': email },
              });
            },
          }),
        });
      },
    });
  },
});

Software Catalog

Entity Model

Backstage Software Catalog 使用定义良好的 entity model 来表示组织的软件生态系统。理解该模型对于有效管理 catalog 至关重要。

Entity Types

Entity KindDescriptionExample
Component软件组件(service、website、library)order-service, payment-api
System构成产品的一组 componentsorder-platform, data-pipeline
Domain将相关 systems 分组的业务 domaincommerce, fulfillment
API由 component 暴露的接口order-api, payment-grpc
Resource基础设施依赖order-db, events-queue
Group团队或组织单元platform-team, commerce-team
User单个个人john.doe

catalog-info.yaml 示例

Component Entity

yaml
# catalog-info.yaml (in the service repository root)
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: order-service
  description: Handles order creation, updates, and lifecycle management
  labels:
    app.kubernetes.io/name: order-service
  annotations:
    backstage.io/techdocs-ref: dir:.
    github.com/project-slug: my-org/order-service
    backstage.io/kubernetes-id: order-service
    argocd/app-name: order-service
    backstage.io/kubernetes-namespace: commerce
  tags:
    - java
    - spring-boot
    - grpc
  links:
    - url: https://grafana.example.com/d/order-service
      title: Grafana Dashboard
      icon: dashboard
    - url: https://runbook.example.com/order-service
      title: Runbook
      icon: docs
spec:
  type: service
  lifecycle: production
  owner: group:commerce-team
  system: order-platform
  providesApis:
    - order-api
  consumesApis:
    - payment-api
    - inventory-api
  dependsOn:
    - resource:order-db
    - resource:order-events-queue

System Entity

yaml
apiVersion: backstage.io/v1alpha1
kind: System
metadata:
  name: order-platform
  description: End-to-end order management platform including order processing, payment, and fulfillment
  annotations:
    backstage.io/techdocs-ref: dir:.
  tags:
    - commerce
    - critical
spec:
  owner: group:commerce-team
  domain: commerce

Domain Entity

yaml
apiVersion: backstage.io/v1alpha1
kind: Domain
metadata:
  name: commerce
  description: All systems related to the online commerce experience including ordering, payments, and fulfillment
spec:
  owner: group:commerce-leadership

API Entity

yaml
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
  name: order-api
  description: REST API for order management operations
  tags:
    - rest
    - json
spec:
  type: openapi
  lifecycle: production
  owner: group:commerce-team
  system: order-platform
  definition: |
    openapi: "3.0.0"
    info:
      title: Order API
      version: 1.0.0
    paths:
      /orders:
        get:
          summary: List orders
          responses:
            '200':
              description: A list of orders
        post:
          summary: Create an order
          responses:
            '201':
              description: Order created
      /orders/{orderId}:
        get:
          summary: Get order by ID
          parameters:
            - name: orderId
              in: path
              required: true
              schema:
                type: string
          responses:
            '200':
              description: Order details

Resource Entity

yaml
apiVersion: backstage.io/v1alpha1
kind: Resource
metadata:
  name: order-db
  description: Aurora PostgreSQL cluster for order data
  annotations:
    aws.amazon.com/rds-cluster-id: order-db-cluster
  tags:
    - postgresql
    - aurora
spec:
  type: database
  owner: group:commerce-team
  system: order-platform

Group Entity

yaml
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
  name: commerce-team
  description: Commerce engineering team responsible for the order platform
spec:
  type: team
  profile:
    displayName: Commerce Team
    email: commerce-team@example.com
    picture: https://avatars.example.com/commerce-team.png
  parent: engineering
  children: []
  members:
    - john.doe
    - jane.smith
    - alex.kim

User Entity

yaml
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
  name: john.doe
  description: Senior Backend Engineer
spec:
  profile:
    displayName: John Doe
    email: john.doe@example.com
    picture: https://avatars.example.com/john-doe.png
  memberOf:
    - commerce-team

GitHub 自动发现

GitHub discovery plugin 会在你的 GitHub organization 中自动查找 catalog-info.yaml 文件:

yaml
# app-config.yaml
catalog:
  providers:
    github:
      myOrgProvider:
        organization: my-org
        catalogPath: /catalog-info.yaml
        filters:
          branch: main
          repository: '.*'  # All repositories
        schedule:
          frequency:
            minutes: 30
          timeout:
            minutes: 3
  rules:
    - allow:
        - Component
        - System
        - Domain
        - API
        - Resource
        - Group
        - User
        - Template
        - Location

安装所需的 backend plugin:

bash
# From the Backstage app root
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-github

在 backend 中注册 module:

typescript
// packages/backend/src/index.ts
import { createBackend } from '@backstage/backend-defaults';

const backend = createBackend();

// Core plugins
backend.add(import('@backstage/plugin-catalog-backend'));
backend.add(import('@backstage/plugin-catalog-backend-module-github'));
// ... other plugins

backend.start();

Kubernetes Cluster 集成

Kubernetes plugin 会在每个 component 的 Backstage catalog 中直接显示实时 Pod、Deployment 和 Service 状态。

安装 Kubernetes Plugin

bash
# Frontend plugin
yarn --cwd packages/app add @backstage/plugin-kubernetes

# Backend plugin
yarn --cwd packages/backend add @backstage/plugin-kubernetes-backend

配置 Backend

注册 Kubernetes backend plugin:

typescript
// packages/backend/src/index.ts
backend.add(import('@backstage/plugin-kubernetes-backend'));

配置 EKS Cluster 访问

yaml
# app-config.yaml
kubernetes:
  serviceLocatorMethod:
    type: multiTenant
  clusterLocatorMethods:
    - type: config
      clusters:
        - name: production-eks
          url: https://ABCDEF1234567890.gr7.ap-northeast-2.eks.amazonaws.com
          authProvider: serviceAccount
          serviceAccountToken: ${K8S_PROD_SA_TOKEN}
          caData: ${K8S_PROD_CA_DATA}
          skipTLSVerify: false
          skipMetricsLookup: false
          dashboardUrl: https://console.aws.amazon.com/eks/home?region=ap-northeast-2#/clusters/production-eks
          dashboardApp: aws
        - name: staging-eks
          url: https://GHIJKL5678901234.gr7.ap-northeast-2.eks.amazonaws.com
          authProvider: serviceAccount
          serviceAccountToken: ${K8S_STAGING_SA_TOKEN}
          caData: ${K8S_STAGING_CA_DATA}
          skipTLSVerify: false
          skipMetricsLookup: false

面向 Backstage 的 ServiceAccount 和 RBAC

在每个 EKS cluster 中创建只读 ServiceAccount,使 Backstage 能够查询 workload 状态:

yaml
# backstage-k8s-rbac.yaml
---
apiVersion: v1
kind: Namespace
metadata:
  name: backstage-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: backstage-reader
  namespace: backstage-system
---
apiVersion: v1
kind: Secret
metadata:
  name: backstage-reader-token
  namespace: backstage-system
  annotations:
    kubernetes.io/service-account.name: backstage-reader
type: kubernetes.io/service-account-token
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: backstage-reader
rules:
  - apiGroups: [""]
    resources:
      - pods
      - services
      - configmaps
      - namespaces
      - endpoints
      - serviceaccounts
    verbs: ["get", "list", "watch"]
  - apiGroups: ["apps"]
    resources:
      - deployments
      - replicasets
      - statefulsets
      - daemonsets
    verbs: ["get", "list", "watch"]
  - apiGroups: ["batch"]
    resources:
      - jobs
      - cronjobs
    verbs: ["get", "list", "watch"]
  - apiGroups: ["networking.k8s.io"]
    resources:
      - ingresses
    verbs: ["get", "list", "watch"]
  - apiGroups: ["autoscaling"]
    resources:
      - horizontalpodautoscalers
    verbs: ["get", "list", "watch"]
  - apiGroups: ["metrics.k8s.io"]
    resources:
      - pods
      - nodes
    verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: backstage-reader-binding
subjects:
  - kind: ServiceAccount
    name: backstage-reader
    namespace: backstage-system
roleRef:
  kind: ClusterRole
  name: backstage-reader
  apiGroup: rbac.authorization.k8s.io
bash
# Apply to each target EKS cluster
kubectl apply -f backstage-k8s-rbac.yaml

# Retrieve the token for Backstage configuration
kubectl get secret backstage-reader-token \
  -n backstage-system \
  -o jsonpath='{.data.token}' | base64 -d

为 Kubernetes Discovery 标注 Components

为了让 Kubernetes plugin 找到正确的 workloads,请为 catalog entities 添加 annotations:

yaml
# In catalog-info.yaml
metadata:
  annotations:
    backstage.io/kubernetes-id: order-service
    backstage.io/kubernetes-namespace: commerce
    backstage.io/kubernetes-label-selector: app=order-service

该 plugin 会通过这些 annotations 匹配 workloads,并直接在 component 页面显示 Pods、Deployments、ReplicaSets 和 HPA 状态。


Software Templates (Golden Paths)

Software Templates 使平台团队能够定义创建新项目的标准化路径。这些 Golden Paths 确保每个新服务从一开始就具备正确的结构、CI/CD pipeline、监控和安全配置。有关 Golden Path 概念的背景信息,请参阅 Platform Engineering Overview

Template 结构

Backstage template 包含:

  1. Parameters:呈现给开发者的输入字段(forms)
  2. Steps:按顺序执行以生成项目脚手架的 actions
  3. Output:完成后显示的链接和信息
template.yaml
├── parameters:     # What the developer fills in
│   ├── service name
│   ├── team/owner
│   ├── language
│   └── features (database, queue, etc.)
├── steps:          # What happens automatically
│   ├── fetch:template (scaffold files)
│   ├── publish:github (create repository)
│   ├── catalog:register (add to Backstage)
│   └── argocd:create-resources (set up CD)
└── output:         # What the developer sees
    ├── repository URL
    ├── catalog entity link
    └── ArgoCD app link

Microservice Golden Path Template

该 template 会生成一个完整 microservice 的脚手架,其中包含 Dockerfile、Helm chart、ArgoCD Application 和 GitHub Actions CI pipeline:

yaml
# templates/microservice/template.yaml
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: microservice-golden-path
  title: Microservice Golden Path
  description: |
    Create a production-ready microservice with CI/CD pipeline,
    Kubernetes deployment, monitoring, and documentation scaffolding.
  tags:
    - recommended
    - microservice
    - kubernetes
spec:
  owner: group:platform-team
  type: service

  parameters:
    - title: Service Information
      required:
        - serviceName
        - owner
        - description
      properties:
        serviceName:
          title: Service Name
          type: string
          description: Unique name for the microservice (lowercase, hyphens only)
          pattern: "^[a-z][a-z0-9-]*$"
          maxLength: 40
          ui:autofocus: true
        description:
          title: Description
          type: string
          description: Brief description of what this service does
          maxLength: 200
        owner:
          title: Owner Team
          type: string
          description: The team that will own this service
          ui:field: OwnerPicker
          ui:options:
            catalogFilter:
              kind: Group
        system:
          title: System
          type: string
          description: The system this service belongs to
          ui:field: EntityPicker
          ui:options:
            catalogFilter:
              kind: System

    - title: Technical Configuration
      required:
        - language
        - port
      properties:
        language:
          title: Programming Language
          type: string
          enum:
            - java-spring
            - go
            - node-express
            - python-fastapi
          enumNames:
            - Java (Spring Boot 3)
            - Go (Gin)
            - Node.js (Express)
            - Python (FastAPI)
        port:
          title: Service Port
          type: integer
          default: 8080
          description: Port the service listens on
        enableDatabase:
          title: Enable Database
          type: boolean
          default: false
          description: Provision an Aurora PostgreSQL database via ACK
        enableQueue:
          title: Enable Message Queue
          type: boolean
          default: false
          description: Provision an SQS queue via ACK

    - title: Deployment Configuration
      required:
        - namespace
        - environment
      properties:
        namespace:
          title: Kubernetes Namespace
          type: string
          default: default
          description: Target namespace for deployment
        environment:
          title: Environment
          type: string
          enum:
            - dev
            - staging
            - production
          default: dev

    - title: Repository Configuration
      required:
        - repoUrl
      properties:
        repoUrl:
          title: Repository Location
          type: string
          ui:field: RepoUrlPicker
          ui:options:
            allowedHosts:
              - github.com
            allowedOwners:
              - my-org

  steps:
    # Step 1: Scaffold the project from a skeleton template
    - id: fetch-skeleton
      name: Fetch Project Skeleton
      action: fetch:template
      input:
        url: ./skeleton/${{ parameters.language }}
        targetPath: .
        values:
          serviceName: ${{ parameters.serviceName }}
          description: ${{ parameters.description }}
          owner: ${{ parameters.owner }}
          system: ${{ parameters.system }}
          port: ${{ parameters.port }}
          namespace: ${{ parameters.namespace }}
          environment: ${{ parameters.environment }}
          enableDatabase: ${{ parameters.enableDatabase }}
          enableQueue: ${{ parameters.enableQueue }}

    # Step 2: Generate the Helm chart
    - id: fetch-helm
      name: Generate Helm Chart
      action: fetch:template
      input:
        url: ./skeleton/helm-chart
        targetPath: ./deploy/helm
        values:
          serviceName: ${{ parameters.serviceName }}
          port: ${{ parameters.port }}
          namespace: ${{ parameters.namespace }}
          enableDatabase: ${{ parameters.enableDatabase }}
          enableQueue: ${{ parameters.enableQueue }}

    # Step 3: Generate GitHub Actions CI pipeline
    - id: fetch-ci
      name: Generate CI Pipeline
      action: fetch:template
      input:
        url: ./skeleton/github-actions/${{ parameters.language }}
        targetPath: ./.github/workflows
        values:
          serviceName: ${{ parameters.serviceName }}
          language: ${{ parameters.language }}

    # Step 4: Generate ArgoCD Application manifest
    - id: fetch-argocd
      name: Generate ArgoCD Application
      action: fetch:template
      input:
        url: ./skeleton/argocd-app
        targetPath: ./deploy/argocd
        values:
          serviceName: ${{ parameters.serviceName }}
          namespace: ${{ parameters.namespace }}
          environment: ${{ parameters.environment }}
          repoUrl: ${{ (parameters.repoUrl | parseRepoUrl).host }}/${{ (parameters.repoUrl | parseRepoUrl).owner }}/${{ (parameters.repoUrl | parseRepoUrl).repo }}

    # Step 5: Publish to GitHub
    - id: publish
      name: Create GitHub Repository
      action: publish:github
      input:
        repoUrl: ${{ parameters.repoUrl }}
        description: ${{ parameters.description }}
        defaultBranch: main
        protectDefaultBranch: true
        repoVisibility: internal
        collaborators:
          - team: ${{ parameters.owner | replace("group:", "") }}
            access: maintain
          - team: platform-team
            access: admin

    # Step 6: Register in Backstage catalog
    - id: register
      name: Register in Backstage Catalog
      action: catalog:register
      input:
        repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }}
        catalogInfoPath: /catalog-info.yaml

  output:
    links:
      - title: Source Code Repository
        url: ${{ steps['publish'].output.remoteUrl }}
      - title: Backstage Catalog Entity
        icon: catalog
        entityRef: ${{ steps['register'].output.entityRef }}
      - title: GitHub Actions CI
        url: ${{ steps['publish'].output.remoteUrl }}/actions

Infrastructure Provisioning Template

该 template 通过 ACKKRO Claims 创建 AWS 基础设施资源,为开发者提供数据库、缓存和队列的自助服务访问:

yaml
# templates/infrastructure/template.yaml
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: aws-infrastructure-provisioning
  title: AWS Infrastructure Provisioning
  description: |
    Self-service provisioning of AWS infrastructure resources (RDS, ElastiCache, SQS)
    using ACK controllers and KRO resource graphs on EKS.
  tags:
    - infrastructure
    - aws
    - self-service
spec:
  owner: group:platform-team
  type: resource

  parameters:
    - title: Resource Information
      required:
        - resourceName
        - owner
        - resourceType
      properties:
        resourceName:
          title: Resource Name
          type: string
          description: Name for the infrastructure resource
          pattern: "^[a-z][a-z0-9-]*$"
          maxLength: 40
        owner:
          title: Owner Team
          type: string
          ui:field: OwnerPicker
          ui:options:
            catalogFilter:
              kind: Group
        resourceType:
          title: Resource Type
          type: string
          enum:
            - aurora-postgresql
            - aurora-mysql
            - elasticache-redis
            - sqs-queue
            - s3-bucket
          enumNames:
            - Aurora PostgreSQL
            - Aurora MySQL
            - ElastiCache Redis
            - SQS Queue
            - S3 Bucket

    - title: Resource Configuration
      required:
        - environment
        - size
      properties:
        environment:
          title: Environment
          type: string
          enum:
            - dev
            - staging
            - production
          default: dev
        size:
          title: Instance Size
          type: string
          enum:
            - small
            - medium
            - large
          enumNames:
            - "Small (dev/test: db.t4g.medium, cache.t4g.small)"
            - "Medium (staging: db.r6g.large, cache.r6g.large)"
            - "Large (production: db.r6g.xlarge, cache.r6g.xlarge)"
          default: small
        namespace:
          title: Target Namespace
          type: string
          default: default
          description: Kubernetes namespace where the resource claim will be created

    - title: Database-Specific Options
      description: Only applicable for database resource types
      properties:
        dbName:
          title: Database Name
          type: string
          default: appdb
          description: Name of the initial database to create
        storageSize:
          title: Storage Size (GiB)
          type: integer
          default: 20
          minimum: 20
          maximum: 1000
        enableMultiAZ:
          title: Enable Multi-AZ
          type: boolean
          default: false
          description: Enable Multi-AZ deployment for high availability

  steps:
    # Step 1: Generate the KRO resource claim
    - id: generate-claim
      name: Generate Resource Claim
      action: fetch:template
      input:
        url: ./skeleton/infrastructure/${{ parameters.resourceType }}
        targetPath: ./infrastructure
        values:
          resourceName: ${{ parameters.resourceName }}
          environment: ${{ parameters.environment }}
          size: ${{ parameters.size }}
          namespace: ${{ parameters.namespace }}
          dbName: ${{ parameters.dbName }}
          storageSize: ${{ parameters.storageSize }}
          enableMultiAZ: ${{ parameters.enableMultiAZ }}
          owner: ${{ parameters.owner }}

    # Step 2: Publish to the infrastructure GitOps repository
    - id: publish
      name: Create Pull Request
      action: publish:github:pull-request
      input:
        repoUrl: github.com?owner=my-org&repo=infrastructure-gitops
        branchName: provision/${{ parameters.resourceName }}
        title: "Provision ${{ parameters.resourceType }}: ${{ parameters.resourceName }}"
        description: |
          ## Infrastructure Provisioning Request

          | Field | Value |
          |-------|-------|
          | Resource | ${{ parameters.resourceName }} |
          | Type | ${{ parameters.resourceType }} |
          | Environment | ${{ parameters.environment }} |
          | Size | ${{ parameters.size }} |
          | Owner | ${{ parameters.owner }} |

          Provisioned via Backstage Software Template.
        targetPath: claims/${{ parameters.namespace }}

    # Step 3: Register as a Resource entity in the catalog
    - id: register
      name: Register Resource in Catalog
      action: catalog:register
      input:
        repoContentsUrl: https://github.com/my-org/infrastructure-gitops/tree/main
        catalogInfoPath: /claims/${{ parameters.namespace }}/${{ parameters.resourceName }}/catalog-info.yaml

  output:
    links:
      - title: Pull Request
        url: ${{ steps['publish'].output.remoteUrl }}
      - title: Catalog Entity
        icon: catalog
        entityRef: ${{ steps['register'].output.entityRef }}

生成的 Aurora PostgreSQL database KRO claim(由 template skeleton 创建):

yaml
# claims/<namespace>/<resource-name>/kro-claim.yaml
apiVersion: kro.run/v1alpha1
kind: DatabaseClaim
metadata:
  name: order-db
  namespace: commerce
  labels:
    app.kubernetes.io/managed-by: backstage
    backstage.io/owner: group:commerce-team
    environment: production
spec:
  engine: aurora-postgresql
  engineVersion: "15.4"
  instanceClass: db.r6g.xlarge
  storageSize: 100
  multiAZ: true
  databaseName: appdb
  backupRetentionDays: 30
  deletionProtection: true

ArgoCD Integration Plugin

要将 Backstage templates 与 ArgoCD 链接起来以实现自动部署,请安装 ArgoCD scaffolder action:

bash
yarn --cwd packages/backend add @roadiehq/scaffolder-backend-argocd
typescript
// packages/backend/src/index.ts
backend.add(import('@roadiehq/scaffolder-backend-argocd'));

添加 ArgoCD 配置:

yaml
# app-config.yaml
argocd:
  appLocatorMethods:
    - type: config
      instances:
        - name: main
          url: https://argocd.example.com
          token: ${ARGOCD_AUTH_TOKEN}

这使 templates 能够包含 ArgoCD steps:

yaml
# In a template step
- id: create-argocd-app
  name: Create ArgoCD Application
  action: argocd:create-resources
  input:
    appName: ${{ parameters.serviceName }}-${{ parameters.environment }}
    argoInstance: main
    namespace: argocd
    repoUrl: ${{ steps['publish'].output.remoteUrl }}
    path: deploy/helm
    projectName: ${{ parameters.namespace }}

TechDocs

TechDocs 为 Backstage 带来 “docs-like-code” 体验,可在 developer portal 中直接渲染基于 MkDocs 的 Markdown 文档。

TechDocs 如何工作

  1. 文档以 Markdown 文件形式与源代码一起编写
  2. mkdocs.yml 配置文件定义文档结构
  3. TechDocs 将文档构建为静态 HTML
  4. 构建后的 docs 存储在 S3(或 GCS/Azure Blob)中以供提供服务
  5. 开发者直接在 Backstage UI 中阅读与所属 component 关联的 docs

S3 Storage 配置

yaml
# app-config.yaml
techdocs:
  builder: external
  generator:
    runIn: local
  publisher:
    type: awsS3
    awsS3:
      bucketName: backstage-techdocs
      region: ap-northeast-2
      bucketRootPath: /
      accountId: "111122223333"
      credentials:
        roleArn: arn:aws:iam::111122223333:role/backstage-techdocs-role

S3 Bucket Policy

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "BackstageTechDocsReadWrite",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111122223333:role/backstage-techdocs-role"
      },
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::backstage-techdocs",
        "arn:aws:s3:::backstage-techdocs/*"
      ]
    }
  ]
}

Service Repositories 中的 MkDocs 配置

每个发布 TechDocs 的 service 都需要在其根目录包含一个 mkdocs.yml

yaml
# mkdocs.yml (in the service repository)
site_name: Order Service Documentation
site_description: Technical documentation for the Order Service
plugins:
  - techdocs-core
nav:
  - Home: index.md
  - Architecture: architecture.md
  - API Reference: api-reference.md
  - Runbook: runbook.md
  - ADRs:
      - ADR-001 Database Choice: adrs/001-database-choice.md
      - ADR-002 Event Schema: adrs/002-event-schema.md

对应的 catalog-info.yaml annotation:

yaml
metadata:
  annotations:
    backstage.io/techdocs-ref: dir:.

External TechDocs Build Pipeline

对于 external builder 模式,设置一个 CI job,在每次 merge 时构建并发布文档:

yaml
# .github/workflows/techdocs.yaml
name: Build and Publish TechDocs
on:
  push:
    branches: [main]
    paths:
      - 'docs/**'
      - 'mkdocs.yml'

jobs:
  publish-techdocs:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - name: Install dependencies
        run: pip install mkdocs-techdocs-core==1.4.*

      - name: Build TechDocs
        run: npx @techdocs/cli generate --no-docker

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::111122223333:role/techdocs-publisher
          aws-region: ap-northeast-2

      - name: Publish TechDocs
        run: |
          npx @techdocs/cli publish \
            --publisher-type awsS3 \
            --storage-name backstage-techdocs \
            --entity default/Component/order-service \
            --awsRoleArn arn:aws:iam::111122223333:role/techdocs-publisher \
            --awsRegion ap-northeast-2

面向 EKS 的 Plugin 生态系统

Backstage 的 plugin system 是它最大的优势。以下 plugins 对在 Amazon EKS 上运行 workloads 的团队特别有价值。

Kubernetes Plugin

Kubernetes plugin 可直接从 catalog entity 页面提供 Pod 和 Deployment 状态的实时可见性。

它显示的内容:

  • Pod 数量和状态(Running、Pending、CrashLoopBackOff)
  • Deployment replica 状态(desired vs. available)
  • 最近的 Pod events 和错误消息
  • HPA 当前指标和目标指标
  • Container resource utilization(CPU、memory)

Frontend integration:

typescript
// packages/app/src/components/catalog/EntityPage.tsx
import { EntityKubernetesContent } from '@backstage/plugin-kubernetes';

// Add to the service entity page
const serviceEntityPage = (
  <EntityLayout>
    <EntityLayout.Route path="/" title="Overview">
      {/* overview content */}
    </EntityLayout.Route>
    <EntityLayout.Route path="/kubernetes" title="Kubernetes">
      <EntityKubernetesContent refreshIntervalMs={10000} />
    </EntityLayout.Route>
  </EntityLayout>
);

ArgoCD Plugin

ArgoCD plugin 会显示每个 component 的 GitOps deployment sync 状态、健康状况和历史记录。

安装:

bash
# Frontend
yarn --cwd packages/app add @roadiehq/backstage-plugin-argo-cd

# Backend
yarn --cwd packages/backend add @roadiehq/backstage-plugin-argo-cd-backend

配置:

yaml
# app-config.yaml
argocd:
  appLocatorMethods:
    - type: config
      instances:
        - name: main
          url: https://argocd.example.com
          token: ${ARGOCD_AUTH_TOKEN}

Catalog annotation:

yaml
metadata:
  annotations:
    argocd/app-name: order-service

Frontend integration:

typescript
// packages/app/src/components/catalog/EntityPage.tsx
import {
  EntityArgoCDOverviewCard,
  EntityArgoCDHistoryCard,
} from '@roadiehq/backstage-plugin-argo-cd';

// Add to the overview page
const overviewContent = (
  <Grid container spacing={3}>
    <Grid item md={6}>
      <EntityArgoCDOverviewCard />
    </Grid>
    <Grid item md={6}>
      <EntityArgoCDHistoryCard />
    </Grid>
  </Grid>
);

Kubecost Plugin

Kubecost plugin 提供按 service 维度的成本可见性,显示每个 component 在 compute、memory 和 storage 方面的成本。

安装:

bash
yarn --cwd packages/app add @kubecost/backstage-plugin
yarn --cwd packages/backend add @kubecost/backstage-plugin-backend

配置:

yaml
# app-config.yaml
kubecost:
  baseUrl: https://kubecost.example.com
  # Optional: filter by cluster
  clusterFilter: production-eks

Catalog annotation:

yaml
metadata:
  annotations:
    kubecost.com/deployment-name: order-service
    kubecost.com/namespace: commerce

Frontend integration:

typescript
// packages/app/src/components/catalog/EntityPage.tsx
import { EntityKubecostCard } from '@kubecost/backstage-plugin';

const overviewContent = (
  <Grid container spacing={3}>
    <Grid item md={6}>
      <EntityKubecostCard />
    </Grid>
  </Grid>
);

KEDA 和 Karpenter 可见性(Custom Plugin 概念)

虽然没有面向 KEDA 或 Karpenter 的官方 Backstage plugin,但你可以构建一个 custom plugin,查询 Kubernetes API 中的 ScaledObject 和 NodePool resources,然后在 component 旁边显示 scaling metrics。

Custom plugin backend 概念:

typescript
// plugins/keda-backend/src/router.ts
import { Router } from 'express';
import { Logger } from 'winston';
import { Config } from '@backstage/config';

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

  router.get('/scaled-objects/:namespace/:name', async (req, res) => {
    const { namespace, name } = req.params;

    // Query the Kubernetes API for ScaledObject
    const scaledObject = await k8sClient.getNamespacedCustomObject(
      'keda.sh',
      'v1alpha1',
      namespace,
      'scaledobjects',
      name,
    );

    res.json({
      name: scaledObject.body.metadata.name,
      minReplicas: scaledObject.body.spec.minReplicaCount,
      maxReplicas: scaledObject.body.spec.maxReplicaCount,
      triggers: scaledObject.body.spec.triggers,
      currentReplicas: scaledObject.body.status?.currentReplicas,
    });
  });

  return router;
}

Custom plugin frontend 概念:

typescript
// plugins/keda/src/components/KedaCard.tsx
import React from 'react';
import { InfoCard, Progress } from '@backstage/core-components';
import { useEntity } from '@backstage/plugin-catalog-react';
import useAsync from 'react-use/lib/useAsync';

export const KedaScalingCard = () => {
  const { entity } = useEntity();
  const namespace =
    entity.metadata.annotations?.['backstage.io/kubernetes-namespace'];
  const name = entity.metadata.name;

  const { value, loading, error } = useAsync(async () => {
    const response = await fetch(
      `/api/keda/scaled-objects/${namespace}/${name}`,
    );
    return response.json();
  });

  if (loading) return <Progress />;
  if (error) return <div>Error loading KEDA data: {error.message}</div>;

  return (
    <InfoCard title="KEDA Autoscaling">
      <p>Min Replicas: {value?.minReplicas}</p>
      <p>Max Replicas: {value?.maxReplicas}</p>
      <p>Current Replicas: {value?.currentReplicas}</p>
      <p>Triggers: {value?.triggers?.length}</p>
    </InfoCard>
  );
};

Plugin 摘要

PluginPurposeData SourceInstallation
@backstage/plugin-kubernetesPod/Deployment 状态Kubernetes API官方
@roadiehq/backstage-plugin-argo-cdSync 状态、deploy 历史ArgoCD API社区(Roadie)
@kubecost/backstage-plugin按 service 的成本明细Kubecost API社区(Kubecost)
Custom KEDA PluginAutoscaler 状态Kubernetes API (keda.sh CRDs)自定义构建
Custom Karpenter PluginNode provisioning 状态Kubernetes API (karpenter.sh CRDs)自定义构建
@backstage/plugin-techdocsDocumentation viewerS3 / GCS官方
@backstage/plugin-github-actionsCI pipeline 状态GitHub API官方

RBAC 与治理

Permission Framework 概述

Backstage 包含内置 permission framework,用于控制对 catalog entities、templates 和 plugin features 的访问。该 permission system 基于策略,并与 catalog 的所有权模型集成。

启用 Permission Framework

yaml
# app-config.yaml
permission:
  enabled: true

基于团队的访问控制

实现一个强制执行基于所有权访问的 custom permission policy:

typescript
// packages/backend/src/plugins/permission.ts
import {
  PolicyDecision,
  AuthorizeResult,
  isPermission,
} from '@backstage/plugin-permission-common';
import {
  PermissionPolicy,
  PolicyQuery,
} from '@backstage/plugin-permission-node';
import {
  catalogEntityDeletePermission,
  catalogEntityCreatePermission,
} from '@backstage/plugin-catalog-common/alpha';
import {
  createCatalogConditionalDecision,
  catalogConditions,
} from '@backstage/plugin-catalog-backend/alpha';
import { BackstageIdentityResponse } from '@backstage/plugin-auth-node';
import { createBackendModule } from '@backstage/backend-plugin-api';
import { policyExtensionPoint } from '@backstage/plugin-permission-node/alpha';

class TeamBasedPermissionPolicy implements PermissionPolicy {
  async handle(
    request: PolicyQuery,
    user?: BackstageIdentityResponse,
  ): Promise<PolicyDecision> {
    // Platform team gets full access
    if (
      user?.identity.ownershipEntityRefs.includes(
        'group:default/platform-team',
      )
    ) {
      return { result: AuthorizeResult.ALLOW };
    }

    // Only owners can delete catalog entities
    if (isPermission(request.permission, catalogEntityDeletePermission)) {
      if (!user) {
        return { result: AuthorizeResult.DENY };
      }
      return createCatalogConditionalDecision(request.permission, {
        anyOf: user.identity.ownershipEntityRefs.map((ref) =>
          catalogConditions.isEntityOwner({ claims: [ref] }),
        ),
      });
    }

    // Everyone can create entities and view the catalog
    if (isPermission(request.permission, catalogEntityCreatePermission)) {
      return { result: AuthorizeResult.ALLOW };
    }

    // Default: allow read operations
    return { result: AuthorizeResult.ALLOW };
  }
}

export const permissionModule = createBackendModule({
  pluginId: 'permission',
  moduleId: 'team-based-policy',
  register(reg) {
    reg.registerInit({
      deps: { policy: policyExtensionPoint },
      async init({ policy }) {
        policy.setPolicy(new TeamBasedPermissionPolicy());
      },
    });
  },
});

治理规则示例

RuleImplementationScope
只有 owners 可以删除 entitiescatalogEntityDeletePermission 使用 Conditional permissionCatalog
只有 platform team 可以创建 templates除非在 platform-team group 中,否则 DENY templateCreatePermissionScaffolder
外部 contractors 只读对 contractor group DENY 所有写权限Global
Template execution requires approvalTemplates 中的 custom approval workflow stepScaffolder
Production deployments restricted基于 entity lifecycle 的 Conditional permissionCatalog + ArgoCD

Audit Logging

Backstage 支持 audit logging,用于跟踪谁做了什么。配置 audit log backend:

yaml
# app-config.yaml
backend:
  audit:
    enabled: true
    logger:
      type: winston
      options:
        transports:
          - type: console
            level: info
          - type: file
            level: info
            filename: /var/log/backstage/audit.log
            maxsize: 10485760
            maxFiles: 10

对于生产环境,将 audit logs 发送到 CloudWatch:

yaml
# app-config.yaml
backend:
  audit:
    enabled: true
    logger:
      type: winston
      options:
        transports:
          - type: cloudwatch
            level: info
            logGroupName: /backstage/audit
            logStreamName: backstage-production
            region: ap-northeast-2

Audit log entries 会捕获:

  • Who:已认证的 user identity
  • What:执行的 action(entity created、template executed、entity deleted)
  • When:action 的 timestamp
  • Where:目标 entity 或 resource
  • Result:成功或失败,并附带错误详情

生产运维

高可用配置

对于生产部署,在 ALB 后运行多个 Backstage replicas,并使用共享的外部 PostgreSQL:

yaml
# backstage-values.yaml (HA configuration)
backstage:
  replicas: 3

  resources:
    requests:
      memory: 1Gi
      cpu: 500m
    limits:
      memory: 2Gi
      cpu: 2000m

  podDisruptionBudget:
    enabled: true
    minAvailable: 2

  affinity:
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 100
          podAffinityTerm:
            labelSelector:
              matchExpressions:
                - key: app.kubernetes.io/name
                  operator: In
                  values:
                    - backstage
            topologyKey: topology.kubernetes.io/zone

  topologySpreadConstraints:
    - maxSkew: 1
      topologyKey: topology.kubernetes.io/zone
      whenUnsatisfiable: DoNotSchedule
      labelSelector:
        matchLabels:
          app.kubernetes.io/name: backstage

  livenessProbe:
    httpGet:
      path: /healthcheck
      port: 7007
    initialDelaySeconds: 60
    periodSeconds: 10
    failureThreshold: 3

  readinessProbe:
    httpGet:
      path: /healthcheck
      port: 7007
    initialDelaySeconds: 30
    periodSeconds: 5
    failureThreshold: 3

postgresql:
  enabled: false  # External RDS with Multi-AZ

HA deployment 架构:

┌─────────────────────────────────────────────────────────────┐
│                        ALB (internet-facing)                 │
│                      backstage.example.com                   │
└──────────┬──────────────────┬──────────────────┬────────────┘
           │                  │                  │
    ┌──────▼──────┐    ┌──────▼──────┐    ┌──────▼──────┐
    │ Backstage   │    │ Backstage   │    │ Backstage   │
    │ Pod (AZ-a)  │    │ Pod (AZ-b)  │    │ Pod (AZ-c)  │
    └──────┬──────┘    └──────┬──────┘    └──────┬──────┘
           │                  │                  │
           └──────────────────┼──────────────────┘

                    ┌─────────▼─────────┐
                    │   RDS PostgreSQL   │
                    │   (Multi-AZ)       │
                    └───────────────────┘

备份与恢复策略

ComponentBackup MethodFrequencyRetention
PostgreSQL (RDS)Automated RDS snapshots每日30 天
PostgreSQL (RDS)Point-in-time recovery持续35 天
TechDocs (S3)S3 versioning + cross-region replication持续90 天
ConfigurationGit repository (app-config.yaml)每次 commit无限期
Catalog entitiesGit repositories (catalog-info.yaml)每次 commit无限期
SecretsAWS Secrets Manager with rotation变更时已版本化

RDS 备份配置(Terraform 示例):

hcl
resource "aws_rds_cluster" "backstage" {
  cluster_identifier      = "backstage-db"
  engine                  = "aurora-postgresql"
  engine_version          = "15.4"
  master_username         = "backstage"
  database_name           = "backstage"
  backup_retention_period = 30
  preferred_backup_window = "03:00-04:00"
  deletion_protection     = true
  storage_encrypted       = true
  kms_key_id              = aws_kms_key.backstage.arn

  copy_tags_to_snapshot = true

  tags = {
    Environment = "production"
    Service     = "backstage"
    ManagedBy   = "terraform"
  }
}

灾难恢复流程:

  1. 数据库损坏或丢失:从最新的 RDS automated snapshot 恢复,或使用 point-in-time recovery 恢复到特定时间戳
  2. TechDocs 丢失:S3 versioning 允许恢复任何先前版本;cross-region replica 提供区域级故障转移
  3. 配置漂移:所有配置都在 Git 中;从已知良好的 commit 重新部署
  4. Catalog 数据丢失:Catalog entities 定义在 Git repositories 中;重新触发 GitHub discovery provider 以重新填充

升级策略

Backstage 会频繁发布新版本(大约每月一次)。遵循以下升级策略:

  1. 显式固定版本:始终在 Dockerfile 和 package.json 中使用精确 version tags,绝不要使用 latest
  2. 阅读 changelog:升级前查看 Backstage changelog 中的 breaking changes
  3. 逐步升级:不要跳过 major versions;一次升级一个 minor version
  4. 在 staging 中测试:先部署到带有生产数据库 schema 副本的 staging environment
  5. 滚动更新:使用带 readiness probes 的 Kubernetes rolling update strategy,确保零停机
yaml
# Deployment strategy in values.yaml
backstage:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0
      maxSurge: 1

升级工作流:

bash
# 1. Update Backstage packages
yarn backstage-cli versions:bump --release 1.36.0

# 2. Run database migrations
yarn backstage-cli db:migrate

# 3. Build and test locally
yarn build:backend
yarn test

# 4. Build new container image
docker build -t backstage:v1.36.0 .

# 5. Push to ECR
docker tag backstage:v1.36.0 \
  111122223333.dkr.ecr.ap-northeast-2.amazonaws.com/backstage:v1.36.0
docker push \
  111122223333.dkr.ecr.ap-northeast-2.amazonaws.com/backstage:v1.36.0

# 6. Update Helm values and upgrade
helm upgrade backstage backstage/backstage \
  --namespace backstage \
  --set backstage.image.tag=v1.36.0 \
  --values backstage-values.yaml \
  --wait --timeout 10m

最佳实践

推荐实践

  1. 从 Software Catalog 开始:在构建 templates 或安装 plugins 之前,先投入时间构建完整且准确的 catalog。填充良好的 catalog 是让其他所有功能有价值的基础。首先注册所有现有服务,并确保所有权正确。

  2. 将 Backstage 视为内部产品:指定 product owner,收集开发者反馈,迭代功能,并跟踪采用指标。平台团队是一个 product team,其客户是其他工程团队。

  3. 定义 Golden Paths,而不是 Golden Cages:Templates 应编码最佳实践和组织标准,但当开发者有合理理由时,始终允许他们偏离。目标是让正确的事情变得容易,而不是让它成为唯一选择。

  4. 自动化 Catalog 填充:使用 GitHub discovery plugin 自动查找并注册 catalog-info.yaml 文件,而不是要求手动注册。这可以减少摩擦并保持 catalog 最新。

  5. 将 app-config.yaml 保存在 Version Control 中:切勿在运行中的 cluster 上手动编辑配置。所有 Backstage 配置都应位于 Git repository 中,并通过与 application 本身相同的 CI/CD pipeline 部署。

  6. 投资文档(TechDocs):没有文档的 developer portal 只是一个 dashboard。让 TechDocs 成为每个 service 的要求 -- 将它包含在你的 Golden Path templates 中,使新服务从第一天起就随附文档脚手架。

  7. 监控 Backstage 本身:从 Backstage backend 暴露 Prometheus metrics,为 API latency、error rates 和 plugin health 设置 dashboards。使用你提供给开发者的同一套 observability stack。

  8. 规划 Plugin 维护:Community plugins 可能落后于 Backstage core releases。固定 plugin versions,在 staging 中测试升级,并维护一份对部署至关重要的 plugins 列表。

常见陷阱

  1. 构建过多、过快:尝试在第一天就部署包含每个 plugin 的 Backstage 会导致复杂性和延迟。先从 catalog 和一个 template 开始,然后根据开发者反馈逐步添加 plugins。

  2. 忽视所有权数据:缺失或不正确的所有权信息会削弱对 catalog 的信任。如果开发者无法找到某个 service 的 owner,他们就会停止使用 catalog。通过对 catalog-info.yaml 的 CI checks 强制执行所有权。

  3. 从一开始就忽略 Authentication:即使在 staging 中,在没有正确 OIDC authentication 的情况下部署 Backstage 也会造成安全缺口,并使之后实施 RBAC 更加困难。在将 Backstage 暴露给用户之前配置 authentication。

  4. 将 Backstage 视为只读:Backstage 的真正力量在于 Software Templates 和自助服务工作流,而不仅仅是查看 catalog 数据。如果开发者可以看到自己的 services,但不能通过 portal 创建新的 services,采用率就会停滞。


参考资料

官方文档

CNCF 和社区

AWS 和 EKS 集成

本 Repository 中的相关文档