Eu escrevi recentemente
O pano de fundo da questão é o seguinte: era uma vez um projeto, no início era um pequeno monólito com utilitários e scripts, mas com o tempo foi crescendo, dividido em serviços, que por sua vez passaram a ser divididos em microsserviços, e então ampliado. No início, tudo isso foi feito em VPS simples, cujos processos de configuração e implantação de código foram automatizados usando Ansible, e cada serviço foi compilado com uma configuração YAML com as configurações e chaves necessárias, e um arquivo de configuração semelhante foi usado para lançamentos locais, o que foi muito conveniente, pois .k essa configuração é carregada em um objeto global, acessível de qualquer lugar do projeto.
No entanto, o crescimento do número de microsserviços, suas conexões e
Nesse sentido, o mecanismo de construção de um objeto de configuração foi modificado para poder funcionar tanto com nosso arquivo de configuração clássico quanto com segredos do Kuber. Também foi especificada uma estrutura de configuração mais rígida, na linguagem do terceiro Python, conforme segue:
Dict[str, Dict[str, União[str, int, float]]]
Ou seja, o cogfig final é um dicionário com seções nomeadas, cada uma delas um dicionário com valores de tipos simples. E as seções descrevem a configuração e o acesso a recursos de um determinado tipo. Um exemplo de parte da nossa configuração:
adminka:
django_secret: "ExtraLongAndHardCode"
db_main:
engine: mysql
host: 256.128.64.32
user: cool_user
password: "SuperHardPassword"
redis:
host: 256.128.64.32
pw: "SuperHardPassword"
port: 26379
smtp:
server: smtp.gmail.com
port: 465
email: [email protected]
pw: "SuperHardPassword"
Ao mesmo tempo, o campo engine
bancos de dados podem ser instalados no SQLite, e redis
definido como mock
, especificando também o nome do arquivo a ser salvo - esses parâmetros são reconhecidos e processados corretamente, o que facilita a execução local do código para depuração, testes unitários e quaisquer outras necessidades. Isso é especialmente importante para nós porque existem muitas outras necessidades - parte do nosso código é destinada a diversos cálculos analíticos, funciona não apenas em servidores com orquestração, mas também com vários scripts, e em computadores de analistas que precisam trabalhar e depure pipelines complexos de processamento de dados sem se preocupar com problemas de back-end. A propósito, não faria mal nenhum compartilhar que nossas principais ferramentas, incluindo o código de layout de configuração, são instaladas via setup.py
– juntos, isso une nosso código em um único ecossistema, independente de plataforma e método de uso.
A descrição de um pod Kubernetes é assim:
containers:
- name : enter-api
image: enter-api:latest
ports:
- containerPort: 80
volumeMounts:
- name: db-main-secret-volume
mountPath: /etc/secrets/db-main
volumes:
- name: db-main-secret-volume
secret:
secretName: db-main-secret
Ou seja, cada segredo descreve uma seção. Os próprios segredos são criados assim:
apiVersion: v1
kind: Secret
metadata:
name: db-main-secret
type: Opaque
stringData:
db_main.yaml: |
engine: sqlite
filename: main.sqlite3
Juntos, isso resulta na criação de arquivos YAML ao longo do caminho /etc/secrets/db-main/section_name.yaml
E para lançamentos locais, é usada a configuração, localizada no diretório raiz do projeto ou ao longo do caminho especificado na variável de ambiente. O código responsável por essas conveniências pode ser visto no spoiler.
config.py
__author__ = 'AivanF'
__copyright__ = 'Copyright 2020, AivanF'
import os
import yaml
__all__ = ['config']
PROJECT_DIR = os.path.abspath(__file__ + 3 * '/..')
SECRETS_DIR = '/etc/secrets'
KEY_LOG = '_config_log'
KEY_DBG = 'debug'
def is_yes(value):
if isinstance(value, str):
value = value.lower()
if value in ('1', 'on', 'yes', 'true'):
return True
else:
if value in (1, True):
return True
return False
def update_config_part(config, key, data):
if key not in config:
config[key] = data
else:
config[key].update(data)
def parse_big_config(config, filename):
'''
Parse YAML config with multiple section
'''
if not os.path.isfile(filename):
return False
with open(filename) as f:
config_new = yaml.safe_load(f.read())
for key, data in config_new.items():
update_config_part(config, key, data)
config[KEY_LOG].append(filename)
return True
def parse_tiny_config(config, key, filename):
'''
Parse YAML config with a single section
'''
with open(filename) as f:
config_tiny = yaml.safe_load(f.read())
update_config_part(config, key, config_tiny)
config[KEY_LOG].append(filename)
def combine_config():
config = {
# To debug config load code
KEY_LOG: [],
# To debug other code
KEY_DBG: is_yes(os.environ.get('DEBUG')),
}
# For simple local runs
CONFIG_SIMPLE = os.path.join(PROJECT_DIR, 'config.yaml')
parse_big_config(config, CONFIG_SIMPLE)
# For container's tests
CONFIG_ENVVAR = os.environ.get('CONFIG')
if CONFIG_ENVVAR is not None:
if not parse_big_config(config, CONFIG_ENVVAR):
raise ValueError(
f'No config file from EnvVar:n'
f'{CONFIG_ENVVAR}'
)
# For K8s secrets
for path, dirs, files in os.walk(SECRETS_DIR):
depth = path[len(SECRETS_DIR):].count(os.sep)
if depth > 1:
continue
for file in files:
if file.endswith('.yaml'):
filename = os.path.join(path, file)
key = file.rsplit('.', 1)[0]
parse_tiny_config(config, key, filename)
return config
def build_config():
config = combine_config()
# Preprocess
for key, data in config.items():
if key.startswith('db_'):
if data['engine'] == 'sqlite':
data['filename'] = os.path.join(PROJECT_DIR, data['filename'])
# To verify correctness
if config[KEY_DBG]:
print(f'** Loaded config:n{yaml.dump(config)}')
else:
print(f'** Loaded config from: {config[KEY_LOG]}')
return config
config = build_config()
A lógica aqui é bastante simples: combinamos configurações grandes do diretório do projeto e caminhos por variável de ambiente e pequenas seções de configuração dos segredos do Kuber e, em seguida, pré-processamos um pouco. Além de algumas variáveis. Observo que ao procurar arquivos em segredos, uma limitação de profundidade é usada, pois o K8s cria uma pasta oculta em cada segredo onde os próprios segredos são armazenados, e apenas um link está localizado em um nível superior.
Espero que o que está descrito seja útil para alguém :) Quaisquer comentários e recomendações sobre segurança ou outras áreas de melhoria são aceitos. A opinião da comunidade também é interessante, talvez valha a pena adicionar suporte para ConfigMaps (nosso projeto ainda não os utiliza) e publicar o código no GitHub/PyPI? Pessoalmente, acho que essas coisas são muito individuais para que os projetos sejam universais, e uma espiada nas implementações de outras pessoas, como a dada aqui, e uma discussão de nuances, dicas e melhores práticas, que espero ver nos comentários , é o suficiente 😉
Apenas usuários registrados podem participar da pesquisa.
Devo publicar como projeto/biblioteca?
-
0,0%Sim, eu usaria /contribution0
-
33,3%Sim, isso parece ótimo4
-
41,7%Não, quem precisa fazer sozinho no seu formato e de acordo com suas necessidades5
-
25,0%Vou me abster de responder3
12 usuários votaram. 3 usuários se abstiveram.
Fonte: habr.com