konfig:採用ConfigMap實現線上配置熱更新
前言
利用kubernetes部署應用越來越流行,而執行在kubernetes中的服務需要的各種各樣的配置如何才能實現熱更新?難道需要在kubernetes中再部署zookeeper或者etcd之類的服務麼?本文采用的方案是利用ConfigMap作為服務配置的持久化方案,並利用kubernetes提供的watch能力主動發現ConfigMap更新並及時更新到服務的配置中。這樣運維人員只需要利用kubernetes的控制檯(cli或者web)修改線上服務的配置,比如修改日誌等級、降級、調整閾值等等。
本文引用原始碼https://github.com/jindezgm/konfig/blob/master/konfig.go
實現
konfig是利用kubernetes的一個ConfigMap實現的一個配置樹,雖然ConfigMap.Data是map[string]string型別,但是konfig會對ConfigMap.Data中的值做進一步(遞迴)yaml解析,前提條件是值是以"---\n"開頭。這樣設計的目的是讓konfig支援多級配置,而ConfigMap只有一級在一些使用場景並不友好。當然,利用一級也是可以實現多級配置,只是把多級體現在key上,例如:"a.b.c", "c.d",筆者認為視覺上不太優雅。
konfig支援以多種型別獲取相同的配置,可以根據需要轉換成指定型別,如下介面定義所示:
// 所有介面都會返回配置的版本號,即ConfigMap.ResourceVersion,keys是多級的key,當keys為空時表示根,即整個ConfigMap.
type Interface interface {
// 如果keys已經被註冊某種型別(參看下面的RegValue介面),則返回指定型別的值,否則返回原生型別的值。
Get(keys ...string) (interface{}, int64)
// 獲取布林型
GetBool(keys ...string) (bool, int64)
// 獲取64、32位整型、
GetInt64(keys ...string) (int64, int64)
GetInt(keys ...string) (int, int64)
GetInt32(keys ...string) (int32, int64)
// 獲取64、32位浮點型
GetFloat64(keys ...string) (float64, int64)
GetFloat32(keys ...string) (float32, int64)
// 獲取字串
GetString(keys ...string) (string, int64)
// 將指定keys下的值註冊為一種型別,配合Get()介面使用可以將keys下的值轉換為註冊的型別返回,其中tag是成員變數的tag名稱,比如json
RegValue(ptr interface{}, tag string, keys ...string) (interface{}, int64)
// 獲取指定型別的value,konfig會將map[string]interface{}轉換為value物件,其中tag是成員變數的tag名稱,比如json.
GetValue(ptr interface{}, tag string, keys ...string) int64
// 將指定的keys下值掛載/解除安裝到環境變數,
MountEnv(keys ...string)
UnmountEnv()
// 獲取版本號
Revision() int64
}
konfig在Get指定型別的配置時,除了原生型別外,盡最大努力將原生型別轉為指定型別,以下是konfig支援的型別轉換:
- int,int32,int64:支援浮點型轉整型、支援字串轉整型(string->float64->intxx)、支援布林型轉整型(true:1,false:0)
- float32,float64:支援字串轉浮點型
- bool:支援整型、浮點型轉布林型(非零:true,零:false),支援字串轉布林型(不區分大小寫的"True":true,不區分大小寫的"False":false)
- string:支援所有型別轉字串,採用fmt.Sprintf("%v")返回
konfig保證了單個介面的原子性,但是如果連續呼叫兩次介面可能會返回兩個不同的版本,說明在兩次呼叫介面之間ConfigMap發生了變化。如果兩次呼叫介面獲得的配置引數對於版本一致性要求比較高的話,就需要重新呼叫,直到所有的配置的版本相同為止。這種情況發生的概率比較低,並且絕大部分重新呼叫一次就可以解決,因為ConfigMap的更新頻率極低。但是,這種方法貌似有點醜陋,使用者可以將此類的配置定義一種struct,然後通過GetValue()一次性拿到所有的配置。筆者常用的方法就是把整個ConfigMap定義為一個型別,然後一次性讀取所有的配置。如下程式碼所示:
import "github.com/jindezgm/konfig"
// 定義自己的配置型別
type MyConfig struct {
Bool bool `json:"bool"`
Int int `json:"int"`
String string `json:"string"`
}
kfg, _ := konfig.NewWithClientset(...)
var my MyStruct
var rev int64
// 應用引用配置的功能實現
for {
// 版本發生更新時重新獲取配置
if r := kfg.Revision(); r > rev {
// 此處的keys為空,這是將整個ConfigMap.Data對映為MyStruct
rev = kfg.GetValue(&my, "json")
}
// 使用配置
...
}
因為GetValue()介面會執行一次類似Unmarshal的過程,所以是有一定開銷的,適用於呼叫頻率不高的場景。如果需要高頻呼叫,建議應用快取配置(如上程式碼),並根據revision決定是否呼叫該介面。如果應用想省去這些麻煩的操作,那就呼叫RegValue()介面將型別註冊到konfig,由konfig按需解析,在配合Get()介面就可以滿足高頻呼叫的場景了。如下程式碼所示:
// 將"my"下的所有值註冊為MyConfig型別
kfg.RegValue(&MyConfig{}, "json", "my")
for {
// 每次引用直接呼叫Get,konfig保證一致性、隔離性以及原子性
value, _ = kfg.Get("my")
my := value.(*MyConfig)
...
}
在一些場景,某個功能點只需要引用一個配置項,使用者每次引用時可以直接呼叫介面,不用在自己的程式碼中快取配置(當revision變大再讀取配置更新快取),因為konfig的讀取效能還是有保證的。如下程式碼所示,按配置列印:
for {
if p, _ := kfg.GetBool("print"); p {
fmt.Println("Hello world")
}
}
當然,如果習慣讀取環境變數的方法獲取配置,而容器更新環境變數又會造成容器重啟,那麼可以用MountEnv()介面將配置掛載到環境變數,如下程式碼所示:
// keys為空,將ConfigMap.Data掛載到環境變數. 需要注意的是,MountEnv()的keys下應該只有一級配置,如果是多級,konfig會用fmt.Sprintf("%v")進行格式化
kfg.MountEnv()
defer kfg.UnmountEnv()
for {
if strings.ToLower(os.Getenv("print")) == "true" {
fmt.Println("Hello world")
}
}
konfig支援兩種建立方式:1.利用Clientset;2.利用SharedInformerFactory。前者適用於應用無需不訪問kubernetes場景,需要為konfig單獨建立一次clientset,這部分可以參考官方例項程式碼;後者適用於應用需要訪問kubernetes的場景,那麼konfig與應用共享Informer。無論哪一種情況,都需要授權pod讀取ConfigMap的許可權。
不足
- 當前konfig遞迴解析只支援yaml格式,其實json也很容易支援,感覺不是很必要;
- konfig不支援回撥,即ConfigMap更新後,將變化的部分回撥給使用者,當前的解決方式是通過revision解決;
- GetValue()雖然能夠一次獲取多個配置,但是需要所有的配置都在一個鍵下,如果一次獲取配置樹不同分支的多個配置,konfig還不支援,感覺可以用flag.FlagSet實現;
- MountEnv()不會比較兩次配置的差異,然後刪除新ConfigMap中沒有的配置,他只是簡單的將每次ConfigMap中的值設定到環境變數中;而UnmountEnv()也不會刪除環境變數,只是不再更新環境變數而已;這些都可以實現,只是暫時還沒有看到必要性;
相關文章
- Java中的動態配置更新:從配置中心到應用熱載入的實現Java
- Kubernetes 實戰——配置應用(ConfigMap、Secret)
- uniapp實現熱更新APP
- iOS熱更新實現方式iOS
- Flutter 熱更新功能實現Flutter
- Android熱更新實現方式Android
- Android熱更新實現原理Android
- 熱更新配置檔案
- 採用spring zookeeper 實現簡單的配置管理Spring
- 少年,想線上熱更新程式碼不?
- 利用 WebSphere Extended Deployment 實現應用程式的無縫線上更新Web
- Apollo配置中心-配置熱釋出如何實現
- ElasticSearch IK熱詞自動熱更新原理與Golang實現ElasticsearchGolang
- React Native 實現熱部署、差異化增量熱更新React Native熱部署
- Unity3D熱更新之LuaFramework篇[09]--資源熱更新與程式碼熱更新的具體實現Unity3DFramework
- Flutter 應用熱更新Flutter
- SpringMVC中採用簡潔的配置實現檔案上傳SpringMVC
- SAP裡如何實現可配置物料採購
- IOS熱更新-JSPatch實現原理+Patch現場恢復iOSJS
- Android 熱更新實現原理及程式碼分析Android
- GeneralUpdate實現應用程式更新
- Android 熱更新 Tinker 整合配置【詳細】Android
- Unity3D熱更新全書-何謂熱更新,為何熱更新,如何熱更新Unity3D
- React Native 熱更新實踐React Native
- kubernetes實踐之三十五:Pod配置管理ConfigMap
- 【詳細】Android熱更新Bugly整合配置Android
- Flutter Android 端熱修復(熱更新)實踐FlutterAndroid
- 熱更新應用--熱補丁Hotfix學習筆記筆記
- Laravel 中引入 Swoole Websocket 並實現熱更新 Reload 程式碼LaravelWeb
- 基於vue-cli3 SSR 程式實現熱更新功能Vue
- 記 Arthas 實現一次 CPU 排查與程式碼熱更新
- 採用α-β演算法實現井字棋遊戲演算法遊戲
- Eloquent 的 whereHas 採用 where in 實現的優化優化
- 採用spark和openfire實現即時通訊系統Spark
- 實現熱鍵啟用後臺程式 (轉)
- 19. 從零開始編寫一個類nginx工具, 配置資料的熱更新原理及實現Nginx
- Eclipse/tomcat 如何實現應用熱部署和熱啟動EclipseTomcat熱部署
- 利用 ACME 實現SSL證書自動化配置更新ACM