Database/Maria,Mysql

mysql에서 time-zone (타임존)을 Asia/Seoul 및 KST로 정의하는 방법

bluebamus 2026. 5. 6.

- Docker / Percona Server 8.0 / Ubuntu 24.04 환경에서 다음 출력을 만들기 위한 핵심 정리.

SELECT @@global.time_zone, @@session.time_zone, @@system_time_zone;
-- Asia/Seoul    Asia/Seoul    KST


1. 설정 방법 (Setup Procedure)

   1.1. 전체 그림

      - 3개 파일로 끝난다. 각 파일이 책임지는 변수가 다르다.

파일 라인 책임지는 변수
docker-compose.yml environment.TZ: Asia/Seoul @@system_time_zone (= KST)
Dockerfile ARG MYSQL_TZ=Asia/Seoul + 빌드 시 init.sql 굽기 @@global.time_zone (= Asia/Seoul)
config/my.cnf timezone 라인 없음


   1.2. 디렉터리 구조

mysql-80/
├── Dockerfile
├── docker-compose.yml
├── config/
│   └── my.cnf
└── data/                  # bind mount, 영속화


   1.3. Step 1 — `Dockerfile` 작성

      - 빌드 시점에 init SQL을 이미지 내부로 굽는다.

ARG MYSQL_TZ=Asia/Seoul
RUN printf "SET PERSIST time_zone = '%s';\n" "${MYSQL_TZ}" \
        > /docker-entrypoint-initdb.d/01-set-timezone.sql; \
    chmod a+r /docker-entrypoint-initdb.d/01-set-timezone.sql


   1.4. Step 2 — `docker-compose.yml` 작성

services:
  mysql:
    build:
      context: .
      dockerfile: Dockerfile
    image: percona-mysql-8.0:ubuntu24.04
    container_name: percona-mysql-8.0
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: test1324
      TZ: Asia/Seoul          # ← OS 레이어 (system_time_zone 결정)
    ports:
      - "3306:3306"
    volumes:
      - ./config/my.cnf:/etc/mysql/conf.d/my.cnf:ro
      - ./data:/var/lib/mysql


   1.5. Step 3 — `config/my.cnf` 작성

      - timezone 관련 어떤 라인도 두지 않는다.
      - `bind-address`, `port`, `innodb_buffer_pool_size`, `binlog_format`, `server_id` 등 부류 A 옵션만 둔다.

 

   1.6. Step 4 — 첫 부팅

mkdir -p data
sudo chown 1001:1001 data        # 컨테이너 내부 mysql 사용자 uid/gid
docker compose up -d --build


   1.7. Step 5 — 검증

docker compose exec mysql mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -B -e \
  "SELECT @@global.time_zone, @@session.time_zone, @@system_time_zone;"


      - 기대 출력:

@@global.time_zone   @@session.time_zone   @@system_time_zone
Asia/Seoul           Asia/Seoul            KST


      - PERSIST 영속화 확인:

docker compose exec mysql cat /var/lib/mysql/mysqld-auto.cnf


      - JSON 안에 `"time_zone": {"Value": "Asia/Seoul", ...}` 가 박혀 있어야 한다.

 

   1.8. 동작 흐름 요약

시점 일어나는 일
빌드 Dockerfile의 RUN printf가 이미지 내부에 /docker-entrypoint-initdb.d/01-set-timezone.sql 생성
첫 부팅 Phase A mysqld --initialize-insecure — datadir 생성. my.cnf에 timezone 라인 없으므로 통과
첫 부팅 Phase B 임시 mysqld → mysql_tzinfo_to_sql가 1700+개 zone 적재 → init.sql 실행 → SET PERSIST → mysqld-auto.cnf 작성
첫 부팅 Phase C 본 mysqld → my.cnf + mysqld-auto.cnf 모두 읽음 → Asia/Seoul 적용. TZ env가 glibc를 거쳐 system_time_zone=KST 결정
이후 부팅 datadir이 채워져 있으므로 Phase A·B 스킵. Phase C만 실행되며 mysqld-auto.cnf로부터 자동 적용


2. 세 변수가 결정되는 곳

변수 결정 주체 본 프로젝트에서
@@system_time_zone OS의 TZ 환경변수 / /etc/localtime (부팅 시 1회 추출, 읽기 전용) docker-compose.yml의 TZ: Asia/Seoul → glibc가 KST로 라벨링
@@global.time_zone my.cnf, mysqld-auto.cnf, 또는 런타임 SET (뒤로 갈수록 우선순위 높음) SET PERSIST 결과로 mysqld-auto.cnf에 영속화
@@session.time_zone 연결 시 global을 상속, 클라이언트가 SET time_zone으로 덮어쓸 수 있음 global 상속


   - 표시 가능한 형식

형식 검증 자원 비고
특수 키워드 SYSTEM 없음 "OS TZ 따라간다"
오프셋 +09:00 단순 파싱 어디서든 안전, DST 추종 불가
약식 KST OS tzdata OS 의존
Named (IANA) Asia/Seoul mysql.time_zone_name 테이블 DST·역사 변경 정확 추종 — 본 요구


3. my.cnf에 `Asia/Seoul`을 못 넣는 이유 (핵심 제약)

   - 컨테이너 첫 부팅의 3단계 라이프사이클 때문이다.
   - Phase A (`mysqld --initialize-insecure`): datadir 생성. 이 시점 `mysql.time_zone_name` 테이블은 비어 있음. my.cnf에 named zone이 있으면 → `Illegal or unknown default time zone 'Asia/Seoul'` → abort.
   - Phase B (임시 부트스트랩 mysqld): `mysql_tzinfo_to_sql`이 1700+개 IANA zone을 적재. 이후 `/docker-entrypoint-initdb.d/*.sql` 실행.
   - Phase C (본 mysqld): my.cnf + `mysqld-auto.cnf` 모두 읽음. tz 테이블이 채워진 상태라 검증 통과.

   - 실측 결과

위치/형식 named Asia/Seoul 오프셋 +09:00
my.cnf (default-time-zone) ❌ Phase A abort ✅ 부팅 OK, literal +09:00
/docker-entrypoint-initdb.d/*.sql (SET PERSIST) ✅ 부팅 OK, literal Asia/Seoul ✅ (불필요)


   - 전통 패키지 설치(apt)에서는 왜 문제없나

      - `mysql_tzinfo_to_sql` 적재(t1) → 운영자가 my.cnf에 named zone 추가(t2) 사이에 시간 간격이 있다.
      - Docker는 이 과정이 entrypoint 안에서 한 번에 압축되므로, named zone을 끼워넣을 슬롯이 `/docker-entrypoint-initdb.d/`뿐이다.

 

4. SET PERSIST와 mysqld-auto.cnf

   4.1. SET 종류

구문 런타임 변경 mysqld-auto.cnf 기록 다음 부팅 적용
SET GLOBAL var = ...
SET PERSIST var = ...
SET PERSIST_ONLY var = ...
SET SESSION var = ... ✅ (해당 연결만)


   4.2. mysqld-auto.cnf 형식

      - 위치: `/var/lib/mysql/mysqld-auto.cnf` (datadir 내부, JSON)

{
  "Version": 2,
  "mysql_dynamic_variables": {
    "time_zone": {
      "Value": "Asia/Seoul",
      "Metadata": { "Host": "localhost", "User": "root", "Timestamp": 1778041898752849 }
    }
  }
}


      - mysqld가 Phase C에서 my.cnf 다음에 이 파일을 읽고 적용. 동시 정의 시 이 파일이 우선.

 

5. 대안 비교 (왜 다른 방식은 기각됐나)

대안 literal 결과 프로젝트 파일 수 문제
my.cnf에 default-time-zone='+09:00' +09:00 3 literal 불일치, DST 추종 불가
my.cnf에 default-time-zone='Asia/Seoul' 3 Phase A abort
config/init/*.sql 별도 마운트 Asia/Seoul 4 "별도 스크립트 파일 없이" 요구 위반
Dockerfile에서 굽기 (채택) Asia/Seoul 3 이미지 내부 한 줄 박힘 (단점 거의 없음)


6. 운영 시나리오

   6.1. 새 환경 구축

mkdir -p data && sudo chown 1001:1001 data
docker compose up -d --build


   6.2. 데이터 유지하면서 zone 변경 (가장 흔한 케이스)

docker compose exec mysql mysql -uroot -p"$MYSQL_ROOT_PASSWORD" \
  -e "SET PERSIST time_zone = 'Asia/Tokyo';"
docker compose restart mysql

 

      - 응용 풀(connection pool)도 함께 재기동해 드라이버 캐시를 무효화할 것.

 

   6.3. 빌드 인자로 다른 zone 지정 + datadir 새로

docker compose down
sudo rm -rf data && mkdir data && sudo chown 1001:1001 data
docker compose build --build-arg MYSQL_TZ=Asia/Tokyo mysql
docker compose up -d


   6.4. TIMESTAMP vs DATETIME

      - `TIMESTAMP` — InnoDB에 항상 UTC로 저장. 읽기/쓰기 시 `@@session.time_zone`으로 자동 변환. → zone 바뀌면 같은 row가 다른 wall-clock으로 보임.
      - `DATETIME` — 변환 없이 그대로 저장. → zone 바뀌어도 같은 wall-clock.

 

7. 트러블슈팅

   7.1. 자주 만나는 에러

증상 / 로그 원인 조치
Illegal or unknown default time zone 'Asia/Seoul' my.cnf에 named zone my.cnf에서 timezone 라인 제거
unknown variable 'time_zone=...' time_zone을 my.cnf에 직접 둠 default-time-zone 옵션명을 쓰거나 라인 제거
Unable to lock ./ibdata1 error: 11 다른 컨테이너가 datadir 점유 docker ps -a로 잔존 컨테이너 제거
Permission denied on /var/lib/mysql/ 호스트 ./data 권한 sudo chown -R 1001:1001 ./data
@@global.time_zone이 SYSTEM으로 보임 mysqld-auto.cnf 미생성 (datadir이 채워진 채 컨테이너만 새로 띄움 → init 단계 스킵) SET PERSIST time_zone='Asia/Seoul'; + restart
@@session.time_zone만 다른 값 응용 드라이버가 연결 시 SET time_zone 발행 드라이버의 serverTimezone / init_command 점검
@@system_time_zone이 KST가 아님 compose TZ: 미설정/오설정 docker compose exec mysql sh -c 'readlink /etc/localtime'


   7.2. 첫 부팅 실패 후 복구

      - Phase A가 abort해도 일부 파일(ibdata1, mysql/ 등)이 이미 만들어져 있을 수 있음.
      - 이 상태에서 다시 띄우면 entrypoint가 `if [ ! -d "$DATADIR/mysql" ]`을 false로 보고 init 단계를 통째로 건너뛴다. → datadir 완전 삭제 후 재시작:

docker compose down
sudo rm -rf data/* && sudo chown 1001:1001 data
docker compose up -d


   7.3. 처음 보는 zone으로 바꾸려는데 실패

-- 1. tz 테이블이 비어있는지 확인
SELECT COUNT(*) FROM mysql.time_zone_name;
SELECT * FROM mysql.time_zone_name WHERE Name='Asia/Tokyo';


      - 비어있다면 수동 적재:

docker compose exec mysql sh -c 'mysql_tzinfo_to_sql /usr/share/zoneinfo \
    | mysql -uroot -p"$MYSQL_ROOT_PASSWORD" mysql'


8. 응용 계층 고려사항

   8.1. JDBC

      - URL 옵션:
         - `serverTimezone=Asia/Seoul` — 드라이버가 사용하는 zone (서버와 일치시킬 것)
         - `forceConnectionTimeZoneToSession=true` — 드라이버가 `SET time_zone` 강제

 

   8.2. Python (FastAPI / asyncmy / aiomysql)

create_async_engine(
    "mysql+asyncmy://...",
    connect_args={"init_command": "SET time_zone='Asia/Seoul'"},
)


   8.3. ORM 캐싱

      - ORM이 첫 연결 시 가져온 zone을 캐시하고 변경을 인지 못 할 수 있음.
      - 운영 중 `SET PERSIST`로 바꾸려면 응용 풀을 함께 리프레시.

 

9. 한 줄 핵심

   - mysqld가 부팅 절차에서 my.cnf 옵션을 검증할 때, 그 검증이 `mysql.*` 시스템 데이터베이스의 데이터를 필요로 하면, 그 옵션은 데이터가 채워진 후의 시점이 보장되는 곳(=`/docker-entrypoint-initdb.d/`의 SQL)에 두어야 한다. 그 결과는 `mysqld-auto.cnf`에 영속화된다.

   - 부록 — 명령 빠른 참조

# 빌드
docker compose build mysql

# 빌드 인자로 zone 바꾸기
docker compose build --build-arg MYSQL_TZ=Asia/Tokyo mysql

# 첫 부팅 (빈 datadir 가정)
sudo chown 1001:1001 data
docker compose up -d

# 검증
docker compose exec mysql mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -B -e \
  "SELECT @@global.time_zone, @@session.time_zone, @@system_time_zone;"

# PERSIST 영속화 확인
docker compose exec mysql cat /var/lib/mysql/mysqld-auto.cnf

# tzinfo 적재 확인
docker compose exec mysql mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -B -e \
  "SELECT COUNT(*) FROM mysql.time_zone_name;"

# 운영 중 zone 변경
docker compose exec mysql mysql -uroot -p"$MYSQL_ROOT_PASSWORD" \
  -e "SET PERSIST time_zone = 'Asia/Seoul';"
docker compose restart mysql

# datadir 초기화 후 처음부터
docker compose down
sudo rm -rf data && mkdir data && sudo chown 1001:1001 data
docker compose up -d --build

 

댓글