Configuración del proyecto dentro y fuera de Kubernetes

Hace poco escribí responder sobre la vida del proyecto en Docker y el código de depuración fuera de él, donde mencionó brevemente que puede crear su propio sistema de configuración para que el servicio funcione bien en Kuber, obtenga secretos y se ejecute convenientemente localmente, incluso fuera de Docker. Nada complicado, pero la “receta” descrita puede resultarle útil a alguien :) El código está en Python, pero la lógica no está ligada al lenguaje.

Configuración del proyecto dentro y fuera de Kubernetes

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 Necesidad de registro y monitoreo centralizados., presagiaba un paso a Kuber, que todavía está en marcha. Junto con la asistencia para resolver los problemas mencionados, Kubernetes ofrece sus enfoques para la gestión de infraestructura, incluyendo los llamados secretos и formas de trabajar con ellos. El mecanismo es estándar y confiable, por lo que es literalmente un pecado no usarlo. Pero al mismo tiempo, me gustaría mantener mi formato actual para trabajar con la configuración: en primer lugar, usarlo de manera uniforme en diferentes microservicios del proyecto y, en segundo lugar, poder ejecutar el código en la máquina local usando un simple archivo de configuración.

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. Registrarsepor favor

¿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

Añadir un comentario