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