Configuración do proxecto dentro e fóra de Kubernetes

Recentemente escribín resposta sobre a vida do proxecto en Docker e código de depuración fóra del, onde mencionou brevemente que podes crear o teu propio sistema de configuración para que o servizo funcione ben en Kuber, obtenga segredos e execútase convenientemente localmente, mesmo fóra de Docker. Nada complicado, pero a "receita" descrita pode ser útil para alguén :) O código está en Python, pero a lóxica non está ligada á linguaxe.

Configuración do proxecto dentro e fóra de Kubernetes

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 necesidade de rexistro e seguimento centralizados, presaxiaba un traslado a Kuber, que aínda está en curso. Xunto coa asistencia para resolver os problemas mencionados, Kubernetes ofrece os seus enfoques para a xestión de infraestruturas, incluíndo os chamados Segredos и formas de traballar con eles. O mecanismo é estándar e fiable, polo que é literalmente un pecado non usalo. Pero, ao mesmo tempo, gustaríame manter o meu formato actual para traballar coa configuración: en primeiro lugar, usalo uniformemente en diferentes microservizos do proxecto e, en segundo lugar, poder executar o código na máquina local usando un sinxelo ficheiro de configuración.

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. Rexístrate, por favor.

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

Engadir un comentario