0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

STECHAdvent Calendar 2024

Day 5

【Go言語】本番環境でだけ`unknown time zone`というエラーが出る

Posted at

先にまとめ

Go言語のtime.LoadLocation("Asia/Tokyo")のような処理は、プログラム以外の外部環境のタイムゾーンのデータに依存しているそうです。

そのため、Dockerのマルチステージビルドなどで、Goのプログラムをピュアなalpine上で実行している場合、alpineにはタイムゾーンのデータが含まれておらず、実行プログラムから参照できなくunknown time zone Asia/Tokyoというエラーになります。

Go 1.15でこのタイムゾーンのデータをビルド成果物に埋め込むことが可能になったそうです。ビルド時のオプションで-tags timetzdataと付けることで、このデータが埋め込まれた形でビルドされ、alpine上で実行したときにもエラーが出なくなります。

問題の再現

東京のタイムゾーンで現在時刻を取得するプログラムを、簡単に以下のように作って実行してみます。

main.go
package main

import (
	"fmt"
	"time"
)

func main() {
	loc, err := time.LoadLocation("Asia/Tokyo")
	if err != nil {
		fmt.Println("!!ERROR!! ", err)
		return
	}

	fmt.Println("Time: ", time.Now().In(loc))
}

ローカルにGoの環境があれば、実行すると画像のように現在時刻の情報が出力されます。

ローカルでの実行結果

開発環境用にDockerの実行環境とdocker composeの設定も用意しておきます。

dev.Dockerfile
FROM golang:1.23.4-alpine3.21

WORKDIR /app

COPY ./main.go .
CMD go run main.go
compose.yaml
services:
  develop:
    build:
      context: .
      dockerfile: ./dev.Dockerfile
    image: test-golang-tzdata-dev

docker compose buildをして、docker compose run --rm developを実行してみるとこちらも現在時刻が表示されました。

Dockerfileを使った開発環境での実行結果

おっけーそう👌、ということで本番用の実行環境をマルチステージビルドを利用して作ります。

prod.Dockerfile
################## ビルドステージ ##################
FROM golang:1.23.4-alpine3.21 AS builder

WORKDIR /app

COPY ./main.go .

RUN go build -o main main.go

################## 実行ステージ ##################
FROM alpine:3.21
WORKDIR /app

# ビルドしたバイナリをコピー
COPY --from=builder /app/main .

CMD /app/main

今回は検証用に、docker composeで本番環境も実行できるようにしておきます。

compose.yaml
services:
  develop:
    build:
      context: .
      dockerfile: ./dev.Dockerfile
    image: test-golang-tzdata-dev
+ product:
+   build:
+     context: .
+     dockerfile: ./prod.Dockerfile
+   image: test-golang-tzdata-prod

改めてdocker compose buildをしておいて、今度はproductのサービスを指定してdocker compose run --rm productと実行してみます。すると今回はエラーになってしまいました!

Dockerfileを使った本番環境での実行結果

原因

上の記事やスライドなどを読んでいると、プログラム中のtime.LoadLocation("Asia/Tokyo")関数は、どうも外部依存(Goのプログラム以外への依存)があるそうです。具体的はOSなどに用意されているタイムゾーンの情報が書かれたファイルを読み込みます。Go言語自体のソースコードの中にもこの情報のデータは入っているようです。

これに対して、今回本番環境で使ったDockerのベースイメージはAlpine Linuxというものです。極端に言えば、できるだけ依存パッケージ等のファイルを減らし、セキュリティリスクの低減やリソース効率を高めたイメージです。

普通のUbuntuなどのディストリビューションであればタイムゾーンの情報がOSに用意されていましたが、Alpineではこのタイムゾーンの情報も含まれていないようで、参照できる情報が無いためunknown time zoneというエラーになってしまっていたようです。

開発環境のDockerコンテナも、golang:1.23.4-alpine3.21というベースイメージでAlpine上のGo言語の環境で実行していましたが、ここにはGo言語自体のソースが含まれているため、その中に含まれるタイムゾーンデータを参照していたものと思われます。

対策

先ほどの記事などを参照すると、Goの1.15からこのタイムゾーンの情報をビルドの成果物に含められるオプションが追加されたようです。-tags timetzdataというオプションをビルド時に付け、以下のように記述します。

prod.Dockerfile
################## ビルドステージ ##################
FROM golang:1.23.4-alpine3.21 AS builder

WORKDIR /app

COPY ./main.go .

- RUN go build -o main main.go
+ RUN go build -tags timetzdata -o main main.go

################## 実行ステージ ##################
FROM alpine:3.21
WORKDIR /app

# ビルドしたバイナリをコピー
COPY --from=builder /app/main .

CMD /app/main

Dockerfileを使った本番環境での実行結果(修正後)

これで実行すると、今回はエラーにならずに実行することができました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?