Um guia desenvolvido pelo Heroku para SaaS (Software entregue como serviço) a.k.a: 99% de tudo o que consumimos na internet.
Garantias:
Os 12 fatores https://12factor.net/pt_br/
settings.py
USERNAME = "admin"
PASSWORD = "1234"
SERVER_IP = "192.168.0.10"
DB_NAME = "customers"
app.py
import settings
print(settings.USERNAME)
print(settings.PASSWORD)
print(settings.SERVER_IP)
print(settings.DB_NAME)
str
ou int
?settings.py
import os
USERNAME = "admin"
PASSWORD = int(os.environ.get('MEU_PASSWORD'))
SERVER_IP = "192.168.0.10"
DB_NAME = "customers"
# E mais 200 variaveis....
algumas libs como o Python-Decouple oferecem uma solução parecida onde vc utiliza o decouple para a leitura das variaveis de ambiente.
PASSWORD = decouple.config('PASSWORD', cast=int)
.
Qual o problema desta abordagem? nenhum!!!
Mas vc ainda terá que explicitamente efetuar o carregamento de cada valor em cada uma das suas 200 variaveis de config, e se tiver dicionarios aninhados terá que fazer em cada nivel.
Então você tem ambiente de development
, staging
e production
como fazer com o arquivo de settings?
MCGYVERISM
settings.py
import os
USERNAME = "admin"
PASSWORD = int(os.environ.get('MEU_PASSWORD'))
SERVER_IP = "192.168.0.10"
DB_NAME = "customers"
# E mais 200 variaveis....
ENV = os.environ.get('MEU_ENV')
if ENV == 'production':
from production_settings import * # noqa
elif ENV == 'staging':
from staging_settings import * # noqa
production_settings.py
SERVER_IP = 'prodserver.com'
staging_settings.py
SERVER_IP = 'stagingserver.com'
Imagine então que o seu projeto é django e você agora tem uma variáveis assim:
DEBUG = False
DATABASES = {
'default': {
'NAME': 'db',
'ENGINE': 'module.foo.engine',
'ARGS': {'timeout': 30}
}
}
Então agora você quer usar variáveis de ambiente para sobrescrever esses valores.
import os
DEBUG = os.environ.get('DEBUG') in ['True', 'true', '1', 'on', 'enabled']
DATABASES = {
'default': {
'NAME': os.environ.get('DATABASES_NAME', 'db'),
'ENGINE': os.environ.get('DATABASES_ENGINE', 'module.foo.engine'),
'ARGS': {'timeout': os.environ.get('DATABASES_TIMEOUT', 30)}
}
}
Vault Server
É tranquilo de acessar mas será preciso escrever o código de cliente e então gerenciar manualmente os ambientes, as coleções e leases.
import hvac
client = hvac.Client()
client = hvac.Client(url='localhost', token='myroot')
# Guardando chaves no vault
client.write('secret/snakes', type='pythons', lease='1h')
# lendo chaves do vault
print(client.read('secret/snakes'))
Primeiro instale o dynaconf
# opções: [all,yaml,ini,redis,vault]
pip install 'dynaconf[yaml,vault,redis]'
$ export DYNACONF_HELLO=world
$ dynaconf list
Working in development environment
HELLO: 'world'
Crie o arquivo settings.yaml
(pode ser qualquer outro formato suportado: yaml, toml, py, json, ini, xml, etc…)
ou utilize o $ dynaconf init
na raiz do projeto.
$ dynaconf init -f yaml \
-v username=admin \
-v server_ip=localhost \
-v db_name=customers \
-s password=1234
O dynaconf irá criar um arquivo settings.yaml
default:
DB_NAME: default
SERVER_IP: default
USERNAME: default
development:
DB_NAME: customers
SERVER_IP: localhost
USERNAME: admin
Repare que o password não está incluido neste arquivo onde ele está?
.secrets.yaml
default:
PASSWORD: default
development:
PASSWORD: 1234
pra quê serve este .secrets.* ?Image Not Showing Possible ReasonsLearn More →
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
.gitignore
# Ignore dynaconf secret files
.secrets.*
Nada de especial, mas agora fica mais fácil de ignorar o arquivo .secrets.*
para não ser enviado para o git e então cada ambiente pode ter seu arquivo de secrets.
(o mais recomendado é usar o vault server que veremos adiante)
podemos manter oImage Not Showing Possible ReasonsLearn More →
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
settings.py
pois o dynaconf vai ler este arquivo, mas o recomendado é ter arquivos estáticos como configurações e não usar arquivos Python que permitem a mistura de lógica com dados.
Apague os arquivos settings.py
e production_settings.py
$ dynaconf list
Working in development environment
DB_NAME: 'customers'
SERVER_IP: 'localhost'
USERNAME: 'admin'
PASSWORD: 1234
Para adicionar mais environments podemos simplesmente editar os arquivos .yaml
e adicionar nossos envs, ex: ['production']
Mas também podemos usar o CLI:
$ dynaconf write yaml -v server_ip=prodserver -s password=9999 -e production
então settings.yaml
default:
DB_NAME: default
SERVER_IP: default
USERNAME: default
development:
DB_NAME: customers
SERVER_IP: localhost
USERNAME: admin
production:
SERVER_IP: prodserver
e .secrets.yaml
default:
PASSWORD: default
development:
PASSWORD: 1234
production:
PASSWORD: 9999
E então para listar somente as variavéis de production:
$ ENV_FOR_DYNACONF=production dynaconf list
Working in production environment
DB_NAME: 'default'
SERVER_IP: 'prodserver'
USERNAME: 'default'
PASSWORD: 9999
Se tentarmos executar o programa app.py
$ python app.py
Traceback (most recent call last):
File "app.py", line 1, in <module>
import settings
ModuleNotFoundError: No module named 'settings'
Precisamos alterar o programa para usar o dynaconf.settings
no lugar do settings
e isso é muito fácil:
-import settings
+from dynaconf import settings
Repare que a linha assert isinstance(settings.PASSWORD, int)
continua funcionando, isto ocorre pois o Dynaconf automaticamente detecta o tipo de dados das variáveis.
Para alternar entre os ambientes basta exportar a variavel ENV_FOR_DYNACONF
export ENV_FOR_DYNACONF=production
ou então colocar em seu arquivo .env
também é possivel mudar essa variavel para algo mais amigavel tipoImage Not Showing Possible ReasonsLearn More →
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
MEUAPP_ENV=development
Recapitulando
Para implantar o Dynaconf basta:
from dynaconf import settings
O guia 12 factor apps deixa bem claro que não é o ideal ter configurações em arquivos como fizemos anteriormente.
Outro aspecto do gerenciamento de configuração é o agrupamento. Às vezes, as aplicações incluem a configuração em grupos nomeados (muitas vezes chamados de ambientes) que remetem a deploys específicos, tais como os ambientes development, test, e production. Este método não escala de forma limpa: quanto mais deploys da aplicação são criados, novos nomes de ambiente são necessários, tais como staging ou qa. A medida que o projeto cresce ainda mais, desenvolvedores podem adicionar seus próprios ambientes especiais como joes-staging, resultando em uma explosão combinatória de configurações que torna o gerenciamento de deploys da aplicação muito frágil.
Em uma aplicação doze-fatores, env vars são controles granulares, cada um totalmente ortogonal às outras env vars. Elas nunca são agrupadas como “environments”, mas em vez disso são gerenciadas independentemente para cada deploy. Este é um modelo que escala sem problemas à medida que o app naturalmente se expande em muitos deploys durante seu ciclo de vida.
12 factor app/ConfigImage Not Showing Possible ReasonsLearn More →
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Isto significa que seu projeto pode ter os arquivos settings.*
apenas para armazenar os valores [default]
e então as variáveis de ambiente sempre serão a fonte definitiva de configurações.
Exemplo:
settings.yaml
default:
DB_NAME: default
SERVER_IP: default
USERNAME: default
.secrets.yaml
default:
PASSWORD: default
E rodar dynaconf list
Working in development environment
DB_NAME: 'default'
SERVER_IP: 'default'
USERNAME: 'default'
PASSWORD: 'default'
E então no ambiente onde a aplicação irá rodar estes valores serão lidos através das variáveis de ambiente.
DYNACONF_PASSWORD=8888 DYNACONF_DB_NAME=foo dynaconf list
Working in development environment
DB_NAME: 'foo'
SERVER_IP: 'default'
USERNAME: 'default'
PASSWORD: 8888
ou
$ DYNACONF_PASSWORD=8888 DYNACONF_DB_NAME=foo python app.py
default
8888
default
foo
Ao invés de ficar passando esses valores na linha de comando pode persistir exportando diretamente em seu processo de deploy.
$ export DYNACONF_PASSWORD=7777
$ export DYNACONF_DB_NAME=mydb
Ou colocar no arquivo .env
na raiz do projeto.
.env
DYNACONF_PASSWORD=7777
DYNACONF_DB_NAME=mydb
o Dynaconf utiliza o formato toml
ao ler as variáveis de ambiente, isso quer dizer que:
export DYNACONF_STR=Bruno
== str('Bruno')
export DYNACONF_INT=36
== int(36)
export DYNACONF_FLOAT=42.1
== float(42.1)
export DYNACONF_BOOL=true
== bool(True)
export DYNACONF_LIST=[1,2,3]
== list([1,2,3])
export DYNACONF_DICT={foo="bar"}
== dict({"foo": "bar"})
E também dá para forçar o tipo quando necessário usando multi quotes:
export DYNACONF_STR_NUM="'32'"
== str('32')
e além disso o dynaconf tem suporte a nested types
, lembra o exemplo dos databases do Django?
default:
DATABASES:
default:
NAME: db
ENGINE: module.foo.engine
ARGS:
timeout: 30
Podemos alterar o ENGINE
via environment variables usando __
(dunder):
export DYNACONF_DATABASES__default__ENGINE=other.module
Agora imagine no nosso app.py
rodando em cloud e se conectando no banco de dados 192.168.0.1
default:
...
SERVER_IP: 192.168.0.1
...
Agora imagine que a aplicação está replicada em 200 instancias para aguentar a carga e então você precisa mudar o SERVER_IP
para 10.10.10.1
settings.yaml
Mas…
Você e o reviewer não prestaram atenção e na verdade tinha que mudar também o USERNAME
Ao invés das configurações ficarem armazenadas em arquivos ou no ambiente de cada uma das instancias podemos usar um servidor centralizado de configurações Redis
ou etcd
ou qualquer bancod e dados de chave-valor.
O Dynaconf já possui suporte built in para Redis e Vault mas é muito tranquilo customizar adicionando seu próprio loader.
Localmente podemos usar o docker:
$ docker run -d -p 6379:6379 redis
Isso vai subir um Redis em localhost:6379
que é o suficiente para desenvolver e testar.
Agora vamos escrever as nossas configs dinâmicas no Redis.
.env
REDIS_ENABLED_FOR_DYNACONF=1
então
$ dynaconf write redis -v server_ip=10.1.1.1 -v username=newuser -s password
=8989
Data successful written to redis
$ dynaconf list
Working in development environment
DB_NAME: 'default'
SERVER_IP: '10.1.1.1'
USERNAME: 'newuser'
PASSWORD: 8989
$ python app.py
newuser
8989
10.1.1.1
default
agora ao invés de alterar os valores em cada uma das instancias basta alterar diretamente no servidor Redis e então todas as instâncias irão ler do mesmo local.
useImage Not Showing Possible ReasonsLearn More →
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
settings.get('SERVER_IP', fresh=True)
para forçar a leitura da variavel atualizada diretamente do Redis ouexport FRESH_VARS_FOR_DYNACONF=['SERVER_IP']
para forçar essa variável a ser sempre fresh!
O Vault é um servidor de segredos desenvolvido pela HashiCorp ele resolve algumas questões na gestão de valores sensiveis como passwords e tokens.
É excelente para times muito grandes, ao invés de fornecer as senhas de produção para todos os devs cada dev tem apenas acesso a uma instancia vault de desenvolvimento e ao fazer deploy apenas o orqustrador de deploy tem acesso as senhas reais via vault.
Para desenvolvimento e testes:
$ docker run -d -e 'VAULT_DEV_ROOT_TOKEN_ID=myroot' -p 8200:8200 vault
Altere o arquivo .env
ou exporte as variaveis para ativar o vault.
.env
VAULT_ENABLED_FOR_DYNACONF=1
VAULT_TOKEN_FOR_DYNACONF=myroot
Para escrever no vault server
$ dynaconf write vault -s password=secret -s server_ip=server.com -e production
Data successful written to vault
Agora pode acessar o painel web do vault via http://localhost:8200 usando o token myroot
pip install Flask
export FLASK_APP=app.py
export FLASK_ENV=development
app.py
from flask import Flask
app = Flask(__name__)
app.config['USERNAME'] = 'Bruno'
@app.route('/')
def hello():
return app.config['USERNAME']
$ flask run
...
http://localhost:5000/
Como fazer para o app.config
ler seus valores através do dynaconf?
settings.toml
[default]
username = 'Bruno'
from flask import Flask
from dynaconf import FlaskDynaconf
app = Flask(__name__)
FlaskDynaconf(app)
@app.route('/')
def hello():
return app.config.USERNAME
ou
export FLASK_USERNAME='Guido Van Rossum'
ao usar a extensão flask o prefixo para variaveis de ambiente passa a ser FLASK ao invés de DYNACONF_Image Not Showing Possible ReasonsLearn More →
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Todos os recursos do Dynaconf funcionam normalmente dentro do Flask
pip install django
django-admin startproject django_app
cd django_app
python manage.py runserver
acesse: http://localhost:8000/
no settings.py
do django_app o DEBUG
está ativado veja: http://localhost:8000/naoexiste vamos mudar isso usando o Dynaconf
Passo 1 ative o dynaconf.
$dynaconf init --django django_app/settings.py
Cofiguring your Dynaconf environment
django_app/settings.py is found do you want to add dynaconf? [y/N]: y
export DJANGO_DEBUG=false
export DJANGO_ALLOWED_HOSTS=['*']
python manage.py runserver
Agora toda variavel exportada com o prefixo DJANGO_
será lida pelo dynaconf e acessivel via o django.conf.settings