Hace poco escribí
El trasfondo de la pregunta es el siguiente: había una vez un proyecto, al principio era un pequeño monolito con utilidades y scripts, pero con el tiempo creció, se dividió en servicios, que a su vez comenzaron a dividirse en microservicios, y luego ampliado. Al principio, todo esto se hizo en un VPS simple, cuyos procesos de configuración e implementación de código se automatizaron usando Ansible, y cada servicio se compiló con una configuración YAML con las configuraciones y claves necesarias, y se usó un archivo de configuración similar para lanzamientos locales, lo cual fue muy conveniente, porque .k esta configuración se carga en un objeto global, accesible desde cualquier parte del proyecto.
Sin embargo, el crecimiento en el número de microservicios, sus conexiones y
En este sentido, se modificó el mecanismo para construir un objeto de configuración para poder trabajar tanto con nuestro archivo de configuración clásico como con secretos de Kuber. También se especificó una estructura de configuración más rígida, en el lenguaje del tercer Python, de la siguiente manera:
Dict[cadena, Dict[cadena, Unión[cadena, int, flotante]]]
Es decir, el cogfig final es un diccionario con secciones con nombre, cada una de las cuales es un diccionario con valores de tipos simples. Y las secciones describen la configuración y el acceso a recursos de un determinado tipo. Un ejemplo de una parte de nuestra 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"
Al mismo tiempo, el campo engine
Las bases de datos se pueden instalar en SQLite, y redis
ajustado a mock
, especificando también el nombre del archivo a guardar; estos parámetros se reconocen y procesan correctamente, lo que facilita la ejecución del código localmente para depuración, pruebas unitarias y cualquier otra necesidad. Esto es especialmente importante para nosotros porque hay muchas otras necesidades: parte de nuestro código está destinado a diversos cálculos analíticos, se ejecuta no solo en servidores con orquestación, sino también con varios scripts y en las computadoras de los analistas que necesitan trabajar. y depurar procesos de procesamiento de datos complejos sin preocuparse por problemas de backend. Por cierto, no estaría de más compartir que nuestras herramientas principales, incluido el código de diseño de configuración, se instalan a través de setup.py
– En conjunto, esto une nuestro código en un único ecosistema, independiente de la plataforma y el método de uso.
La descripción de un pod de Kubernetes es la siguiente:
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
Es decir, cada secreto describe una sección. Los secretos en sí se crean así:
apiVersion: v1
kind: Secret
metadata:
name: db-main-secret
type: Opaque
stringData:
db_main.yaml: |
engine: sqlite
filename: main.sqlite3
En conjunto, esto da como resultado la creación de archivos YAML a lo largo de la ruta. /etc/secrets/db-main/section_name.yaml
Y para los lanzamientos locales, se utiliza la configuración ubicada en el directorio raíz del proyecto o en la ruta especificada en la variable de entorno. El código responsable de estas comodidades se puede ver en el spoiler.
configuración.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 lógica aquí es bastante simple: combinamos configuraciones grandes del directorio del proyecto y rutas por variable de entorno, y pequeñas secciones de configuración de los secretos de Kuber, y luego las preprocesamos un poco. Más algunas variables. Observo que cuando se buscan archivos de secretos, se utiliza una limitación de profundidad, porque K8s crea una carpeta oculta en cada secreto donde se almacenan los secretos en sí, y solo se ubica un enlace en un nivel superior.
Espero que lo descrito sea de utilidad para alguien :) Se aceptan comentarios y recomendaciones sobre seguridad u otras áreas de mejora. La opinión de la comunidad también es interesante, ¿tal vez valga la pena agregar soporte para ConfigMaps (nuestro proyecto aún no los usa) y publicar el código en GitHub/PyPI? Personalmente, creo que esas cosas son demasiado individuales para que los proyectos sean universales, y un poco de vistazo a las implementaciones de otras personas, como la que se da aquí, y una discusión sobre matices, consejos y mejores prácticas, que espero ver en los comentarios. , es suficiente 😉
Solo los usuarios registrados pueden participar en la encuesta.
¿Debería publicar como proyecto/biblioteca?
-
0,0%Sí, usaría /contribución0
-
33,3%Sí, eso suena genial4
-
41,7%No, quién necesita hacerlo él mismo en su propio formato y según sus necesidades5
-
25,0%Me abstendré de contestar3
12 usuarios votaron. 3 usuarios se abstuvieron.
Fuente: habr.com