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/時間和時區
https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.20.md#deprecation