DevOps
Dockerfile, compose, CI, Kubernetes, Helm, Terraform, nginx, Prometheus e SBOM — gerados do que o projeto realmente usa, e não de um modelo.
dataforge devops gera os arquivos que põem um projeto no ar — e sai da frente. Não há deploy que fale com Docker e Kubernetes por dentro: no dia em que alguém precisa mudar uma camada, tem de haver onde mexer.
dataforge devops # tudo o que faz sentido para este projeto
dataforge devops doctor # o que falta para subir
dataforge devops --seco # mostra o que faria, sem escreverOs onze subcomandos#
| Comando | Gera |
|---|---|
devops init | tudo o que faz sentido para este projeto (o padrão) |
devops docker | Dockerfile, .dockerignore, docker-compose.yml |
devops ci github | .github/workflows/ci.yml |
devops k8s | deployment, service, ingress, configmap, hpa |
devops helm | um chart com values.yaml |
devops terraform | o esqueleto |
devops nginx | proxy reverso com TLS, WebSocket e SSE |
devops observar | Prometheus, Grafana e OpenTelemetry |
devops sbom | o inventário, em CycloneDX |
devops secrets | .env.example e o .gitignore |
devops doctor | o que falta para este projeto subir |
Os artefatos saem do que o projeto ADOTA#
Não é um modelo com buracos para preencher. O gerador lê o forge.toml e varre os .df procurando o que o código realmente usa:
$ dataforge devops --seco
◆ loja 2.1.0 (Kiln, banco)
+ .dockerignore 43 linha(s)
+ .env.example 15 linha(s)
+ .github/workflows/ci.yml 75 linha(s)
+ Dockerfile 40 linha(s)
+ deploy/nginx.conf 67 linha(s)
+ docker-compose.yml 41 linha(s)
+ k8s/configmap.yml 13 linha(s)
+ k8s/deployment.yml 89 linha(s)
+ k8s/hpa.yml 30 linha(s)
+ k8s/ingress.yml 31 linha(s)
+ k8s/service.yml 16 linha(s)O (Kiln, banco) ali é o que ele detectou — e é o que decide o resultado. Quem não usa banco não ganha um Postgres no compose; quem não usa Kiln não ganha EXPOSE nem sonda de saúde.
O que o Dockerfile carrega, e por quê#
O que ele gera não é esboço: é o que se poria em produção. Cinco decisões, e o problema de cada uma:
| No artefato | Sem ele |
|---|---|
USER forge | um escape de container vira root no host |
| o manifesto copiado antes do código | um commit numa linha reinstala tudo — o build vai de 8 s para 2 min |
.env no .dockerignore | o segredo fica na camada, e docker history o mostra — mesmo apagado numa camada seguinte |
resources + as duas sondas no Deployment | um pod come o nó inteiro; e o Service manda tráfego antes da hora |
depends_on: service_healthy | a aplicação falha na primeira consulta, de forma intermitente |
# A sonda usa o proprio Python: 'curl' nao esta na imagem
# slim, e instalar 30 MB para uma sonda de 200 bytes seria
# trocar tamanho por conveniencia.
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD python -c "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8080/saude', timeout=2).status == 200 else 1)"`doctor` — e ele funciona num projeto que não compila#
$ dataforge devops doctor
◆ loja 2.1.0 (Kiln, banco)
✓ há um forge.toml
✓ a entrada existe (src/main.df)
✓ o código passa no 'check'
✗ há Dockerfile
✗ há .dockerignore
✓ nenhum segredo versionado
! não há .env.example cada pessoa nova descobre as variáveis por tentativa
✗ há pipeline de CI
✗ há testes
Para produção:
· há um proxy reverso na frente (TLS, compressão, HTTP/2)
· a sessão do Kiln vive na memória do processo: UM processo por aplicação
· 'Kiln.secure_headers()' como middleware de saída
4 coisa(s) a resolver:
1. gere com 'dataforge devops docker'
2. sem ele, o '.env' entra na imagem — e continua nela depois de
apagado numa camada seguinte
3. gere com 'dataforge devops ci github'Cada linha vermelha vem com o comando que a resolve. Um diagnóstico que diz "falta X" e não diz como obter X transfere o trabalho de volta.
Segredo versionado: a conferência é no git#
Não no disco. Um .env que existe e está no .gitignore é normal; um .env que o git rastreia já vazou — e apagá-lo agora não o tira do histórico.
`--host=0.0.0.0` é obrigatório dentro de um container#
Vale para o ignite do Kiln (at "0.0.0.0") e para a Vitrine (--host=0.0.0.0). O padrão é 127.0.0.1, que de dentro do container significa o próprio container.
O sintoma é enganoso: o log diz "no ar", a porta está publicada, e o curl de fora não recebe nada. Os artefatos gerados já trazem a forma certa.
// 'at "0.0.0.0"' e obrigatorio dentro de um container.
ignite loja on 8080 at "0.0.0.0"Kubernetes: o que os manifestos trazem#
spec:
replicas: 3
# 'maxUnavailable: 0' faz a atualizacao nao derrubar capacidade: o
# pod novo entra em servico antes de o velho sair.
strategy:
rollingUpdate:
maxUnavailable: 0
…
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
Mais resources (requests e limits), readinessProbe, livenessProbe, e um hpa que escala por CPU. O ingress usa o domínio que você passar em --dominio=.
O YAML é escrito à mão#
Escrever o YAML à mão, em vez de depender de uma biblioteca, é a mesma política de zero dependências do resto da linguagem — e aqui ela cobra o preço de conhecer essas armadilhas em vez de herdá-las resolvidas.
Duas metades separadas de propósito#
| Arquivo | Faz |
|---|---|
dataforge/devops.py | produz texto |
dataforge/devops_cli.py | escreve arquivo |
A separação deixa os geradores testáveis sem tocar em disco — os 65 testes validam o compose com docker compose config, conferem os manifestos como dado e executam a sonda do HEALTHCHECK — e põe a política de "o que fazer quando o arquivo já existe" num lugar só.
O que ele não é#
Não é um orquestrador. Não sobe nada, não fala com nenhuma nuvem, não guarda estado. Um deploy mágico esconderia o que a imagem é, e a primeira vez que algo desse errado em produção não haveria onde olhar.
O que ele é: a diferença entre saber que você precisa de um readinessProbe e ter um.