파드의 타임존은 노드를 따라가지 않는다. 컨테이너 이미지가 들고 있는 /etc/localtime 을 쓰고 대부분의 기본 이미지는 UTC 다. 그래서 노드가 KST 여도 파드 안의 date 와 애플리케이션 로그는 UTC 로 찍힌다.
TZ 환경변수를 넣고 호스트의 zoneinfo 를 마운트한다.
spec:
containers:
- name: app
env:
- name: TZ
value: Asia/Seoul
volumeMounts:
- name: tz
mountPath: /etc/localtime
readOnly: true
volumes:
- name: tz
hostPath:
path: /usr/share/zoneinfo/Asia/Seoul
TZ 만으로 되는 런타임과 /etc/localtime 을 봐야 하는 런타임이 섞여 있으므로 둘 다 넣어 두는 편이 안전하다.
파드마다 YAML 을 고치기 어려우면 admission webhook 으로 주입하는 도구를 쓴다. k8tz 가 그 용도로 알려져 있다. 네임스페이스나 파드에 레이블을 붙이면 웹훅이 파드 생성 시점에 타임존 볼륨과 환경변수를 넣어 준다.
kubectl label namespace <namespace> k8tz.io/inject=true
주입은 파드가 새로 생성될 때만 일어난다. 이미 떠 있는 파드에는 적용되지 않으므로 롤아웃 재시작이 필요하다.
kubectl -n <namespace> rollout restart deploy
배포 방식과 지원 쿠버네티스 버전은 도구 저장소에서 확인한다 (확인 필요).
파드 타임존이 KST 인데 애플리케이션 로그만 UTC 로 찍힌다면 로깅 프레임워크가 포맷에 타임존을 박아 두고 있는 것이다. 이때는 컨테이너 타임존이 아니라 로그 포맷을 고쳐야 한다.
JVM 이면 -Duser.timezone=Asia/Seoul 을 함께 준다. 포맷 문자열에 타임존 이름을 직접 끼워 넣는 방식은 대개 통하지 않는다. 포맷 문자로 해석되어 오류가 난다.
ERROR: The %d precision yyyy-MM-dd HH:mm:ss, contains the pattern character A which is unknown.
타임존은 런타임 설정으로 정하고 포맷에는 오프셋 토큰을 붙여 어느 시간대인지 드러낸다.
%d{yyyy-MM-dd HH:mm:ss Z} -> 2025-03-28 17:40:34 +0900