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.
Exercícios
Exercício 1
No módulo api-stack, duas linhas tratam o ambiente de produção de forma especial:
multi_az = var.ambiente == "production" ? true : var.banco_dados.multi_az
num_nos = var.ambiente == "production" ? max(var.redis.num_nos, 2) : var.redis.num_nos
Um time declara multi_az = false e num_nos = 5 com ambiente = "production". O que ele recebe? As duas linhas se comportam da mesma maneira?
Ver resposta
✓ Resposta: Ele recebe multi_az = true (o que pediu foi ignorado) e num_nos = 5 (o que pediu foi respeitado). As duas linhas parecem simétricas e não são.
A primeira é uma imposição: em produção o valor é true, ponto — o input do time não é sequer consultado. A segunda é um piso: max(5, 2) devolve 5, porque o time pediu mais do que o mínimo. Se tivesse pedido 1, receberia 2.
A distinção é exatamente a filosofia de paved roads que o artigo defende, expressa em código. O max() diz "não deixo você ficar abaixo do seguro, mas acima é escolha sua"; o ternário fixo diz "isto não se negocia". E cada um é adequado a um tipo de requisito: alta disponibilidade de banco em produção é regra da organização, enquanto o número de nós de cache é dimensionamento, que pertence a quem conhece a carga.
Vale notar o que acontece do lado de quem consome. O time escreve multi_az = false, o terraform plan mostra multi_az = true, e não há nada no plano explicando por quê — a decisão está enterrada em um módulo remoto versionado. Um guard-rail silencioso confunde tanto quanto protege. A saída é tornar a regra visível: uma precondition que falha com mensagem explícita é mais honesta que uma sobrescrita muda.
lifecycle {
precondition {
condition = var.ambiente != "production" || var.banco_dados.multi_az
error_message = "Banco em produção exige multi_az = true (política de disponibilidade da plataforma)."
}
}
Repare também no optional(string, "db.t3.micro") das variáveis: ele permite que o time declare apenas { habilitado = true } e receba defaults sensatos no resto — outro mecanismo de caminho pavimentado, agora por omissão em vez de imposição.
Exercício 2
Os steps do template de scaffolding rodam em sequência: fetch-base, publish (cria o repositório no GitHub), provision-infra (chama o runner do Terraform) e register (registra no catálogo). O passo provision-infra falha porque a conta AWS atingiu o limite de instâncias RDS. Em que estado o desenvolvedor fica?
Ver resposta
✓ Resposta: Em um estado parcial e órfão. O repositório já existe no GitHub, com branch protection, code owners e o pipeline configurado — o publish concluiu antes. A infraestrutura não existe. E o register nunca rodou, então o serviço não aparece no catálogo: existe um repositório novo que a plataforma desconhece.
O Backstage não desfaz os passos anteriores — o scaffolder não é transacional. Não há rollback automático, e o desenvolvedor vê apenas uma tela de erro no meio do fluxo, sem indicação clara do que sobrou para trás.
Pior é o que costuma acontecer em seguida: a pessoa tenta de novo. O publish falha agora porque o repositório já existe, e a execução para ainda mais cedo — sem nunca chegar ao provisionamento. O fluxo fica travado até alguém apagar o repositório manualmente, que é exatamente o tipo de ticket ao time de plataforma que a IDP existia para eliminar.
Duas correções tornam isso administrável. A primeira é de ordem: colocar os passos que podem falhar por motivos externos antes dos que criam artefatos duradouros, ou dividir o template em uma etapa de validação (checar quota, permissões, disponibilidade do nome) que roda antes de qualquer criação. A segunda é tornar cada passo idempotente, de modo que repetir a execução converge em vez de colidir — o publish reaproveitando um repositório vazio já existente, o provision-infra reconhecendo um estado Terraform já iniciado.
É também um argumento a favor do Crossplane, apresentado adiante no artigo: como a infraestrutura vira um recurso do Kubernetes, quem faz a convergência é o controller, com reconciliação contínua. Um pedido que falha por quota fica pendente e é atendido sozinho quando a quota liberar — em vez de exigir que o desenvolvedor recomece um fluxo de quatro passos.
Exercício 3
No manifesto do Crossplane, o desenvolvedor declara compositionSelector: matchLabels: ambiente: production e, logo abaixo, parameters com classe: db.r6g.large e multiAz: true. Se ele já especifica a classe e o multi-AZ, para que serve o seletor de composição?
Ver resposta
✓ Resposta: Eles operam em níveis diferentes: os parameters são o que o time pede; a Composition é o que a organização entrega quando alguém pede aquilo em produção.
O PostgreSQLDatabase é um tipo criado pela empresa — não existe na AWS nem no Crossplane. Por trás dele, a Composition selecionada define tudo o que o desenvolvedor não escreveu e provavelmente nem deveria decidir: em qual VPC e subnets o banco nasce, qual security group o protege, qual chave KMS o criptografa, se há réplica de leitura, para onde vão os backups, quais tags de custo são aplicadas, qual janela de manutenção vale. Uma Composition rotulada ambiente: staging pode materializar o mesmo pedido com uma instância pequena, sem multi-AZ e com retenção de 1 dia.
É a diferença entre declarar intenção e declarar implementação. O time diz "preciso de um banco PostgreSQL de produção com essa capacidade"; a plataforma decide como isso se traduz em recursos reais — e pode mudar essa tradução (trocar a família de instância, adotar Aurora, migrar de região) sem que nenhum time altere seu manifesto.
O writeConnectionSecretToRef completa o desenho: as credenciais geradas aparecem como um Secret do Kubernetes no namespace do time, prontas para o Deployment consumir. O desenvolvedor nunca vê o console da AWS, nunca escreve Terraform e nunca manipula a senha.
Vale o contraponto honesto: nada impede que ele peça classe: db.r6g.16xlarge. Um parâmetro exposto é um parâmetro que alguém vai exagerar — por isso Compositions maduras validam faixas aceitáveis no schema da Composite Resource Definition, e não apenas confiam no bom senso de quem preenche o YAML.
Exercício 4
Uma migração vai derrubar o rds-producao por 40 minutos numa madrugada. Usando exclusivamente o que está declarado no catalog-info.yaml do artigo, como descobrir quem precisa ser avisado? E qual o ponto fraco desse método?
Ver resposta
✓ Resposta: Pelo grafo de dependências. O manifesto declara dependsOn: [component:banco-usuarios, resource:rds-producao, resource:redis-producao], então basta consultar o catálogo por todos os componentes que dependem de resource:rds-producao. Cada um traz seu owner — aqui group:checkout-team —, e o grupo no catálogo carrega os membros e canais de contato. Os links ainda dão o PagerDuty de cada serviço, o que resolve a pergunta seguinte: quem está de plantão naquela madrugada.
O consumesApis estende o alcance para o segundo grau: quem não usa o banco diretamente, mas depende de uma API de quem usa, também sofre o impacto. É justamente a pergunta que ninguém consegue responder de memória em uma organização com 127 serviços — e a razão principal de o catálogo existir. Sem ele, a alternativa é mandar mensagem no canal geral e torcer para a pessoa certa estar lendo.
O ponto fraco é que a declaração é manual e não é verificada. O dependsOn reflete o que alguém escreveu no YAML no dia em que criou o serviço — não o que o código realmente faz hoje. Dependências adicionadas depois raramente voltam ao manifesto, e dependências removidas continuam listadas. O grafo apodrece em silêncio, e o modo de falha é traiçoeiro: ele parece completo. Confia-se nele para a comunicação da migração, e o time que faltava no grafo é surpreendido às 3 da manhã.
Mitigações que funcionam: derivar parte do grafo de fontes que não mentem — rastros do OpenTelemetry mostram quem realmente chamou quem no último mês, e Network Policies declaram conexões permitidas —, e transformar a manutenção do manifesto em rotina de revisão, e não em formulário de cadastro. Um item de checklist no template de PR ("mudou uma dependência? atualize o catalog-info.yaml") custa pouco e mantém o dado vivo.
Exercício 5
O painel de saúde da plataforma mostra "Serviços criados via template: 89%". Suponha que, seis meses depois, esse número caia para 40%. Segundo a filosofia de golden paths defendida no artigo, o que o time de plataforma deve concluir?
Ver resposta
✓ Resposta: Que o template deixou de servir — e não que os times ficaram indisciplinados.
Essa é a inversão que define Platform Engineering. O artigo afirma que os caminhos pavimentados devem ser "a opção mais fácil, não a única": se são de fato os mais fáceis, a adoção acontece por conveniência, sem precisar ser cobrada. Quando 60% dos times escolhem o caminho mais trabalhoso — escrever pipeline do zero, montar Terraform à mão, configurar observabilidade manualmente —, isso significa que, para eles, sair do padrão ainda compensa. É um dado sobre o produto, não sobre as pessoas.
A adoção funciona como métrica justamente por ser voluntária. Uma métrica que se pode cumprir por decreto para de medir qualquer coisa: tornar o template obrigatório levaria a adoção a 100% instantaneamente, e o número perderia todo o valor diagnóstico — ao mesmo tempo em que empurraria os times para workarounds, gerando serviços que nascem do template e são desfigurados no primeiro commit.
As causas prováveis são investigáveis, e é isso que o time deve fazer antes de qualquer coisa: o template cobre uma stack que deixou de ser a dominante (chegaram times de Go ou Python); ele carrega opiniões que envelheceram; está desatualizado a ponto de exigir correções logo após a geração; ou o fluxo de quatro passos falha com frequência — como no cenário do exercício 2 — e ganhou fama de não funcionar.
As outras métricas do painel ajudam a separar as hipóteses. Se os tickets de infraestrutura por semana voltarem a subir junto com a queda, os times estão sofrendo fora do caminho pavimentado — a plataforma perdeu alcance. Se os tickets seguirem baixos e as métricas DORA dos times continuarem boas, eles encontraram formas melhores de trabalhar, e o papel do time de plataforma passa a ser aprender com elas e incorporá-las ao template — que é exatamente o que "opiniões, não mandatos" significa na prática.