歡迎光臨
每天分享高質量文章

Kubernetes Pod 中的 ConfigMap 配置更新

業務場景里經常會碰到配置更新的問題,在 “GitOps“樣式下,Kubernetes 的 ConfigMap 或 Secret 是非常好的配置管理機制。但是,Kubernetes 到目前為止(1.13版本)還沒有提供完善的 ConfigMap 管理機制,當我們更新 ConfigMap 或 Secret 時,取用了這些物件的 Deployment 或 StatefulSet 並不會發生滾動更新。因此,我們需要自己想辦法解決配置更新問題,讓整個流程完全自動化起來。
GitOps 的大體來說就是使用 git repo 儲存資源描述的代碼(比如各類 Kubernetes 資源的 yaml 檔案),再通過 CI 或控制器等手段保證集群狀態與倉庫代碼的同步,最後通過 Pull Request 流程來審核,執行或回滾運維操作。
這篇文章中的所有知識對 Secret 物件也是通用的,為了簡明,下文只稱 ConfigMap。
概述

 

首先,我們先給定一個背景,假設我們定義瞭如下的 ConfigMap:
  1. apiVersion: v1
  2. kind: ConfigMap
  3. metadata:
  4. name: test-config
  5. data:
  6. config.yml: |-
  7. start-message: 'Hello, World!'
  8. log-level: INFO
  9. bootstrap.yml:
  10. listen-address: '127.0.0.1:8080'
這個 ConfigMap 的 data 欄位中宣告了兩個配置檔案,config.yml 和 bootstrap.yml,各自有一些內容。當我們要取用裡面的配置信息時,Kubernetes 提供了兩種方式:
  • 使用 configMapKeyRef 取用 ConfigMap 中某個檔案的內容作為 Pod 中容器的環境變數;

  • 將所有 ConfigMap 中的檔案寫到一個臨時目錄中,將臨時目錄作為 Volume 掛載到容器里,也就是 ConfigMap 型別的 Volume;

好了,假設我們有一個 Deployment,它的 Pod 模板中以取用了這個 ConfigMap。現在的問題是,我們希望當 ConfigMap 更新時,這個 Deployment 的業務邏輯也能隨之更新,有哪些方案?
  • 最好是在當 ConfigMap 發生變更時,直接進行熱更新,從而做到不影響 Pod 的正常運行

  • 假如無法熱更新或熱更新完成不了需求,就需要觸發對應的 Deployment 做一次滾動更新

接下來,我們就探究一下不同場景下的幾種應對方案。
場景一:針對可以做熱更新的容器,進行配置熱更新

 

當 ConfigMap 作為 Volume 進行掛載時,它的內容是會更新的。為了更好地理解何時可以做熱更新,我們要先簡單分析 ConfigMap volume 的更新機制:
更新操作由 kubelet 的 Pod 同步迴圈觸發。每次進行 Pod 同步時(預設每 10 秒一次),kubelet 都會將 Pod 的所有 ConfigMap Volume 標記為“需要重新掛載(RequireRemount)”,而 kubelet 中的 Volume 控制迴圈會發現這些需要重新掛載的 Volume,去執行一次掛載操作。
在 ConfigMap 的重新掛載過程中,kubelet 會先比較遠端的 ConfigMap 與 Volume 中的 ConfigMap 是否一致,再做更新。要註意,“拿遠端的 ConfigMap” 這個操作可能是有快取的,因此拿到的並不一定是最新版本。
由此,我們可以知道,ConfigMap 作為 Volume 確實是會自動更新的,但是它的更新存在延時,最多的可能延遲時間是:
Pod 同步間隔(預設10秒)+ ConfigMap 本地快取的 TTL。
kubelet 上 ConfigMap 的獲取是否帶快取由配置中的 ConfigMapAndSecretChangeDetectionStrategy 決定。
註意,假如使用了 subPath 將 ConfigMap 中的某個檔案單獨掛載到其它目錄下,那這個檔案是無法熱更新的(這是 ConfigMap 的掛載邏輯決定的)。
有了這個底,我們就明確了:
  • 假如應用對配置熱更新有實時性要求,那麼就需要在業務邏輯里自己到 ApiServer 上去 watch 對應的 ConfigMap 來做更新。或者,乾脆不要用 ConfigMap,換成 etcd 這樣的一致性 KV 儲存來管理配置;

  • 假如沒有實時性要求,那我們其實可以依賴 ConfigMap 本身的更新邏輯來完成配置熱更新。

當然,配置檔案更新完不代表業務邏輯就更新了,我們還需要通知應用重新讀取配置進行業務邏輯上的更新。比如對於 Nginx,就需要發送一個 SIGHUP 信號量。這裡有幾種落地的辦法。
熱更新一:應用本身監聽本地配置檔案
假如是我們自己寫的應用,我們完成可以在應用代碼里去監聽本地檔案的變化,在檔案變化時觸發一次配置熱更新。甚至有一些配置相關的第三方庫本身就包裝了這樣的邏輯,比如說 viper。
熱更新二:使用 sidecar 來監聽本地配置檔案變更
Prometheus 的 Helm Chart 中使用的就是這種方式。這裡有一個很實用的鏡像叫做 configmap-reload,它會去 watch 本地檔案的變更,併在發生變更時通過 HTTP 呼叫通知應用進行熱更新。
但這種方式存在一個問題:Sidecar 發送信號(Signal)的限制比較多,而很多開源組件比如 Fluentd,Nginx 都是依賴 SIGHUP 信號來進行熱更新的。主要的限制在於,Kubernetes 1.10 之前,並不支持 Pod 中的容器共享同一個 pid namespace,因此 SideCar 也就無法向業務容器發送信號了。而在 1.10 之後,雖然支持了 pid 共享,但在共享之後 pid namespace 中的 1 號行程會變成基礎的 /pause 行程,我們也就無法輕鬆定位到標的進行的 pid 了。
當然了,只要是 Kubernetes 版本在 1.10 及以上並且開啟了 ShareProcessNamespace 特性,我們多寫點代碼,通過行程名去找 pid,總是能完成需求的。但是 1.10 之前就是完全沒可能用 sidecar 來做這樣的事情了。
熱更新三:胖容器
既然 SideCar 限制重重,那我們只能回歸有點”反樣式”的胖容器了。還是和 sidecar 一樣的思路,但這次我們通過把主行程和sidecar 行程打在同一個鏡像里,這樣就直接繞過了 pid namespace 隔離的問題。當然,假如允許的話,還是用上面的一號或二號方案更好,畢竟容器本身的優勢就是輕量可預測,而複雜則是脆弱之源。
場景二:無法熱更新時,滾動更新 Pod

 

無法熱更新的場景有很多:
  • 應用本身沒有實現熱更新邏輯,而一般來說自己寫的大部分應用都不會特意去設計這個邏輯;

  • 使用 subPath 進行 ConfigMap 的掛載,導致 ConfigMap 無法自動更新;

  • 在環境變數或 init-container 中依賴了 ConfigMap 的內容;

 

最後一點額外解釋一下,當使用 configMapKeyRef 取用 ConfigMap 中的信息作為環境變數時,這個操作只會在 Pod 創建時執行一次,因此不會自動更新。而 init-container 也只會運行一次,因此假如 init-contianer 的邏輯依賴了 ConfigMap 的話,這個邏輯肯定也不可能按新的再來一遍了。
當碰到無法熱更新的時候,我們就必須去滾動更新 Pod 了。相信你一定想到了,那我們寫一個 controller 去 watch ConfigMap 的變更,watch 到之後就去給 Deployment 或其它資源做一次滾動更新不就可以了嗎?沒錯,但就我個人而言,我更喜歡依賴簡單的東西,因此我們還是從簡單的方案講起。
Pod 滾動更新一:修改 CI 流程
這種辦法異常簡單,只需要我們寫一個簡單的 CI 腳本:給 ConfigMap 算一個 Hash 值,然後作為一個環境變數或 Annotation 加入到 Deployment 的 Pod 模板當中。
舉個例子,我們寫這樣的一個 Deployment yaml 然後在 CI 腳本中,計算 Hash 值替換進去:
  1. ...
  2. spec:
  3. template:
  4. metadata:
  5. annotations:
  6. com.aylei.configmap/hash: ${CONFIGMAP_HASH}
  7. ...
這時,假如 ConfigMap 變化了,那 Deployment 中的 Pod 模板自然也會發生變化,Kubernetes 自己就會幫助我們做滾動更新了。另外,如何 ConfigMap 不大,直接把 ConfigMap 轉化為 JSON 放到 Pod 模板中都可以,這樣做還有一個額外的好處,那就是在排查故障時,我們一眼就能看到這個 Pod 現在關聯的 ConfigMap 內容是什麼。
Pod 滾動更新二:Controller
還有一個辦法就是寫一個 Controller 來監聽 ConfigMap 變更並觸發滾動更新。在自己動手寫之前,推薦先看看一下社區的這些 Controller 能否能滿足需求:
  • Reloader:https://github.com/stakater/Reloader

  • ConfigmapController:https://github.com/fabric8io/configmapcontroller

  • k8s-trigger-controller:https://github.com/mfojtik/k8s-trigger-controller

結尾

 

上面就是我針對 ConfigMap 和 Secret 熱更新總結的一些方案。最後我們選擇的是使用 sidecar 進行熱更新,因為這種方式更新配置帶來的開銷最小,我們也為此主動避免掉了”熱更新環境變數這種場景”。
當然了,配置熱更新也完全可以不依賴 ConfigMap,etcd + Confd,阿裡的 Nacos,攜程的 Apollo 包括不那麼好用的 Spring-Cloud-Config 都是可選的辦法。但它們各自也都有需要考慮的東西,比如 etcd + Confd 就要考慮 etcd 里的配置項變更怎麼管理;Nacos,Apollo 這種則需要自己在 client 端進行代碼集成。相比之下,對於剛起步的架構,用 Kubernetes 本身的 ConfigMap 和 Secret 可以算是一種最快最通用的選擇了。
原文鏈接:https://aleiwu.com/post/configmap-hotreload/
赞(0)

分享創造快樂