À la découverte de Backstage

Avec l'arrivée des micro-services, du DevOps et la multiplication des projets dans les départements IT, il devient de plus en plus compliqué de recenser et retrouver les différents projets d'une équipe ou d'un département dans son ensemble.

Pour permettre une meilleure visibilité de ces composants, des outils, qu'on appelle « Portail Développeur », commencent à voir le jour.

Qu'est-ce qu'un Portail Développeur ?

Un Portail Développeur (ou « Developer Portal » en anglais) est en général un site web qui répertorie les différents composants des projets internes d'une société ou d'un département. Il recense les documentations ainsi que les API et les relie tous entre eux. Souvent, l'équipe responsable de la CI/CD propose également des modèles de « Scaffolding » (modèle de projet + ou - complexe qui permet de démarrer rapidement avec les bonnes pratiques) pour créer de nouveaux projets/composants directement sur le portail.

La navigation, la découverte et la création de services sont donc simplifiées dans un outil unique, pivot central pour le développeur, l'architecte ou même le directeur SI. Le « onboarding », en particulier, et l'expérience développeur, en général, s'en voient améliorés.

Le plus connu de ces outils est certainement « Backstage » de Spotify que je vais vous présenter ici.

Let's go !

Présentation de Backstage

Backstage est un outil qui permet de créer ce fameux Portail Développeur. Ce n'est pas un produit « clé-en-main » et il demande quelques compétences techniques, notamment sur NodeJS pour être déployé et en Typescript/React pour être totalement personnalisé.

D'où vient-il ?

Backstage est la solution développée en R&D chez Spotify dès 2016 alors que la société est en pleine croissance, avec de nouveaux ingénieurs et de nouveaux composants créés. Le projet est rapidement devenu un besoin critique pour remédier au chaos ambiant.

En 2020, Spotify ouvre le code de Backstage puis, plus tard dans l'année, l'offre à la CNCF. Le projet entre alors dans une phase d'expansion.

De grandes sociétés fournissent également des services d'hébergement ou de conseil (comme Roadie ou ThoughtWorks) ou des plugins pour intégrer différentes technologies (comme Zalando, RedHat, etc.).

Que peut-on faire avec ?

Collecter les composants logiciels

Par défaut, après avoir créé une instance de Backstage en local avec npm, il permet de collecter manuellement les informations de composants ou projets dans un « catalogue » centralisé et de les afficher dans un portail web simple et élégant.

Vue (personnalisée) du catalogue

Pour activer la collecte automatique, il suffit de modifier la configuration dans le fichier app-config.yaml. Dans la liste des providers, il faut ajouter le nom du fournisseur du DVCS avec les informations nécessaires à la collecte comme le chemin et le nom du fichier qui contient les métadonnées, des filtres optionnels pour ne pas scanner des dépôts spécifiques et un ordonnancement.

Exemple de configuration pour GitHub :

[...]
catalog:
  providers:
    github:
      providerId:
        organization: IpponTechnologies
        catalogPath: '/**/catalog-info.yaml'
        filters:
          repository: '.*'
        schedule:
          frequency: { hours: 4 }
          timeout: { minutes: 2 }
[...]

Tous les composants logiciels peuvent être collectés : applications, bibliothèques, services, pipelines, etc. Il est nécessaire d'ajouter un fichier de métadonnées au format YAML, en général nommé catalog-info.yaml, dans le dépôt de code source et dans un répertoire qui correspond à la configuration définie précédemment. Dans l'exemple ci-dessus, Backstage va parser les fichiers toutes les 4 heures.

Exemple de fichier de composants :

---
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: my-web-api
  title: My Web API
  description: My Web API which provide some useful Rest endpoints
  annotations:
    backstage.io/techdocs-ref: dir:.
spec:
  type: backend-service
  lifecycle: production
  owner: my_dev_squad
  providesApis: [my-web-api-openapi]
---
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
  name: my-web-api-openapi
  description: The provided REST API for My Web API
spec:
  type: openapi
  lifecycle: production
  owner: my_dev_squad
  definition:
    $yaml: ./openapi.yml # embed a local file
---
apiVersion: backstage.io/v1alpha1
kind: Resource
metadata:
  name: my-web-api-db
  description: A database for My Web API
spec:
  type: database
  owner: my_dev_squad
  dependsOn: [resource:mongo-instance] # reference an external resource

Ce fichier exemple contient 3 « documents » (séparés par ---) décrivant chacun une partie du composant : l'exécutable (composant principal), son API et sa base de données (ressource).

Chaque document est constitué de 3 parties :

  • La première partie est l'entête (apiVersion et kind) du document et permet de définir ce que contient le document.
  • La seconde partie contient les métadonnées (metadata) qui sont affichées sur le portail (nom, description, etc.).
  • La dernière partie (spec) fournit les spécificités du composant ; chaque composant pouvant avoir des attributs complètement différents.
Vue d'un composant

Créer un nouveau composant

Avant de pouvoir créer un composant depuis le portail, il faut ajouter un modèle. Celui-ci est constitué d'un fichier template.yaml (à l'image du fichier précédent) et d'un ensemble de fichiers spécifiques à l'environnement du projet (langage, infra, etc.) dans lesquels des variables pourront être substituées.

Exemple de fichier de modèle :

---
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: my-template
  title: My Custom Template
  description: Description of the template
  tags: [recommended, other-tag]
spec:
  type: template
  owner: group:devex_squad
  parameters:
    - title: Default information
      required: [name]
      properties:
        name:
          title: Name
          type: string
          description: Unique name of the component
          pattern: "^([a-zA-Z][a-zA-Z0-9]*)(-[a-zA-Z0-9]+)*$"
          ui:autofocus: true
          ui:options:
            rows: 5
        owner:
          title: Owner
          type: string
          description: Owner of the component
          ui:field: OwnerPicker
          ui:options:
            catalogFilter:
              kind: [User, Group]
    - title: Choose a location
      required:
        - repoUrl
      properties:
        repoUrl:
          title: Repository Location
          type: string
          ui:field: RepoUrlPicker
          ui:options:
            allowedHosts: ["github.com"]
            allowedOwners: ["IpponTechnologies"]

  steps:
    - id: fetch-base
      name: Fetch Base
      action: fetch:template
      input:
        url: ./skeleton
        destinationPath: .
        copyWithoutTemplating:
          - .github/workflows/*
        values:
          name: ${{ parameters.name }}
          owner: ${{ parameters.owner }}
    - id: publish
      name: Publish
      action: publish:github
      input:
        allowedHosts: ["github.com"]
        allowedOwners: [IpponTechnologies]
        allowMergeCommit: false
        description: This is ${{ parameters.name }}
        repoUrl: ${{ parameters.repoUrl }}
        repoVisibility: "public"
        requireCodeOwnerReviews: true
        defaultBranch: main
        protectDefaultBranch: true
        gitAuthorName: ${{ user.entity.metadata.name }}
        gitAuthorEmail: ${{ user.entity.spec.profile.email }}
    - id: register
      name: Register
      action: catalog:register
      input:
        repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }}
        catalogInfoPath: "/catalog-info.yaml"

  output:
    links:
      - title: Open GitHub Repository
        url: ${{ steps['publish'].output.remoteUrl }}
      - title: Open in catalog
        icon: catalog
        entityRef: ${{ steps['register'].output.entityRef }}

Ce fichier exemple contient les 3 parties détaillées plus haut. La dernière partie est elle-même divisée en 3 parties :

  • La première partie contient des groupes de paramètres, chacun représentant une étape dans l'assistant visuel du portail et contenant les paramètres. Chaque paramètre fournit un titre, une description, un type de données (string, number, etc.) des validations optionnelles pour les valeurs saisies par l'utilisateur, et des options d'affichage.
  • La seconde partie contient les différentes étapes exécutées après l'assistant pour générer la structure de base du composant. Chaque étape est responsable d'une partie de la génération :
    • Première étape : récupérer les fichiers « squelette » du modèle en remplaçant les valeurs des paramètres par ceux fournis par l'utilisateur dans l'assistant,
    • Seconde étape : publier le code dans un dépôt sur GitHub
    • Dernière étape : enregistrer le composant dans le portail
  • La dernière partie permet d'afficher un résumé de la génération avec des liens permettant de naviguer vers le dépôt nouvellement créé ou vers le composant enregistré dans le portail.

Dès lors, les développeurs peuvent créer un nouveau composant directement depuis Backstage en fournissant les informations demandées. Ils peuvent également créer leurs propres modèles si les modèles proposés ne correspondent pas à leurs attentes ou besoins.

Afficher la documentation

Backstage utilise MkDocs pour générer et afficher la documentation d'un composant depuis le code source. Il est donc assez facile de fournir une documentation au sein du portail.

La documentation peut être enregistrée dans le portail via l'utilisation de l'annotation backstage.io/techdocs-ref dans le fichier catalog-info.yaml et en la fournissant au format MkDocs.

Par défaut, la documentation est convertie en HTML statique par Backstage lors du premier accès ou si la documentation a changé dans le dépôt de code source.

Cette approche n'étant pas recommandée, il est préférable de générer la documentation lors de la CI/CD, en déposant les fichiers HTML statiques dans un espace de stockage externe (type S3 ou GCS).

Il faudra pour cela modifier la configuration techdocs.builder dans le fichier app-config.yaml en la changeant de local à external. Après ce changement, la documentation ne sera plus générée par Backstage.

Documentation technique

Comment peut-on l'étendre et le personnaliser ?

Avec un peu de Typescript + React + CSS, il est possible de personnaliser l'interface simplement. On peut également créer des plugins personnalisés et utiliser les plugins existants. Il en existe quelques-uns sur la « marketplace » de Backstage.

La marketplace des plugins

Quelques exemples :

  • Navigateurs d'APIs : AsyncAPI, OpenAPI, GraphQL, gRPC, etc.
  • Intégration de linters : Spectral et Zalando
  • Intégration des principaux providers Cloud : AWS, Azure, GCP
  • Intégration des DVCS : Gerrit, GitHub, GitLab, etc.
  • Intégration des outils d'analyse de code : SonarQube, SonarCloud, etc.
  • Intégration des outils d'analyse de sécurité : BetterScan, Snyk, etc.
  • Intégration des outils de CI/CD : ArgoCD, AzureDevOps, CircleCI, GitHub Actions, GitLab, Travis CU, etc.
  • Intégration des outils de monitoring : DataDog, DynaTrace, Grafana, OpenDORA, OpenCost

Pour chaque plugin, il faudra tout de même mettre un peu « la main à la pâte » : ce n'est pas automatisé et quelques modifications du code seront nécessaires. Heureusement, les explications de chacun des modules sont relativement simples à mettre en place et bien expliquées. Je n'ai eu aucun souci pour intégrer qui m'intéressait !

Actuellement, je suis en train de créer un plugin pour exécuter n'importe quelle commande shell lors de l'exécution des modèles de scaffolding. Après avoir exécuté l'outil de Backstage en CLI qui prépare la structure des fichiers, l'ajout des fonctionnalités est relativement simple pour un développeur même débutant. Je vous en parlerai plus longuement dans un prochain article.

Comment le déployer ?

Le déploiement de l'outil est relativement simple et conforme à ce qu'il se fait ailleurs.

Il faudra quand même rajouter une base de données, par exemple PostgreSQL plutôt que d'utiliser SQLite qui est activé par défaut, afin d'assurer une reprise de service rapide après une mise à jour ou autre. Mais ce n'est pas plus compliqué que le reste : il faut remplacer la configuration backend.database dans le fichier app-config.yaml ; comme ceci :

[...]
backend:
  database:
    client: pg
    connection:
      host: ${POSTGRES_HOST}
      port: ${POSTGRES_PORT}
      user: ${POSTGRES_USER}
      password: ${POSTGRES_PASSWORD}
[...]

L'utilisation de variables d'environnement plutôt que d'avoir des valeurs en dur est recommandée. Il faudra simplement les passer lors de l'exécution du serveur Node.

Il est également possible d'intégrer des services d'authentification comme Azure Entra, GitHub, GitLab ou LDAP. Un service d'authentification permettra aux utilisateurs de s'identifier après que Backstage ait collecter les informations sur les groupes et les utilisateurs. Chaque fournisseur demandant une configuration particulière, je détaillerais ceci dans un article ultérieur.

Conclusion

Backstage n'est pas un outil « sur étagère » mais déployer une application sur un socle open-source permet de gagner du temps au démarrage du projet. L'extensibilité est également une des forces de l'outil, en intégrant des plugins existants rapidement et en en développant d'autres au besoin. Il faudra donc le gérer comme une application interne, avec des tests et un cycle de développement à définir. Le déploiement et/ou la maintenance de l'outil devrait être assuré par l'équipe chargée de la CI/CD ou de la "Developer Experience".