DevOps

Platform Engineering: Construindo a Plataforma Interna de Desenvolvimento Já leu

15 min de leitura

Platform Engineering: Construindo a Plataforma Interna de Desenvolvimento
Quando uma organização tem dois ou três times de desenvolvimento, o modelo DevOps tradicional funciona bem: cada time opera sua própria infraestrutura, define seus próprios pipelines e resolve seus próprios problemas ope

Quando uma organização tem dois ou três times de desenvolvimento, o modelo DevOps tradicional funciona bem: cada time opera sua própria infraestrutura, define seus próprios pipelines e resolve seus próprios problemas operacionais. O conhecimento é compartilhado informalmente, as ferramentas são razoavelmente padronizadas e o atrito é manejável.

À medida que a organização cresce — dez times, vinte, cinquenta — esse modelo começa a se fragmentar. Cada time reinventa a roda de maneiras ligeiramente diferentes: pipelines de CI/CD com estruturas inconsistentes, configurações de infraestrutura copiadas de repositórios uns dos outros com pequenas variações, práticas de segurança aplicadas de forma irregular, onboarding de novos desenvolvedores que leva semanas porque não há um caminho padronizado. O time de plataforma ou SRE se torna um gargalo — respondendo a perguntas repetitivas, revisando configurações similares, resolvendo os mesmos problemas em contextos diferentes.

O Platform Engineering é a disciplina que resolve esse problema construindo uma Internal Developer Platform — uma plataforma interna que abstrai a complexidade operacional e oferece aos times de desenvolvimento uma experiência de autoatendimento bem definida. O objetivo não é centralizar o controle, mas criar "paved roads" — caminhos pavimentados que tornam a maneira correta de fazer as coisas também a mais fácil.


O Conceito de Internal Developer Platform

Uma IDP — Internal Developer Platform — não é um único produto ou ferramenta. É um conjunto de capacidades, APIs, ferramentas e fluxos de trabalho que o time de plataforma constrói e mantém para que os times de produto possam ser autônomos sem precisar ser especialistas em infraestrutura.

As capacidades típicas de uma IDP madura incluem:

Self-service de infraestrutura — um desenvolvedor consegue provisionar um ambiente completo — banco de dados, cache, filas, monitoramento — sem abrir um ticket para o time de infraestrutura.

Templates de aplicação — novos serviços são criados a partir de templates que já incluem as melhores práticas de segurança, observabilidade e CI/CD.

Portal do desenvolvedor — uma interface centralizada onde o desenvolvedor encontra toda a informação relevante sobre os serviços que opera: status, métricas, logs, dependências, documentação, contato do time.

Catálogo de serviços — visibilidade de todos os serviços da organização, quem os possui, em que estado estão e como se comunicam entre si.


Backstage: O Portal do Desenvolvedor da Spotify

O Backstage é a plataforma de portal do desenvolvedor open-source criada pela Spotify e doada à CNCF. É a base sobre a qual a maioria das organizações constrói sua IDP — um framework extensível que oferece o catálogo de serviços, um sistema de templates e um marketplace de plugins.

Instalando e Configurando o Backstage

# Cria um novo app Backstage
npx @backstage/create-app@latest --path minha-plataforma

cd minha-plataforma

# Instala dependências
yarn install

# Inicia em modo de desenvolvimento
yarn dev

A estrutura do projeto Backstage:

minha-plataforma/
├── app-config.yaml          # Configuração principal
├── app-config.production.yaml
├── packages/
│   ├── app/                 # Frontend (React)
│   │   └── src/
│   │       ├── App.tsx
│   │       └── components/
│   └── backend/             # Backend (Node.js/Express)
│       └── src/
│           └── index.ts
└── plugins/                 # Plugins customizados

Configuração principal do Backstage:

# app-config.yaml
app:
  title: Plataforma de Desenvolvimento — Empresa
  baseUrl: https://plataforma.empresa.com

organization:
  name: Empresa S.A.

backend:
  baseUrl: https://plataforma.empresa.com
  listen:
    port: 7007

  database:
    client: pg
    connection:
      host: ${POSTGRES_HOST}
      port: ${POSTGRES_PORT}
      user: ${POSTGRES_USER}
      password: ${POSTGRES_PASSWORD}
      database: backstage

  cache:
    store: redis
    connection: ${REDIS_URL}

# Autenticação via GitHub OAuth
auth:
  providers:
    github:
      development:
        clientId: ${GITHUB_CLIENT_ID}
        clientSecret: ${GITHUB_CLIENT_SECRET}

# Integração com GitHub para importar componentes
integrations:
  github:
    - host: github.com
      token: ${GITHUB_TOKEN}

# Catálogo — onde o Backstage descobre os componentes
catalog:
  import:
    entityFilename: catalog-info.yaml
    pullRequestBranchName: backstage-integration

  rules:
    - allow:
        [
          Component,
          System,
          API,
          Resource,
          Location,
          Domain,
          Group,
          User,
        ]

  locations:
    # Importa todos os repositórios da organização com catalog-info.yaml
    - type: github-org
      target: https://github.com/empresa
      rules:
        - allow: [Group, User]

    # Importa templates de scaffolding
    - type: url
      target: https://github.com/empresa/plataforma-templates/blob/main/all-templates.yaml

# TechDocs — documentação integrada
techdocs:
  builder: external
  generator:
    runIn: local
  publisher:
    type: awsS3
    awsS3:
      bucketName: ${TECHDOCS_BUCKET}
      region: us-east-1

# Configuração do Kubernetes para mostrar workloads
kubernetes:
  serviceLocatorMethod:
    type: multiTenant
  clusterLocatorMethods:
    - type: config
      clusters:
        - url: ${K8S_CLUSTER_URL}
          name: producao
          authProvider: serviceAccount
          skipTLSVerify: false
          serviceAccountToken: ${K8S_SERVICE_ACCOUNT_TOKEN}
          caData: ${K8S_CA_DATA}

Registrando um Serviço no Catálogo

Cada repositório de serviço inclui um arquivo catalog-info.yaml que descreve o componente para o Backstage:

# catalog-info.yaml — na raiz do repositório do serviço
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: minha-api
  title: API de Pedidos
  description: |
    API REST responsável pelo gerenciamento do ciclo de vida de pedidos,
    incluindo criação, atualização de status, cancelamento e consultas.

  # Etiquetas para filtragem no catálogo
  tags:
    - nodejs
    - api
    - postgres
    - redis

  # Links úteis para o time
  links:
    - url: https://grafana.empresa.com/d/minha-api
      title: Dashboard Grafana
      icon: dashboard
    - url: https://minha-api.empresa.com/docs
      title: Documentação da API
      icon: docs
    - url: https://empresa.pagerduty.com/service-directory/minha-api
      title: PagerDuty
      icon: alert

  annotations:
    # Integração com GitHub
    github.com/project-slug: empresa/minha-api

    # Integração com Kubernetes — mostra os pods no Backstage
    backstage.io/kubernetes-id: minha-api
    backstage.io/kubernetes-namespace: producao

    # Integração com PagerDuty
    pagerduty.com/service-id: PXXXXXX

    # Integração com Sentry
    sentry.io/project-slug: minha-api-producao

    # Documentação técnica via TechDocs
    backstage.io/techdocs-ref: dir:.

spec:
  type: service
  lifecycle: production  # experimental | deprecated | production
  owner: group:checkout-team

  # Sistemas que este componente integra
  system: sistema-pedidos

  # Dependências declaradas
  dependsOn:
    - component:banco-usuarios
    - resource:rds-producao
    - resource:redis-producao

  # APIs que este serviço provê e consome
  providesApis:
    - api-pedidos-v2

  consumesApis:
    - api-catalogo-v1
    - api-pagamentos-v1

A API fornecida pelo serviço:

# api-definition.yaml
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
  name: api-pedidos-v2
  title: API de Pedidos v2
  description: API REST para gestão de pedidos
  tags:
    - rest
    - v2
spec:
  type: openapi
  lifecycle: production
  owner: group:checkout-team
  system: sistema-pedidos

  definition:
    $text: ./openapi.yaml  # Referencia a spec OpenAPI do repositório

Templates de Scaffolding: Criando Novos Serviços

Os Software Templates do Backstage permitem criar novos serviços a partir de templates pré-configurados que já incluem as melhores práticas de segurança, observabilidade e CI/CD:

# templates/nodejs-api/template.yaml
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: nodejs-api-template
  title: API Node.js com Express e PostgreSQL
  description: |
    Cria uma nova API Node.js com Express, PostgreSQL, Redis,
    observabilidade com OpenTelemetry e pipeline de CI/CD completo.
  tags:
    - nodejs
    - api
    - postgres
    - recommended

spec:
  owner: group:plataforma
  type: service

  # Parâmetros coletados do desenvolvedor
  parameters:
    - title: Informações do Serviço
      required: [nome, descricao, owner]
      properties:
        nome:
          title: Nome do serviço
          type: string
          description: Nome em kebab-case (ex: api-pagamentos)
          pattern: '^[a-z][a-z0-9-]*$'
          ui:autofocus: true

        descricao:
          title: Descrição
          type: string
          description: Descreva brevemente a responsabilidade deste serviço

        owner:
          title: Time responsável
          type: string
          description: Time que será dono deste serviço
          ui:field: OwnerPicker
          ui:options:
            catalogFilter:
              kind: Group

    - title: Configuração de Infraestrutura
      properties:
        banco_dados:
          title: Banco de dados PostgreSQL
          type: boolean
          default: true

        redis:
          title: Cache Redis
          type: boolean
          default: false

        filas:
          title: Filas SQS
          type: boolean
          default: false

        ambiente_inicial:
          title: Ambiente inicial
          type: string
          enum: [staging, production]
          default: staging

    - title: Repositório
      required: [repo_url]
      properties:
        repo_url:
          title: Localização do repositório
          type: string
          ui:field: RepoUrlPicker
          ui:options:
            allowedHosts: [github.com]
            allowedOwners: [empresa]

  # Passos de criação do serviço
  steps:
    - id: fetch-base
      name: Baixa o template base
      action: fetch:template
      input:
        url: ./skeleton
        values:
          nome: ${{ parameters.nome }}
          descricao: ${{ parameters.descricao }}
          owner: ${{ parameters.owner }}
          banco_dados: ${{ parameters.banco_dados }}
          redis: ${{ parameters.redis }}
          filas: ${{ parameters.filas }}

    - id: publish
      name: Publica no GitHub
      action: publish:github
      input:
        allowedHosts: [github.com]
        description: ${{ parameters.descricao }}
        repoUrl: ${{ parameters.repo_url }}
        defaultBranch: main
        repoVisibility: private
        requireCodeOwnerReviews: true
        requiredApprovingReviewCount: 1
        topics:
          - nodejs
          - api
          - ${{ parameters.owner }}

    - id: provision-infra
      name: Provisiona infraestrutura via Terraform
      action: http:backstage:request
      input:
        method: POST
        path: /api/terraform-runner/provision
        body:
          servico: ${{ parameters.nome }}
          banco_dados: ${{ parameters.banco_dados }}
          redis: ${{ parameters.redis }}
          ambiente: ${{ parameters.ambiente_inicial }}

    - id: register
      name: Registra no catálogo
      action: catalog:register
      input:
        repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }}
        catalogInfoPath: /catalog-info.yaml

  # O que mostrar ao desenvolvedor após a criação
  output:
    links:
      - title: Repositório GitHub
        url: ${{ steps.publish.output.remoteUrl }}
      - title: Abrir no Catálogo
        entityRef: ${{ steps.register.output.entityRef }}
    text:
      - title: Próximos passos
        content: |
          Seu novo serviço foi criado! 🎉

          **O que foi configurado automaticamente:**
          - Repositório GitHub com branch protection
          - Pipeline de CI/CD (GitHub Actions)
          - Infraestrutura AWS provisionada via Terraform
          - Observabilidade com OpenTelemetry
          - Registro no catálogo de serviços

          **Próximos passos:**
          1. Clone o repositório e explore a estrutura
          2. Configure os secrets no repositório GitHub
          3. Execute o primeiro deploy via pipeline

Skeleton do Template

O diretório skeleton contém a estrutura do serviço com placeholders substituídos pelo Backstage:

templates/nodejs-api/skeleton/
├── catalog-info.yaml
├── package.json
├── Dockerfile
├── .github/
│   └── workflows/
│       ├── ci.yml
│       └── deploy.yml
├── src/
│   ├── server.js
│   ├── health/
│   │   └── health.routes.js
│   └── observability/
│       └── tracing.js
├── terraform/
│   ├── main.tf
│   ├── variables.tf
│   └── outputs.tf
├── docs/
│   ├── index.md
│   └── mkdocs.yml
└── ${{ values.nome }}.md  # README gerado com o nome do serviço

Self-Service de Infraestrutura com Terraform e Crossplane

O self-service de infraestrutura permite que desenvolvedores provisionem recursos sem interagir com o time de plataforma. Duas abordagens complementares:

Módulos Terraform Aprovados

O time de plataforma publica módulos Terraform que encapsulam as melhores práticas e os desenvolvedores os consomem:

# Módulo publicado pelo time de plataforma
# terraform-registry.empresa.com/empresa/api-stack/aws

variable "nome_servico" {
  description = "Nome do serviço — usado como prefixo de todos os recursos"
  type        = string
}

variable "ambiente" {
  type    = string
  default = "staging"
}

variable "banco_dados" {
  description = "Configuração do banco de dados"
  type = object({
    habilitado     = bool
    classe         = optional(string, "db.t3.micro")
    multi_az       = optional(bool, false)
    storage_gb     = optional(number, 20)
  })
  default = { habilitado = false }
}

variable "redis" {
  description = "Configuração do Redis"
  type = object({
    habilitado  = bool
    tipo_no     = optional(string, "cache.t3.micro")
    num_nos     = optional(number, 1)
  })
  default = { habilitado = false }
}

# O módulo cria todos os recursos necessários para um serviço
module "banco" {
  count  = var.banco_dados.habilitado ? 1 : 0
  source = "./modules/rds"

  nome       = "${var.nome_servico}-${var.ambiente}"
  classe     = var.banco_dados.classe
  multi_az   = var.ambiente == "production" ? true : var.banco_dados.multi_az
  storage_gb = var.banco_dados.storage_gb
}

module "cache" {
  count  = var.redis.habilitado ? 1 : 0
  source = "./modules/elasticache"

  nome    = "${var.nome_servico}-${var.ambiente}"
  tipo_no = var.redis.tipo_no
  num_nos = var.ambiente == "production" ? max(var.redis.num_nos, 2) : var.redis.num_nos
}

module "observabilidade" {
  source = "./modules/observabilidade"
  nome   = var.nome_servico
}

# Outputs padronizados que o serviço consome
output "database_url_secret_arn" {
  value = var.banco_dados.habilitado ? module.banco[0].secret_arn : null
}

output "redis_endpoint" {
  value = var.redis.habilitado ? module.cache[0].endpoint : null
}

output "namespace_kubernetes" {
  value = kubernetes_namespace.servico.metadata[0].name
}

Uso pelo time de produto — um arquivo platform.tf simples:

# Na raiz do repositório do serviço — minha-api/terraform/platform.tf
module "plataforma" {
  source  = "terraform-registry.empresa.com/empresa/api-stack/aws"
  version = "~> 2.0"

  nome_servico = "minha-api"
  ambiente     = var.environment

  banco_dados = {
    habilitado = true
    classe     = var.environment == "production" ? "db.r6g.large" : "db.t3.micro"
    multi_az   = var.environment == "production"
  }

  redis = {
    habilitado = true
  }
}

Crossplane: Infraestrutura como Recursos Kubernetes

O Crossplane é uma extensão do Kubernetes que permite criar recursos de cloud — RDS, S3, ElastiCache — usando o mesmo modelo declarativo de manifests YAML que os desenvolvedores já conhecem:

# kubernetes/infra/banco-dados.yaml
# O desenvolvedor declara o banco que precisa como um recurso Kubernetes
apiVersion: database.empresa.com/v1alpha1
kind: PostgreSQLDatabase
metadata:
  name: minha-api-db
  namespace: producao
spec:
  # Classe de composição define o que "staging" vs "production" significa
  compositionSelector:
    matchLabels:
      ambiente: production

  parameters:
    classe: db.r6g.large
    storageGb: 100
    multiAz: true
    backupRetentionDays: 14

  writeConnectionSecretToRef:
    name: minha-api-db-credenciais
    namespace: producao

O Crossplane recebe esse manifest, provisiona o RDS na AWS via Terraform ou AWS Provider, e armazena as credenciais como um Kubernetes Secret — tudo sem que o desenvolvedor precise interagir com o console da AWS ou escrever Terraform.


Métricas de Saúde da Plataforma

O time de plataforma usa métricas próprias para medir a eficácia da IDP:

DORA Metrics por time — mede se a plataforma está realmente acelerando a entrega dos times que a usam.

Tempo de onboarding — quanto tempo um novo desenvolvedor leva para fazer o primeiro deploy. O objetivo é reduzir de dias para horas.

Adoção de templates — percentual de novos serviços criados via templates vs criados manualmente. Alta adoção indica que os templates são úteis e de baixo atrito.

Toil reduction — redução do trabalho repetitivo e manual do time de plataforma medida em tickets, tempo de resposta e número de perguntas repetitivas.

# Painel de saúde da plataforma no Backstage
# plugins/platform-health/src/components/PlatformHealthCard.tsx

# Métricas exibidas no dashboard do time de plataforma:
# - Serviços no catálogo: 127
# - Serviços criados via template: 89% (últimos 6 meses)
# - Tempo médio de onboarding: 4 horas (meta: < 2h)
# - Tickets de infraestrutura/semana: 12 (baseline: 45)
# - DORA — Deploy Frequency: 8.2/dia (todos os times)
# - DORA — Lead Time: 42min (meta: < 60min)
# - DORA — Change Failure Rate: 1.8% (meta: < 5%)
# - DORA — MTTR: 18min (meta: < 60min)

Golden Paths: Opiniões, Não Mandatos

Um princípio fundamental de Platform Engineering é que os "paved roads" — caminhos pavimentados — devem ser a opção mais fácil, não a única. Um time com requisitos genuinamente diferentes deve poder sair do caminho padrão, desde que documente o motivo e aceite a responsabilidade adicional.

Essa filosofia se manifesta em como a plataforma é construída:

Os templates oferecem uma experiência de alta qualidade para o caso de uso mais comum — uma API Node.js com PostgreSQL e Redis, por exemplo. Times que precisam de Python, Go ou uma arquitetura radicalmente diferente podem criar seus próprios templates ou usar os módulos Terraform diretamente.

As restrições de segurança são aplicadas em camadas — algumas são mandatórias e aplicadas no nível de organização (criptografia em repouso, não expor portas desnecessárias ao mundo), outras são recomendações que os times podem sobrescrever com justificativa documentada.

O catálogo de serviços captura a realidade — não apenas o estado ideal. Serviços legados, serviços em modo experimental e serviços que deliberadamente saíram do caminho padrão são todos registrados, com suas exceções documentadas.


O Que Vem a Seguir

O próximo artigo cobre o penúltimo tema da série: Cultura e Práticas Organizacionais em DevOps — como medir a maturidade de uma organização DevOps, como conduzir revisões pós-incidente que geram aprendizado real e como construir uma cultura de melhoria contínua que sustenta as práticas técnicas ao longo do tempo.


Referências para Aprofundamento

Backstage - Backstage Documentation — backstage.io — Documentação oficial do Backstage cobrindo instalação, configuração do catálogo, software templates e desenvolvimento de plugins. - Backstage Plugins — backstage.io — Catálogo oficial de plugins do Backstage com integrações para GitHub, PagerDuty, Kubernetes, Grafana, SonarQube e centenas de outras ferramentas.

Platform Engineering - Team Topologies — teamtopologies.com — Livro fundamental sobre organização de times de tecnologia, introduzindo os conceitos de Stream-aligned teams, Platform teams e Enabling teams que embasam o Platform Engineering. - CNCF Platforms White Paper — tag-app-delivery.cncf.io — White paper oficial da CNCF sobre plataformas internas de desenvolvimento, cobrindo capacidades, modelo de maturidade e casos de uso.

Crossplane - Crossplane Documentation — docs.crossplane.io — Documentação completa do Crossplane com guias de instalação, criação de Composite Resource Definitions e integração com provedores AWS, Azure e GCP.

Comentários

Mais em DevOps

Grafana: Dashboards e Alertas que Fazem Sentido
Grafana: Dashboards e Alertas que Fazem Sentido

Um dashboard mal projetado é quase tão ruim quanto não ter dashboard. Quando...

DevSecOps: Integrando Segurança em Todo o Pipeline
DevSecOps: Integrando Segurança em Todo o Pipeline

Durante décadas, a segurança foi tratada como uma fase — algo que acontecia d...

Variáveis de Ambiente e Secrets em Pipelines
Variáveis de Ambiente e Secrets em Pipelines

Em abril de 2022, a empresa de segurança GitGuardian publicou um relatório re...