A configuração de aplicações é um dos pilares do DevOps moderno. Em ambientes distribuídos, com múltiplos estágios (desenvolvimento, homologação, produção) e deploys contínuos, a forma como gerenciamos as configurações pode determinar o sucesso ou o fracasso de uma operação. Uma configuração mal feita pode levar a falhas de segurança, indisponibilidade e retrabalho. Nesta aula, vamos mergulhar nos princípios do 12-Factor App, que fornecem um guia robusto para construir aplicações modernas e escaláveis, com foco especial no gerenciamento de configuração.

Abordaremos desde a visão geral do 12-Factor App até as práticas recomendadas para separar configuração de código, usar variáveis de ambiente e lidar com arquivos de configuração. Ao final, você terá um entendimento sólido de como estruturar a configuração das suas aplicações para que sejam portáveis, seguras e fáceis de operar em qualquer ambiente.

12-Factor App (visão geral)

O 12-Factor App é uma metodologia criada por engenheiros da Heroku, que define doze princípios para construir aplicações web modernas, com foco em portabilidade, escalabilidade e facilidade de deploy. Esses princípios são especialmente relevantes no contexto DevOps, pois ajudam a criar aplicações que podem ser executadas de forma consistente em diferentes ambientes, desde o laptop do desenvolvedor até a nuvem.

Os doze fatores são: 1) Base de Código, 2) Dependências, 3) Configuração, 4) Serviços de Apoio, 5) Build, Release, Run, 6) Processos, 7) Vínculo de Porta, 8) Concorrência, 9) Descartabilidade, 10) Paridade dev/prod, 11) Logs, 12) Processos Administrativos. Nesta aula, focaremos no terceiro fator: Configuração, que nos diz que toda configuração que varia entre ambientes deve ser armazenada no ambiente, não no código.

O princípio da configuração é fundamental: ele recomenda que a configuração seja estritamente separada do código. Isso significa que nenhuma informação como senhas, chaves de API, URLs de banco de dados ou credenciais deve ser embutida no código-fonte. Em vez disso, essas informações devem ser fornecidas pelo ambiente de execução, geralmente por meio de variáveis de ambiente. Essa separação permite que o mesmo código seja implantado em diferentes ambientes sem modificação, apenas alterando as variáveis de ambiente. Isso simplifica o pipeline de deploy e reduz o risco de vazamento de segredos no repositório de código.

Além disso, o 12-Factor App recomenda que a configuração seja tratada como um artefato do ambiente, não do aplicativo. Isso significa que, ao criar um deploy, você deve combinar o código (build) com a configuração específica do ambiente (release) para gerar uma versão executável. Esse processo é chamado de build, release, run e é uma prática comum em pipelines de CI/CD.

Vamos ver um exemplo simples de como isso se aplica na prática. Considere uma aplicação que precisa se conectar a um banco de dados. Em vez de colocar a URL do banco diretamente no código, você pode defini-la como uma variável de ambiente:

export DATABASE_URL="postgres://user:pass@localhost:5432/mydb"

No código da aplicação, você lê essa variável de ambiente, por exemplo, em Node.js:

const dbUrl = process.env.DATABASE_URL;

Dessa forma, o mesmo código pode ser usado em desenvolvimento, homologação e produção, apenas alterando o valor da variável de ambiente em cada ambiente.

Config por ambiente

Ambientes diferentes têm necessidades diferentes. Em desenvolvimento, você pode querer usar um banco de dados local, enquanto em produção você usa um serviço gerenciado na nuvem. Além disso, configurações como níveis de log, URLs de API e recursos de segurança podem variar. A configuração por ambiente é a prática de gerenciar essas diferenças de forma organizada e segura.

Existem várias abordagens para configurar por ambiente. A mais comum é usar variáveis de ambiente, que são definidas no sistema operacional ou no orquestrador de contêineres (como Kubernetes). Outra abordagem é usar arquivos de configuração específicos para cada ambiente, como config/dev.json, config/prod.json, e selecionar o arquivo com base em uma variável de ambiente. No entanto, essa abordagem pode levar a problemas de sincronização e ao risco de incluir arquivos de configuração com segredos no repositório.

Uma prática recomendada é usar um sistema de gerenciamento de configuração, como o HashiCorp Vault, ou serviços de nuvem como AWS Secrets Manager, para armazenar segredos de forma centralizada e segura. Em pipelines de CI/CD, você pode injetar as variáveis de ambiente no momento do deploy, garantindo que cada ambiente tenha seus valores corretos.

Vejamos um exemplo de como configurar uma aplicação para diferentes ambientes usando um arquivo .env (comum em aplicações Node.js e Python). O arquivo .env contém pares chave=valor, e é carregado pela aplicação no início da execução. Por exemplo, um arquivo .env.development pode ter:

DB_HOST=localhost
DB_PORT=5432
DB_USER=dev_user
DB_PASSWORD=dev_password

E um arquivo .env.production pode ter:

DB_HOST=prod-db.example.com
DB_PORT=5432
DB_USER=prod_user
DB_PASSWORD=prod_password

No código, você pode carregar o arquivo apropriado com base na variável de ambiente NODE_ENV (Node.js) ou APP_ENV (Python). Por exemplo, em Node.js, usando a biblioteca dotenv:

require('dotenv').config({ path: `.env.${process.env.NODE_ENV || 'development'}` });

Essa abordagem permite manter configurações separadas, mas requer cuidado para não versionar arquivos com segredos. Uma alternativa mais segura é usar variáveis de ambiente diretamente, sem arquivos, e injetá-las no ambiente de execução. Em orquestradores como Kubernetes, você pode definir variáveis de ambiente nos manifestos, ou usar ConfigMaps e Secrets para gerenciar a configuração de forma declarativa.

Variáveis vs arquivos

Uma das decisões mais importantes na configuração de aplicações é escolher entre usar variáveis de ambiente ou arquivos de configuração. Cada abordagem tem vantagens e desvantagens, e a escolha depende do contexto da aplicação e da infraestrutura.

Variáveis de ambiente são a recomendação do 12-Factor App. Elas são fáceis de definir, não são versionadas (a menos que você as coloque em um arquivo .env, o que deve ser evitado para segredos), e são suportadas por praticamente todas as plataformas de deploy. Elas são ideais para configurações que variam entre ambientes, como URLs de conexão, chaves de API e credenciais. Por exemplo, em um deploy no Heroku, você pode definir variáveis de ambiente na interface ou via CLI:

heroku config:set DATABASE_URL=postgres://user:pass@host:5432/db

No código, você lê a variável de ambiente:

const dbUrl = process.env.DATABASE_URL;

Arquivos de configuração são úteis para configurações complexas, como listas de recursos, mapeamentos ou estruturas aninhadas. Eles podem ser versionados, mas é preciso ter cuidado para não incluir segredos. Uma prática comum é versionar um arquivo de exemplo (por exemplo, config.example.yaml) e depois criar o arquivo real localmente, sem versioná-lo. Em aplicações Java, por exemplo, é comum usar arquivos application.properties ou application.yml, que podem ser sobrepostos por variáveis de ambiente.

A escolha entre variáveis e arquivos também depende da plataforma. Em contêineres, é comum usar variáveis de ambiente, pois são fáceis de injetar e não exigem que o arquivo esteja presente na imagem. Em aplicações monolíticas tradicionais, arquivos de configuração podem ser mais adequados.

Uma abordagem híbrida é usar arquivos de configuração para parâmetros não sensíveis e que não variam entre ambientes, e usar variáveis de ambiente para valores que variam ou são sensíveis. Por exemplo, uma aplicação pode ter um arquivo config.json com parâmetros de negócio, e as credenciais de banco de dados são lidas de variáveis de ambiente.

É importante lembrar que, independentemente da abordagem, a configuração deve ser tratada como um artefato externo ao código. A aplicação deve ser capaz de rodar em qualquer ambiente sem alterações no código-fonte.

Boas práticas

Existem várias boas práticas que ajudam a garantir que a configuração seja gerenciada de forma eficaz e segura no contexto DevOps. A seguir, destacamos as mais importantes:

  • Nunca versionar segredos: Senhas, chaves privadas, tokens de API e outras informações sensíveis nunca devem ser commitadas no repositório de código. Utilize ferramentas como git-secrets ou pre-commit hooks para evitar que segredos sejam acidentalmente versionados.
  • Usar variáveis de ambiente como padrão: Para a maioria dos casos, prefira variáveis de ambiente, pois são simples e universais. Elas evitam que você tenha que gerenciar múltiplos arquivos de configuração.
  • Fornecer valores padrão: Em desenvolvimento, forneça valores padrão seguros para que a aplicação possa rodar localmente sem configuração adicional. Em produção, os valores reais devem ser sempre injetados.
  • Centralizar a gestão de segredos: Use ferramentas como Vault, AWS Secrets Manager ou Kubernetes Secrets para armazenar e fornecer segredos de forma segura, em vez de espalhá-los em arquivos ou variáveis de ambiente.
  • Documentar as variáveis de ambiente: Mantenha um arquivo .env.example ou uma seção no README listando todas as variáveis de ambiente necessárias, com uma breve descrição de cada uma.
  • Validar a configuração na inicialização: A aplicação deve verificar se todas as variáveis de ambiente obrigatórias estão definidas e se os valores são válidos, falhando rapidamente se algo estiver errado.
  • Usar níveis de configuração: Em arquivos de configuração, permita que valores sejam sobrescritos por variáveis de ambiente. Por exemplo, no Spring Boot, as propriedades podem ser sobrescritas por variáveis de ambiente com o mesmo nome.
  • Separar configuração por ambiente: Nunca misture configurações de diferentes ambientes em um único arquivo versionado. Use perfis ou arquivos separados, mas evite versionar arquivos com valores reais.

Ao seguir essas práticas, você garante que suas aplicações sejam mais seguras, portáveis e fáceis de operar, reduzindo a fricção entre desenvolvimento e operações.

Exercícios

  1. Exercício 1: Explique por que o 12-Factor App recomenda armazenar a configuração no ambiente, e não no código. Dê um exemplo de um problema que pode ocorrer quando a configuração é embutida no código.
  2. ✓ Resposta: O 12-Factor App recomenda armazenar a configuração no ambiente porque isso permite que o mesmo código seja implantado em diferentes ambientes sem modificação, aumentando a portabilidade e facilitando o deploy. Além disso, evita que informações sensíveis, como senhas e chaves de API, sejam expostas no repositório de código, o que é um risco de segurança. Um exemplo de problema: se uma URL de banco de dados estiver hardcoded no código e o banco for migrado para outro servidor, será necessário alterar o código e fazer um novo deploy, o que é demorado e sujeito a erros.
  3. Exercício 2: Em uma aplicação Node.js, como você carregaria um arquivo .env específico para o ambiente de produção, usando a variável de ambiente NODE_ENV? Escreva o código necessário.
  4. ✓ Resposta: Para carregar um arquivo .env.production quando NODE_ENV for igual a "production", você pode usar a biblioteca dotenv da seguinte forma:
    require('dotenv').config({ path: `.env.${process.env.NODE_ENV || 'development'}` });
    Isso carrega o arquivo .env.production se NODE_ENV estiver definido como "production". Caso contrário, carrega o arquivo .env.development por padrão.
  5. Exercício 3: Quais são as vantagens e desvantagens de usar variáveis de ambiente em comparação com arquivos de configuração? Cite pelo menos duas de cada.
  6. ✓ Resposta: Vantagens das variáveis de ambiente: 1) São suportadas em todas as plataformas e orquestradores; 2) Não são versionadas, evitando exposição de segredos; 3) São fáceis de alterar sem modificar o código. Desvantagens: 1) Para configurações complexas (listas, objetos), podem se tornar difíceis de gerenciar; 2) Em alguns sistemas, há limites de tamanho ou caracteres. Vantagens dos arquivos de configuração: 1) Permitem estruturas complexas e legibilidade; 2) Podem ser versionados para versionamento de configuração. Desvantagens: 1) Risco de incluir segredos no repositório se não forem tratados; 2) Exigem mecanismos para selecionar o arquivo correto por ambiente, o que pode ser frágil.
  7. Exercício 4: Cite duas ferramentas de gerenciamento de segredos e explique como elas podem ser integradas a um pipeline de CI/CD.
  8. ✓ Resposta: Duas ferramentas são HashiCorp Vault e AWS Secrets Manager. O Vault pode ser integrado ao pipeline usando a CLI ou API: por exemplo, em um estágio de deploy, você pode autenticar e obter segredos dinâmicos para configurar a aplicação. O AWS Secrets Manager pode ser usado em pipelines do AWS CodePipeline ou Jenkins, usando a CLI da AWS para recuperar segredos e injetá-los como variáveis de ambiente no ambiente de execução. Ambas as ferramentas oferecem controle de acesso, rotação de segredos e auditoria.
  9. Exercício 5: Descreva um cenário em que o uso de arquivos de configuração é mais adequado do que variáveis de ambiente, e justifique.
  10. ✓ Resposta: Um cenário é quando a aplicação possui uma configuração complexa e hierárquica, como um sistema de regras de negócio com múltiplos níveis de aninhamento. Por exemplo, um sistema de recomendação pode ter um arquivo YAML com categorias, subcategorias e pesos. Representar isso em variáveis de ambiente seria impraticável. Além disso, se essa configuração não varia entre ambientes (apenas a parte sensível varia), pode ser versionada no repositório para versionamento. Nesse caso, arquivos de configuração são mais adequados.

Referências