DEV Community

Cover image for Backstage, App template e Crossplane no Amazon EKS [Lab Session]
Paulo Ponciano for AWS Community Builders

Posted on • Edited on • Originally published at Medium

Backstage, App template e Crossplane no Amazon EKS [Lab Session]

Seguindo em mais um lab praticando #platformengineering junto com meu amigo Gabriel Portela, dessa vez explorando mais o Backstage combinado com GitHub Actions, Crossplane, ArgoCD. E claro, rodando no kubernetes.

An open platform for building developer portals
Powered by a centralized software catalog, Backstage restores order to your infrastructure and enables your product teams to ship high-quality code quickly — without compromising autonomy.
https://backstage.io/

Logo e visão geral do Backstage

Considerações:
Todos os recursos que rodam no kubernetes estão no mesmo cluster para esse lab. O post Serverless com Crossplane composition no EKS + GitOps [Lab Session] pode ajudar no setup do cluster EKS já com ArgoCD e Crossplane. E claro, é possível rodar em qualquer cluster kubernetes.


Nova app backstage

Inicialmente, criamos a app backstage localmente para inicializar o repositório git com a estrutura necessária e criar a primeira imagem docker que, posteriormente, entregaremos no kubernetes.

Documentation on Creating an App Backstage

Building a Docker image | Backstage Software Catalog and Developer Platform

  • Criando a app:
npx @backstage/create-app@latest
Enter fullscreen mode Exit fullscreen mode

Execução do npx create-app do Backstage

Estrutura inicial da app gerada pelo create-app

cd backstage
Enter fullscreen mode Exit fullscreen mode

Entrando no diretório da app backstage

yarn install --frozen-lockfile
yarn tsc
Enter fullscreen mode Exit fullscreen mode

Saída do yarn install e do type checking

yarn build:backend
Enter fullscreen mode Exit fullscreen mode

Início do build do backend

Conclusão do build do backend

Até aqui, tudo default. Neste lab, utilizamos também alguns outros plugins do backstage, então adicionamos:

yarn add --cwd packages/backend @backstage/plugin-kubernetes-backend
yarn add --cwd packages/app @backstage/plugin-kubernetes
yarn add --cwd packages/app @backstage/integration-aws-node
yarn add --cwd packages/app @backstage/plugin-home
Enter fullscreen mode Exit fullscreen mode

Caso siga com o padrão, não é necessário adicionar os plugins acima, porém o código/config que está no repositório do lab precisa deles. Assim, também é necessário modificar no typescript. Seguimos as documentações:

Installation | Backstage Software Catalog and Developer Platform

Backstage homepage - Setup and Customization | Backstage Software Catalog and Developer Platform

@backstage/integration-aws-node | Backstage Software Catalog and Developer Platform

  • Criando imagem docker e subindo para o DockerHub. Já existe um Dockerfile para build do backend, foi criado junto com a estrutura inicial da app quando rodamos o npx:
docker image build . -f packages/backend/Dockerfile --tag backstage-app
Enter fullscreen mode Exit fullscreen mode

Build da imagem docker do backstage

docker images
Enter fullscreen mode Exit fullscreen mode

Listagem das imagens docker locais

docker tag db926b361c29 paulofponciano/backstage-app:latest
docker push paulofponciano/backstage-app:latest
Enter fullscreen mode Exit fullscreen mode

Push da imagem do backstage para o DockerHub

Agora basta subir o código para o GitHub. Para facilitar nossa vida, criamos também um workflow para que esse build seja feito de forma automática sempre que subirmos alguma alteração para o repositório:

  • backstage-app/.github/workflows/master.yaml
name: Main Master Build

on:
  push:
    branches: [main]
    paths-ignore:
      - 'k8s/**'
      - 'catalog-entities/**'

jobs:
  build:
    runs-on: ubuntu-latest

    env:
      CI: true
      NODE_OPTIONS: --max-old-space-size=4096

    steps:
      - uses: actions/checkout@v3

      # Beginning of yarn setup
      - name: use node.js 18.x
        uses: actions/setup-node@v3
        with:
          node-version: 18.x
          registry-url: https://registry.npmjs.org/ # Needed for auth

      - name: cache all node_modules
        id: cache-modules
        uses: actions/cache@v3
        with:
          path: '**/node_modules'
          key: ${{ runner.os }}-node_modules-${{ hashFiles('yarn.lock', '**/package.json') }}

      - name: find location of global yarn cache
        id: yarn-cache
        if: steps.cache-modules.outputs.cache-hit != 'true'
        run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT

      - name: cache global yarn cache
        uses: actions/cache@v3
        if: steps.cache-modules.outputs.cache-hit != 'true'
        with:
          path: ${{ steps.yarn-cache.outputs.dir }}
          key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}
          restore-keys: |
            ${{ runner.os }}-yarn-

      - name: yarn install
        run: yarn install --immutable
      # End of yarn setup

      - name: type checking and declarations
        run: yarn tsc:full

      - name: build
        run: yarn --cwd packages/backend build

      - name: Login to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USER }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: build image and push to docker hub
        uses: docker/build-push-action@v4
        with:
          context: .
          file: packages/backend/Dockerfile
          push: true
          tags: paulofponciano/backstage:${{ github.sha }}, paulofponciano/backstage:latest
Enter fullscreen mode Exit fullscreen mode

Estamos usando os secrets armazenados em repository secrets do GitHub para conectar ao DockerHub:

Repository secrets do GitHub com as credenciais do DockerHub

GitHub OAuth App

Para autenticação, utilizamos também o GitHub na parte de OAuth Apps.

  • Basta criar uma nova application (Settings > Developer settings > OAuth Apps). Com isso, teremos um Client ID e Client secrets, que utilizaremos como um secret do kubernetes para o backstage. É necessário informar a Homepage URL e Authorization callback URL:

Criação do OAuth App no GitHub com Homepage e callback URL

Manifestos k8s

Criamos um diretório 'k8s' no repositório do backstage para concentrar os manifestos que aplicamos no cluster.

  • Criar namespace:
apiVersion: v1
kind: Namespace
metadata:
  name: backstage
  labels:
    istio-injection: enabled
Enter fullscreen mode Exit fullscreen mode
  • Backstage secrets:
# kubernetes/backstage-secrets.yaml
apiVersion: v1
kind: Secret
metadata:
  name: backstage-secrets
  namespace: backstage
type: Opaque
data:
  GITHUB_TOKEN: '' # sample base 64
Enter fullscreen mode Exit fullscreen mode

Este GITHUB_TOKEN é necessário para que o backstage consiga acessar os repositórios onde estão os catálogos, templates, etc. Basta gerar um PAT (Personal access token) e colocar em base 64. No GitHub: Settings > Developer Settings > Personal access tokens (Classic).

  • Postgres secrets:
apiVersion: v1
kind: Secret
metadata:
  name: postgres-secrets
  namespace: backstage
type: Opaque
data:
  POSTGRES_USER: YmFja3N0YWdl # sample base 64
  POSTGRES_PASSWORD: aHVudGVyMg== # sample base 64
Enter fullscreen mode Exit fullscreen mode
  • Postgres — Deployment, PVC, Service:
---
# kubernetes/postgres-storage.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: postgres-storage
  namespace: backstage
  labels:
    type: local
spec:
  storageClassName: gp3
  capacity:
    storage: 2G
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  hostPath:
    path: '/mnt/data'
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-storage-claim
  namespace: backstage
spec:
  storageClassName: gp3
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 2G
---
# kubernetes/postgres.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
  namespace: backstage
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:13.2-alpine
          imagePullPolicy: 'IfNotPresent'
          ports:
            - containerPort: 5432
          envFrom:
            - secretRef:
                name: postgres-secrets
          volumeMounts:
            - mountPath: /var/lib/postgresql/data
              name: postgresdb
      volumes:
        - name: postgresdb
          persistentVolumeClaim:
            claimName: postgres-storage-claim
---
# kubernetes/postgres-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: postgres
  namespace: backstage
spec:
  selector:
    app: postgres
  ports:
    - port: 5432
Enter fullscreen mode Exit fullscreen mode
  • GitHub Auth Secrets:
apiVersion: v1
kind: Secret
metadata:
  name: github-auth-secrets
  namespace: backstage
type: Opaque
data:
  AUTH_GITHUB_CLIENT_ID: '' # base 64
  AUTH_GITHUB_CLIENT_SECRET: '' # base 64
Enter fullscreen mode Exit fullscreen mode

Neste informamos o Client ID e Client secret gerados anteriormente, quando criamos o GitHub OAuth App.

  • Backstage ingestion secret:
kubectl -n kube-system create serviceaccount backstage-ingestion
kubectl create clusterrolebinding backstage-ingestion --clusterrole=cluster-admin --serviceaccount=kube-system:backstage-ingestion
Enter fullscreen mode Exit fullscreen mode
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: backstage-ingestion
  namespace: kube-system
  annotations:
    kubernetes.io/service-account.name: backstage-ingestion
type: kubernetes.io/service-account-token
EOF
Enter fullscreen mode Exit fullscreen mode
kubectl -n kube-system get secret backstage-ingestion -o go-template='{{.data.token | base64decode}}'
Enter fullscreen mode Exit fullscreen mode

A saída desse comando é o Service account token, que utilizamos como SA_TOKEN no plugin de kubernetes do backstage, possibilitando a ingestão dos dados do cluster para o backstage.

  • EKS secrets:
# kubernetes/eks-secrets.yaml
apiVersion: v1
kind: Secret
metadata:
  name: eks-secrets
  namespace: backstage
type: Opaque
data:
  SA_TOKEN: '' # base 64
  CA_DATA: '' # base 64
Enter fullscreen mode Exit fullscreen mode

Informamos o SA_TOKEN gerado anteriormente e o certificate authority gerado para o EKS. No secrets como CA_DATA. É possível localizar pela console do EKS:

Localização do certificate authority na console do EKS

  • Backstage — Deployment, SA, Service:
# kubernetes/backstage.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backstage
  namespace: backstage
spec:
  replicas: 1
  selector:
    matchLabels:
      app: backstage
  template:
    metadata:
      labels:
        app: backstage
    spec:
      serviceAccountName: backstage-service-account
      containers:
        - name: backstage
          image: paulofponciano/backstage:latest
          imagePullPolicy: 'Always'
          ports:
            - name: http
              containerPort: 7007
          envFrom:
            - secretRef:
                name: postgres-secrets
            - secretRef:
                name: backstage-secrets
            - secretRef:
                name: github-auth-secrets
            - secretRef:
                name: eks-secrets
          env:
          - name: POSTGRES_PORT
            value: "5432"
          - name: POSTGRES_HOST
            value: "postgres.backstage.svc.cluster.local"
# Uncomment if health checks are enabled in your app:
# https://backstage.io/docs/plugins/observability#health-checks
#          readinessProbe:
#            httpGet:
#              port: 7007
#              path: /healthcheck
#          livenessProbe:
#            httpGet:
#              port: 7007
#              path: /healthcheck
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: backstage-service-account
  namespace: backstage
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: backstage-cluster-ro
subjects:
- namespace: backstage
  kind: ServiceAccount
  name: backstage-service-account
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:aggregate-to-view
---
# kubernetes/backstage-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: backstage
  namespace: backstage
spec:
  selector:
    app: backstage
  ports:
    - name: http
      port: 80
      targetPort: http
Enter fullscreen mode Exit fullscreen mode
  • Backstage ingress — Istio Gateway, Virtual Service:
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: backstage-gateway
  namespace: backstage
spec:
  selector:
    istio: ingressgateway
  servers:
    - hosts:
        - backstage.pauloponciano.digital
      port:
        name: http
        number: 80
        protocol: HTTP
      tls:
        httpsRedirect: true
    - hosts:
        - backstage.pauloponciano.digital
      port:
        name: https-workloads
        number: 443
        protocol: HTTP
      tls:
        mode: PASSTHROUGH
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: backstage
  namespace: backstage
spec:
  gateways:
    - backstage-gateway
  hosts:
    - backstage.pauloponciano.digital
  http:
    - route:
        - destination:
            host: backstage.backstage.svc.cluster.local
            port:
              number: 80
          weight: 100
Enter fullscreen mode Exit fullscreen mode

Backstage app-config.yaml

No repositório do backstage, temos o arquivo app-config.yaml com as configurações e integrações. Até o momento, esse arquivo está default. Para esse lab, deixamos conforme abaixo e fizemos commit e push para o repo, acionando assim um novo build com GitHub Actions (workflow master.yaml) que já criamos.

app:
  title: Community Backstage App
  baseUrl: https://backstage.pauloponciano.digital

organization:
  name: Community

backend:
  baseUrl: https://backstage.pauloponciano.digital
  listen:
    port: 7007
  csp:
    connect-src: ["'self'", 'http:', 'https:']
  cors:
    origin: https://backstage.pauloponciano.digital
    methods: [GET, HEAD, PATCH, POST, PUT, DELETE]
    credentials: true
  database:
    client: pg
    connection:
      host: ${POSTGRES_HOST}
      port: ${POSTGRES_PORT}
      user: ${POSTGRES_USER}
      password: ${POSTGRES_PASSWORD}

integrations:
  github:
    - host: github.com
      token: ${GITHUB_TOKEN}
  aws:
    mainAccount:
    accounts:
      - accountId: ${AWS_ACCOUNT_ID}
        accessKeyId: ${AWS_ACCESS_KEY_ID}
        secretAccessKey: ${AWS_SECRET_ACCESS_KEY}
        region: ${AWS_REGION}

proxy:
  '/test':
    target: 'https://example.com'
    changeOrigin: true
techdocs:
  builder: 'local' # Alternatives - 'external'
  generator:
    runIn: 'docker' # Alternatives - 'local'
  publisher:
    type: 'local' # Alternatives - 'googleGcs' or 'awsS3'. Read documentation for using alternatives.

auth:
  environment: production
  providers:
    github:
      production:
        clientId: ${AUTH_GITHUB_CLIENT_ID}
        clientSecret: ${AUTH_GITHUB_CLIENT_SECRET}

scaffolder:
  defaultAuthor:
    name: ":robot: [backstage-bot]"
    email: paulofponciano@gmail.com

catalog:
  import:
    entityFilename: catalog-info.yaml
    pullRequestBranchName: backstage-integration
  rules:
    - allow: [Component, System, Group, Resource, Location, Template, API]
  locations:
    - type: url
      target: https://github.com/paulofponciano/backstage-app/blob/main/catalog-entities/locations.yaml

kubernetes:
  serviceLocatorMethod:
    type: 'multiTenant'
  clusterLocatorMethods:
    - type: 'config'
      clusters:
        - url: https://api-server-endpoint.gr7.us-east-2.eks.amazonaws.com
          name: pegasus # cluster name
          authProvider: 'serviceAccount'
          skipTLSVerify: false
          skipMetricsLookup: true
          serviceAccountToken: ${SA_TOKEN}
          caData: ${CA_DATA}
          customResources:
            - group: 'api.pauloponciano.digital' # Crossplane
              apiVersion: 'v1alpha1'
              plural: 'xcustomdatabases'
            - group: 'api.pauloponciano.digital' # Crossplane
              apiVersion: 'v1alpha1'
              plural: 'xcustomorders'
Enter fullscreen mode Exit fullscreen mode

Build no GitHub Actions disparado pelo push do app-config

Backstage

Após finalizar a etapa anterior com sucesso no build, aplicamos todos os manifestos k8s no cluster. Pode ser feito em sequência:

kubectl apply -f backstage_ns.yaml
kubectl apply -f backstage_secrets.yaml
kubectl apply -f postgres_secrets.yaml
kubectl apply -f postgres.yaml
kubectl apply -f github_auth_secrets.yaml
kubectl apply -f eks_secrets.yaml
kubectl apply -f backstage.yaml
kubectl apply -f backstage_istio_ingress.yaml
Enter fullscreen mode Exit fullscreen mode

Como criamos um CNAME em um DNS Público apontando para o NLB de entrada do istio, já podemos acessar o backstage:

Tela de login do Backstage

Página inicial do Backstage após o login

Já temos alguns componentes que foram registrados na subida da app:

Catálogo do Backstage com os componentes registrados

Esse import vem por esse bloco do app-config.yaml. Algumas dessas entidades no catálogo já não são default, mas servem de exemplo. Podem encontrá-las aqui.

catalog:
  import:
    entityFilename: catalog-info.yaml
    pullRequestBranchName: backstage-integration
  rules:
    - allow: [Component, System, Group, Resource, Location, Template, API]
  locations:
    - type: url
      target: https://github.com/paulofponciano/backstage-app/blob/main/catalog-entities/locations.yaml
Enter fullscreen mode Exit fullscreen mode

GitHub Organization

  • Criamos uma Organization no GitHub para centralizar os repositórios de templates de apps, gitops (ArgoCD) e workflows reutilizáveis (GitHub Actions). Os repositórios criados pelo backstage com base nos templates para novas apps também ficam na organização:

Repositórios da Organization no GitHub

  • Definimos também os secrets a nível de organização para utilizar nos workflows. Neste momento, o GH_PAT é o mesmo Personal access token que criamos para passar no secrets do kubernetes:

Organization secrets configurados no GitHub

ArgoCD

O ArgoCD é o recurso central para as entregas no cluster EKS, nos permitindo utilizar a estratégia de GitOps. Assim, conectamos o Argo ao repositório 'gitops' da organização com a estrutura abaixo:

  • deployed_apps/applications
  • deployed_infra/system:default

Estrutura do repositório gitops conectado ao ArgoCD

Utilizamos esse manifesto para criar as applications, project e conectar ao repositório:

apiVersion: v1
kind: Secret
metadata:
  name: public-repo-gitops
  namespace: argocd
  labels:
    argocd.argoproj.io/secret-type: repository
stringData:
  type: git
  url: https://github.com/paulofponciano-idp/gitops.git
---
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: idp
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  description: Internal Developer Portal
  sourceRepos:
    - 'https://github.com/paulofponciano-idp/gitops.git'
  destinations:
    - namespace: '*'
      server: 'https://kubernetes.default.svc'
      name: 'in-cluster'
  clusterResourceWhitelist:
    - group: '*'
      kind: '*'
  namespaceResourceWhitelist:
    - group: '*'
      kind: '*'
  orphanedResources:
    warn: true
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: idp-infra-aws
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: idp
  source:
    repoURL: https://github.com/paulofponciano-idp/gitops.git
    targetRevision: HEAD
    path: deployed_infra/system:default
  destination:
    server: https://kubernetes.default.svc
  syncPolicy:
    automated:
      selfHeal: true
      prune: true
      allowEmpty: true
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: idp-apps
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: idp
  source:
    repoURL: https://github.com/paulofponciano-idp/gitops.git
    targetRevision: HEAD
    path: deployed_apps/applications
  destination:
    server: https://kubernetes.default.svc
  syncPolicy:
    automated:
      selfHeal: true
      prune: true
      allowEmpty: true
Enter fullscreen mode Exit fullscreen mode

Applications idp-apps e idp-infra-aws sincronizadas no ArgoCD

Template GO (Backstage fetch:template)

No repositório go-template-backstage da organização, temos uma estrutura padrão como exemplo para uma aplicação em golang. Esse repositório serve como template para o backstage quando algum consumidor acionar a criação de uma nova app.

Estrutura do repositório go-template-backstage

Uma parte importante desse template é o workflow que também será utilizado no momento em que o backstage realizar o fetch e criar um novo repositório (push). Esse workflow é responsável por acionar outros workflows (workflow_call) que estão no repositório .github da organização.

Do lado do backstage utilizamos o template app-go.yaml abaixo, que guardamos no próprio repositório do backstage nesse caso:

apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: golang-template
  title: Golang Template
  description: Create a new Golang App

  tags:
    - golang
    - website
    - component
spec:
  owner: group:sre
  type: component

  parameters:
    - title: Provide some simple information
      required:
        - component_id
        - system
        - lifecycle
        - owner
        - type
      properties:
        component_id:
          title: Name
          type: string
          pattern: "^([a-z0-9\\-]+)$"
          description: Unique name of the component
          ui:field: EntityNamePicker
          ui:autofocus: true
        description:
          title: Description
          type: string
          description: Help others understand what this website is for.
        system:
          title: System
          type: string
          description: System of the component
          ui:field: EntityPicker
          ui:options:
            allowedKinds:
              - System
            defaultKind: System
        lifecycle:
          title: Lifecycle
          description: 'Application lifecycle'
          type: string
          default: experimental
          enum:
            - deprecated
            - experimental
            - production
        type:
          title: Type
          description: 'Application type'
          type: string
          default: service
          enum:
            - service
            - website
            - library
        owner:
          title: Owner
          type: string
          description: Owner of the component
          ui:field: OwnerPicker
          ui:options:
            allowedKinds:
              - Group

    - title: Choose a location
      required:
        - repoUrl
      properties:
        repoUrl:
          title: Repository Location
          type: string
          ui:field: RepoUrlPicker
          ui:options:
            allowedHosts:
              - github.com
            allowedOwners:
              - paulofponciano-idp

    - title: Infrastructure
      properties:
        kube:
          title: Create Kubernetes App
          description: Checking this will also create Kubernetes App
          type: boolean
          default: false
        env:
          title: Environment
          description: 'Environment to create resources'
          type: string
          default: dev
          enum:
            - dev
            - stg
            - prd
        dryRun:
          title: Only perform a dry run, don't publish anything
          type: boolean
          default: false

  steps:
    - id: template
      name: Fetch Application Template on GitHub Repo
      action: fetch:template
      cookiecutterCompat: true
      input:
        url: https://github.com/paulofponciano-idp/go-template-backstage
        copyWithoutTemplating:
          - .github/workflows/*
        values:
          component_id: ${{ parameters.component_id }}
          system: ${{ parameters.system }}
          description: ${{ parameters.description }}
          destination: ${{ parameters.repoUrl | parseRepoUrl }}
          owner: ${{ parameters.owner }}
          lifecycle: ${{ parameters.lifecycle }}
          type: ${{ parameters.type }}
          env: ${{ parameters.env}}

    - id: publish
      name: Publish Application
      action: publish:github
      if: ${{ parameters.dryRun !== true }}
      input:
        allowedHosts:
          - github.com
        description: This is ${{ parameters.component_id }}
        repoUrl: ${{ parameters.repoUrl }}
        defaultBranch: main
        repoVisibility: public
        collaborators:
          - team: sre
            access: maintain
          - team: ${{ parameters.owner }}
            access: push

    - id: fetch-kube
      name: Fetch Kubernetes Template
      action: fetch:template
      if: ${{ parameters.kube == true }}
      input:
        targetPath: ./kube
        url: https://github.com/paulofponciano/backstage-app/tree/main/catalog-entities/skeleton/kubernetes/apps/kustomize
        values:
          component_id: ${{ parameters.component_id }}
          description: ${{ parameters.description }}
          destination: ${{ parameters.repoUrl | parseRepoUrl }}
          owner: ${{ parameters.owner }}

    - id: kube-pr
      name: "Open PR in GitOps Repository"
      action: publish:github:pull-request
      if: ${{ parameters.kube == true }}
      input:
        repoUrl: github.com?repo=gitops&owner=paulofponciano-idp
        branchName: create-${{ parameters.component_id }}
        title: ':robot: [backstage-bot] Create new App ${{ parameters.component_id }}'
        description: |
          # New project: ${{ parameters.component_id }}
          ${{ parameters.description if parameters.description }}
        sourcePath: kube
        targetPath: deployed_apps

    - id: register
      name: Register Application in Catalog
      action: catalog:register
      if: ${{ parameters.dryRun !== true }}
      input:
        repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }}
        catalogInfoPath: "/catalog-info.yaml"

  output:
    links:
      - title: Go to Repository
        url: ${{ steps['publish'].output.repoContentsUrl }}
      - title: Go to GitOps Pull Request
        url: ${{ steps.kube-pr.output.remoteUrl }}
      - title: Open in catalog
        icon: catalog
        entityRef: ${{ steps['register'].output.entityRef }}
Enter fullscreen mode Exit fullscreen mode

Skeleton k8s (Backstage fetch:template)

No passo anterior, cuidamos do template de aplicação e das ações do backstage no momento de criar essa nova aplicação. Como essa app é entregue (argocd) e opera no kubernetes, precisamos também do 'esqueleto' para os manifestos. Guardamos o skeleton aqui com essa estrutura:

Estrutura do skeleton de manifestos kubernetes

Registrar template GO no Backstage

Registrar o template é um processo simples, apenas informar a url e importar:

Tela de registro de template no Backstage

Preview do template antes da importação

Template GO importado no catálogo

Criando nova app (go) através do Backstage

Validamos agora tudo que foi criado até o momento, buscando o fluxo que desenhamos:

Diagrama do fluxo de criação de uma nova app

  • Input das informações:

Formulário de input das informações da nova app

Seleção da localização do repositório

Opções de infraestrutura do template

Revisão final antes de criar a app

Execução dos steps do scaffolder no Backstage

  • Aprovar pull-request no repositório de GitOps:

Pull request aberto no repositório de GitOps

Conteúdo do pull request de GitOps

Merge do pull request de GitOps

Podemos ver o replace das informações que passamos dentro dos manifestos que o backstage pegou no diretório de 'skeleton'; esses manifestos agora estão no diretório 'deployed_apps' de GitOps.

  • Checando o novo repositório da app e workflow de first-release:

Novo repositório da app criado pelo Backstage

Workflow de first-release no novo repositório

O workflow de first-release é acionado no momento do push que o backstage faz para criação do novo repositório. Este, por sua vez, faz a chamada (workflow_call) dos workflows reutilizáveis de env, build e deploy:

Execução dos workflows reutilizáveis de env, build e deploy

  • Checando o sync no ArgoCD, vemos que a nova app já está sendo entregue no cluster:

Nova app sincronizada no ArgoCD

Detalhe dos recursos da nova app no ArgoCD

  • Agora em nosso Backstage Catalog, temos o novo componente registrado. Podemos visualizar os detalhes:

Novo componente registrado no Backstage Catalog

Visão geral do componente no catálogo

Detalhes dos recursos no kubernetes relacionados à aplicação:

Recursos kubernetes da app exibidos no Backstage

Detalhe de um recurso kubernetes da app

Detalhes de CI/CD do GitHub Actions:

Detalhes de CI/CD do GitHub Actions no Backstage

Histórico de execuções de CI/CD no Backstage

Acessando a app de exemplo:

App de exemplo em execução

Backstage + Crossplane para provisionar recursos

Levando em conta que já temos o crossplane rodando no cluster EKS como parte do setup da infraestrutura, podemos utilizá-lo no cenário de provisionamento de recursos junto ao backstage, neste lab usando AWS como provider. A estratégia é a mesma, utilizar o backstage para fazer o input das informações que serão passadas em um manifesto (claim) e entregar com ArgoCD.

Diagrama do fluxo de provisionamento com Backstage e Crossplane

Registro do template de RDS no Backstage

Preview do template de RDS

  • Skeleton do claim para composition de rds:
# CLAIM
---
apiVersion: api.pauloponciano.digital/v1alpha1
kind: CustomDatabase
metadata:
  name: ${{values.name}}-${{values.env}}-${{values.engine}}-db
  namespace: environment-crossplane
  labels:
    backstage.io/kubernetes-id: rds
spec:
  compositionSelector:
    matchLabels:
      db-engine: ${{values.engine}}
  resourceConfig:
    providerConfigName: aws
    region: ${{values.region}}
    size: ${{values.size}}
    engine: ${{values.engine}}
    tags:
      automation-by: crossplane
      ownerName: ${{values.owner}}
Enter fullscreen mode Exit fullscreen mode

Composition e definition já devem estar aplicadas no cluster para o crossplane. Podem encontrá-las aqui.

Composition e definition de RDS aplicadas no cluster

  • Input das informações:

Formulário de input do template de RDS

Seleção dos parâmetros do banco de dados

Revisão das informações do claim de RDS

Execução dos steps de provisionamento do RDS

  • Aprovar pull-request no repositório de GitOps:

Pull request do claim de RDS no repositório de GitOps

Merge do pull request do claim de RDS

  • Checando o sync no ArgoCD, na application 'idp-infra-aws':

Claim de RDS sincronizado na application idp-infra-aws

  • No catálogo do backstage, podemos checar que a composite do crossplane foi criada no cluster:

Composite do Crossplane exibida no catálogo do Backstage

  • Olhando na console AWS, podemos confirmar a criação do recurso:

Recurso RDS criado confirmado na console AWS


Com isso, podemos ver as grandes possibilidades e facilidades que o backstage, combinado com outras soluções, pode trazer para os times de uma organização.

Agradecimento a todos que publicam conteúdos abertos para fortalecer a comunidade, e que ajudaram muito nesse lab.


Happy building!

Top comments (0)