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

對於本機啟動,使用位於專案根目錄或沿著環境變數中指定的路徑的配置。 負責這些便利的程式碼可以在劇透中看到。

設定檔

__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

添加評論