【Go、Docker】「api」という名前のパッケージを作るとビルド出来なくなる

Code

はじまり

リサちゃん
リサちゃん

う~ん、これはどうなっているんだ??

135ml
135ml

デプロイが出来ないな。

まず、何が起きてるんだ

最近、Goを使ってGinベースのWebアプリケーションを開発していました。このアプリケーションをCloud Runにデプロイしようとしたところ、奇妙な現象に遭遇しました。

今回使っていたGoのバージョンは、1.23.5です。

まあ、例えばこんな感じのディレクトリ構造になっているとします。

/home/user/my-module
|--.dockerignore
|--.gcloudignore
|--.git
| |--COMMIT_EDITMSG
| |--FETCH_HEAD

...

|--api
| |--handlers
| | |--user.go
| |--logger.go
| |--logger_test.go
| |--middleware
| | |--auth.go
| | |--rate_limiter.go
| |--router.go
| |--router_test.go
| |--swagger.go
| |--swagger_test.go
| |--util.go
| |--util_test.go
|--coverage.html
|--coverage.out
|--db_pc_stats
| |--domain.go
| |--domain_test.go
| |--model.go
| |--model_error_test.go
| |--model_test.go
| |--util.go

...

Cloud Runにデプロイする時に使ったコマンドは普通にこんな感じです。

gcloud run deploy <image_name> --source . --project=<project_id> --region=<region> --allow-unauthenticated --timeout=60s

そして今回、そのサービスのデプロイが失敗して、Cloud Buildのログの中に以下のようなエラーメッセージが表示されたのです。

main.go:9:2: no required module provides package github.com/user/my-module/api; to add it:
	go get github.com/user/my-module/api
The command '/bin/sh -c go build -v -o main' returned a non-zero code: 1
ERROR
ERROR: build step 0 "gcr.io/cloud-builders/docker" failed: step exited with non-zero status: 1

エラーが発生している箇所は、以下のようなインポート文でした。

api "github.com/user/my-module/api"

あれ・・・?

ローカル環境ではgo build -v -o mainを実行すると問題なくビルド出来ていました。しかし、Dockerコンテナ内で同じコマンドを実行するとエラーになるなんて・・・。これは一体どういうことでしょうか?

今回、アプリケーションの構造として、APIのルーティングやSwagger UIの実装を「api」というパッケージに配置していました。しかし、このディレクトリ構造の状態で、ローカル環境では問題なくビルドできていたのですが、Dockerコンテナ内でビルドすると失敗する。・・・という謎の現象が発生したのです。

原因を探る。

最初は、開発中にgo.modのモジュール名を変更したことが原因かと思いました。しかし、他のプロジェクトでは同様のディレクトリ構成でもデプロイできていました。モジュール名も「my-project」や「github.com/username/mymodule」のような形式でも問題なくデプロイできていました。

次に、Dockerfileを見てみました。「RUN go build -v -o main」のレイヤーでエラーになりました。このレイヤーの前にgo mod tidygo mod editを入れても相変わらずエラーになりました。

# Build stage
FROM debian:11 AS builder
WORKDIR /app
# 依存関係をダウンロード(go.mod, go.sumがある場合)
RUN apt-get update && apt-get upgrade -y && apt-get install -y curl gcc
COPY go.mod go.sum ./
RUN curl -OL <https://go.dev/dl/go1.23.7.linux-amd64.tar.gz> &&\\\\
    tar -C /usr/local -xzf go1.23.7.linux-amd64.tar.gz &&\\\\
    rm -rf go1.23.7.linux-amd64.tar.gz
ENV PATH $PATH:/usr/local/go/bin
RUN go mod download
# ソースコードをコピー
COPY . .
# ↓↓ アプリケーションのビルド ↓↓ <------------------------------- HERE!!
RUN go build -v -o main

# Final stage: 軽量な実行環境
# FROM gcr.io/distroless/base-debian10
FROM debian:11 AS deploy
WORKDIR /
RUN apt-get update && apt-get upgrade -y
RUN apt-get install -y openssl ca-certificates
# ビルドしたバイナリをコピー
COPY --from=builder /app/main .
# Cloud Runのためにポート8080を公開
EXPOSE 8080
# コンテナ起動時に実行されるコマンド
CMD ["./main"]

原因が判明する

そんなこんなで、色々と試しながら30分ぐらい経った頃でしょうか・・・、試行錯誤の末、ついに「api」ディレクトリの名前を「app」に変更したところ、デプロイが成功しました。その箇所だけ変更してデプロイし直すと結果が違ったので、たぶん間違いないかと。

ええっ・・・、そんなことってあるの・・・??

「api」っていう名前のパッケージで、Dockerコンテナ内でGoモジュールがビルド出来なくなる・・・??

Goのプロジェクトモジュールのディレクトリ構造の参考として、「golang-standards/project-layout」というものがあります。

GitHub - golang-standards/project-layout: Standard Go Project Layout
Standard Go Project Layout. Contribute to golang-standards/project-layout development by creating an account on GitHub.

その「golang-standards/project-layout」のリポジトリで紹介されているようなプロジェクトレイアウトのサンプルでは「api」ディレクトリがあるけど、それはどういうことなの? ・・・とも思いましたが、このディレクトリの中のREADMEを見ると、「OpenAPI/Swagger specs, JSON schema files, protocol definition files.」と書かれているので、おそらくこの「api」ディレクトリにはGoファイルは格納されないんだと思います。そう考えれば、今回の事象の原因の反例にはならない。

でも、「api」ディレクトリが原因ならば、その旨をエラーメッセージに出して欲しいんだけどなぁ・・・。

考えられる理由

なぜ「api」という名前のパッケージがDockerコンテナ内でビルドに失敗するのでしょうか?いくつかの可能性が考えられます。

  1. 名前の衝突: 「api」という名前が、Goの標準ライブラリや内部的な何かと衝突している可能性があります。
  2. 特別な扱い: 「api」ディレクトリは、OpenAPIなどのAPI仕様書を格納するための特別なディレクトリとして認識されている可能性があります。
  3. 環境の違い: ローカル環境とDockerコンテナ環境の微妙な違いにより、同じコードでも挙動が異なる可能性があります。

うーん、でも今回事象が発生したプロジェクトでは「api」ディレクトリでAPIのルーティングをしてるし、SwaggerUIを作る部分もこの中でやってるから、「api」という名前のGoパッケージにしたかったのだが・・・。そうかぁ・・・。

まとめ

今回のまとめです。

Goでアプリケーションを開発する際、パッケージ名には注意が必要です。特に「api」という名前は、ローカル環境では問題なくても、Dockerコンテナ内でビルドする際に問題を引き起こす可能性がありました。

そして、「api」ディレクトリを「app」に変更することで問題が解決しました。APIのルーティングやSwagger UIの実装は「app」パッケージ内で行うことにしました。

そしてまあ、今回のような問題に遭遇した場合に解決した流れは以下の通りです。

  1. パッケージ名の変更: 「api」という名前を避け、「app」「apiserver」「endpoints」など別の名前に変更する。
  2. モジュール構造の見直し: プロジェクトのモジュール構造を見直し、標準的なレイアウトに合わせる。
  3. ビルド環境のテスト: デプロイ前に、Dockerコンテナ内でのビルドテストを行い、問題を早期に発見する。

また、Goのプロジェクト構造については、golang-standards/project-layoutのような標準的なレイアウトを参考にすると良いでしょう。まあ、個人開発の水準としては、丸写しするにはかなり手が込んでいますが・・・。(このリポジトリでも留意事項に同じようなことが書いてありました。)

おしまい

リサちゃん
リサちゃん

また変なところで沼ったなあ

135ml
135ml

分かってよかったです。

以上になります!

コメント

タイトルとURLをコピーしました