1. 程式人生 > >04.Fabric原始碼解析——配置系統

04.Fabric原始碼解析——配置系統

Fabric原始碼解析4——配置系統

Fabric的配置系統是程式原始資料的來源之一,雖然簡單卻很重要。在閱讀原始碼過程中對於具象化程式也很有幫助。在分析peer的具體交易工作之前,我們可以先分析一下fabric的配置系統。我們還將我們的目光聚焦在/fabric/peer/main.go的main函式中,除了一系列mainCmd的命令操作,還有viper進行的一系列配置操作,並通過err := common.InitConfig(cmdRoot)進行了配置的初始化。

Fabric索取配置的途徑有:環境變數,命令列引數,各種格式的配置檔案。其中以配置檔案為主,環境變數和命令列引數輔助,三者可以相互作用。主要的配置檔案有core.yaml,orderer.yaml等

,在/fabric/sampleconfig中有示例。主要使用的配置程式碼集中在/fabric/core/config下。

viper簡介

Fabric的配置系統主要運用第三方包viper可在github.com/spf13/viper下載。viper可以對系統環境變數,yaml/json等格式的配置檔案甚至是遠端配置進行讀取和設定,並可以在不重啟服務的情況下動態設定新的配置項的值並使之實時生效,是一個專門處理配置的解決方案。 而且,viper,眼鏡蛇,稱自己與cobra(見peer命令結構一文)是companion,足見使用viper的理由。

viper的基礎用法如下

//設定一個要讀取的配置檔名(不包含字尾),一個viper只支援一個檔名
viper.SetConfigName("config")
//設定一個搜尋配置檔案的路徑,viper的搜尋路徑可以有多個
viper.AddConfigPath("/etc/appname/")
viper.AddConfigPath(".")
//讀取配置檔案
viper.ReadInConfig()
//獲取其中一個name項的值
viper.Get("name")
//將name的值設定為Bill
viper.Set("name", "Bill")

viper搜尋路徑和檔案

peer命令對core.yaml的引入也是通過viper,具體過程如下:

1. /fabric/peer/main.go中定義const cmdRoot = "core"。
2. main函式中呼叫err := common.InitConfig(cmdRoot),該引數一路向下傳遞。
3. InitConfig函式在/fabric/peer/common/common.go中定義,其中呼叫了config.InitViper(nil, cmdRoot)和viper.ReadInConfig()。
4. InitViper在/fabric/core/config/config.go中定義,接收cmdRoot作為引數,最終呼叫了viper.SetConfigName(),也即將core設定為了配置檔名。
5. common.InitConfig(cmdRoot)中的viper.ReadInConfig()則讀取了該配置檔案

orderer命令則使用orderer.yaml配置檔案,由viper引入,具體過程如下:

1. 在/fabric/orderer/main.go中main函式呼叫了config.Load()。
2. Load在/fabric/orderer/localconfig/config.go中定義。該檔案中定義了Prefix = "ORDERER"和configNamestring,並在init初始化函式中將configName賦值為strings.ToLower(Prefix),即orderer,也即所用的配置檔名為orderer。Load函式新建了一個用於orderer自己的viper,並呼叫了cf.InitViper(config, configName),其中config引數為新建的用於orderer自身的viper,configName為配置檔名orderer
3. InitViper在/fabric/core/config/config.go中定義,最終呼叫了viper.SetConfigName(),也即將orderer設定為了配置檔名Load隨後呼叫了config.ReadInConfig(),讀取了配置檔案

InitViper

上述步驟中peer和orderer在初始化配置檔案時,最終都將呼叫的終點指向了/fabric/core/config/config.go中定義InitViper()。下面集中分析InitViper

1. 首先判斷環境變數FABRIC_CFG_PATH是否有值,如果有值,則是手工定義了FABRIC的配置檔案所在路徑。參考Getting Started中關於手工設定export FABRIC_CFG_PATH=$PWD(當前目錄)。

2. 若沒有定義該環境變數的值,則用程式碼新增三個路徑作為搜尋配置檔案的路徑:當前工作目錄,$GOPATH/src/github.com/hyperledger/fabric/sampleconfig,/etc/hyperledger/fabric。

3. 當前工作目錄,呼叫addConfigPath(v, “./”)新增,其內部呼叫的是viper.AddConfigPath()。

4. $GOPATH/src/github.com/hyperledger/fabric/sampleconfig,通過呼叫AddDevConfigPath(v)新增。

5. AddDevConfigPath首先呼叫GetDevConfigDir(),讀取GOPATH路徑下是否存在src/github.com/hyperledger/fabric/sampleconfig目錄,若存在,則通過filepath.Join拼接GOPATH路徑下是否存在src/github.com/hyperledger/fabric/sampleconfig目錄,若存在,則通過filepath.Join拼接GOPATH和src/github.com/hyperledger/fabric/sampleconfig,形成完整的路徑並返回。
6. AddDevConfigPath接著呼叫addConfigPath函式,其內部呼叫的是viper.AddConfigPath()。/etc/hyperledger/fabric。定義了OfficialPath = “/etc/hyperledger/fabric”常量,如果該路徑存在,則呼叫addConfigPath加入該路徑。
7. 呼叫SetConfigName()設定配置檔名,所指的的配置檔名configName是由引數傳遞進來的。

8. 由經由InitViper,形成了以下viper配置:
 - 搜尋路徑(二選一)
FABRIC_CFG_PATH指定的路徑
./,$GOPATH/src/github.com/hyperledger/fabric/sampleconfig,/etc/hyperledger/fabric
- 搜尋的配置檔名
- core —— 核心配置,供各個模組使用
- orderer —— orderer配置,orderer使用
- 另外注意InitViper的第一個引數,v 
- *viper.Viper。在InitViper函式中,無論是新增搜尋路徑(使用的是addConfigPath函式),還是設定要搜尋的配置檔名(viper自身的SetConfigName函式),都分為全域性的viper和特定的viper(也就是引數v)。最終由viper.AddConfigPath或viper.SetConfigName完成的,則是全域性的,由v.AddConfigPath或v.SetConfigName完成的,則是特定的。這樣就可以很方便的初始化需要單獨使用viper的模組,如orderer就是單獨使用一條毒蛇,其在/fabric/orderer/localconfig/config.go中的Load函式中,config := viper.New()新養了一條自己的蛇,然後將此蛇通過引數傳給InitViper,cf.InitViper(config, configName)。

安全檔案配置

安全配置相關的程式碼在/fabric/peer/main.go中沒有體現,而是在/fabric/peer/node/start.go中的serve函式中才初次出現。若grpc服務中使用了TLS網路,則需要.key,.crt,.ca檔案配套檔案。在此簡略介紹,關於TLS,將會在將來專門的文章中詳細介紹。

在/fabric/peer/node/start.go中的serve函式中secureConfig, err := peer.GetSecureConfig()獲取安全配置。

使用的安全配置結構為/fabric/core/comm/server.go中定義的SecureServerConfig,用於一個grpc服務端例項。
.key,.crt,.ca檔案所在的目錄都是在core.yaml中定義都的tls資料夾中,當使用TLS網路時,會讀取這些檔案的資料到SecureServerConfig物件中。在GetSecureConfig()函式中,使用ioutil.ReadFile讀取由/fabric/core/config/config.go中定義的config.GetPath(“…”)函式獲取的tls路徑下的相應檔案。如/etc/hyperledger/fabric/tls/server.crt。

命令選項配置

以peer start命令為例,在/fabric/peer/node/start.go中startCmd()函式中,flags.BoolVarP(&chaincodeDevMode, “peer-chaincodedev”, “”, false,“Whether peer in chaincode development mode”)設定了peer start命令的選項之一為peer-chaincodedev,用於賦值檔案中的全域性變數chaincodeDevMode,該變數指定了chaincode的模式。chaincode的模式在core.yaml中也有定義,chaincode.mode的值為net,為預設選項。而當執行peer start 命令時指定了選項peer-chaincodedev=true,也即將chaincodeDevMode賦值為true,在serve()函式中,就會使用viper.Set(“chaincode.mode”, chaincode.DevModeUserRunsChaincode)將chaincode的模式值設定成了dev。

環境變數配置

在fabric目前的階段,各個peer都是在容器中執行的,因此環境變數指的是各個容器中的環境變數。在各個容器的啟動指令碼中對容器的一些環境變數也進行了設定。在peer-base-no-tls.yaml(見原始碼解析1——線頭)中,如peer容器中,設定瞭如下環境變數:

- CORE_PEER_ADDRESSAUTODETECT=true
- CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
- CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=e2ecli_default
- CORE_LOGGING_LEVEL=ERROR
...

在fabric/peer/main.go中的開始,即對容器的環境變數進行了獲取並設定:

//設定了環境變數前置,在此也就是peer
viper.SetEnvPrefix(cmdRoot)
//將環境變數載入進來了
viper.AutomaticEnv()
replacer := strings.NewReplacer(".", "_")
//將環境變數中的_換成.,這樣就和yaml檔案的配置相匹配了。
//因為viper讀取yaml檔案所形成的配置項就是按層級並以.分隔的格式,如peer.address
viper.SetEnvKeyReplacer(replacer)