Designing Configuration Loading Strategies


這篇原文是寫在 SRE 社群的一篇 筆記,借保哥的 整理 發揮另一個常見的東西:配置與設定的讀取策略設計

註:

  • 配置:Configuration, 通常指的是檔案 或者 存在 DB 裡的形式
  • 設定:Settings, 每個配置裡的 K/V 一對一對的東西稱為設定.

配置注入來源

通常 APP 在設計時,在 Code Level 會有一些 預設 設定值,然後在第一次啟動應用程式時,自動產生使用者配置,裡面會寫入一份預設值 (或者留白)。另外應用程式本身的執行程式通常也可以透過參數 (保哥文中提到的 --explicitly-allowed-ports=10080) 複寫這些設定值,或者可以透過 環境變數 (ENV) 選擇不同的 配置

所以配置的設定值的來源有這些地方:

  • 程式碼裡的預設值 (Code Level):在應用程式裡宣告的預設值。
    • 通常是寫在 Constants or Interface or Enum 或者 c 的 header (.h)
    • 相對於 使用者層級設定,預設就是 系統層級設定 (System Level Settings)
  • 使用者層級設定 (User Level Settings):
    • 使用者層級的配置,通常會放在 $HOME/.<appname>/ (目錄) 或者 $HOME/.<appname>rc (檔案)
    • 第一次應用程式初始的時候,初始設定的策略有幾種,依照應用程式設計特性而定。
      1. 系統層級複製一份過來
      2. 從出廠預設複製一份
      3. 或者留空
      4. 初始透過問答 (prompt, wizard) 的方式產生 (像是 oh-my-zsh 的初始過程)
    • 大家可以在自己系統 (macOS / linux / Windows) $HOME 底下發現很多 用點 (dot) 開頭的目錄或者檔案
    • 我很常舉例的就是 unix 登入時的初始流程,相關說明參閱 Shell and Bash Concepts
  • 臨時的參數啟動 (Inject by Arguments)
    • 通常給進階使用者、開發者自己使用,可以臨時替換配置檔裡的設定,不需要改檔案內容就能測試。
    • Designing Test Architecture and Framework 提到的 Testcase 的 Config Management 就有這樣的設計。開發測試案例者,可以透過參數化的方式 overwrite 檔案的配置,提高 測試 - 測試案例 的便利性。
  • 環境變數指定:選擇適當的配置項目,像是選擇不同身份以及配置
    • AWS CLI 預設就是透過 AWS_PROFILE 選擇不同的 AK/SK,沒有指定則讀取 default

讀取策略

在大部分的 Open Source 設計配置大概都有上述的東西,而他們讀取的策略大部分的 先後次序 如下圖:

  1. [紅色] 讀取環境變數設定值:根據現在的 Session ,讀取環境變數
    • 環境變數的用途是 選擇用途,而不是 指定數值,像選擇 Profile,而不是指定 port number。
  2. [橘色] 先看看參數是否有指定 (args / options),有的話會 覆寫 或者 跳過 2) User Level 的設定
  3. [綠色] 讀取 使用者設定檔 (Config):通常讀取次序在環境變數之後,透過環境變數的選擇用途,指定大範圍的 檔案 或者 目錄結構
  4. [紫色] 上述如果都沒有,直接使用 System Level (程式碼) 裡的預設值,像是寶哥舉例的 kRestrictedPorts

常見的一些工具讀取配置的策略大概都是這樣,像是大家常用的 kubectl、aws cli、 vscode 、git … 有些除了上述四的步驟,會多了認證授權的 credential.

這個 配置與設定的讀取策略設計 是大家可以留意的,出問題時,或者在用新工具、在開發新應用程式的時候,依照這個邏輯,才能正確確認應用程式有讀取到正確的配置。

另外,通常建議設計一個 Flag (像是 –verbose or –debug) ,讓應用程式初始化之後顯示上述的配置,可以用來快速確認目前啟動是否正確。甚至直接做 Validation,只要配置有錯誤,就直接中斷應用程式。

環境變數的應用

環境變數常見的用法有以下幾種:

  1. 指定使用者的 Profile:環境變數本身是 Session Base,適用的情境就是依照使用者身份,提供 Profile 選擇。
  2. 關鍵路徑的指定,像是 HOMELOG_PATH
  3. 系統層級的交互餐數:像是 C compiler command 、 LDFLAGS (linker flags)

應用程式自己業務邏輯的參數,建議都放在 Configuration 裡,而不是透過 Environment 注入。

Application Interface Spec

通常在除錯時,都要確認一些基礎資訊的正確性:

a. 由外而內: 輸入資料來源的正確性 (API Request Payload)
b. 由內而外: 應用程式的配置正確性

如果系統已經執行一段時間,通常找問題的點都會在 a),也就是輸入資料的正確性,像是 API 的 Payload 某些值有錯誤、超出範圍。

如果系統是新部署 (更版 / 業務拓展 / 測試環境),通常要先確認的是 b)、再來才是 a)。很多時候都是應用程式初始過程中的配置有問題,像是外部依賴的位址給錯了 (通常是 copy & paste 忘了改)、或者某些 secret 給錯、對於系統外部程式的依賴版本錯誤 … 等,而怎麼初始配置的流程則是除錯過程中必要知道的 Know How。

註:這裡的 內、外 指的是應用程式從 Artifact 跑起來變成 Process 之後,給予的資訊流。以 WebApp 來講,由外而內 就是透過 WebAPI 提供資訊所造成的行為。而 由內而外 則是 Process 自身內部 Event 或者生命週期提供的資訊,像是 Configuration。

上述的 a) + b) 我把它稱為 Application Interface Spec,範圍涵蓋以下:

  1. 由外而內使用者 看得到的介面,形式有以下
    1. API: 包含 WebAPI、Libraries API
    2. 通訊協議與資料結構: 像是 HTTP Header、JWT Payload、Cookies … etc
    3. 通訊認證:
      • 企業內部系統之間的通訊認證模式
      • 對外部系統的通訊認證模式
  2. 由內而外開發團隊 看得到的介面
    1. Environment Variables
    2. Configuration: Config 與 Settings 的定義、配置檔案的策略、設定操作策略 (static / dynamic)
      • Static (or 被動 / pull): file or db base
      • Dynamic (or 主動 / push): event base, 像是 feature toggle 的實作, consul … etc.
    3. Secret: 算是 Configuration 的一環,牽涉敏感,所以通常獨立於 Configuration 之外。
      • Loading 的技術概念與 Configuration 一致,同樣分成 static / dynamic or pull / push 等模式。
      • Data Storage Source 會配合 KMS 方式加解密,也會有其他的管理政策配合。
    4. Database Schema: 不管是 RDB or NoSQL,都是要有結構定義的
    5. Storage Structure: 非結構性的資料存放結構

而本文談的是 2.1)2.2) 的設計原則。

開發者很常討論 OOD 的 DI (Dependency Injection, 依賴注入),我把 Application Interface Spec 當作應用程式層級的 DI,透過他可以靈活的控制應用程式的行為、邏輯,讓使用者有能力控制整個應用程式的行為,他是初始一個應用程式的時候,就必須確立好範圍,然後持續迭代的。就像是在 Code Level 想要控制某一些 Class 的行為,透過 DI 以及物件生命週期 Scope 可以讓 Runtime 的 Object 之間有良好的依賴關係。


延伸閱讀




Comments