Docker 容器里的时间总比宿主机慢 8 小时,折腾一圈的解决记录
上周看一个服务日志的时候,我发现一件很别扭的事:宿主机上 date 显示下午 3 点,容器里同一时刻打出来的日志却是早上 7 点。整整差了 8 小时。
第一反应是怀疑自己的眼神,第二反应是怀疑代码里是不是哪里把时间格式化错了。后来才反应过来——这根本不是代码的问题,是容器时区的问题,而且是个特别经典的坑。
为什么容器里的时间会不对
先说结论:容器里的时间(时钟本身)和宿主机是同一个,走的是同一个内核时钟,不存在"容器时间走得慢"这回事。差的是时区解释。
宿主机一般装系统的时候就设好了时区,比如 Asia/Shanghai,所以 date 显示北京时间。而 Docker 镜像——尤其是那些官方基础镜像——默认时区是 UTC。你在东八区,UTC+8 换算过来正好差 8 小时,严丝合缝。
验证方法很简单,宿主机和容器里各跑一次:
# 宿主机
date
# 容器里
docker exec -it 容器名 date
两个结果一对比,是不是整整差 8 小时、分钟和秒对得上,对得上就是时区问题,跑不了。
三种改法,我都试过
方法一:加环境变量,最省事
docker run -e TZ=Asia/Shanghai ...
compose 里则是:
services:
app:
image: your-image
environment:
- TZ=Asia/Shanghai
这个方法对大多数应用有效,因为程序读时间的时候会认 TZ 这个环境变量。但有个例外:如果你的镜像是 Alpine 这种极简系统,里面可能没有装时区数据库,设了 TZ 也不认识,得先装 tzdata 包。Java 应用还有自己的时区逻辑,有时还要额外加 -Duser.timezone=Asia/Shanghai,这个要单独注意。
方法二:挂载宿主机的时区文件
docker run -v /etc/localtime:/etc/localtime:ro ...
把宿主机的 /etc/localtime 只读挂进去,容器直接"借用"宿主机的时区。好处是宿主机改时区容器跟着变,坏处是有些镜像的程序会尝试写这个文件(只读挂载会报错),另外在个别发行版上挂载单个文件而不是软链接时,glibc 的行为略有差异。小规模自用没什么问题,我自己的几个容器用的就是这个办法,图省事。
方法三:构建镜像时写死
FROM python:3.12-slim
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
如果是自己维护的镜像、要分发给别人用,选这个。时区跟着镜像走,不依赖部署环境,最干净。
我的选择建议
- 临时跑一个容器验证东西:方法一,一行环境变量解决;
- 自己的机器、自己的服务:方法二或者方法一,怎么方便怎么来;
- 镜像要给别人用、或者要上生产:老老实实方法三,别把时区问题留给部署的人。
最后提一句为什么这事值得较真
时间戳差 8 小时,平时看着只是别扭,真出故障的时候是会要命的:你按北京时间去对日志、找故障时间线,整个顺序全是错的,很容易把因果关系搞反。还有定时相关的逻辑,比如容器里跑 cron、做按天切分的统计,时区不对,触发时间就全错位。
所以现在我起新容器的习惯是:服务跑起来先 docker exec 进去看一眼 date,十秒钟的事,省得后面排查问题的时候被时间线带沟里去。
- 点赞
- 收藏
- 关注作者
评论(0)