Projectconfiguratie binnen en buiten Kubernetes

schreef ik onlangs antwoord over de levensduur van het project in Docker en het debuggen van code daarbuiten, waar hij kort vermeldde dat je je eigen configuratiesysteem kunt maken, zodat de service goed werkt in Kuber, geheimen ophaalt en gemakkelijk lokaal draait, zelfs buiten Docker helemaal. Niets ingewikkelds, maar het beschreven “recept” kan voor iemand nuttig zijn :) De code is in Python, maar de logica is niet gebonden aan de taal.

Projectconfiguratie binnen en buiten Kubernetes

De achtergrond van de vraag is deze: er was eens één project, aanvankelijk was het een kleine monoliet met hulpprogramma's en scripts, maar na verloop van tijd groeide het, verdeeld in services, die op hun beurt weer werden onderverdeeld in microservices, en vervolgens opgeschaald. In eerste instantie werd dit allemaal gedaan op kale VPS, waarbij de processen voor het opzetten en implementeren van code werden geautomatiseerd met behulp van Ansible, en elke service werd gecompileerd met een YAML-configuratie met de nodige instellingen en sleutels, en een soortgelijk configuratiebestand werd gebruikt voor lokale lanceringen, wat erg handig was, omdat .k deze configuratie in een globaal object wordt geladen, dat overal in het project toegankelijk is.

De groei van het aantal microservices, hun verbindingen en behoefte aan gecentraliseerde registratie en monitoring, was een voorafschaduwing van een overstap naar Kuber, die nog steeds aan de gang is. Samen met hulp bij het oplossen van de genoemde problemen biedt Kubernetes zijn benaderingen van infrastructuurbeheer, inclusief zogenaamde geheimen и manieren om ermee te werken. Het mechanisme is standaard en betrouwbaar, dus het is letterlijk zonde om het niet te gebruiken! Maar tegelijkertijd zou ik mijn huidige formaat voor het werken met de configuratie willen behouden: ten eerste om het uniform te gebruiken in verschillende microservices van het project, en ten tweede om de code op de lokale machine uit te kunnen voeren met behulp van één eenvoudige configuratiebestand.

In dit opzicht is het mechanisme voor het construeren van een configuratieobject aangepast om zowel met ons klassieke configuratiebestand als met geheimen van Kuber te kunnen werken. Er werd ook een stijvere configuratiestructuur gespecificeerd, in de taal van de derde Python, als volgt:

Dict[str, Dict[str, Union[str, int, float]]]

Dat wil zeggen, de laatste cogfig is een woordenboek met benoemde secties, elk een woordenboek met waarden van eenvoudige typen. En secties beschrijven de configuratie en toegang tot bronnen van een bepaald type. Een voorbeeld van een stukje van onze configuratie:

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"

Tegelijkertijd het veld engine databases kunnen op SQLite worden geïnstalleerd, en redis ingesteld op mock, waarbij ook de naam wordt opgegeven van het bestand dat moet worden opgeslagen. Deze parameters worden correct herkend en verwerkt, waardoor het gemakkelijk wordt om de code lokaal uit te voeren voor foutopsporing, unit-tests en andere behoeften. Dit is vooral belangrijk voor ons omdat er nog veel meer behoeften zijn: een deel van onze code is bedoeld voor verschillende analytische berekeningen, het draait niet alleen op servers met orkestratie, maar ook met verschillende scripts, en op de computers van analisten die er doorheen moeten werken en debuggen van complexe gegevensverwerkingspijplijnen zonder zorgen over backend-problemen. Het zou trouwens geen kwaad kunnen om te delen dat onze belangrijkste tools, inclusief de configuratie-indelingscode, zijn geïnstalleerd via setup.py – samen verenigt dit onze code in één ecosysteem, onafhankelijk van platform en gebruiksmethode.

De beschrijving van een Kubernetes-pod ziet er als volgt uit:

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

Dat wil zeggen, elk geheim beschrijft één sectie. De geheimen zelf worden als volgt gemaakt:

apiVersion: v1
kind: Secret
metadata:
  name: db-main-secret
type: Opaque
stringData:
  db_main.yaml: |
    engine: sqlite
    filename: main.sqlite3

Samen resulteert dit in het maken van YAML-bestanden langs het pad /etc/secrets/db-main/section_name.yaml

En voor lokale lanceringen wordt de configuratie gebruikt, gelegen in de hoofdmap van het project of langs het pad dat is opgegeven in de omgevingsvariabele. De code die verantwoordelijk is voor deze gemakken is te zien in de 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()

De logica hier is vrij eenvoudig: we combineren grote configuraties uit de projectmap en paden per omgevingsvariabele, en kleine configuratiesecties uit Kuber-geheimen, en verwerken ze vervolgens een beetje voor. Plus enkele variabelen. Ik merk op dat bij het zoeken naar bestanden uit geheimen een dieptebeperking wordt gebruikt, omdat K8s in elk geheim een ​​verborgen map aanmaakt waar de geheimen zelf worden opgeslagen, en alleen een link zich op een hoger niveau bevindt.

Ik hoop dat wat wordt beschreven nuttig zal zijn voor iemand :) Alle opmerkingen en aanbevelingen met betrekking tot beveiliging of andere gebieden voor verbetering worden geaccepteerd. De mening van de community is ook interessant, misschien is het de moeite waard om ondersteuning voor ConfigMaps toe te voegen (ons project gebruikt ze nog niet) en de code op GitHub / PyPI te publiceren? Persoonlijk denk ik dat zulke dingen te individueel zijn om projecten universeel te laten zijn, en een beetje kijken naar de implementaties van anderen, zoals die hier gegeven, en een bespreking van nuances, tips en best practices, die ik hoop terug te zien in de commentaren , is genoeg 😉

Alleen geregistreerde gebruikers kunnen deelnemen aan het onderzoek. Inloggen, Alsjeblieft.

Moet ik publiceren als een project/bibliotheek?

  • 0,0%Ja, ik zou /contribution0 gebruiken

  • 33,3%Ja, dat klinkt geweldig4

  • 41,7%Nee, die moeten het zelf doen, in hun eigen format en volgens hun behoeften5

  • 25,0%Ik zal mij onthouden van antwoord 3

12 gebruikers hebben gestemd. 3 gebruikers onthielden zich van stemming.

Bron: www.habr.com

Voeg een reactie