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中修改時間

時間依賴時間標准,時間的表示有兩個標准:localtimeUTC(Coordinated Universal Time)

  • UTC 是與時區無關的全球時間標准。盡管概念上有差別,UTC 和 GMT (格林威治時間) 是一樣的
  • localtime 標准則依賴於當前時區

時間標准由操作系統設定,Windows默認使用localtimeMac OS默認使用UTCUNIX系列的操作系統兩者都有。使用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校准硬件時鍾?

事實上是不可以的,在容器內部通過默認權限修改時間會報錯

這是因為容器的隔離是基於LinuxCapability機制實現的,可以通過給容器添加--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可以在容器啟動的時候注入一些信息

PodPreset1.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這個環境變量

需要注意的是,PodPresetnamespace級別的對象,其作用范圍只能是同一個命名空間下的容器

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM