6月19日 23:48

Docker 容器时区配置怎么做才稳妥?

Docker 容器里的时间不对,最先影响的一般不是页面展示,而是日志、定时任务和排查问题的效率。比如宿主机已经是北京时间,容器日志却显示 UTC,凌晨任务提前 8 小时执行,排查起来很容易绕晕。

Docker 容器时区配置常见有几种做法:设置 TZ 环境变量、安装 tzdata、挂载宿主机时区文件,或者在 Compose、Kubernetes 中统一声明。实际选哪一种,要看镜像基础系统、应用运行时和部署环境。

用 TZ 环境变量设置时区

最轻量的方式是在容器中设置 TZ 环境变量:

dockerfile
ENV TZ=Asia/Shanghai

运行容器时也可以临时传入:

bash
docker run -e TZ=Asia/Shanghai your-image

这种方式优点是简单、可移植,不依赖宿主机的 /etc/localtime。如果同一个镜像要部署到不同地区,只需要改环境变量,不用重新改镜像逻辑。

不过要注意,TZ 是否生效取决于镜像里是否有可用的时区数据。很多精简镜像没有完整的 timezone 数据库,只设置环境变量可能不够。

Debian 和 Alpine 镜像要安装 tzdata

如果基础镜像是 Debian 或 Ubuntu,可以在 Dockerfile 里安装 tzdata

dockerfile
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y 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

如果是 Alpine:

dockerfile
RUN apk add --no-cache tzdata ENV TZ=Asia/Shanghai

Alpine 镜像更精简,很多时候没有预装时区数据。只写 ENV TZ=Asia/Shanghai,但没有 tzdata,程序可能仍然按 UTC 或默认时区处理。

挂载宿主机时区文件可以用,但别过度依赖

另一种常见写法是挂载宿主机的时区配置:

bash
docker run -v /etc/localtime:/etc/localtime:ro -v /etc/timezone:/etc/timezone:ro your-image

这样容器会跟随宿主机时区。它适合单机部署、内部工具或对宿主机环境强绑定的服务。

但它也有几个坑:

  • 不同 Linux 发行版不一定都有 /etc/timezone
  • macOS、Windows Docker Desktop 的路径语义和 Linux 不完全一样;
  • 容器和宿主机绑定过紧,迁移到 Kubernetes 或其他环境时容易失效;
  • 如果宿主机时区配置错误,容器也会一起错。

所以生产环境更推荐把时区配置显式写进镜像或部署文件,而不是假设宿主机一定正确。

Docker Compose 里怎么写

Compose 中通常直接配置环境变量:

yaml
services: app: image: your-image environment: TZ: Asia/Shanghai

如果你的镜像需要系统级时区文件,也可以挂载:

yaml
services: app: image: your-image environment: TZ: Asia/Shanghai volumes: - /etc/localtime:/etc/localtime:ro

更稳妥的做法是:镜像里安装好 tzdata,部署层只负责传 TZ。这样 Compose、Kubernetes、普通 docker run 都能复用同一套镜像。

Kubernetes 中怎么配置

Kubernetes 里一般通过环境变量设置:

yaml
containers: - name: app image: your-image env: - name: TZ value: Asia/Shanghai

如果确实要挂载宿主机的 /etc/localtime,可以用 hostPath,但这会让 Pod 依赖节点环境,不利于迁移和调度。除非你很清楚集群节点的时区配置一致,否则不建议作为默认方案。

UTC 还是本地时区,怎么选

很多团队会纠结:容器到底该用 UTC,还是用 Asia/Shanghai

如果是国际化系统、跨地区服务、分布式链路追踪,UTC 更适合作为统一存储和计算时间。数据库、消息、审计日志使用 UTC,可以减少夏令时和跨时区换算问题。

如果是面向国内业务、内部管理后台、定时任务强依赖本地时间,用 Asia/Shanghai 会更直观。比如每天 9 点发报表、凌晨 2 点跑清算,本地时区能降低理解成本。

比较稳的原则是:系统内部时间尽量统一,展示层再转换成本地时区。如果日志、数据库、应用各用各的时区,问题会非常难查。

日志和 cron 最容易暴露时区问题

时区配置不一致,最常见的两个问题是日志和定时任务。

日志方面,容器内 date 显示北京时间,但应用日志仍然是 UTC,通常说明应用运行时没有读取系统时区,或者日志框架单独配置了时区。

cron 方面,容器里的 cron 会按容器系统时区执行。如果容器实际是 UTC,而你按北京时间写了 crontab,任务就会偏 8 小时。

可以先在容器里确认时间:

bash
docker exec -it container-name date

也可以看时区文件:

bash
docker exec -it container-name sh -c 'date && ls -l /etc/localtime'

如果安装了 tzdata,还可以检查:

bash
zdump Asia/Shanghai | head

应用运行时也可能有自己的时区规则

容器系统时区正确,不代表应用一定正确。Java、Node.js、Python 都可能有自己的处理方式。

Java

Java 常见做法是设置 JVM 参数:

bash
-Duser.timezone=Asia/Shanghai

Spring Boot 项目还可能涉及 Jackson 序列化时区、数据库连接时区等配置。如果接口返回时间偏移,不能只看容器的 date

Node.js

Node.js 会受 TZ 环境变量影响,但不同运行环境和镜像差异较大。建议在启动前设置:

bash
TZ=Asia/Shanghai node server.js

如果项目使用 dayjs、moment、luxon 这类库,还要确认是否启用了对应的 timezone 插件或配置。

Python

Python 的 datetime.now()time.localtime() 会受系统时区影响,但推荐业务代码使用明确的 timezone-aware datetime,避免混用 naive datetime。

例如在 Python 3.9+ 中可以使用:

python
from zoneinfo import ZoneInfo from datetime import datetime now = datetime.now(ZoneInfo("Asia/Shanghai"))

这样代码表达更清楚,也不完全依赖容器系统配置。

NTP 负责校准时间,不负责选择时区

NTP 解决的是“时间准不准”,不是“显示哪个时区”。容器通常不需要单独跑 NTP 客户端,因为容器共享宿主机内核时间,宿主机时间同步正常,容器拿到的时间基准也正常。

如果容器时间整体漂移,应该优先检查宿主机或节点的 NTP、chrony、systemd-timesyncd 配置。容器里单独跑 NTP 反而会增加权限和运维复杂度。

推荐做法

如果只是普通业务容器,推荐这样处理:

  1. 镜像中安装 tzdata
  2. 使用 ENV TZ=Asia/Shanghai 或部署文件传入 TZ
  3. 不默认依赖宿主机 /etc/localtime
  4. Java、Node.js、Python 等运行时单独确认时区行为;
  5. docker exec date、应用日志、cron 执行时间一起验证。

容器时区配置看起来只是一个小参数,但它会影响日志排查、定时任务、数据审计和用户看到的时间。最怕的不是用 UTC 或北京时间,而是系统里同时混着几套时间规则。

标签:Docker