J'ai récemment écrit
Le contexte de la question est le suivant : il était une fois un projet, au début c'était un petit monolithe avec des utilitaires et des scripts, mais au fil du temps, il s'est développé, divisé en services, qui à leur tour ont commencé à être divisés en microservices, et puis intensifié. Au début, tout cela a été fait sur un VPS nu, les processus de configuration et de déploiement de code sur lesquels ont été automatisés à l'aide d'Ansible, et chaque service a été compilé avec une configuration YAML avec les paramètres et les clés nécessaires, et un fichier de configuration similaire a été utilisé pour des lancements locaux, ce qui était très pratique, car .k cette configuration est chargée dans un objet global, accessible depuis n'importe où dans le projet.
Cependant, la croissance du nombre de microservices, de leurs connexions et
À cet égard, le mécanisme de construction d'un objet de configuration a été modifié pour pouvoir fonctionner à la fois avec notre fichier de configuration classique et avec les secrets de Kuber. Une structure de configuration plus rigide a également été spécifiée, dans le langage du troisième Python, comme suit :
Dict[str, Dict[str, Union[str, int, float]]]
Autrement dit, le rouage final est un dictionnaire avec des sections nommées, chacune étant un dictionnaire avec des valeurs de types simples. Et les sections décrivent la configuration et l'accès aux ressources d'un certain type. Un exemple d'un morceau de notre configuration :
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"
En même temps, le terrain engine
les bases de données peuvent être installées sur SQLite, et redis
mis à mock
, en spécifiant également le nom du fichier à enregistrer - ces paramètres sont correctement reconnus et traités, ce qui facilite l'exécution du code localement pour le débogage, les tests unitaires et tout autre besoin. Ceci est particulièrement important pour nous car il existe de nombreux autres besoins - une partie de notre code est destinée à divers calculs analytiques, il s'exécute non seulement sur des serveurs avec orchestration, mais également avec divers scripts, et sur les ordinateurs des analystes qui doivent travailler et déboguer des pipelines de traitement de données complexes sans vous soucier des problèmes de back-end. À propos, cela ne ferait pas de mal de partager que nos principaux outils, y compris le code de configuration, sont installés via setup.py
– ensemble, cela unit notre code en un seul écosystème, indépendant de la plateforme et de la méthode d’utilisation.
La description d'un pod Kubernetes ressemble à ceci :
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
Autrement dit, chaque secret décrit une section. Les secrets eux-mêmes sont créés comme ceci :
apiVersion: v1
kind: Secret
metadata:
name: db-main-secret
type: Opaque
stringData:
db_main.yaml: |
engine: sqlite
filename: main.sqlite3
Ensemble, cela aboutit à la création de fichiers YAML le long du chemin /etc/secrets/db-main/section_name.yaml
Et pour les lancements locaux, la configuration est utilisée, située dans le répertoire racine du projet ou le long du chemin spécifié dans la variable d'environnement. Le code responsable de ces commodités peut être vu dans le 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()
La logique ici est assez simple : nous combinons de grandes configurations du répertoire du projet et des chemins par variable d'environnement, et de petites sections de configuration des secrets Kuber, puis les prétraitons un peu. Plus quelques variables. Je note que lors de la recherche de fichiers à partir de secrets, une limitation de profondeur est utilisée, car K8s crée un dossier caché dans chaque secret, où les secrets eux-mêmes sont stockés, et juste un lien est situé à un niveau supérieur.
J'espère que ce qui est décrit sera utile à quelqu'un :) Tous les commentaires et recommandations concernant la sécurité ou d'autres domaines à améliorer sont acceptés. L'avis de la communauté est également intéressant, peut-être vaut-il la peine d'ajouter le support des ConfigMaps (notre projet ne les utilise pas encore) et de publier le code sur GitHub/PyPI ? Personnellement, je pense que de telles choses sont trop individuelles pour que les projets soient universels, et je jette un coup d'œil aux implémentations d'autres personnes, comme celle donnée ici, et une discussion sur les nuances, les astuces et les meilleures pratiques, que j'espère voir dans les commentaires , ça suffit 😉
Seuls les utilisateurs enregistrés peuvent participer à l'enquête.
Dois-je publier en tant que projet/bibliothèque ?
-
0,0%Oui, j'utiliserais /contribution0
-
33,3%Oui, ça a l'air génial4
-
41,7%Non, qui doit le faire lui-même dans son propre format et selon ses besoins5
-
25,0%je m'abstiendrai de répondre3
12 utilisateurs ont voté. 3 utilisateurs se sont abstenus.
Source: habr.com