每一个发展迅速的工程团队最终都会遇到同样的问题。

有开发人员需要一个新的测试环境,于是他们提交了工单。平台团队将这个请求加入队列中处理。

两周后,新的测试环境终于准备好了。不过它的配置与之前的版本有所不同,命名规则也与生产环境不一致,而且缺少之前环境中那些用于监控的工具。开发人员进行了部署,结果却出了问题——但没有人知道原因何在。

问题并不在于工单处理流程,而在于缺乏一个真正的平台:这样一个能够让开发人员自行配置基础设施、进行部署,并确保所有操作都具有一致性、可审计性和安全性的系统,而不需要每次都有平台工程师来协助处理每个请求。

内部开发人员平台(IDP)可以解决这个问题。它并不是通过取消对平台工程师的需求来解决问题的,而是将他们的工作重点从处理单个请求转移到构建那些能够自动完成这些请求的系统上。

本手册介绍了如何利用2026年时CNCF推荐的三种核心工具来构建一个具备生产级功能的IDP:其中Backstage作为开发人员门户和软件目录,ArgoCD作为GitOps持续交付引擎,Crossplane则作为与Kubernetes兼容的基础设施管理平台。

通过使用这个平台,开发人员将能够无需提交任何工单,就能完成配置云数据库、在测试环境中部署应用程序以及在软件目录中注册新服务等操作。

目录

你将学到什么

  • IDP的三层架构,以及为什么每一层都必须按照特定的顺序进行实现

  • 如何安装并配置ArgoCD,以及如何使用ApplicationSets来实现多环境下的GitOps部署

  • 如何利用Crossplane Compositions将云基础设施定义为Kubernetes的自定义资源

  • 如何配置Backstage,使其具备软件目录和模板功能

    如何将Backstage、ArgoCD和Crossplane有机地结合起来,构建一个完善的自助服务体系

  • 如何在IDP上实现成本归属机制,确保每一项资源配置都能关联到相应的团队和成本中心

  • 如何利用CNCF平台工程成熟度模型来评估你所构建的IDP的成熟程度

让我们开始构建它吧。

先决条件

在继续之前,您需要具备以下条件:

知识要求:

  • 熟悉Kubernetes的使用:能够部署应用程序、编写YAML配置文件,并理解命名空间和RBAC机制

  • 具备基本的GitOps概念:了解“将Git作为数据源”的实际意义

  • 能熟练阅读Helm、Terraform HCL以及TypeScript相关文档

  • 了解AWS相关服务:EKS、RDS、S3、IAM等

工具与访问权限:

  • 一个运行Kubernetes 1.28或更高版本的EKS集群,且至少拥有3个节点(m5.xlarge型号或同等配置的节点)

  • 已配置好并指向您的集群的kubectl工具

  • 已安装了版本为3.12或更高的helm工具

  • 已配置好的AWS CLI v2,且具备执行配置操作所需的管理员权限

  • Node.js 18或更高版本,以及用于Backstage项目的Yarn工具

  • 您能够控制的GitHub组织账户(用于托管GitOps仓库及实现与Backstage的集成)

配套代码库:

git clone https://github.com/aayostem/platform-toolkit
cd platform-toolkit

该代码库包含了本指南中提到的所有配置文件、Helm配置文件、Crossplane组件以及Backstage模板。这些内容分别对应代码库中的不同目录。

预计耗时:对于经验丰富的平台工程师来说,完成整个项目的实施过程需要一到两天的时间。其中第1至3部分的内容可以在早上就完成,从而构建出一个可正常使用的GitOps交付系统。

第1部分:IDP架构——三层模型

1.1 IDP究竟是什么

内部开发平台并不是一种工具,而是一种产品——它是由一系列工具、工作流程以及抽象层构成的系统。平台团队会构建并维护这些组件,这样应用程序开发人员就可以在不直接管理基础设施的情况下高效地开展工作。

这种区分非常重要,因为它会直接影响所有的架构设计决策。工具只是被安装和配置而已;而产品则是为用户设计的,会根据用户的反馈不断进行迭代改进,其成功与否也取决于用户是否真正采用了它。那些致力于打造深受开发人员喜爱的内部开发平台的团队,他们的思维方式更像产品经理,而不是系统管理员。

DORA 2025报告指出,目前几乎有90%的企业都拥有某种形式的内部开发平台。但是,仅仅拥有一个平台与让开发人员真正使用这个平台是两回事。

调查发现,开发人员对内部开发平台的满意度差异很大。而那些满意度高的团队与满意度低的团队之间的差距,直接取决于平台团队是将IDP视为具有发展路线图和用户调研的产品来建设,还是仅仅将其视为一个需要处理各种任务的单纯的基础设施项目来对待。

本指南中提到的三种工具——Backstage、ArgoCD和Crossplane——是2026年用于生产环境中的IDP部署的最广泛采用的开源技术栈。不过,将这些工具连接起来的架构同样重要。

1.2 三层架构

一个生产环境中的IDP系统由三个不同的层次构成,每个层次都承担着特定的职责:

第一层:开发者界面(Backstage)
├── 软件目录——包含所有服务、API和资源的清单
├── 软件模板——用于触发配置流程的自助服务工具
├── 技术文档——与每个软件条目一同存储的说明文件
└── 插件——用于与ArgoCD、Kubernetes、PagerDuty、Grafana等工具进行集成

第二层:部署层(ArgoCD)
├── GitOps同步——持续将集群状态与Git代码库保持一致
├── 应用组配置——通过单一定义实现多环境部署
├── 部署管理——包含健康检查机制的渐进式部署流程
└── 审计追踪——所有部署变更都会与相应的Git提交记录关联起来

第三层:基础设施层(Crossplane)
├── 复合资源——以Kubernetes CRD形式定义的云资源
├── 配置模板——可将简单的资源需求转化为完整的AWS基础设施配置
├── 供应商配置——针对不同云服务提供商设置的身份凭证和区域参数
└● 使用情况跟踪——每项被部署的资源都会标明所属团队和成本中心

一个至关重要的架构原则是:Backstage从不直接与Kubernetes或云API进行交互。当开发者通过Backstage提交一个软件模板时,其输出结果实际上是一个Git提交记录——这个YAML文件代表了Crossplane所需的配置信息或ArgoCD的应用程序配置文件。ArgoCD会获取这个提交记录并将其应用到集群中,而Crossplane则会将这些配置转化为实际的云基础设施。
这种间接的交互方式并非为了增加复杂性,而是为了让每一项基础设施变更都能被清晰地记录下来:每一次变更都会形成一条Git提交记录,其中包含作者信息、时间戳、拉取请求信息以及审核流程。审计追踪是自动完成的,而回滚操作则可以通过`git revert`命令来实现。
开发者 → Backstage模板 → Git提交 → ArgoCD → Crossplane → AWS

单一的数据来源
完整的审计追踪机制
回滚操作 = git revert

下面来看错误的实现方式——Backstage直接调用云API:
错误示例:Backstage模板直接调用AWS SDK
// 没有审计追踪记录,无法回滚,也无法进行状态同步
// 如果调用过程中出现故障,会导致部分基础设施处于异常状态且没有任何记录可查
import { S3Client, CreateBucketCommand } from "@aws-sdk/client-s3";

const client = new S3Client({ region: "us-east-1" });
await client.send(new CreateBucketCommand({ Bucket: bucketName }));

而正确的实现方式应该是——Backstage将配置信息生成Git提交记录,然后再由ArgoCD和Crossplane将其转化为实际的云基础设施。

# 正确的做法:使用后台模板进行输出,然后将相应的配置信息提交到Git中;
# ArgoCD会应用这些配置,Crossplane会负责协调这些配置的落实,而AWS则会创建相应的存储桶;
# 每一个步骤都会被记录下来,而且都可以被审计和撤销。
apiVersion: platform.cloudfrugal.com/v1alpha1
kind: S3Bucket
metadata:
  name: ${{ values.bucket_name }}
  namespace: ${{ values.team_namespace }}
  labels:
    team: ${{ values.team_name }}
    cost-centre: ${{ values.cost_centre }}
    environment: ${{ values.environment }}
spec:
  versioning: true
  encryption: AES256
  region: us-east-1

1.3 实施顺序

请按照以下顺序进行构建。如果违反这个顺序,就会导致集成问题,而这些问题会非常难以调试:

步骤1:ArgoCD——其他所有组件都依赖于它,它是实现持续交付的基础;
步骤2:Crossplane——作为基础设施控制层,由ArgoCD负责部署;
步骤3:Backstage——这个门户系统会将ArgoCD和Crossplane作为后端服务进行使用;
步骤4:结合这些组件——使用软件模板来生成GitOps配置文件;
步骤5:FinOps层——在每一项被创建的资源中添加用于记录成本归属的元数据。

第2部分:ArgoCD——GitOps的基础

ArgoCD是一款专为Kubernetes设计的声明式持续交付工具,它实现了GitOps架构。如果你之前没有使用过GitOps工具,那么它的核心理念其实非常简单:你的Git仓库就是决定你的集群中应该运行哪些资源的唯一权威来源,而ArgoCD会不断检查集群的实际状态,并确保它与Git仓库中的配置保持一致。

如果开发人员手动修改了集群中的某个资源,ArgoCD会立即检测到这种变化,并从Git仓库中重新获取最新的配置信息,然后应用到集群中。因此,完全不需要人工干预,而且我们也强烈建议不要进行人工干预——因为我们的目标就是让集群的状态始终能够通过Git仓库中的代码来完全解释。

ArgoCD是一个已经获得CNCF认证的项目,这意味着它已经具备了在生产环境中使用的条件,并且被广泛地应用着。它会在你的集群中以一组Pod的形式运行,同时提供Web界面、命令行接口以及REST API。所有用于管理多环境部署的功能都集中在一个地方。

2.1 安装ArgoCD

# 创建ArgoCD对应的命名空间
kubectl create namespace argocd

# 使用官方配置文件来安装ArgoCD
kubectl apply -n argocd \
  -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# 等待所有Pod启动完毕后再继续下一步操作
kubectl wait --for=condition=Ready pods \
  --all -n argocd --timeout=300s

# 获取初始管理员密码
argocd_password=$(kubectl -n argocd get secret argocd-initial-admin-secret \
  -o jsonpath="{.data.password}" | base64 -d)

echo "ArgoCD的初始密码是:$argocd_password"
echo "在继续下一步操作之前,请将这个密码保存在一个安全的地方"

# 打开端口以便在当地访问ArgoCD的Web界面
kubectl port-forward svc/argocd-server -n argocd 8080:443 &

# 通过命令行接口登录到ArgoCD
argocd login localhost:8080 \
  --username admin \
  --password "$argocd_password" \
  --insecure

# 立即更改密码
argocd account update-password \
  --current-password "$argocd_password" \
  --new-password "你的安全密码"

2.2 GitOps的仓库结构

ArgoCD所监控的仓库结构决定了你管理多个环境的方式。其中,最适用于大规模应用的架构是“每个环境对应一个目录”,而具体的配置覆盖信息则由Kustomize工具来管理。

Kustomize是一款专为Kubernetes设计的配置管理工具,它允许你一次性定义基础配置,然后再在此基础上添加针对特定环境的修改内容。这意味着,你的测试环境和生产环境的配置文件会使用相同的YAML结构,但它们在副本数量、镜像标签以及资源限制等方面存在差异。

gitops-repo/
├── apps/
│   ├── base/                    # 所有环境共用的基础配置
│   │   ├── payment-api/
│   │   │   ├── deployment.yaml
│   │   │   ├── service.yaml
│   │   │   └── kustomization.yaml
│   │   └── user-api/
│   │       ├── deployment.yaml
│   │       ├── service.yaml
│   │       └── kustomization.yaml
│   └── overlays/
│       ├── staging/             # 仅适用于测试环境的配置覆盖
│       │   ├── payment-api/
│       │   │   └── kustomization.yaml   # 修改内容:1个副本,使用测试环境专属的镜像标签
│       │   └── kustomization.yaml
│       └── production/          # 仅适用于生产环境的配置覆盖
│           ├── payment-api/
│           │   └── kustomization.yaml   # 修改内容:3个副本,使用生产环境专属的镜像标签
│           └── kustomization.yaml
└── infrastructure/
    ├── crossplane/              # 负责跨平台的配置管理及相关服务
    ├── monitoring/              # 用于监控的工具,如Prometheus和Grafana
    └── ingress/                 # 负责处理外部访问请求的控制器,如NGINX或ALB

2.3 ApplicationSets——管理多个环境

ApplicationSet是ArgoCD提供了一种资源类型,它能够根据一个模板生成多个应用程序对象。与为每个环境、每项服务分别创建配置文件相比,这种方式在处理大规模应用时更加高效。你只需要定义一个ApplicationSet,就可以覆盖所有环境中的所有服务。ArgoCD会自动结合环境列表与Git仓库目录的内容,从而生成所有可能的配置组合:

# applicationset-apps.yaml
# 这个资源文件会根据不同的环境与应用程序目录组合,
# 为每个组合生成一个对应的ArgoCD应用程序对象
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: platform-apps
  namespace: argocd
spec:
  generators:
    - matrix:
        generators:
          # 第一种生成规则:确定环境列表
          - list:
              elements:
                - environment: staging
                  cluster: https://staging.eks.cluster.local
                - environment: production
                  cluster: https://production.eks.cluster.local

          # 第二种生成规则:查找对应的应用程序目录
          - git:
              repoURL: https://github.com/your-org/gitops-repo
              revision: HEAD
              directories:
                - path: apps/overlays/{{environment}}/*

  template:
    metadata:
      name: "{{environment}}-{{path basename }}"
      labels:
        environment: "{{environment]}"
        app: "{{pathbaseline }}"
    spec:
      project: default
      source:
        repoURL: https://github.com/your-org/gitops-repo
        targetRevision: HEAD
        path: "apps/overlays/{{environment}}/{{pathbasename}}"
      destination:
        server: "{{cluster」

        namespace: "{{path.basename }}"
      syncPolicy:
        automated:
          prune: true        # 删除从Git仓库中移除的资源
          selfHeal: true     # 自动恢复因手动操作而导致的配置错误
        syncOptions:
          - CreateNamespace=true
          - PrunePropagationPolicy=foreground

验证ApplicationSet是否正在生成预期的应用程序:

# 列出所有生成的应用程序
kubectl get applications -n argocd

# 预期输出:每个环境、每个应用程序对应一个条目
# staging-payment-api    已同步    运行正常
# staging-user-api       已同步    运行正常
# production-payment-api  已同步    运行正常
# production-user-api  已同步    运行正常

# 检查特定应用程序的同步状态
argocd app get staging-payment-api

2.4 ArgoCD为平台团队提供的RBAC功能

在多团队环境中,不同团队对ArgoCD的访问权限需求各不相同。应用程序团队应能够查看并同步自己负责的应用程序;而平台团队则需要更广泛的访问权限。不过,任何人都不应该通过ArgoCD获得无限制的集群管理权限。

默认的策略是readonly——所有经过身份验证的用户都可以查看所有信息,但无法进行任何修改:

# argocd-rbac-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-rbac-cm
  namespace: argocd
data:
  policy.default: role:readonly
  policy.csv: |
    # 平台团队:对所有应用程序拥有完全访问权限
    p, role:platform-team, applications, *, */*, allow
    p, role:platform-team, clusters, get, *, allow
    p, role:platform-team, repositories, *, *, allow

    # 应用程序团队:仅能同步和查看自己负责的应用程序
    p, role:app-team, applications, get, */staging-*, allow
    p, role:app-team, applications, sync, */staging-*, allow

    # 将角色与GitHub团队关联起来
    g, your-org:platform-engineers, role:platform-team
    g, your-org:developers, role:app-team

  scopes: '[groups]'

第3部分:Crossplane——将基础设施视为Kubernetes资源

Crossplane是一个经过CNCF认证的开源框架,它能够将Kubernetes扩展为一种通用的基础设施控制平台。

其核心理念在于:不必使用Terraform或CloudFormation等独立工具来管理集群外的云资源,而是可以直接将这些云资源(如RDS数据库、S3存储桶、VPC或IAM角色)定义为Kubernetes的自定义资源。

一旦将Crossplane资源应用到集群中,Crossplane的控制器就会自动将目标状态与实际的AWS资源状态进行对比并使其保持一致,这一过程与Kubernetes管理Pods的方式完全相同。

Crossplane所引入的关键抽象概念是“复合资源”。平台团队可以定义一种高层次的PostgreSQLDatabase类型,这种类型能够概括实际RDS实例所需的众多配置参数。

开发人员只需与这种简化后的类型进行交互,Crossplane会在后台将其转换为完整的AWS资源配置,并自动应用平台团队制定的安全与运营标准——由于开发人员看不到底层的配置细节,因此他们也无法绕过这些标准。

3.1 安装Crossplane

Crossplane是通过ArgoCD部署到您的集群中的——这是这两种工具首次实现集成。通过使用ArgoCD应用程序来安装Crossplane,而不是直接运行`helm install`命令,就可以使Crossplane本身成为由GitOps管理的基础设施的一部分。对Crossplane配置的任何修改都会通过Git提交并进行审核:

# infrastructure/crossplane/application.yaml
# 通过Helm安装Crossplane的ArgoCD应用程序
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: crossplane
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://charts.crossplane.io/stable
    chart: crossplane
    targetRevision: 1.15.0
    helm:
      values: |
        provider:
          packages:
            # AWS提供程序——用于管理所有的AWS资源
            - xpkg.upbound.io/upbound/provider-aws-s3:v1.2.0
            - xpkg.upbound.io/upbound/provider-aws-rds:v1.2.0
            - xpkg.upbound.io/upbound/provider-aws-iam:v1.2.0
  destination:
    server: https://kubernetes.default.svc
    namespace: crossplane-system
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
# 应用ArgoCD应用程序——ArgoCD会负责安装Crossplane
kubectl apply -f infrastructure/crossplane/application.yaml

# 监控CrossplanePod的启动情况
kubectl get pods -n crossplane-system -w

# 验证提供程序是否已成功安装且运行正常
kubectl get providers
# 预期输出:
# NAME                          INSTALLED   HEALTHY   PACKAGE
# upbound-provider-aws-s3       True        True      xpkg.upbound.io/...
# upbound-provider-aws-rds      True        True      xpkg.upbound.io/...

3.2 提供程序凭证

Crossplane需要AWS凭证才能配置资源。对于EKS环境来说,推荐使用服务账户的IAM角色(IRSA)——这种机制可以让Kubernetes Pod直接继承这些IAM角色,而无需在集群中存储任何凭证。

Pod对应的Kubernetes服务账户会被标注上相应的IAM角色ARN,当Pod发起API调用时,AWS会自动提供临时有效的凭证。这样一来,就不存在需要管理的访问密钥或秘密信息,也不会有凭证泄露的风险:

# 为Crossplane创建具有所需AWS权限的IAM角色
aws iam create-role \
  --role-name CrossplaneProviderRole \
  --assume-role-policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::YOUR_ACCOUNT_ID:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/YOUR OIDC_ID"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.eks.us-east-1.amazonaws.com/id/YOUROIDC_ID:sub":
            "system:serviceaccount:crossplane-system:provider-aws"
        }
      }
    }]
  }'

# 将权限策略应用到该角色
aws iam attach-role-policy \
  --role-name CrossplaneProviderRole \
  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
# provider-config.yaml
# 使用IRSA配置AWS提供者——无需使用静态凭据
apiVersion: aws.upbound.io/v1beta1
kind: ProviderConfig
metadata:
  name: default
spec:
  credentials:
    source: IRSA   # 使用与提供者服务账户关联的IAM角色

3.3 定义复合资源——PostgreSQL数据库

IDP抽象层就是在这里实现的。平台团队会定义两个YAML文件:CompositeResourceDefinition(XRD),它规定了开发者可以请求的资源类型;以及Composition,它说明了这些请求如何被转换为符合平台标准的实际AWS资源。

XRD实际上是开发者与平台之间的API契约。因此应该保持其简洁性——只有那些开发者确实需要控制的字段才应该出现在其中:

# xrd-postgresql.yaml
# 定义开发者可以请求的PostgreSQLDatabase类型
# 开发者永远不会看到下面这些针对RDS的特定配置选项
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
  name: xpostgresqldatabases.platform.cloudfrugal.com
spec:
  group: platform.cloudfrugal.com
  names:
    kind: XPostgreSQLDatabase
    plural: xpostgresqldatabases
  claimNames:
    kind: PostgreSQLDatabase     # 这就是开发者最终会创建的资源类型
    plural: postgresqldatabases
  versions:
    - name: v1alpha1
      served: true
      referenceable: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                # 仅针对开发者的字段——简单且有明确的取值范围
                storageGB:
                  type: integer
                  minimum: 20
                  maximum: 1000
                  description: "存储空间大小,单位为GB。最小值为20,最大值为1000。”
                instanceClass:
                  type: string
                  enum: ["small", "medium", "large"]
                  description: "small对应db.t4g.medium实例类型,medium对应db.r7g.large实例类型,large对应db.r7g.2xlarge实例类型"
                environment:
                  type: string
                  enum: ["staging", "production"]

Composition文件则是平台团队的实现部分。它将开发者提供的简单信息映射为完整的RDS配置,并强制应用那些开发者无法覆盖的平台标准:

# composition-postgresql.yaml
# 定义PostgreSQLDatabase资源具体会包含哪些内容
# 这里会应用平台标准(如加密、备份、删除保护等)
# 开发者无法更改这些设置——平台会强制执行它们
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: postgresql-aws-composition
  labels:
    provider: aws
spec:
  compositeTypeRef:
    apiVersion: platform.cloudfrugal.com/v1alpha1
    kind: XPostgreSQLDatabase

  resources:
    # 实际的RDS实例——由开发者提供的简单信息生成而来
    - name: rds-instance
      base:
        apiVersion: rds.aws.upbound.io/v1beta1
        kind: Instance
        spec:
          forProvider:
            region: us-east-1
            engine: postgres
            engineVersion: "15.4"
            # 平台标准——始终会被应用,开发者无法进行配置
            storageEncrypted: true           # 数据总是被加密存储
            backupRetentionPeriod: 7         # 总是会保留7天的备份数据
            deletionProtection: true         # 数据总是会受到删除保护
            multiAZ: false                   # 在生产环境中会自动设置为true(具体配置需参考相关补丁文件)
            dbSubnetGroupNameSelector:
              matchLabels:
                platform.cloudfrugal.com/subnet-group: private
      patches:
        # 将开发者指定的简单instanceClass映射为实际的RDS实例类型
        - type: CombineFromComposite
          combine:
            variables:
              - fromFieldPath: spec.instanceClass
            strategy: string
            string:
              fmt: |
                %s
          toFieldPath: spec.forProvider.dbInstanceClass
          transforms:
            - type: map
              map:
                small:  db.t4g.medium
                medium: db.r7g.large
                large:  db.r7g.2xlarge

        # 在生产环境中自动启用多AZ配置
        - type: FromCompositeFieldPath
          fromFieldPath: spec.environment
          toFieldPath: spec.forProvider.multiAZ
          transforms:
            - type: map
              map:
                staging:    "false"
                production: "true"

        # 将团队标签从资源定义信息中复制到RDS实例上,以便进行成本分配
        - type: FromCompositeFieldPath
          fromFieldPath: metadata.labels
          toFieldPath: spec.forProvider.tags

现在,请求使用 PostgreSQL 数据库的开发者只需编写如下代码即可完成配置:

# 开发者在自己团队的命名空间中创建这个资源
# 不需要了解 RDS 的相关知识,也不需要进行 IAM 配置或子网组查询。
apiVersion: platform.cloudfrugal.com/v1alpha1
kind: PostgreSQLDatabase
metadata:
  name: payment-service-db
  namespace: payments-team
  labels:
    team: payments
    cost-centre: payments-engineering
    environment: staging
spec:
  storageGB: 100
  instanceClass: medium
  environment: staging

Crossplane 会在几分钟内将这个配置转化为一个完整的 RDS 实例,同时会自动应用加密、备份等所有平台标准。

3.4 验证 Crossplane 的资源配置情况

# 监控配置状态——它应该会变为 “Ready=True”
kubectl get postgresqldatabases -n payments-team -w

# 查看该复合资源的详细状态
kubectl describe xpostgresqldatabases.platform.cloudfrugal.com

# 确认实际的 AWS 资源是否已被创建
aws rds describe-db-instances \
  --query 'DBInstances[?TagList[?Key==`team` && Value==`payments`]].[DBInstanceIdentifier,DBInstanceStatus]' \
  --output table

第 4 部分:开发者的后台管理门户

Backstage 是一个由 Spotify 开发、目前由 CNCF 推广的开源框架。它作为 IDP 对开发者提供的接口,让开发者能够在一个地方查找服务、申请基础设施资源以及查阅相关文档,而无需了解这些资源实际上是由哪些系统提供的。

Backstage 提供了三项核心功能:

  1. 一个软件目录,用于列出组织内所有的服务、API、库和资源

  2. 软件模板,为开发者提供自助服务工具,以便他们能够配置基础设施或搭建新的服务框架

  3. 技术文档,将相关文档与所描述的资源放在同一个地方,确保用户可以轻松找到这些文档

Backstage 是用 TypeScript 编写的,前端采用 React,后端使用 Node.js。它是一种配置型工具,而不是需要安装的软件:你需要创建一个 Backstage 应用程序,根据自己组织的具体需求进行配置,然后将其部署到集群中。

4.1 创建并配置 Backstage

# 创建一个新的 Backstage 应用程序
npx @backstage/create-app@latest

# 根据提示填写相关信息:
# 应用程序名称:platform-portal
# 选择 SQLite 用于本地开发,选择 PostgreSQL 用于生产环境

cd platform-portal

接下来需要配置 Backstage,使其能够连接到你的 ArgoCD 实例和 GitHub:

# production 配置文件
app-config.production.yaml
app:
  title: Cloudfrugal Platform Portal
  baseUrl: https://platform.your-company.com

backend:
  baseUrl: https://platform.your-company.com
  database:
    client: pg
    connection:
      host: ${POSTGRES_HOST}
      port: 5432
      user: ${POSTGRES_USER}
      password: ${POSTGRES_PASSWORD}
      database: backstage

# 与 GitHub 集成,以便查找目录信息和使用模板
integrations:
  github:
    - host: github.com
      apps:
        - appId: ${GITHUB_APP_ID}
          webhookSecret: ${GITHUB_WEBHOOK_SECRET}
          clientId: ${GITHUB_CLIENT_ID}
          clientSecret: ${GITHUB_CLIENT_SECRET}
          privateKey: ${GITHUB_PRIVATE_KEY}

# ArgoCD 插件配置
argocd:
  username: ${ARGOCD_USERNAME}
  password: ${ARGOCD_PASSWORD}
  appLocatorMethods:
    - type: 'config'
      instances:
        - name: main
          url: https://argocd.your-company.com

# 自动查找目录信息——会在你的 GitHub 仓库中搜索 catalog-info.yaml 文件
catalog:
  providers:
    github:
      your-org:
        organization: 'your-github-org'
        catalogPath: '/catalog-info.yaml'
        filters:
          branch: 'main'

4.2 软件目录——服务注册

平台中的所有服务、API、库和资源都应通过提交到相应服务仓库中的`catalog-info.yaml`文件,在Backstage目录中完成注册。借助与GitHub的集成,Backstage会自动检测到这些文件;只要文件存在,就无需进行手动注册:

# catalog-info.yaml — 提交到每个服务的仓库根目录
# Backstage会通过GitHub集成自动检测到该文件
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: payment-api
  title: 支付API
  description: "核心支付处理服务,负责交易发起、授权和结算操作。"
  annotations:
    # 链接ArgoCD,以便在Backstage用户界面中查看部署状态
    argocd/app-name: production-payment-api
    # 链接GitHub Actions工作流程的状态
    github.com/project-slug: your-org/payment-api
    # 链接用于显示该服务的Grafana仪表盘
    grafana/dashboard-selector: "title=Payment API"
    # 链接PagerDuty的值班排班表
    pagerduty.com/service-id: P123456
  tags:
    - payments
    - typescript
    - critical
  links:
    - url: https://payment-api.docs.your-company.com
      title: 文档资料
    - url: https://grafana.your-company.com/d/payment-api
      title: Grafana仪表盘
spec:
  type: service
  lifecycle: production
  owner: group:payments-team
  system: payment-platform
  dependsOn:
    - component:user-api
    - resource:payment-service-db
  providesApis:
    - payment-api-v2

4.3 软件模板——自助服务基础设施

软件模板是一种Backstage表单,提交后会生成一个Git提交。该提交包含了模板所定义的YAML代码或配置信息。

对于基础设施配置而言,模板生成的输出是一个Crossplane声明;而对于新服务的搭建来说,输出则是一个完整的服务框架,会被提交到一个新的仓库中。

关键的设计原则是:模板应该生成拉取请求,而不是直接进行合并。这样的设计可以让平台团队了解相关情况,让开发人员有机会进行审核,同时也能为所有相关人员提供审计追踪记录。一旦人们对模板的输出结果有了足够的信任,自动合并机制就可以省略审核环节,从而加快低风险配置任务的执行速度:

# templates/postgresql-database/template.yaml
# 这个模板为开发人员提供了申请PostgreSQL数据库的表单
# 生成的结果是一个会被提交到GitOps仓库中的Crossplane PostgreSQLDatabase声明
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: postgresql-database
  title: PostgreSQL数据库
  description: 在AWS RDS上配置一个受管理的PostgreSQL数据库。平台会自动处理加密、备份和删除保护等设置。
  tags:
    - database
    - postgresql
    - aws
spec:
  owner: group:platform-team
  type: infrastructure

  # 开发人员在Backstage用户界面中填写的表单信息
  parameters:
    - title: 数据库配置
      required: [name, team, environment, storageGB, instanceClass]
      properties:
        name:
          title: 数据库名称
          type: string
          description: "只能使用小写字母和连字符,例如:payment-service-db"
          pattern: '^[a-z][a-z0-9-]*$'

        team:
          title: 所属团队
          type: string
          description: "用于成本分配和确定责任归属的团队名称。"
          ui:field: OwnerPicker
          ui:options:
            catalogFilter:
              kind: Group

        environment:
          title: 环境类型
          type: string
          enum: [staging, production]
          default: staging

        storageGB:
          title: 存储空间(GB)
          type: integer
          minimum: 20
          maximum: 1000
          default: 50

        instanceClass:
          title: 实例规格
          type: string
          enum: [small, medium, large]
          enumNames:
            - "Small (db.t4g.medium) — 适用于开发/测试环境"
            - "Medium (db.r7g.large) — 适合中等流量的生产环境"
            - "Large (db.r7g.2xlarge) — 适用于高流量的生产环境"
          default: small

  # 模板提交后执行的操作
  steps:
    - id: generate-claim
      name: 生成Crossplane声明
      action: fetch:template
      input:
        url: ./skeleton    # 包含Crossplane声明的YAML模板文件
        values:
          name: ${{ parameters.name }}
          team: ${{ parameters.team | parseEntityRef | pick('name') }}
          environment: ${{ parameters.environment }}
          storageGB: ${{ parameters.storageGB }}
          instanceClass: ${{ parameters.instanceClass }}

    - id: create-pr
      name: 向GitOps仓库创建拉取请求
      action: publish:github:pull-request
      input:
        repoUrl: github.com?repo=gitops-repo&owner=your-org
        title: "平台:为${{ parameters.team }}团队配置${{ parameters.name }} PostgreSQL数据库"
        branchName: "provision-db-${{ parameters.name }}-${{ '' | now }}"
        description: |
          正在请求通过Crossplane工具配置PostgreSQL数据库。

          - **名称:** ${{ parameters.name }}
          - **团队:** ${{ parameters.team }}
          - **环境:** ${{ parameters.environment }}
          - **存储空间:** ${{ parameters.storageGB }}GB
          - **实例规格:** ${{ parameters.instanceClass }}

          请批准此拉取请求以启动配置流程。ArgoCD会捕获这一变更,Crossplane会在合并后约5分钟内创建RDS实例。
        sourcePath: ./skeleton

  output:
    links:
      - title: 查看拉取请求
        url: ${{ steps['create-pr'].output.remoteUrl }}
      - title: 在ArgoCD中跟踪配置进度
        url: https://argocd.your-company.com/applications

模板框架目录中包含了包含模板变量占位符的Crossplane配置文件:

# templates/postgresql-database/skeleton/databases/${{ values.name }}.yaml
apiVersion: platform.cloudfrugal.com/v1alpha1
kind: PostgreSQLDatabase
metadata:
  name: ${{ values.name }}
  namespace: ${{ values.team }}-platform
  labels:
    team: ${{ values.team }}
    cost-centre: ${{ values.team }}-engineering
    environment: ${{ values.environment }}
    managed-by: backstage-scaffolder
spec:
  storageGB: ${{ values.storageGB }}
  instanceClass: ${{ values.instanceClass }}
  environment: ${{ values.environment }}

第5部分:将各环节连接起来——实现“黄金路径”

“黄金路径”代表一个完整的端到端工作流程:开发人员使用Backstage来申请所需的基础设施资源,这一申请会转化为Git提交命令;ArgoCD会将这些提交应用到集群中;Crossplane则会实际分配AWS资源,最终这些资源会同时显示在Backstage目录和ArgoCD控制面板中。

5.1 完整的工作流程

开发人员在Backstage中填写表格
    ↓
Backstage软件模板会生成相应的Crossplane配置文件YAML代码
    ↓
Backstage会在GitOps仓库中创建一个拉取请求
    ↓
平台工程师会批准并合并这个拉取请求
    ↓>
ArgoCD会检测到GitOps仓库中的新文件
    ↓>
ArgoCD会将这些配置应用到集群中
    ↓>
Crossplane会将这些配置转化为实际的AWS RDS实例
    ↓>
开发人员会通过Kubernetes Secret获取数据库的访问信息
    ↓>
Backstage目录会显示这些新资源,且这些资源归申请该资源的团队所有

5.2 在Backstage中查看资源状态

Backstage的Kubernetes插件会从您的集群中获取实时的Pod及资源状态,并将这些信息显示在每个目录页面上。开发人员无需离开Backstage界面,也无需学习如何使用kubectl命令,就能了解他们的服务是否正在运行、有多少副本是正常的,以及最后一次部署操作是否已经完成。

# 安装Kubernetes插件
cd platform-portal
yarn --cwd packages/app add @backstage/plugin-kubernetes
yarn --cwd packages/backend add @backstage/plugin-kubernetes-backend
# app-config.production.yaml — 添加Kubernetes集群配置
kubernetes:
  serviceLocatorMethod:
    type: 'multiTenant'
  cluster LocatorMethods:
    - type: 'config'
      clusters:
        - name: production-eks
          url: ${PRODUCTION_CLUSTER_URL}
          authProvider: serviceAccount
          serviceAccountToken: ${PRODUCTION_SA_TOKEN}
          caData: ${PRODUCTION_CA_DATA}
        - name: staging-eks
          url: ${STAGING_cluster_URL}
          authProvider: serviceAccount
          serviceAccountToken: ${STAGING-SA_TOKEN}
          caData: ${STAGING_CA DATA}

为每个目录实体添加注释,以便将其与相应的Kubernetes资源关联起来:

# 在每个服务的catalog-info.yaml文件中添加以下注释:
annotations:
  backstage.io/kubernetes-label-selector: 'app=payment-api'
  backstage.io/kubernetes-namespace: payments-team

5.3 安装ArgoCD插件

ArgoCD插件可以直接在Backstage实体页面上显示部署历史和同步状态。当开发人员在目录中打开payment-api页面时,他们可以查看最近10次部署情况、当前的同步状态以及应用程序是否正常运行——而无需打开ArgoCD用户界面:

yarn --cwd packages/app add @roadiehq/backstage-plugin-argo-cd
// files/packages/app/src/components/catalog/EntityPage.tsx
import { EntityArgoCDOverviewCard } from '@roadiehq/backstage-plugin-argo-cd';

// 将该插件添加到服务实体页面的布局中
const serviceEntityPage = (
  
    
        
        
      <\/Grid>
    <\/EntityLayout.Route>
  
);

第6部分:FinOps集成——在IDP上实现成本归属功能

如果使用IDP来配置资源却不进行成本归属,就会产生新的问题:虽然基础设施的配置过程是自动化的,但却无法明确知道由此产生的费用应由哪个团队承担。因此,通过IDP创建的每一项资源,都必须从被配置的那一刻起就携带相应的团队和成本中心元数据。

6.1 每个跨平台组合中都必须添加强制性标签

成本归属功能实际上是在平台层实现的——而不是在开发人员使用的申请系统中。这些标签会作为标记被传递到实际的AWS资源上,因此它们会出现在AWS Cost Explorer中,也可以用来生成团队级别的成本报告:

# 在每个跨平台组合中,添加强制性的成本归属标签
patches:
  # 这些标签会被直接添加到实际的AWS资源上
  # 开发人员无法忽略或修改这些标签
  - type: FromCompositeFieldPath
    fromFieldPath: metadata.labels[team]
    toFieldPath: spec.forProvider.tags[team]

  - type: FromCompositeFieldPath
    fromFieldPath: metadata.labels[cost-centre]
    toFieldPath: spec.forProvider-tags[cost-centre]

  - type: FromCompositeFieldPath
    fromFieldPath: metadata.labels[environment]
    toFieldPath: spec.forProvider.tags[environment]

  # 添加一个“managed-by”标签,用于标识所有通过IDP配置的资源
  - type: FromCompositeFieldPath
    fromFieldPath: metadata.name
    toFieldPath: spec.forProvider-tags[managed-by]
    transforms:
      - type: string
        string:
          fmt: "idp-crossplane"

6.2 成本归属查询

由于所有资源都配备了必要的标签,因此你可以直接通过AWS Cost Explorer按团队查询实际成本:

# 按团队划分的月度成本明细——所有由IDP提供的资源
aws ce get-cost-and-usage \
  --time-period Start=$(date -d 'last month' +%Y-%m-01),End=$(date +%Y-%m-01) \
  --granularity MONTHLY \
  --filter '{
    "Tags": {
      "Key": "managed-by",
      "Values": ["idp-crossplane"]
    }
  }' \
  --group-by Type=TAG,Key=team \
  --metrics UnblendedCost \
  --query 'ResultsByTime[0].Groups[*].{Team:Keys[0],Cost:Metrics.UnblendedCost.Amount}' \
  --output table

现在,任何通过IDP配置资源的团队,在成本报告中都会有一条记录,上面会标注他们的名称。正是这种费用归属机制,使得FinOps能够在大规模平台上持续运作——成本分配是自动完成的,而非人工操作。

第7部分:平台成熟度模型——衡量你所构建的成果

CNCF平台工程成熟度模型将平台的成熟度划分为五个等级。了解自己目前所处的阶段,有助于你决定接下来应该开发什么功能,并向工程团队领导层汇报进展。

等级 名称 特征
1 初步阶段 使用临时脚本进行配置,采用手动操作,没有标准化工具
2 运营阶段 使用标准化工具,部分流程实现了自动化,开始部署Kubernetes
3 可扩展阶段 拥有自助服务门户,采用GitOps进行代码交付,有明确的最佳实践流程
4 优化阶段 能够实现成本归属管理,平台本身具备服务水平目标,同时设有用户反馈机制
5 优化完成阶段 采用人工智能辅助资源配置,具备预测性扩展能力,FinOps各项功能已实现全面整合

如果你的系统完整实现了Backstage、ArgoCD和Crossplane,并且具备了成本归属管理功能以及能够满足开发者常见需求的软件模板,那么你就处于第3级。要进入第4级,就需要在平台上添加服务水平目标警报机制,定期开展开发者体验调查,并根据成本归属标签生成按团队划分的月度成本报告。

在第3级阶段,最常见的错误就是一味地开发新功能,而忽略了实际使用情况。如果一个平台拥有12个软件模板,但只有2个被经常使用,那么它实际上还没有达到第3级——它仍然处于第2级,只不过使用了更多的YAML配置文件而已。因此,你需要了解哪些最佳实践流程真正被开发者所采用,与那些没有使用该平台的开发者进行交流,并解决其中存在的问题,然后再继续添加新的功能。

最佳实践总结

建议:应按顺序进行开发:先实现ArgoCD,再开发Crossplane,最后完成Backstage的构建。每一层功能的实现都依赖于前一层的基础。

建议:应将Backstage作为生成Git提交命令的工具来使用,而不是用来直接操作基础设施。所有与基础设施相关的变更都必须通过可审计的Git提交来完成。

建议这样做:应在 Crossplane Composition 层中添加成本分配标签,而不要在开发者的配置中设置这些标签。如果开发者可以绕过这些分配规则,那么它们就会被真正地忽略掉。

建议这样做:先从两三个软件模板开始入手,确保这些模板的质量达到标准后再继续开发其他模板。模板的使用情况应该是您在初期评估项目进展时最重要的参考指标。

建议这样做:从项目开始的第一天起,就应将所有服务都注册到 Backstage 目录中。目录的价值与其覆盖的范围成正比。

建议这样做:应通过 ArgoCD 将 Crossplane 部署到您的集群中,而不是使用 helm install 命令。凡是 IDP 负责管理的资源,都应该由 IDP 自身来处理。

不要这样做:不要直接将 Backstage 与云服务 API 连接起来。这样会导致没有审计追踪机制、无法进行回滚操作,也无法实现数据的一致性管理。

不要这样做:不要直接让开发者使用 Crossplane 的 XRD 功能。设置 Composition 抽象层的目的就是为了隐藏与 RDS 相关的配置细节,并强制所有操作遵循平台标准。如果绕过这一机制,那么设置这些规则的意义就完全丧失了。

不要这样做:不要孤立地开发 IDP 系统,然后就宣布它已经完成开发。平台工程实际上属于产品工程的一部分;在前两个模板正式投入使用之后,再安排与用户的交流会议吧。

不要这样做:千万不要忽略 ArgoCD 的 RBAC 配置。如果一个 IDP 通过部署层为所有开发者提供了集群管理员权限,那么它所引发的安全问题将会远远超过它原本想要解决的问题。

资源

Comments are closed.