Конфигурация на проекта вътре и извън Kubernetes

Наскоро писах отговор за живота на проекта в Docker и код за отстраняване на грешки извън него, където той накратко спомена, че можете да направите своя собствена система за конфигурация, така че услугата да работи добре в Kuber, да извлича тайни и да работи удобно локално, дори извън Docker като цяло. Нищо сложно, но описаната "рецепта" може да бъде полезна за някого :) Кодът е на Python, но логиката не е обвързана с езика.

Конфигурация на проекта вътре и извън Kubernetes

Предисторията на въпроса е следната: едно време имаше един проект, първоначално беше малък монолит с помощни програми и скриптове, но с времето се разрасна, раздели се на услуги, които от своя страна започнаха да се разделят на микроуслуги и след това увеличен. Отначало всичко това беше направено на гол VPS, процесите на настройка и внедряване на код, върху който бяха автоматизирани с помощта на Ansible, и всяка услуга беше компилирана с YAML конфигурация с необходимите настройки и ключове и подобен конфигурационен файл беше използван за локални стартирания, което беше много удобно, защото .k тази конфигурация се зарежда в глобален обект, достъпен отвсякъде в проекта.

Въпреки това нарастването на броя на микроуслугите, техните връзки и необходимост от централизирано регистриране и наблюдение, предвещаваше преминаване към Kuber, което все още е в ход. Заедно с помощта при решаването на споменатите проблеми, Kubernetes предлага своите подходи за управление на инфраструктурата, в т.ч така наречените Тайни и начини за работа с тях. Механизмът е стандартен и надежден, така че е буквално грях да не го използвате! Но в същото време бих искал да запазя текущия си формат за работа с конфигурацията: първо, да го използвам еднакво в различни микроуслуги на проекта, и второ, да мога да изпълнявам кода на локалната машина с помощта на един прост конфигурационен файл.

В тази връзка механизмът за конструиране на конфигурационен обект беше модифициран, за да може да работи както с нашия класически конфигурационен файл, така и с тайни от Kuber. Беше определена и по-строга конфигурационна структура на езика на третия Python, както следва:

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

Тоест последният cogfig е речник с именувани раздели, всеки от които е речник със стойности от прости типове. А разделите описват конфигурацията и достъпа до ресурси от определен тип. Пример за част от нашата конфигурация:

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"

В същото време полето engine базите данни могат да бъдат инсталирани на SQLite и redis настроен на mock, като посочите и името на файла за запазване - тези параметри се разпознават и обработват правилно, което улеснява стартирането на кода локално за отстраняване на грешки, тестване на единици и всякакви други нужди. Това е особено важно за нас, защото има много други нужди - част от нашия код е предназначен за различни аналитични изчисления, работи не само на сървъри с оркестрация, но и с различни скриптове, както и на компютрите на анализатори, които трябва да работят през и отстранявайте грешки в сложни тръбопроводи за обработка на данни, без да се тревожите за проблеми с бекенда. Между другото, няма да навреди да споделим, че основните ни инструменти, включително кода за оформление на конфигурацията, се инсталират чрез setup.py – заедно това обединява нашия код в една екосистема, независима от платформата и начина на използване.

Описанието на Kubernetes pod изглежда така:

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

Тоест всяка тайна описва един раздел. Самите тайни се създават така:

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

Заедно това води до създаването на YAML файлове по пътя /etc/secrets/db-main/section_name.yaml

А за локални стартирания се използва конфигурацията, разположена в главната директория на проекта или по пътя, посочен в променливата на средата. Кодът, отговорен за тези удобства, може да се види в спойлера.

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()

Логиката тук е доста проста: комбинираме големи конфигурации от директорията на проекта и пътеки по променлива на средата и малки конфигурационни секции от тайните на Kuber и след това малко ги обработваме предварително. Плюс някои променливи. Отбелязвам, че при търсене на файлове от тайни се използва ограничение на дълбочината, тъй като K8s създава скрита папка във всяка тайна, където се съхраняват самите тайни, и просто връзката се намира на по-високо ниво.

Надявам се описаното да е полезно за някого :) Приемат се всякакви коментари и препоръки относно сигурността или други области за подобрение. Мнението на общността също е интересно, може би си струва да добавим поддръжка за ConfigMaps (нашият проект все още не ги използва) и да публикуваме кода в GitHub / PyPI? Лично аз смятам, че такива неща са твърде индивидуални, за да бъдат проектите универсални, и малко надникване в чужди реализации, като даденото тук, и обсъждане на нюанси, съвети и добри практики, които се надявам да видя в коментарите , достатъчно е 😉

В анкетата могат да участват само регистрирани потребители. Впиши се, Моля те.

Трябва ли да публикувам като проект/библиотека?

  • 0,0%Да, бих използвал /contribution0

  • 33,3%Да, това звучи страхотно4

  • 41,7%Не, който трябва да го направи сам в свой собствен формат и според нуждите си5

  • 25,0%Ще се въздържа от отговор3

12 потребители гласуваха. 3 потребители се въздържаха.

Източник: www.habr.com

Добавяне на нов коментар