1. κ°œμš”

μ»¨ν…Œμ΄λ„ˆ ν™˜κ²½μ—μ„œ νƒ€μž„μ‘΄(Timezone)이 λ§žμ§€ μ•ŠμœΌλ©΄ 둜그 μ‹œκ°„μ΄ μ–΄κΈ‹λ‚˜κ³  μŠ€μΌ€μ€„λŸ¬κ°€ 잘λͺ»λœ μ‹œκ°„μ— λ™μž‘ν•œλ‹€. λŒ€μˆ˜λ‘­μ§€ μ•Šκ²Œ λ„˜κ²Όλ‹€κ°€ λ‚˜μ€‘μ— 디버깅이 λ³΅μž‘ν•΄μ§€λŠ” κ²½μš°κ°€ λ§ŽμœΌλ―€λ‘œ, μ»¨ν…Œμ΄λ„ˆ νƒ€μž„μ‘΄ μ„€μ • 방법을 정리해 λ‘μ—ˆλ‹€.

2. 이슈

μΏ λ²„λ„€ν‹°μŠ€(Kubernetes) 상에 λ™μž‘ 쀑인 νŒŒλ“œ(Pod)λ§ˆλ‹€ μ„œλ‘œ λ‹€λ₯Έ timezone을 κ°€μ§€κ³  μžˆμ—ˆκ³ , 이λ₯Ό GMT+9 (Asia/Seoul)둜 일괄 λ³€ν™˜ν•˜λŠ” 방법이 ν•„μš”ν–ˆλ‹€.

3. ν•΄κ²°

μ»¨ν…Œμ΄λ„ˆμ˜ νƒ€μž„μ‘΄μ„ μ„€μ •ν•˜λŠ” μ£Όμš” 방법은 λ‹€μŒκ³Ό κ°™λ‹€.

1. Dockerfile을 ν†΅ν•œ μ„€μ • (Build-time)

μ»¨ν…Œμ΄λ„ˆ 이미지λ₯Ό λΉŒλ“œν•˜λŠ” μ‹œμ μ— Dockerfile λ‚΄μ—μ„œ νƒ€μž„μ‘΄μ„ μ„€μ •ν•˜λŠ” 방법이닀. 이 방법은 μ»¨ν…Œμ΄λ„ˆκ°€ μ‹œμž‘λ  λ•Œλ§ˆλ‹€ λ³„λ„μ˜ μ„€μ • 없이 μΌκ΄€λœ νƒ€μž„μ‘΄μ„ 보μž₯ν•˜μ—¬ κ°€μž₯ ꢌμž₯λ˜λŠ” 방식 쀑 ν•˜λ‚˜μ΄λ‹€.

  • 원리: λŒ€λΆ€λΆ„μ˜ Linux μ‹œμŠ€ν…œμ€ /etc/timezone νŒŒμΌμ— νƒ€μž„μ‘΄ 정보λ₯Ό μ €μž₯ν•˜κ±°λ‚˜, /etc/localtime 파일이 μ‹€μ œ νƒ€μž„μ‘΄ 데이터 파일(예: /usr/share/zoneinfo/Asia/Seoul)을 κ°€λ¦¬ν‚€λŠ” 심볼릭 링크둜 κ΅¬μ„±λœλ‹€. tzdata νŒ¨ν‚€μ§€λŠ” μ΄λŸ¬ν•œ νƒ€μž„μ‘΄ 데이터 νŒŒμΌμ„ μ œκ³΅ν•œλ‹€. ENV TZ λͺ…λ Ήμ–΄λŠ” μ»¨ν…Œμ΄λ„ˆ λ‚΄λΆ€μ˜ ν™˜κ²½ λ³€μˆ˜λ₯Ό μ„€μ •ν•˜μ—¬ μ‹œμŠ€ν…œμ΄ ν•΄λ‹Ή νƒ€μž„μ‘΄μ„ μ‚¬μš©ν•˜λ„λ‘ μ§€μ‹œν•œλ‹€.
# μ˜ˆμ‹œ: Ubuntu/Debian 기반 이미지
FROM ubuntu:latest
 
# tzdata νŒ¨ν‚€μ§€ μ„€μΉ˜ (νƒ€μž„μ‘΄ 데이터 제곡)
# noninteractive λͺ¨λ“œλ‘œ μ„€μΉ˜ν•˜μ—¬ μ‚¬μš©μž μž…λ ₯ 없이 μ§„ν–‰
RUN apt-get update && apt-get install -y tzdata && rm -rf /var/lib/apt/lists/*
 
# ν™˜κ²½ λ³€μˆ˜ TZλ₯Ό Asia/Seoul둜 μ„€μ •
ENV TZ=Asia/Seoul
 
# μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ‹€ν–‰ λͺ…λ Ήμ–΄ (μ˜ˆμ‹œ)
CMD ["/bin/bash"]

이식성은 κ°€μž₯ μ’‹μ§€λ§Œ, νƒ€μž„μ‘΄μ„ λ°”κΏ€ λ•Œλ§ˆλ‹€ 이미지λ₯Ό λ‹€μ‹œ λΉŒλ“œν•΄μ•Ό ν•œλ‹€. λ‚©ν’ˆ ν™˜κ²½λ§ˆλ‹€ νƒ€μž„μ‘΄μ΄ λ‹€λ₯Ό 수 μžˆμœΌλ‹ˆ ν•„μžλŠ” 잘 μ“°μ§€ μ•ŠλŠ”λ‹€.

2. Volume Mountλ₯Ό ν†΅ν•œ μ„€μ • (Run-time)

호슀트 μ‹œμŠ€ν…œμ˜ νƒ€μž„μ‘΄ 정보λ₯Ό μ»¨ν…Œμ΄λ„ˆ λ‚΄λΆ€λ‘œ λ§ˆμš΄νŠΈν•˜μ—¬ μ‚¬μš©ν•˜λŠ” 방법이닀. 이 방법은 μ»¨ν…Œμ΄λ„ˆ 이미지λ₯Ό μˆ˜μ •ν•˜μ§€ μ•Šκ³  λŸ°νƒ€μž„μ— νƒ€μž„μ‘΄μ„ μœ μ—°ν•˜κ²Œ μ„€μ •ν•  수 μžˆλ‹€.

  • 원리: 호슀트 μ‹œμŠ€ν…œμ˜ /etc/localtime 파일 λ˜λŠ” /usr/share/zoneinfo 디렉토리λ₯Ό μ»¨ν…Œμ΄λ„ˆ λ‚΄λΆ€μ˜ /etc/localtime 경둜둜 λ§ˆμš΄νŠΈν•œλ‹€. μ»¨ν…Œμ΄λ„ˆλŠ” 마운트된 호슀트의 νƒ€μž„μ‘΄ 정보λ₯Ό μ‚¬μš©ν•˜κ²Œ λœλ‹€.

Docker CLI

$ docker run -d \
  -v /etc/localtime:/etc/localtime:ro \
  -v /usr/share/zoneinfo/Asia/Seoul:/etc/localtime:ro # νŠΉμ • νƒ€μž„μ‘΄ 파일 마운트 (더 λͺ…μ‹œμ )
  your-image-name
  • /etc/localtime:/etc/localtime:ro: 호슀트의 ν˜„μž¬ νƒ€μž„μ‘΄ 섀정을 μ»¨ν…Œμ΄λ„ˆλ‘œ 읽기 μ „μš©(read-only)으둜 λ§ˆμš΄νŠΈν•œλ‹€.
  • /usr/share/zoneinfo/Asia/Seoul:/etc/localtime:ro: 호슀트의 νŠΉμ • νƒ€μž„μ‘΄ 데이터 파일(Asia/Seoul)을 μ»¨ν…Œμ΄λ„ˆμ˜ /etc/localtime둜 λ§ˆμš΄νŠΈν•œλ‹€. 이 방법은 호슀트의 ν˜„μž¬ νƒ€μž„μ‘΄ μ„€μ •κ³Ό λ¬΄κ΄€ν•˜κ²Œ μ»¨ν…Œμ΄λ„ˆμ— νŠΉμ • νƒ€μž„μ‘΄μ„ κ°•μ œν•  λ•Œ μœ μš©ν•˜λ‹€.

Kubernetes ν™˜κ²½

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  template:
    spec:
      containers:
      - name: my-container
        image: your-image-name
        volumeMounts:
        - name: tz-config
          mountPath: /etc/localtime # /etc/localtime νŒŒμΌμ„ 마운트
          readOnly: true
      volumes:
      - name: tz-config
        hostPath:
          path: /usr/share/zoneinfo/Asia/Seoul # 호슀트의 Asia/Seoul νƒ€μž„μ‘΄ νŒŒμΌμ„ μ‚¬μš©
          type: File # νŒŒμΌμž„μ„ λͺ…μ‹œ
  • μž₯점:
    • 이미지 μž¬λΉŒλ“œ 없이 λŸ°νƒ€μž„μ— νƒ€μž„μ‘΄μ„ λ³€κ²½ν•  수 μžˆλ‹€.
    • 호슀트의 νƒ€μž„μ‘΄κ³Ό λ™κΈ°ν™”ν•˜κ±°λ‚˜, ν˜ΈμŠ€νŠΈμ— μ‘΄μž¬ν•˜λŠ” νŠΉμ • νƒ€μž„μ‘΄ νŒŒμΌμ„ μ„ νƒν•˜μ—¬ μ‚¬μš©ν•  수 μžˆλ‹€.
  • 단점:
    • μ»¨ν…Œμ΄λ„ˆκ°€ 호슀트 μ‹œμŠ€ν…œμ— μ˜μ‘΄ν•˜κ²Œ λœλ‹€. 호슀트의 νƒ€μž„μ‘΄ 섀정이 λ³€κ²½λ˜λ©΄ μ»¨ν…Œμ΄λ„ˆμ—λ„ 영ν–₯을 λ―ΈμΉœλ‹€.
    • hostPath μ‚¬μš© μ‹œ λ…Έλ“œ κ°„ 이식성이 μ €ν•˜λ  수 μžˆλ‹€ (λͺ¨λ“  λ…Έλ“œμ— λ™μΌν•œ νƒ€μž„μ‘΄ 파일이 μ‘΄μž¬ν•΄μ•Ό ν•œλ‹€).
    • λ³΄μ•ˆμƒμ˜ 이유둜 hostPath μ‚¬μš©μ„ μ œν•œν•˜λŠ” ν™˜κ²½λ„ μžˆλ‹€.

3. ν™˜κ²½ λ³€μˆ˜ TZ만 μ„€μ • (Run-time)

μ»¨ν…Œμ΄λ„ˆ 이미지에 이미 tzdata νŒ¨ν‚€μ§€κ°€ μ„€μΉ˜λ˜μ–΄ μžˆλŠ” 경우, TZ ν™˜κ²½ λ³€μˆ˜λ§Œ μ„€μ •ν•˜μ—¬ νƒ€μž„μ‘΄μ„ λ³€κ²½ν•  수 μžˆλ‹€.

  • 원리: TZ ν™˜κ²½ λ³€μˆ˜λŠ” C 라이브러리(glibc)μ—μ„œ μ‹œκ°„μ„ μ²˜λ¦¬ν•  λ•Œ μ°Έμ‘°ν•˜λŠ” νƒ€μž„μ‘΄μ„ μ§€μ •ν•œλ‹€. tzdata νŒ¨ν‚€μ§€κ°€ μ„€μΉ˜λ˜μ–΄ μžˆλ‹€λ©΄, /usr/share/zoneinfo κ²½λ‘œμ— λ‹€μ–‘ν•œ νƒ€μž„μ‘΄ 데이터 파일이 μ‘΄μž¬ν•˜λ©°, TZ λ³€μˆ˜λŠ” 이 쀑 μ–΄λ–€ νŒŒμΌμ„ μ‚¬μš©ν• μ§€ μ•Œλ €μ£ΌλŠ” 역할을 ν•œλ‹€.

Docker CLI

$ docker run -d \
  -e TZ=Asia/Seoul \
  your-image-name

Kubernetes ν™˜κ²½

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  template:
    spec:
      containers:
      - name: my-container
        image: your-image-name
        env:
        - name: TZ
          value: Asia/Seoul

κ°€μž₯ κ°„λ‹¨ν•˜κ³  μœ μ—°ν•΄μ„œ ν•„μžκ°€ 주둜 μ“°λŠ” 방법이닀. tzdata만 이미지에 λ“€μ–΄μžˆμœΌλ©΄ ν™˜κ²½λ³€μˆ˜ ν•˜λ‚˜λ‘œ λλ‚œλ‹€. λ‹€λ§Œ tzdataκ°€ μ—†μœΌλ©΄ TZκ°€ λ¬΄μ‹œλ˜κ³  UTC둜 λ–¨μ–΄μ§€λ‹ˆ, 베이슀 이미지에 ν¬ν•¨λ˜μ–΄ μžˆλŠ”μ§€ 확인이 ν•„μš”ν•˜λ‹€.

4. timedatectl λͺ…λ Ήμ–΄ μ‚¬μš© (μ»¨ν…Œμ΄λ„ˆ λ‚΄λΆ€)

일뢀 μ»¨ν…Œμ΄λ„ˆ μ΄λ―Έμ§€μ—λŠ” systemdκ°€ ν¬ν•¨λ˜μ–΄ μžˆμ§€ μ•Šμ•„ timedatectl λͺ…λ Ήμ–΄λ₯Ό 직접 μ‚¬μš©ν•  수 μ—†λŠ” κ²½μš°κ°€ λ§Žλ‹€. ν•˜μ§€λ§Œ 디버깅 λͺ©μ μ΄λ‚˜ νŠΉμ • 베이슀 μ΄λ―Έμ§€μ—μ„œλŠ” μœ μš©ν•  수 μžˆλ‹€.

# μ»¨ν…Œμ΄λ„ˆ λ‚΄λΆ€μ—μ„œ μ‹€ν–‰ (μ˜ˆμ‹œ)
$ docker exec -it <container_id> /bin/bash
# μ»¨ν…Œμ΄λ„ˆ μ‰˜ λ‚΄λΆ€μ—μ„œ
$ timedatectl set-timezone Asia/Seoul

λŒ€λΆ€λΆ„μ˜ κ²½λŸ‰ μ»¨ν…Œμ΄λ„ˆμ—λŠ” systemdκ°€ μ—†μ–΄μ„œ 이 λͺ…λ Ήμ–΄λ₯Ό μ‚¬μš©ν•  수 μ—†λ‹€. μž¬μ‹œμž‘ν•˜λ©΄ 섀정도 μœ μ§€λ˜μ§€ μ•ŠμœΌλ―€λ‘œ 디버깅 μš©λ„ μ™Έμ—λŠ” μ˜λ―Έκ°€ μ—†λ‹€.

4. 확인

μ»¨ν…Œμ΄λ„ˆ λ‚΄λΆ€μ—μ„œ νƒ€μž„μ‘΄μ΄ μ˜¬λ°”λ₯΄κ²Œ μ„€μ •λ˜μ—ˆλŠ”μ§€ ν™•μΈν•˜λŠ” 방법이닀.

# date λͺ…λ Ήμ–΄ μ‚¬μš©
$ date
Tue Nov 22 07:44:20 KST 2022 # μ˜ˆμ‹œ 좜λ ₯: KST (ν•œκ΅­ ν‘œμ€€μ‹œ)둜 ν‘œμ‹œλ˜μ—ˆλ‹€.
 
# timedatectl λͺ…λ Ήμ–΄ μ‚¬μš© (timedatectl이 μ„€μΉ˜λœ 경우)
$ timedatectl
               Local time: ν™” 2022-11-22 07:44:20 KST
           Universal time: μ›” 2022-11-21 22:44:20 UTC
                 RTC time: μ›” 2022-11-21 22:44:20
                Time zone: Asia/Seoul (KST, +0900)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no

μš”μ•½

방법μž₯μ λ‹¨μ κΆŒμž₯ μ‹œλ‚˜λ¦¬μ˜€
Dockerfile이미지에 νƒ€μž„μ‘΄ λ‚΄μž₯, 이식성 λ†’μŒ, μΌκ΄€λœ μ„€μ • 보μž₯이미지 μž¬λΉŒλ“œ ν•„μš”, 이미지 크기 μ•½κ°„ μ¦κ°€λŒ€λΆ€λΆ„μ˜ ν”„λ‘œλ•μ…˜ ν™˜κ²½, μΌκ΄€λœ νƒ€μž„μ‘΄μ΄ ν•„μš”ν•œ 경우
Volume Mount이미지 μž¬λΉŒλ“œ λΆˆν•„μš”, λŸ°νƒ€μž„ μœ μ—°μ„±, ν˜ΈμŠ€νŠΈμ™€ 동기화 κ°€λŠ₯호슀트 μ˜μ‘΄μ„±, λ…Έλ“œ κ°„ 이식성 μ €ν•˜, hostPath λ³΄μ•ˆ λ¬Έμ œλŸ°νƒ€μž„μ— νƒ€μž„μ‘΄ 변경이 μž¦κ±°λ‚˜ ν˜ΈμŠ€νŠΈμ™€ 동기화가 ν•„μš”ν•œ 경우
ν™˜κ²½ λ³€μˆ˜ TZκ°€μž₯ 간단, 이미지 μž¬λΉŒλ“œ λΆˆν•„μš”, λŸ°νƒ€μž„ μœ μ—°μ„±tzdata μ„€μΉ˜ ν•„μˆ˜, μ„€μΉ˜λ˜μ§€ μ•Šμ€ 경우 κΈ°λ³Έ UTCtzdataκ°€ ν¬ν•¨λœ κ²½λŸ‰ 이미지 μ‚¬μš© μ‹œ, κ°„λ‹¨ν•œ μ„€μ •
timedatectlμ»¨ν…Œμ΄λ„ˆ λ‚΄λΆ€μ—μ„œ 직접 μ„€μ • κ°€λŠ₯λŒ€λΆ€λΆ„μ˜ κ²½λŸ‰ μ΄λ―Έμ§€μ—μ„œ μ‚¬μš© λΆˆκ°€, μ»¨ν…Œμ΄λ„ˆ μž¬μ‹œμž‘ μ‹œ μ„€μ • μœ μ§€ μ•ˆλ¨, 디버깅 λͺ©μ  μ™Έ λΉ„κΆŒμž₯디버깅 λͺ©μ , νŠΉμ • 베이슀 이미지 μ‚¬μš© μ‹œ

μ°Έκ³