Kubernetes 内外のプロジェクト構成

最近書きました Docker でのプロジェクトのライフサイクルと Docker 外部でのコードのデバッグについての回答そこで彼は、独自の構成システムを作成して、サービスが Kuber で適切に動作し、シークレットを取得し、Docker の外部でもローカルで便利に実行できるようにできると簡単に述べました。 何も複雑なことはありませんが、説明されている「レシピ」は誰かに役立つかもしれません :) コードは Python ですが、ロジックは言語に関連付けられていません。

Kubernetes 内外のプロジェクト構成

この質問の背景は次のとおりです。かつて、プロジェクトが XNUMX つありました。最初はユーティリティとスクリプトを備えた小さな一枚岩でしたが、時間の経過とともに成長し、サービスに分割され、さらにマイクロサービスに分割され始めました。その後スケールアップしました。 当初、これはすべてベア VPS 上で行われ、コードのセットアップとデプロイのプロセスは Ansible を使用して自動化され、各サービスは必要な設定とキーを含む YAML 構成でコンパイルされ、同様の構成ファイルが使用されました。 .k この設定はグローバル オブジェクトにロードされ、プロジェクト内のどこからでもアクセスできるため、ローカルで起動します。これは非常に便利でした。

ただし、マイクロサービスの数、その接続、および 集中的なログ記録と監視の必要性、現在も進行中のKuberへの移行を予感させた。 前述の問題の解決を支援するとともに、Kubernetes はインフラストラクチャ管理への次のようなアプローチを提供します。 いわゆる秘密 и 彼らと協力する方法。 このメカニズムは標準的で信頼性が高いので、それを使用しないのは文字通り罪です。 しかし同時に、構成を操作するための現在の形式を維持したいと考えています。第一に、プロジェクトのさまざまなマイクロサービスで均一に使用すること、第二に、XNUMX つの単純なコマンドを使用してローカル マシンでコードを実行できるようにすることです。設定ファイル。

この点で、構成オブジェクトを構築するメカニズムが変更され、従来の構成ファイルと Kuber のシークレットの両方で動作できるようになりました。 次のように、より厳格な構成構造も XNUMX 番目の 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 ポッドの説明は次のようになります。

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

つまり、各シークレットは XNUMX つのセクションを説明します。 シークレット自体は次のように作成されます。

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 がシークレット自体を保存する各シークレットに隠しフォルダーを作成し、リンクのみが上位レベルに配置されるためです。

ここで説明した内容が誰かの役に立つことを願っています :) セキュリティやその他の改善領域に関するコメントや推奨事項は受け付けられます。 コミュニティの意見も興味深いです。ConfigMap のサポートを追加し (私たちのプロジェクトではまだ使用していません)、GitHub / PyPI でコードを公開する価値はあるでしょうか? 個人的には、そのようなものはプロジェクトが普遍的であるには個別すぎると思います。ここで挙げたような他の人の実装を少し覗いてみたり、ニュアンス、ヒント、ベスト プラクティスについての議論をコメントで確認できることを期待しています。 、十分です😉

登録ユーザーのみがアンケートに参加できます。 ログインお願いします。

プロジェクト/ライブラリとして公開する必要がありますか?

  • 視聴者の38%がはい、/contribution0 を使用します

  • 視聴者の38%がはい、それは素晴らしいですね4

  • 視聴者の38%がいいえ、独自の形式でニーズに合わせて自分で行う必要がある人はいません5

  • 視聴者の38%が回答は控えさせていただきます3

12 人のユーザーが投票しました。 3名のユーザーが棄権した。

出所: habr.com

コメントを追加します