天天看點

如何在環境中存儲配置

關于「在環境中存儲配置」,是 The Twelve-Factor App 倡導的方法論之一。通常,應用的配置在不同環境(預釋出、生産環境、開發環境等等)間會有很大差異,比如說資料庫的使用者名密碼等等配置,通過把配置和代碼分離,我們可以保證部署在不同環境的代碼完全一緻,如何把配置和代碼分離呢?最佳實戰是把配置存儲到環境變量中,它可以非常友善地在不同的部署間做修改,卻不動一行代碼;與配置檔案不同,不小心把它們簽入代碼庫的機率微乎其微;此外環境變量與語言和系統無關。

在實際應用中,現在比較流行的解決方案是 dotenv(Ruby dotenv、PHP dotenv):首先建立一個 .env 檔案,然後把配置資訊都儲存在裡面,接着把這些資訊加載的環境變量裡,最後直接使用環境變量。

通過使用此方案,我們可以給不同的環境設定不同的 .env 檔案,在一定程度上實作了配置和代碼分離,可惜還有一些明顯的缺點,比如:

  • 如果有很多台伺服器需要同步配置,那麼是一件很痛苦的事情。
  • 如果忘了把 .env 加入到 .gitignore,那麼很有可能洩露敏感資訊。
  • 如果部署不當,那麼很可能洩露敏感資訊,比如這裡。

通過引入服務發現機制可以解決多台伺服器同步配置的問題,主流方案如下:

  • etcd + confd
  • consul + consul-template

它們的實作機制類似,都是把配置儲存在服務發現的存儲裡,一旦發生變化,可以自動通過模闆技術靜态化儲存成本地檔案,進而解決多台伺服器同步配置的問題。

不過這些方案歸根到底還是要需要靜态化儲存成本地檔案的,有沒有直接使用環境變量儲存配置的解決方案呢?答案就是 envconsul,其工作原理如下:在 consul 中儲存配置,然後 envconsul 啟動後會加載配置,并通過環境變量的方式傳遞給子程序,此外 envconsul 還會通過 consul 的 http 接口以 long polling 的方式監聽,一旦發現配置出現了變動,就會發送信号給子程序,進而完成配置的更新。

如果你已經安裝好了 consul 和 envconsul,那麼讓我們來試一試(未考慮權限控制):

shell> consul kv put app/db/username root
shell> consul kv put app/db/password 123456

shell> envconsul \
    -pristine \
    -sanitize \
    -upcase \
    -prefix app \
    env

DB_USERNAME=root
DB_PASSWORD=123456           

複制

如上,我使用 env 指令作為 envconsul 的子程序來顯示環境變量,實際使用中,你可以把 ruby,php 之類的應用作為 envconsul 的子程序,下面我用一個 shell 腳本來展示配置發生變化的時候 envconsul 是如何應對的,shell 腳本名為 test.sh,内容如下:

#! /bin/bash

signals=(HUP INT QUIT TERM USR1 USR2)

for signal in "${signals[@]}"
do
    trap "echo $signal; exit" "$signal"
done

for i in {1..1000}
do
    echo $i: PASSWORD: $DB_PASSWORD
    sleep 1
done           

複制

其作用就是監聽信号,并且顯示 DB_PASSWORD 環境變量,這次我們開啟兩個指令行視窗,一個運作 envconsul,另一個運作 consul kv put app/db/password … 來修改配置:

shell> envconsul \
    -pristine \
    -sanitize \
    -upcase \
    -prefix app \
    /path/to/test.sh

1: PASSWORD: <OLD VALUE>
2: PASSWORD: <OLD VALUE>
INT
1: PASSWORD: <NEW VALUE>
2: PASSWORD: <NEW VALUE>           

複制

我們能看到,當 envconsul 發現配置改變了之後,預設情況下會發送 INT 信号(可配置)給子程序,使子程序完成重新開機,進而加載到新的配置。

此外還有一些細節問題需要考慮,比如:假設有一百台應用伺服器,都是通過 envconsul 運作的,那麼當配置發生變化的時候,如果這一百台應用伺服器同時重新開機程序的話,無疑是一場災難,實際上 envconsul 已經考慮到了此類情況,你可以通過配置 splay 選項把重新開機的時間随機化,避免「Thundering herd problem」;再假設配置發生變化的時候,如果子程序一直沒有完成重新開機怎麼辦,envconsul 有一個 kill_timeout 選項,重新開機逾時的話被直接強殺子程序。其它更多配置參見文檔說明,篇幅所限,恕不贅述。

結尾再推薦一篇不同的聲音:Why you shouldn’t use ENV variables for secret data,其以安全性為由,不建議使用環境變量,而是推薦使用 docker swarm 的密鑰機制來管理敏感資訊(相關教程),這很酷,如果你使用 docker,不妨一試。

回到 envconsul,環境變量僅針對子程序有效,雖然在一定程度上降低了風險,但是确實有可能洩露敏感資訊,比如在 PHP 裡,如果能運作 phpinfo 函數的話,那麼可以列印出所有的環境變量,但我覺得不能因噎廢食,以 PHP 為例,在生産環境中,類似 phpinfo,eval 之類的危險函數,原本就應該通過 disable_functions 禁用,而且資料庫密碼之類的資訊,一般有 ip 通路限制,即便洩露了也影響有限,但這并不意味着可以不假思索的把任何資訊都往環境變量裡塞,比如銀行卡密碼,比特币密鑰之類高度敏感的資訊,如果洩露了就全完了,此時還是用 Vault 比較好,當然,envconsul 也支援 Vault。