发布时间:2026-09-17 12:31 更新时间:2026-09-17 12:31 阅读量:0
不少站长第一次看容器日志时会愣一下:宿主机上明明是下午三点,容器里打出来的时间却是早上七点,差了整整八小时。更麻烦的是跑在容器里的定时任务、证书校验、数据库写入时间戳,全都跟着偏。这个问题不复杂,但网上流传的做法有好几种,有的只改一半,重启容器又失效。本文把容器时区的来龙去脉讲清楚,给出可以直接抄的配置,也说明哪些做法只适合临时用。
容器不是虚拟机,它和宿主机共用同一个内核,所以内核维护的时间(也就是 UTC 时钟)是一致的。真正会不一致的是「本地时间」这个概念:Linux 靠 /etc/localtime 这个文件(通常是指向 /usr/share/zoneinfo/ 下某个时区文件的软链接)和 /etc/timezone 来决定把 UTC 换算成哪个时区的本地时间。
基础镜像为了通用和体积考虑,默认往往就是 UTC,没有安装 tzdata,也没有配好 localtime。于是容器内 date 输出 UTC,Java、Python、PHP 这类运行时会读取系统时区,结果日志时间戳也跟着变成 UTC。注意区分两件事:时钟走得准不准由宿主机 NTP/chrony 负责,容器不单独校时;显示成哪个时区才是本文要解决的问题。如果你的问题是时间「不准」而不是「时区不对」,那要先去看宿主机的校时配置。
第一种是设置 TZ 环境变量。它的优点是轻量、不改镜像、随时可调,缺点是只对「会读取 TZ 的程序」生效。绝大多数语言运行时(glibc 的 localtime 系列函数、Java、Python、Node)都会优先读 TZ,但一些静态编译的二进制、部分基础镜像里缺少 tzdata 时,TZ 设了也可能无效。
# 临时验证:进入容器看看 TZ 是否生效
docker exec -it myapp sh -c 'echo $TZ; date'
启动时指定时区(Asia/Shanghai)
docker run -d --name myapp \
-e TZ=Asia/Shanghai \
myimage:latest
第二种是挂载宿主机的 /etc/localtime。它让容器直接复用宿主机的时区文件,对所有读系统时区的程序都生效,兼容性更好。代价是容器与宿主机时区绑定,换宿主机就可能变。只读挂载是推荐做法,避免容器内进程误改宿主机文件。
# 只读挂载宿主机时区文件与 timezone 描述
docker run -d --name myapp \
-v /etc/localtime:/etc/localtime:ro \
-v /etc/timezone:/etc/timezone:ro \
myimage:latest
如果镜像里压根没有 tzdata,只挂 localtime 有时仍不够,因为程序可能还需要 zoneinfo 目录下的数据文件。稳妥做法是两者一起上:既挂 localtime,又设 TZ,再确认镜像里装了 tzdata(Debian/Ubuntu 系是 apt-get install -y tzdata,Alpine 是 apk add --no-cache tzdata,具体包名以官方文档为准)。
用 Docker Compose 的项目,把时区写进 compose 文件最省事,团队成员拉起环境就一致。下面这段同时用了环境变量和只读挂载,属于比较稳的组合。
services:
web:
image: myimage:latest
environment:
- TZ=Asia/Shanghai
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
restart: unless-stopped
如果你希望镜像本身就带对时区、不依赖宿主机,可以在 Dockerfile 里装 tzdata 并设好默认值。这样即使别人在 UTC 的机器上跑你的镜像,行为也一致。注意构建阶段设置 ENV 只是默认值,运行时仍可被 -e 覆盖。
# Dockerfile 片段(Debian/Ubuntu 基础镜像)
RUN apt-get update \
&& apt-get install -y --no-install-recommends tzdata \
&& ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone \
&& rm -rf /var/lib/apt/lists/*
ENV TZ=Asia/Shanghai
第一,只改宿主机的 localtime 不会自动传导到容器,容器有自己的文件系统命名空间,必须显式挂载或设变量。第二,数据库容器要格外注意:MySQL、PostgreSQL 的时区既受系统时区影响,也有自己的参数(如 MySQL 的 default-time-zone、PG 的 timezone),建议在配置文件里显式指定,别只靠 TZ。第三,日志时间对不上时先分清是应用打的还是容器运行时打的:docker logs 的时间戳来自宿主机,一般不受容器时区影响,别拿它当判断依据。第四,Kubernetes 环境用 env 和 volumeMounts 实现同样效果,思路一致。
排查时可以按这个顺序走:先进容器 date 看输出、cat /etc/timezone 看配置、ls -l /etc/localtime 看软链接指向,再确认应用自己有没有额外读时区参数。改完后重启容器再验证一次,避免只在 exec 会话里临时生效。
小结一下:临时调试用 TZ 变量最快;长期稳定运行建议「装 tzdata + 设 TZ + 只读挂载 localtime」三件套一起上,Compose 或 Dockerfile 里固化下来。这样日志、定时任务和数据库时间戳才能对得上,排查问题时也不会再被八小时时差带偏。下一步可以顺手检查一下你现有 compose 文件里有没有漏掉时区配置,补上后重启验证即可。
| 📑 | 📅 |
|---|---|
| 服务器 swap 使用率飙升:正常换页还是内存真不够用 | 2026-09-17 |
| 系统盘满了却找不到大文件:被删除但仍被进程占用的句柄排查与恢复空间 | 2026-09-17 |
| MySQL表空间与ibdata1膨胀处理:独立表空间、碎片整理与磁盘回收 | 2026-09-17 |
| SSH 连接频繁断开与卡顿排查:心跳、MTU 与 DNS 反解 | 2026-09-17 |
| Linux内存被缓存吃满:buff/cache该不该手动释放 | 2026-09-17 |
| journald 日志占满 /var/log:持久化与容量限制配置 | 2026-09-18 |
| Linux网卡丢包与TCP重传排查:ip -s link、ss -ti与ethtool实战 | 2026-09-18 |
| Nginx 静态资源 404 与权限被拒排查:root、alias、try_files 的坑 | 2026-09-18 |
| systemd-journald 日志转发远程 syslog:rsyslog 对接与丢日志排查 | 2026-09-19 |
| MySQL连接被拒绝Connection refused逐层排查思路 | 2026-09-19 |