Platform Engineering 实战:用 Backstage 构建内部开发者平台
去年我花了两周时间在团队里搭了一套 Backstage,前后踩了无数坑,今天把整个过程和关键决策写下来。
先说结论:Backstage 不是开箱即用的产品,而是一个半成品框架。你得到的不是"开发者门户",而是一套可以拼成门户的乐高积木。 能不能搭好,取决于你对自己团队的开发者体验有多少理解。
为什么要搞平台工程
事情要从一次故障说起。
我们有个微服务项目,30 多个服务分布在 5 个 Git 仓库里,每个服务的 CI 流程、部署方式、监控面板都不一样。新同学 onboarding 需要看三天的文档才能搞清楚"上线一个新服务要走哪些流程"。
第 37 次被问"这个服务的 API 文档在哪"之后,我决定不再忍受了。
平台工程的核心命题很简单:把基础设施的复杂性封装起来,给开发者一条"黄金路径"。 Backstage 是 Spotify 开源出来的方案,也是目前社区最活跃的内部开发者门户框架。
选型:为什么是 Backstage
市面上做开发者门户的选择其实不多:
| 方案 | 类型 | 成本 | 定制性 |
|---|---|---|---|
| Backstage | 开源框架 | 部署+维护成本 | 极高 |
| 自研 | 全自建 | 极高 | 最高 |
| 商业方案(Portal等) | SaaS | 按量付费 | 受限于平台 |
我选 Backstage 的原因很简单:社区生态够活跃,插件机制够灵活。 当时它的 GitHub 星星已经快 3 万了,有 200+ 官方和社区插件,从 TechDocs 到 Kubernetes 到 CI/CD 都有现成的集成。
环境搭建:第一道坎
官方文档写得很美,实际跑起来是另外一回事。
安装脚手架
npx @backstage/create-app@latest --template backstage
这步看起来人畜无害,实际上它会从模板生成一个 monorepo,里面包含了 Frontend(React)、Backend(Express)、Client(API 请求层)三个包。第一次编译时 node_modules 能到 1.5GB。
编译命令:
cd my-backstage
yarn install
yarn dev
默认跑在 localhost:3000(前端)和 localhost:7007(后端)。
依赖地狱
第一次 yarn install 就挂了。报错信息指向某个 Python 包的编译失败——Backstage 用了 node-gyp,它依赖系统的 Python 和 C++ 编译器。
在 macOS 上我跑了:
xcode-select --install
在 Linux 上需要:
sudo apt-get install python3 g++ make
Windows 朋友……建议你先用 WSL2。
注意: Backstage 3.0+ 开始迁移到 New Frontend System,插件注册方式变了。如果你跟进最新版本,很多教程里的写法(用
createPlugin注册路由)已经不适用。我建议新项目直接走新系统,老教程参考时要留个心眼。
核心概念:先搞懂这几个关键东西
在动手配置之前,必须先理解 Backstage 的几个核心抽象,不然你会一直感觉在"盲填配置"。
Entity(实体)
Backstage 里的万物皆实体。用一个 catalog-info.yaml 文件描述一个组件、API、资源或系统:
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: user-service
description: 用户中心服务
annotations:
github.com/project-slug: myorg/user-service
backstage.io/techdocs-ref: dir:.
spec:
type: service
lifecycle: production
owner: team-platform
这个文件放在每个服务的 Git 仓库根目录。Backstage 通过扫描这些文件来构建你的"目录"。
Catalog(目录)
所有实体的注册中心。可以理解成公司的"服务黄页"。支持从多个来源导入:GitHub、GitLab、LDAP、甚至 CSV 文件。
Scaffolder(脚手架)
这是 Backstage 最值钱的能力——用模板创建新服务。
比如定义一个"标准 Go 微服务"模板,开发者点几个按钮,填个服务名,Backstage 就能在 GitHub 上创建仓库、初始化代码、配置 CI、注册到 Catalog,一气呵成。
TechDocs(技术文档)
把你仓库里的 Markdown 文档渲染成整洁的文档站点,支持代码高亮、Mermaid 图表、搜索。这功能单独拿出来都能当一个产品卖了。
Plugins(插件)
一切功能都是插件。Backstage 本身只是一个"空壳子",所有业务功能通过插件注入。
实战:搭一个最小可用门户
我的目标是:让团队能在 Backstage 上看到所有服务、找到每个服务的文档、用一个模板创建新服务。
第一步:配置 Catalog
Backstage 默认从自身的 examples 目录加载实体,显然这不够。我需要从 GitHub 组织拉取所有仓库的 catalog-info.yaml。
编辑 app-config.yaml:
catalog:
import:
entityFilename: catalog-info.yaml
pullRequestBranchName: backstage-integration
rules:
- allow: [Component, System, API, Resource, Location]
locations:
# 扫描整个 GitHub 组织
- type: github-discovery
target: https://github.com/myorg/*
rules:
- allow: [Component]
第二步:接入 GitHub OAuth
要让用户登录才能看实体信息,我配置了 GitHub OAuth App:
auth:
providers:
github:
development:
clientId: ${AUTH_GITHUB_CLIENT_ID}
clientSecret: ${AUTH_GITHUB_CLIENT_SECRET}
在生产环境还要配置 session 加密密钥:
curl https://backend:7007/api/auth/keys/generate
# 把返回的密钥写入环境变量
第三步:部署 TechDocs
TechDocs 需要额外的存储后端。我选的是最简单的方案——本地文件构建 + AWS S3 存储。
首先安装插件:
yarn --cwd packages/backend add @backstage/plugin-techdocs-backend
然后在 packages/backend/src/index.ts 注册:
backend.add(import('@backstage/plugin-techdocs-backend'));
注意: 生产环境中 TechDocs 的构建消耗很大。每个文档仓库会启动一个 Docker 容器来构建,建议至少给构建节点 4GB 内存。别问我怎么知道的——我第一次部署时直接把 2GB 的 ECS 打爆了。
第四步:创建 Scaffolder 模板
这是整个平台最花时间的部分,但也是收益最大的。
在 catalog/scaffolder-template.yaml 里定义一个模板:
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: go-microservice
title: Go 微服务
description: 创建一个标准 Go 微服务项目
spec:
owner: team-platform
type: service
parameters:
- title: 基础信息
required:
- serviceName
properties:
serviceName:
title: 服务名
type: string
description: 建议用连字符命名,如 user-service
servicePort:
title: 端口
type: number
default: 8080
steps:
- id: fetch-template
name: 拉取模板
action: fetch:template
input:
url: ./templates/go-microservice
values:
serviceName: ${{ parameters.serviceName }}
servicePort: ${{ parameters.servicePort }}
- id: publish
name: 推送到 GitHub
action: publish:github
input:
allowedHosts: ['github.com']
repoUrl: github.com?repo=${{ parameters.serviceName }}&owner=myorg
description: 自动创建的微服务项目
- id: register
name: 注册到 Catalog
action: catalog:register
input:
repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }}
catalogInfoPath: '/catalog-info.yaml'
模板文件放在 templates/go-microservice/ 目录下,里面是 Go 项目的基础结构,加上必填的 catalog-info.yaml 和 README 模板。
踩坑实录
坑 1:插件版本不兼容
Backstage 的插件生态虽然丰富,但版本地狱很真实。
有次我升级了 Backstage 版本,结果 @backstage/plugin-catalog-graph 和当前核心库不兼容,Graph 页面直接白屏。排查了 3 小时才在 GitHub Issue 里找到线索——某个内部依赖改了接口签名,但类型声明文件没更新。
教训:每次升级时不要直接 yarn upgrade。走官方提供的升级脚本:
yarn backstage-cli versions:bump
它会帮你处理版本对齐,虽然有时候也会翻车,但至少比手动升级靠谱。
坑 2:TechDocs 构建环境不一致
本地构建文档一切正常,上了 CI 就炸。原因是 TechDocs 的构建容器需要访问公司内部的 npm registry(@scope/packages),但 Docker 容器里没有配置 npm registry。
解决方案是在 app-config.yaml 里配置:
techdocs:
builder: 'local'
generators:
techdocs: 'docker'
containerImage: 'spotify/techdocs'
preparers:
techdocs: 'docker'
然后在 Docker 构建命令里注入环境变量。最后我把构建逻辑单独抽成了一个 GitHub Action,避免每次依赖环境。
坑 3:RBAC 权限配置复杂
Backstage 自带的权限系统很基础,要精细化权限控制得靠插件。
我用了 @backstage/plugin-permission-backend + @backstage/plugin-permission-common 来实现"每个团队只能看到自己的服务"。
配置示例:
import { createPermission } from '@backstage/plugin-permission-common';
export const serviceReadPermission = createPermission({
name: 'service.read',
attributes: { action: 'read' },
});
但这部分的文档写得比较松散,我至少看了 5 个 Issue 才搞清楚 permission policy 的正确写法。
上线后的效果
跑了一个月后我统计了几个数字:
- Onboarding 时间:从 3 天降到 4 小时(新同学来了直接看 TechDocs,不用等人教)
- 新服务创建时间:从小时级降到 10 分钟(Scaffolder 一键搞定)
- 团队成员对基础设施的提问:下降了 70%(因为黄金路径把复杂度藏起来了)
当然也有不理想的:不是所有人都买账。 有些老炮开发者觉得"你们搞个 Portal 反而把流程搞复杂了,以前我直接 SSH 上机器改多快"。这类声音要正视但不需要完全迎合——平台工程的价值在于规模化后的效率,而不是让某一个人的操作少点几下鼠标。
延伸思考
- 不要追求"大而全":我见过最离谱的 Backstage 配置接入了 20 多个插件,最终两个月后没人用了。从"解决一个具体问题"开始,比如先只做服务目录和文档,跑通了再加更多功能。
- 平台工程是组织问题,不是技术问题:技术上把 Backstage 跑起来只需一天,真正难的是让团队接受"以后创建服务必须走模板"这个流程变革。这是文化和习惯的问题,工具只是载体。
- 与现有工具的关系:Backstage 不是来替代 Jira、Confluence 或 GitLab 的,而是聚合它们的信息到一个入口。不要想着 All-in-One,那是违反事物发展规律的。
最后一句总结:Backstage 不会自动解决你的问题,但如果你清楚自己的开发者痛点在哪,它是一个非常好的"乐高底板"——剩下的,就是你看清痛点后一块一块往上拼积木的事。

评论已关闭!