Внутренняя платформа разработчика IDP: Создание корпоративного портала разработчиков с Backstage 2026
Внутренняя платформа разработчика 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
- Сложность обнаружения сервисов: Сотни микросервисов, невозможно найти, кому что принадлежит, как развернуть, где мониторить
- Разрозненная документация: Confluence, GitLab Wiki, Notion... не найти, не понять
- Медленный онбординг проектов: Каждый раз скаффолдинг с нуля, CI/CD, мониторинг, настройка логирования повторяются
- Фрагментированная цепочка инструментов: Jenkins, ArgoCD, Grafana, Jira... постоянное переключение контекста убивает продуктивность
- Кошмар комплаенс-аудита: Кто что изменил, у кого есть доступ, граф зависимостей сервисов — хаос
Паттерн 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
Попробуйте эти локальные браузерные инструменты — регистрация не требуется →