Recentemente escribín
O antecedente da pregunta é o seguinte: había unha vez un proxecto, ao principio era un pequeno monólito con utilidades e scripts, pero co paso do tempo foi crecendo, dividido en servizos, que á súa vez comezaron a dividirse en microservizos e despois escalado. Nun primeiro momento, todo isto facíase en VPS nu, os procesos de configuración e despregamento de código nos que se automatizaban mediante Ansible, e cada servizo foi compilado cunha configuración YAML coa configuración e as claves necesarias, e utilizouse un ficheiro de configuración similar para lanzamentos locais, que era moi cómodo, porque .k esta configuración cárgase nun obxecto global, accesible desde calquera lugar do proxecto.
Non obstante, o crecemento do número de microservizos, as súas conexións e
Neste sentido, modificouse o mecanismo para construír un obxecto de configuración para poder funcionar tanto co noso ficheiro de configuración clásico como con segredos de Kuber. Tamén se especificou unha estrutura de configuración máis ríxida, na linguaxe do terceiro Python, como segue:
Dict[str, Dict[str, Unión[str, int, float]]]
É dicir, o cogfig final é un dicionario con seccións nomeadas, cada unha das cales é un dicionario con valores de tipos sinxelos. E as seccións describen a configuración e o acceso a recursos dun determinado tipo. Un exemplo dunha peza da nosa configuración:
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
as bases de datos pódense instalar en SQLite e redis
configurado en mock
, especificando tamén o nome do ficheiro a gardar: estes parámetros son recoñecidos e procesados correctamente, o que facilita a execución do código localmente para a depuración, probas unitarias e calquera outra necesidade. Isto é especialmente importante para nós porque hai moitas outras necesidades: parte do noso código está destinado a varios cálculos analíticos, non só funciona en servidores con orquestración, senón tamén con varios scripts e nos ordenadores dos analistas que necesitan traballar. e depurar canalizacións complexas de procesamento de datos sen preocuparse por problemas de backend. Por certo, non estaría de máis compartir que as nosas ferramentas principais, incluído o código de deseño de configuración, están instaladas mediante setup.py
– xuntos isto une o noso código nun único ecosistema, independente da plataforma e do método de uso.
A descrición dun pod de Kubernetes ten o seguinte aspecto:
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
É dicir, cada segredo describe unha sección. Os segredos son creados así:
apiVersion: v1
kind: Secret
metadata:
name: db-main-secret
type: Opaque
stringData:
db_main.yaml: |
engine: sqlite
filename: main.sqlite3
En conxunto, isto resulta na creación de ficheiros YAML ao longo do camiño /etc/secrets/db-main/section_name.yaml
E para os lanzamentos locais, utilízase a configuración, situada no directorio raíz do proxecto ou ao longo da ruta especificada na variable de ambiente. O código responsable destas comodidades pódese ver 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óxica aquí é bastante sinxela: combinamos configuracións grandes do directorio do proxecto e rutas por variable de ambiente, e pequenas seccións de configuración dos segredos de Kuber, e despois preprocesámolas un pouco. Ademais dalgunhas variables. Observo que ao buscar ficheiros desde segredos utilízase unha limitación de profundidade, porque K8s crea un cartafol oculto en cada segredo onde se almacenan os propios segredos e só se atopa unha ligazón nun nivel superior.
Espero que o descrito sexa útil para alguén :) Admítense calquera comentario e recomendación sobre seguridade ou outras áreas de mellora. A opinión da comunidade tamén é interesante, quizais paga a pena engadir soporte para ConfigMaps (o noso proxecto aínda non os usa) e publicar o código en GitHub/PyPI? Persoalmente, creo que este tipo de cousas son demasiado individuais para que os proxectos sexan universais, e un pouco de ollada ás implementacións doutras persoas, como a que se ofrece aquí, e unha discusión de matices, consellos e mellores prácticas, que espero ver nos comentarios. , é suficiente 😉
Só os usuarios rexistrados poden participar na enquisa.
Debo publicar como proxecto/biblioteca?
-
0,0%Si, eu usaría /contribution0
-
33,3%Si, iso soa xenial4
-
41,7%Non, quen precisa facelo eles mesmos no seu propio formato e para atender ás súas necesidades5
-
25,0%Absterrei de responder3
Votaron 12 usuarios. 3 usuarios abstivéronse.
Fonte: www.habr.com