Внутренняя платформа разработчика IDP: Создание корпоративного портала разработчиков с Backstage 2026

DevOps

Внутренняя платформа разработчика IDP: Создание корпоративного портала разработчиков с Backstage

Вы только что устроились в компанию с 200 микросервисами. В первый день ваш лид говорит «задеплой order-service». Затем вы тратите 3 часа на поиск Git-репозитория, 2 часа на поиск конфигурации CI/CD, 1 час на поиск документации API — день прошёл, прежде чем вы написали хоть одну строку кода.

Это повседневная жизнь без Внутренней платформы разработчика (IDP). Backstage, фреймворк портала разработчиков с открытым исходным кодом от Spotify, становится стандартом де-факто для платформенной инженерии. Эта статья описывает 5 основных паттернов для создания IDP корпоративного уровня с нуля.

Основные концепции вкратце

Концепция Описание Аналогия
IDP (Внутренняя платформа разработчика) Единый портал самообслуживания для разработчиков Корпоративный «App Store»
Backstage Фреймворк IDP с открытым исходным кодом от Spotify Kubernetes для IDP
Software Catalog Реестр метаданных для всех сервисов «Жёлтые страницы» для сервисов
TechDocs Система технической документации на основе Markdown Wiki инженерного уровня
Software Template Стандартизированное создание проекта в один клик Веб-CLI для скаффолдинга
Plugin Модуль расширения Backstage Расширение браузера

5 главных проблем без IDP

  1. Сложность обнаружения сервисов: Сотни микросервисов, невозможно найти, кому что принадлежит, как развернуть, где мониторить
  2. Разрозненная документация: Confluence, GitLab Wiki, Notion... не найти, не понять
  3. Медленный онбординг проектов: Каждый раз скаффолдинг с нуля, CI/CD, мониторинг, настройка логирования повторяются
  4. Фрагментированная цепочка инструментов: Jenkins, ArgoCD, Grafana, Jira... постоянное переключение контекста убивает продуктивность
  5. Кошмар комплаенс-аудита: Кто что изменил, у кого есть доступ, граф зависимостей сервисов — хаос

Паттерн 1: Архитектура и установка Backstage

Backstage использует монолитную архитектуру на основе плагинов. Ядро состоит из четырёх модулей: 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       # Определения сущностей каталога
# └── app-config.yaml         # Конфигурация приложения

# 3. Запустить dev-сервер
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: Software 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:
    - john.doe
    - jane.smith
    - bob.wilson
// TypeScript: Пользовательский процессор сущностей каталога
// Среда: 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: Техническая документация сервиса заказов
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 -->
# Сервис заказов

## Обзор

Сервис заказов — основной микросервис системы заказов, обрабатывающий создание, оплату и выполнение заказов.

## Быстрые ссылки

| Ресурс | Ссылка |
|----------|------|
| Исходный код | [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('Плагин здоровья сервиса инициализирован');
      },
    });
  },
});

// 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(`Ошибка получения статуса здоровья ${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 Endpoint',
      status: 'healthy',
      latency_ms: 45,
      last_check: new Date().toISOString(),
      message: 'Все эндпоинты отвечают нормально',
    },
    {
      name: 'Database Connection',
      status: 'healthy',
      latency_ms: 12,
      last_check: new Date().toISOString(),
      message: 'Пул соединений здоров',
    },
    {
      name: 'Kafka Consumer',
      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. Добавить Helm-репозиторий Backstage
helm repo add backstage https://backstage.github.io/charts
helm repo update

# 2. Создать файл значений override
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-info.yaml локально без регистрации Location
✅ Правильно: Добавить locations в app-config.yaml или зарегистрировать Location в UI

Ловушка 2: Ошибки сборки TechDocs

❌ Неправильно: Запускать MkDocs напрямую на хосте, отсутствующие зависимости вызывают ошибки сборки
✅ Правильно: Настроить techdocs.generator.runIn: 'docker' для контейнеризированных сборок

Ловушка 3: Недостаточные права токена интеграции GitHub/GitLab

# ❌ Неправильно: Токен имеет только разрешение repo:read
# ✅ Правильно: Токен нуждается в этих разрешениях
# GitHub: repo, read:org, read:user
# GitLab: api, read_repository, read_api

Ловушка 4: Горячая перезагрузка плагина не работает

# ❌ Неправильно: Не перезапускать dev-сервер после изменения кода плагина
# ✅ Правильно: Убедиться, что yarn dev запущен, Backstage поддерживает HMR hot reload
# Если 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 или недостаточные права Проверить права токена, убедиться в наличии 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
Unauthorized: Invalid token Некорректная конфигурация OIDC/OAuth Проверить clientId/clientSecret и URL обратного вызова
CORS error Несовпадение доменов фронтенда/бэкенда Настроить backend.cors.origin для совпадения с URL фронтенда
Location not found URL catalog-info.yaml недоступен Проверить доступность URL и права токена
Out of memory Недостаточно heap-памяти 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: Автообнаружение каталога

// 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: Зарегистрировать в каталоге
      action: catalog:register
      input:
        repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }}
        catalogInfoPath: '/catalog-info.yaml'

  output:
    links:
      - title: URL репозитория
        url: ${{ steps.publish.output.remoteUrl }}
      - title: Открыть в каталоге
        url: https://idp.mycompany.com/catalog/default/component/${{ parameters.name }}

Сравнение платформ IDP

Измерение Backstage Port Humanitec
Открытый исходный код ✅ Apache 2.0 ❌ Коммерческий SaaS ❌ Коммерческий SaaS
Self-Hosted ✅ Полный контроль ❌ Облачный хостинг ❌ Облачный хостинг
Экосистема плагинов 200+ плагинов сообщества Встроенные интеграции Встроенные интеграции
Кривая обучения Средняя-Высокая (TypeScript) Низкая (визуальная конфигурация) Средняя (платформенная инженерия)
Кастомизация Очень высокая (на уровне кода) Средняя (API-расширение) Средняя (определение Score)
Software Catalog ✅ Основная функция ✅ Основная функция ✅ Основная функция
TechDocs ✅ Нативная поддержка ❌ Требуется интеграция ❌ Требуется интеграция
Стоимость Инженерные усилия $50-200/пользователь/месяц $100-500/пользователь/месяц
Масштаб 50-5000+ разработчиков 10-500 разработчиков 100-5000+ разработчиков
Известные клиенты Spotify, American Airlines Lyft, Palo Alto BMW, Qonto

Заключение

Backstage — оптимальный выбор для создания Внутренней платформы разработчика в 2026 году — открытый исходный код, self-hosted, с богатой экосистемой плагинов. Но учтите:

  • Малые команды (<50): Сначала рассмотрите Port или аналогичные SaaS-решения; поддержка Backstage может быть нерентабельной
  • Средние команды (50-500): Backstage — лучший выбор, 2-3 инженера для поддержки
  • Крупные команды (500+): Backstage + шаблоны Golden Path + автообнаружение для полноценной системы платформенной инженерии

Ключевые факторы успеха: Начните с Catalog и TechDocs, постепенно добавляйте плагины, затем создавайте шаблоны Golden Path. Не пытайтесь сварить океан.

Рекомендуемые онлайн-инструменты

  • /ru/json/format - Форматирование JSON для конвертации YAML-в-JSON сущностей каталога
  • /ru/dev/curl-to-code - Генератор кода из cURL для вызовов API Backstage
  • /ru/encode/hash - Калькулятор хешей для генерации уникальных идентификаторов сервисов
  • /ru/text/diff - Сравнение текстов для отслеживания изменений в catalog-info.yaml

Попробуйте эти локальные браузерные инструменты — регистрация не требуется →

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