Канфігурацыя праекта ўнутры і па-за Kubernetes

Нядаўна я напісаў адказ аб жыцці праекта ў Докерах і адладцы кода па-за ім, Дзе мімаходам згадаў аб тым, што можна зрабіць сваю сістэму канфігуравання, каб сэрвіс і ў Куберы добра працаваў, падцягваў сакрэты, і лакальна зручна запускаўся, у тым ліку наогул па-за Докерам. Нічога складанага, але апісаны "рэцэпт" можа камусьці спатрэбіцца 🙂 Код на Пітон, але логіка да мовы не прывязана.

Канфігурацыя праекта ўнутры і па-за Kubernetes

Перадгісторыя пытання такая: жыў-быў адзін праект, спачатку ён быў маленькім маналітам з утылітамі і скрыптамі, але з часам рос, дзяліўся на сэрвісы, якія ў сваю чаргу сталі дзяліцца на мікрасэрвісы, а потым яшчэ і скейліцца. Спачатку гэта ўсё выконвалася на голых VPS, працэсы налады і разгортвання кода на якіх былі аўтаматызаваны з дапамогай Ansible, і кожнаму сэрвісу складаўся YAML-канфіг з патрэбнымі наладамі і ключамі, і аналагічны конфіг-файл выкарыстоўваўся для лакальных запускаў, што было вельмі зручна, т .да гэты канфіг грузіцца ў глабальны аб'ект, даступны з любога месца ў праекце.

Аднак рост колькасці мікрасэрвісаў, іх сувязей, а таксама патрэбнасць у цэнтралізаваным лагаванні і маніторынгу, прадвесцілі пераезд у Кубер, які да гэтага часу яшчэ ў працэсе. Разам з дапамогай у вырашэнні згаданых задач, Kubernetes прапануе свае падыходы да кіравання інфраструктурай, у тым ліку т.н Сакрэты и спосабы працы з імі. Механізм стандартны і надзейны, таму ў літаральным сэнсе грэх ім не скарыстацца! Але пры гэтым хацелася б захаваць свой бягучы фармат працы з канфігам: па-першае, аднастайна выкарыстоўваць яго ў розных мікрасэрвісах праекта, а па-другое, мець магчымасць запускаць код на лакальнай машыне выкарыстоўваючы адзін просты канфіг-файл.

У сувязі з гэтым, механізм пабудовы аб'екта-канфігурацыі быў дапрацаваны так, каб умець працаваць як з нашым класічным канфіг-файлам, так і з сакрэтамі з Кубера. Таксама была зададзена больш цвёрдая структура канфіга, кажучы мовай трэцяга Пітона, такая:

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

Гэта значыць, выніковы когфіг гэта слоўнік з найменнымі секцыямі, кожная з якіх з'яўляецца слоўнікам са значэннямі з простых тыпаў. А секцыі апісваюць канфігурацыю і доступы да рэсурсаў вызначанага выгляду. Прыклад кавалка нашага канфіга:

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 выглядае так:

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

Логіка тут даволі простая: аб'ядноўваем буйныя канфігі з дырэкторыі праекта і шляхі па зменнай асяроддзі, і невялікія канфігі-секцыі з сакрэтаў Кубера, а затым трохі іх прадапрацоўваем. Плюс некаторыя зменныя. Заўважу, што пры пошуку файлаў з сакрэтаў выкарыстоўваецца абмежаванне глыбіні, бо K8s у кожным сакрэце стварае яшчэ ўтоеную тэчку, дзе самі сакрэты і захоўваецца, а ўзроўнем вышэй знаходзіцца проста спасылка.

Спадзяюся, апісанае апынецца каму-небудзь карысным 🙂 Прымаюцца любыя каментары і рэкамендацыі датычна бяспекі ці іншых момантаў на паляпшэнне. Таксама цікава меркаванне супольнасці, ці магчыма варта дадаць падтрымку ConfigMaps (у нашым праекце яны пакуль не выкарыстоўваецца) і аформіць код на ГітХабе / PyPI? Асабіста я думаю, што такія рэчы занадта індывідуальныя для праектаў, каб быць універсальнымі, і дастаткова невялікага падглядвання на чужыя рэалізацыі, накшталт прыведзенай тут, ды абмеркавання нюансаў, парад і best practices, якое я спадзяюся ўбачыць у каментарах 😉

Толькі зарэгістраваныя карыстачы могуць удзельнічаць у апытанні. Увайдзіце, Калі ласка.

Ці варта публікаваць як праект/бібліятэку?

  • 0,0%Так, я б выкарыстаў / контрибутил0

  • 33,3%Так, гучыць выдатна4

  • 41,7%Не, каму трэба зробяць самі ў сваім фармаце і пад свае патрэбы5

  • 25,0%Устрымаюся ад адказу3

Прагаласавалі 12 карыстальнікаў. Устрымаліся 3 карыстальніка.

Крыніца: habr.com

Дадаць каментар