k8s環境下處理容器時間問題的多種姿勢

1、背景概述
在 Linux
環境下,預設安裝作業系統時都需要正確設定系統的時區為當前所在的時區
在容器環境下,除了業務映象外,我們有很多情況都是使用的官方映象或第三方映象,而這些映象一般都不是國人制作。因此使用這些映象的時候,自然會有一個問題,即容器映象的預設時區不正確
簡而言之,在容器環境中需要處理時間(時區)問題的原因一般有
- 時間不對,和正確的(例如北京時間)有偏差
- 時區不對,映象預設時區和當前時區不符合
- 某些特殊業務需要臨時修改時間。例如電商秒殺業務,將時間設定超前或滯後,在內部測試業務的時間控制功能
2、硬體時鐘和系統時間
先來看看作業系統以及容器是如何獲取時間的
時鐘一般分為硬體時鐘(RTC,Real Time Clock)和作業系統時鐘(OS,System Clock)
硬體時鐘跟執行在 cpu
上的程式是獨立不相關的,甚至在伺服器關機之後仍然可以正常執行,這就保證了伺服器時間的正常執行,硬體時間也有著各種各樣的稱呼,例如: hardware clock
, real time clock
, RTC
, BIOS clock
以及 CMOS clock
等,在目前主流的伺服器都採用 RTC
晶片實現
作業系統時間稱為系統時鐘或者系統時間,這就是平時在系統中經常接觸到的時間,也是應用程式在執行與時間相關的操作會用到的時間,它只是在系統執行時存在,其記錄形式為 UTC
時間(the number of seconds since 00:00:00 January 1, 1970 UTC)
硬體時鐘和系統時間的關係
硬體時鐘是用來保證在作業系統關機之後仍然可以正常計時的必要硬體,而系統時間是我們在日常操作中才會經常使用到的時間,僅僅在作業系統初始化時,作業系統才會去 RTC
晶片中拿到硬體時鐘的值,之後便是獨立執行和獨立計時
時鐘的運作機制如下

3、Linux中修改時間
時間依賴時間標準,時間的表示有兩個標準: localtime
和 UTC
(Coordinated Universal Time)
- UTC 是與時區無關的全球時間標準。儘管概念上有差別,UTC 和 GMT (格林威治時間) 是一樣的
- localtime 標準則依賴於當前時區
時間標準由作業系統設定, Windows
預設使用 localtime
, Mac OS
預設使用 UTC
而 UNIX
系列的作業系統兩者都有。使用 Linux
時,最好將硬體時鐘設定為 UTC
標準,並在所有作業系統中使用。這樣 Linux
系統就可以自動調整夏令時設定,而如果使用 localtime
標準那麼系統時間不會根據夏令時自動調整
通過如下命令可以檢查當前設定,終端執行
timedatectl status | grep local
硬體時間可以用 hwclock 命令設定,將硬體時間設定為 localtime
timedatectl set-local-rtc 1
硬體時間設定成 UTC
,終端執行
timedatectl set-local-rtc 0
上述命令會自動生成 /etc/adjtime
,無需單獨設定
在日常使用中,修改時間一般通過 date
修改日期時間,通過 hwclock
校準硬體時鐘
這裡提到了 夏令時
,再分享一個有意思的事情,可能大多數人還不知道,我國在解放後是實行過夏令時的

4、嘗試在容器中修改時間
在容器中能否通過 date
修改日期時間,通過 hwclock
校準硬體時鐘?
事實上是不可以的,在容器內部通過預設許可權修改時間會報錯

這是因為容器的隔離是基於 Linux
的 Capability
機制實現的,可以通過給容器新增 --privileged
或 --cap-add SYS_TIME
來實現目的,但並不推薦,因為這樣會直接影響到容器所在主機的時間
Linux
核心中將 timekeeper
設定為全域性變數,所以只要去修改系統時間,這個影響就是核心層面的,所以在 docker
的實現中預設是禁止在容器內修改時間的,因為容器與虛擬化的區別就在於是否共享核心,這就意味著一旦在容器中修改了時間,這個影響就是全域性性的
5、處理時間問題的多種姿勢
前面聊得有點多,該到重點了
在 k8s
環境下如何處理容器的時間,也就是 pod
的時間
在處理之前,先保證 pod
宿主機 node
的時間同步及時區設定正常,和當前時間一樣
# timedatectl Local time: Thu 2021-08-26 00:16:28 CST Universal time: Thu 2021-08-26 16:16:28 UTC RTC time: Thu 2021-08-26 16:16:28 Time zone: Asia/Shanghai (CST, +0800) NTP enabled: yes NTP synchronized: yes RTC in local TZ: no DST active: n/a
下面分享處理容器時間的多種方法,主要分為兩個方向,校準時間和調整時間
5.1 在Dockerfile中新增時區
為了便於操作,一勞永逸,可以通過在 Dockerfile
中新增時區
# Set timezone RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && echo "Asia/Shanghai" > /etc/timezone
這種做法對於自制的業務映象來說很方便,也很容易操作,畢竟只需要在通過 Dockerfile
製作業務映象新增此內容即可
5.2 將時區檔案掛載到Pod中
在定義 pod
上層控制器的時候,新增一個用於掛載時區的卷,掛載宿主機的時區檔案
... containers: - name: xxx ... volumeMounts: - name: timezone mountPath: /etc/localtime volumes: - name: timezone hostPath: path: /usr/share/zoneinfo/Asia/Shanghai
5.3 通過環境變數定義時區
同樣的,在定義 pod
上層控制器的時候,新增一個用於指定時區的環境變數
TZ
環境變數用於設定時區。它由各種時間函式用於計算相對於全球標準時間 UTC
(以前稱為格林威治標準時間 GMT
)的時間。格式由作業系統指定
... containers: - name: xxx ... env: - name: TZ value: Asia/Shanghai
5.4 通過PodPreset全域性修改時間
往往遇到修改 Pod
時區的需求,都是要求所有的 Pod
都在同一個時區,按照前面的方式需要我們對每一個 Pod
手動做這樣的操作,在 k8s
環境下更好的方式就是利用 PodPreset
來預設時間, PodPreset
可以在容器啟動的時候注入一些資訊
PodPreset
在 1.20
版本後被移除了,我也沒找到什麼原因
如果是 1.20
以前的版本,具體配置方法如下
首先啟用 PodPreset
# 在 kube-apiserver 啟動引數 -runtime-config 增加 settings.k8s.io/v1alpha1=true; —runtime-config=rbac.authorization.k8s.io/v1alpha1=true,settings.k8s.io/v1alpha1=true # 然後在 --admission-control 增加 PodPreset 啟用 —admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,ResourceQuota,PodPreset
修改好後重啟服務,檢視是否有 podpresets api
型別
kubectl api-resources |grep podpresets
建立 PodPresents
資源物件
apiVersion: settings.k8s.io/v1alpha1 kind: PodPreset metadata: name: tz-env spec: selector: matchLabels: env: - name: TZ values: Asia/Shanghai
這裡需要注意的地方是,一定需要寫 selector...matchLabels
,儘管 matchLabels
為空,表示應用於所有容器,建立上面這個資源物件,然後再去建立一個普通的 Pod
可以檢視下是否注入了上面的 TZ
這個環境變數
需要注意的是, PodPreset
是 namespace
級別的物件,其作用範圍只能是同一個名稱空間下的容器
5.5 調整時間到預設值
以上方法都是用於校準時間,如果需要在 pod
容器中調整時間,也是有解決辦法的,目的是將時間調整到一個預設的時間
這裡的方法實現主要原理是在 OS
層面攔截系統時間欺騙應用,實現返回任意的時間給應用層使用
攔截的主要思路是以動態庫的載入為基礎的,採用 LD_PRELOAD
機制,自行實現這個方法並編譯成動態庫依靠動態庫載入的先後順序來覆蓋原始的方法
已經有 libfaketime專案 實現,按照其文件,主要步驟為
- 克隆程式碼進行編譯
git clone https://github.com/wolfcw/libfaketime.git cd libfaketime && make install
- 編譯完成後,把庫檔案拷貝到容器中
docker cp /usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1 e6e239e5fba7:/usr/local/lib/
- 再進入容器中執行命令改變環境變數
export LD_PRELOAD=/usr/local/lib/libfaketime.so.1 FAKETIME="-5d"
容器環境下,手動按照上面的步驟操作是可以生效的,唯一不足的就是一旦容器重啟就會失效
在容器( k8s
環境)中如何解決?
前面的步驟可以將編譯完的庫檔案通過 dockerfile
打包到映象中,如果需要修改時間,只需要在 Pod
控制器定義時新增環境變數即可
... containers: - name: xxx ... env: - name: LD_PRELOAD value: "/usr/local/lib/libfaketime.so.1" - name: FAKETIME value: "-5d"
另外一種思路是,時間調整一般是暫時的,以及多 pod
時間同步的需求,將 LD_PRELOAD
的開啟與否放到應用的執行環境中,採用 configmap
作為應用時間的標準,將時間變更值 faketime
作為 configmap
apiVersion: v1 kind: ConfigMap metadata: name: faketimerc namespace: default data: faketimerc: | +10d
最後所有的 pod
都以 volume
的形式掛載該 configmap
... containers: - name: xxx ... volumeMounts: - name: faketimerc mountPath: /etc/faketimerc volumes: - name: faketimerc configMap: name: faketimerc items: - key: faketimerc path: faketimerc
See you ~
參考:
https://developer.toradex.com/knowledge-base/how-to-use-the-real-time-clock-in-linux
https://wiki.deepin.org/wiki/%E6%97%B6%E9%97%B4%E5%92%8C%E6%97%B6%E5%8C%BA
https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.20.md#deprecation
- Gradle打包工具入門
- 服務網格和Istio初識-續
- 服務網格和Istio初識
- Golang與非對稱加密
- ack叢集Terway網路場景下的vSwitch擴容
- Golang與對稱加密
- 基於ack k8s叢集排程的方案設計
- 基於Dockerfile構建容器映象的最佳實踐
- Golang反射-下篇
- Golang反射-上篇
- Azure DevOps的使用入門
- Golang介面型別-下篇
- Golang介面型別-上篇
- 基於Python實現原生的登入驗證碼
- Golang開發命令列工具之flag包的使用
- Golang檔案操作-下篇
- k8s環境下處理容器時間問題的多種姿勢
- Golang基準測試
- 淺談Prometheus的資料儲存
- Golang單元測試