Cómo empezar con Backstage de Spotify

Estos días de vacaciones he estado jugando con Backstage para ver cómo encaja dentro de esta disciplina llamada Platform Engineering de la cual te hablaré en breve en mi canal de YouTube 🙂 Lo cierto es que no me ha resultado muy sencillo empezar y es por ello que hoy quiero compartir contigo algunas de las cosas que he ido configurando para probar los tres puntos que a mi me parecen principales: el catálogo, documentación y las plantillas.

Configuración de un Dev Container para Backstage

Desde hace ya mucho tiempo procuro que todas las configuraciones que un proyecto concreto necesita no las realice directamente en mi local sino que hago uso de un Dev Container para configurar en él todo lo que necesito. En el caso de Backstage necesitas la versión 18 o 20 de Node.js para que funcione correctamente, además de una base de datos Postgresql si quieres simular el mismo comportamiento que potencialmente tendrá en un entorno productivo (cuando creas tu proyecto de Backstage utiliza una base de datos en memoria). Por lo tanto, antes de empezar a crear lo necesario para Backstage, me he creado un directorio vacío, el cual he llamado backstage-demo, y he añadido el archivo .devcontainer/devcontainer.json con la siguiente configuración:

// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/debian
{
	"name": "Spotify Backstage Dev Container",
	// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
	"dockerComposeFile": "docker-compose.yml",
	"service": "app",
	"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
	// "image": "mcr.microsoft.com/devcontainers/base:bullseye",
	"features": {
		"ghcr.io/devcontainers/features/node:1": {
			"nodeGypDependencies": true,
			"version": "18",
			"nvmVersion": "latest"
		},
		"ghcr.io/devcontainers/features/docker-in-docker:2": {}
	},
	"forwardPorts": [
		3000,
		7007,
		8080
	],
	"containerEnv": {
		"POSTGRES_HOST": "db",
		"POSTGRES_PORT": "5432",
		"POSTGRES_USER": "postgres",
		"POSTGRES_PASSWORD": "example"
	},
	"customizations": {
		"vscode": {
			"extensions": [
				"ms-ossdata.vscode-postgresql"
			]
		}
	}
	// Features to add to the dev container. More info: https://containers.dev/features.
	// "features": {},
	// Use 'forwardPorts' to make a list of ports inside the container available locally.
	// "forwardPorts": [],
	// Configure tool-specific properties.
	// "customizations": {},
	// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
	// "remoteUser": "root"
}

Este a su vez hace uso de una configuración para Docker Compose, almacenada en la ruta .devcontainer/docker-compose.yml, que nos permite configurar una base de datos junto el workspace del proyecto que te voy a mostrar.

version: "3.8"
services:
  
  app:
    image: mcr.microsoft.com/devcontainers/base:bullseye
    volumes:
      - ../..:/workspaces:cached
    command: sleep infinity
    networks:
    - backstage
  db:    
    image:  postgres
    environment:
      POSTGRES_PASSWORD: example
    networks:
      - backstage
    ports:
      - 5432:5432
      
networks:
  backstage:

Crear tu aplicación de Bbackstage

En realidad, Backstage se trata de una aplicación en React que se compone inicialmente de dos partes: un front end y un back end. Esta debes crearla, mantenerla y hospedarla en algún sitio, que ya te mostraré en otro artículo más adelante. Una vez que ya estés dentro de tu Dev Container, ejecuta el siguiente comando para crearla:

npx @backstage/create-app@latest

Este proceso va a tardar varios minutos en completarse, ya que tiene muchas dependencias que descargar. Una vez que finalice, puedes ejecutar el mismo con lo siguiente:

cd backstage && yarn dev

¡Felicidades 🎉! Ya tienes tu instancia de Backstage creada y ejecutándose. Ahora solo falta todo lo demás 🙃 Sigue leyendo para poder configurar la base de datos, integración con GitHub, etcétera…

Configurar la base de datos para Backstage

Cuando creas tu Backstage este utiliza una base de datos SQLite para almacenar la información, lo cual está muy bien para un entorno de desarrollo. Sin embargo, para que nos acerquemos a producción lo más posible podemos hacer que este se conecte con el Postgres que hemos arrancado como parte de nuestro Dev Container. El cliente para Postgres ya está instalado, pero te recomiendo actualizarlo con este comando:

# From your Backstage root directory
yarn --cwd packages/backend add pg

En cuanto se trata de configuración, Backstage hace uso de unos archivos llamados app-config.*.yaml. Lo ideal es que en estos no almacenes información sensible, ya que los mismos quedarán versionados junto con tu código, por lo que preferiblemente deberías hacer mención en los mismos a variables de entorno. Si no quieres/puedes en esta fase, lo ideal es que utilices el archivo llamado app-config.local.yaml, el cual forma parte del archivo .gitignore, y estarás a salvo de que se suba como parte de tu código, y este sobrescribe al archivo app-config.yaml… pero no almacenarás la configuración que hayas realizado junto con tu repo y puede ser que en algún momento se te olvide 🙃. Para la base de datos, vamos a modificar el llamado app-config.yaml, para que si que forma parte de mi repo, en la sección database con lo siguiente:

  database:
    # client: better-sqlite3
    # connection: ':memory:'
    client: pg
    connection:
      host: ${POSTGRES_HOST}
      port: ${POSTGRES_PORT}
      user: ${POSTGRES_USER}
      password: ${POSTGRES_PASSWORD}

Como puedes ver, he comentado las dos lineas anteriores, client y connection, para sustituirlo con la configuración necesaria para Postgres. En mi ejemplo, estos valores los cogerá de las variables de entorno establecidas en la configuración que hice al inicio para mi Dev Container.

Iniciar sesión en Backstage con tu cuenta de GitHub

Si finalmente te decides a tener un IDP en tu compañía, lo más probable es que quieras integrar el mismo con tu proveedor de identidades. En este artículo te muestro cómo sería con GitHub para que veas cómo sería el proceso. En primer lugar, debes modificar el archivo backstage/packages/app/src/App.tsx. Debes añadir un par de imports adicionales, además de la configuración de inicio de sesión para GitHub.

import { 
  SignInProviderConfig
} from '@backstage/core-components';
import { githubAuthApiRef } from '@backstage/core-plugin-api';
const githubProvider: SignInProviderConfig = {
  id: 'github-auth-provider',
  title: 'GitHub',
  message: 'Sign in using GitHub',
  apiRef: githubAuthApiRef,
};

Una vez la tengas, dentro de createApp tienes un apartado llamado components donde es necesario modificar la propiedad SigInPage de la siguiente forma:

// components: {
  //   SignInPage: props => <SignInPage {...props} auto providers={['guest']} />,
  // },
  components: {
    SignInPage: props => (
      <SignInPage
        {...props}
        auto
        provider={githubProvider}
      />
    ),
  },

Aquí como ves he cambiado el provider guest por el llamado githubProvider, que es la configuración que definiste más arriba.

Una vez que ya tienes la parte de React lista, lo siguiente es crear en tu cuenta de GitHub una OAuth App aquí: https://github.com/settings/applications/new. En esta debes configurar lo siguiente:

  • Home URL: http://localhost:3000
  • Authorization callback URL: http://localhost:7007/api/auth/github

Recupera el client id y genera un client secret para configurar este proveedor en tu Backstage. De nuevo, esta configuración puedes añadirla en el archivo app-config.yaml haciendo uso de variables de entorno o puedes utilizar el archivo a para estas pruebas con la siguiente información

# Backstage override configuration for your local development environment
auth:
  # see https://backstage.io/docs/auth/ to learn about auth providers
  environment: development
  providers:
    guest: {}
    github:
      development:
        clientId: <YOUR_CLIENT_ID>
        clientSecret: <YOUR_SECRET_ID>
        signIn:
          resolvers:
            - resolver: usernameMatchingUserEntityName

Para que este proveedor funcione, debes añadir el módulo que se corresponde al mismo en el archivo packages/backend/src/index.ts en el apartado //auth login

// auth plugin
backend.add(import('@backstage/plugin-auth-backend'));
// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
// backend.add(import('@backstage/plugin-auth-backend-module-guest-provider'));
// See https://github.com/backstage/backstage/blob/master/docs/auth/guest/provider.md
// For github login
backend.add(import('@backstage/plugin-auth-backend-module-github-provider'));

y luego por último, para este ejemplo, necesitas modificar el archivo examples/org.yaml con tu usuario de GitHub. Aquí te dejo un ejemplo de cómo sería con el mio:

---
# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
  name: guest
spec:
  memberOf: [guests]
---
# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
  name: 0GiS0
spec:
  memberOf: [guests]
---
# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
  name: guests
spec:
  type: team
  children: []

Si ahora vuelves a arrancar la aplicación verás que ya puedes logarte con tu cuenta de GitHub en tu portal de Backstage 🎉 Ahora vamos a ver algunas de las funcionalidades principales de este.

Catálogo de componentes

Uno de los principales objetivos de un IDP es tener un inventario de lo que el desarrollador tiene en su ecosistema. Un catálogo donde pueda ver qué componentes ya existen, cómo se relacionan entre ellos, quién es el propietario del mismo dentro de la organización, etcétera. En Backstage podemos ver los mismos directamente en el aparto Home (que si te fijas en la ruta lleva a /catalog).

Lo primero que debes saber es que para que un componente sea un componente de Backstage tiene que tener un archivo que recopile la información necesaria sobre el mismo. Este archivo tiene como nombre catalog-info.yaml y suele estar en la raíz de los repositorios. Para mis pruebas he creado este en mi repositorio tour-of-heroes-dotnet-api, el cual tiene la siguiente pinta:

apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: heroes-api
  description: Heroes API is a RESTful API that provides manages heroes.
  tags:
    - dotnet
    - data
  links:
    - url: https://github.com/0GiS0/tour-of-heroes-dotnet-api
      title: Source Code
      icon: github
    - url: https://api.heroes.com
      title: Production
      icon: globe
  annotations:
    # this could also be `url:<url>` if the documentation isn't in the same location
    backstage.io/techdocs-ref: dir:.
spec:
  type: service
  lifecycle: production
  owner: 0GiS0
  system: tour-of-heroes
  dependsOn: ['resource:heroes-db']
  apiConsumedBy: ['component:www-heroes']

El mismo tiene un formato YAML, para que sea sencillo su mantenimiento, y da el nombre al componente, además de otros metadatos, como etiquetas, enlaces relacionados, etcétera. Por otro, en el apartado spec podemos indicar el tipo de componente que es, en este caso un servicio, el ciclo de vida, el owner, a qué sistema (aplicación) pertenece, entre otras cosas.

Y ahora bien ¿Cómo incluyo este componente en mi portal de Backstage? Desde el mismo apartado Home o en el propio menú lateral tienes la opción Create que, al pulsarlo, irás a un apartado donde tienes a su vez una opción que dice Register Existing Component.

Cómo registrar un componente en Backstage

Verás que puedes añadir una URL y, en mi ejemplo, he incluido esta: https://github.com/0GiS0/tour-of-heroes-dotnet-api/blob/main/catalog-info.yaml. Si pasa la validación de forma satisfactoria, podrás continuar con la importación, haciendo clic en el botón Import, y al terminar podrás ir al componente a través del botón View Component, que te mostrará algo como lo siguiente:

Componente que representa mi API de Tour of Heroes en Backstage

En él puedes ver que tienes toda la información que generé en el archivo catalog-info.yaml, además de una serie de pestañas que no tienen por qué estar del todo funcionales, pero ya tienes un primer componente con el que jugar.

TechDocs

Una de las funcionalidades core de Backstage es la posibilidad de importar la documentación que hayas generado en los diferentes repositorios que alojan tus componentes. De esta forma te aseguras de que tienes toda la información que tus desarrolladores necesitan en un mismo sitio.

En el mismo repositorio que utilicé para importar mi primer componente he dejado en el directorio docs un par de ejemplos de lo que podría ser la documentación de esta API. Para poder importar esta información dentro de Backstage, como parte de mi componente añadí la siguiente anotación:

  annotations:
    # this could also be `url:<url>` if the documentation isn't in the same location
    backstage.io/techdocs-ref: dir:.

Por otro lado necesitamos un archivo llamado mkdocs.yaml que también he incluido como parte de este repo:

site_name: Tour of Heroes
site_description: Tour of Heroes is a sample application that helps a fictional organization manage heroes.
site_author: 0GiS0
site_url: https://tour-of-heroes.es
repo_url: https://github.com/0GiS0/tour-of-heroes-dotnet-api
edit_uri: edit/main/docs
plugins: 
  - techdocs-core
nav:
  - Home: index.md
  - Management: managing-heroes.md   
  

Estos markdowns a los que hago referencia en este último va a ir a buscarlos al directorio docs.

Como ya hemos importado este componente, lo único que nos quedaría por hacer es ir o bien al apartado DOCS del componente en cuestión o hacer clic en la opción del menú llamada Docs. En cualquiera de los dos casos puedes ver algo como lo siguiente:

Documentación de mi API de Tour of Heroes integrada en Backstage

Software templates e integración con GitHub

Para terminar con este artículo extra largo, otro de los puntos fuertes de un IDP, y muchas veces al que más importancia se le da, es el que nos permite utilizar plantillas de proyectos para desplegarlos directamente desde estos portales para el desarrollador. En este caso he probado la integración con GitHub para que sea este el destino de estas plantillas.

En las últimas versiones Backstage lo que han intentado es que el mismo sea más modular con el fin de no tener que cargar absolutamente todo si es que realmente no lo necesitas. Por lo que lo primero que necesitas es modificar el archivo backstage/packages/backend/src/index.ts para registrar esta integración con la siguiente linea:

// github integration
backend.add(import('@backstage/plugin-scaffolder-backend-module-github'));

Por otro lado necesitas un PAT (usando fine-grained personal access tokens) con los scopes Read & Write para los apartados Contents, Administration y Workflows . Una vez que lo tengas, necesitas volver a modificar el archivo app-config.local.yaml con este otro apartado:

integrations:
  github:
    - host: github.com
      # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information
      # about setting up the GitHub integration here: https://backstage.io/docs/integrations/github/locations#configuration
      token: github_pat_XXXX

Nota: esto también se puede conseguir (y es lo recomendado en un entorno productivo) haciendo uso de GitHub Apps, pero para esta primera toma de contacto en local un PAT es más que suficiente.

Y ahora ya, para que puedas probar que esto funciona como se espera, puedes utilizar la plantilla que viene de ejemplo llamada Example Node.js Template, que puedes encontrar en el apartado Create Una vez que completas el asistente el resultado será algo como lo siguiente:

Repositorio creado a través de una plantilla de Backstage

y podrás comprobar que tienes un nuevo repositorio en tu cuenta de GitHub con la plantilla de ejemplo de Backstage.

El código de este artículo lo tienes en este repo de mi cuenta de GitHub.

¡Saludos!