GoアプリケーションをDocker化した話
初めに
株式会社アルファドライブにてバックエンド開発をやっております @くも と申します。今日は自社で開発しているToB向けSaaS型GoアプリケーションをDocker化した話を共有したいと思います。
構成
ざっくりした構成ですが、フロントエンドはNext.jsでバックエンドはGoを採用しており、RESTful APIで繋がっています。
背景
大きく2つあります。
- チームに新メンバーがジョインする時のセットアップ時間を削減したい
- 個人の環境に依存せず、全員同じ環境で開発できるようにしたい
やったこと
- すべてのコンテナ関連の設定を一つの
docker-compose.yml
にまとめてdocker-compose
コマンドでコントロールする - air を導入してホットリロードにも対応する
- DBと接続するアプリケーションなので、DBの作成、マイグレーション、リフレッシュは Flyway を使って対応する
-
docker-compose.yml
のdepends_on
機能を使ってserviceの立ち上げ順番をコントロールする
docker-compose.yml のサンプル
x-template: &flyway-template # 5で説明
image: flyway/flyway:8.4.4
volumes:
- ./migration/flyway/sql:/flyway/sql # オブジェクト作成用ファイル一覧
- ./migration/flyway/local_conf:/flyway/conf # ローカルのPostgreSQLに接続するためのflywayの設定ファイル
- ./migration/flyway/local_sql:/flyway/local_sql # ローカル用初期データ作成ファイル一覧
services:
server-api:
ports:
- 58080:8080
- 52345:2345 # delve debug
build:
dockerfile: ./docker/Dockerfile.server
volumes:
- ./:/app/ # 1で説明
environment:
- TZ=Asia/Tokyo
- SERVER_ROOT=/app/server
depends_on:
postgresql:
condition: service_healthy # 2で説明
postgresql:
image: postgres:13-alpine3.18
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=demo
- POSTGRES_INITDB_ARGS=--encoding=UTF-8
- POSTGRES_HOST_AUTH_METHOD=trust
- TZ=Asia/Tokyo
ports:
- 55432:5432
user: root
healthcheck:
test: ["CMD", "pg_isready", "-d", "demo", "-U", "user"] # 3で説明
interval: 10s
timeout: 5s
retries: 5
volumes:
- postgresql:/var/lib/postgresql/data
- ./migration/ddl/init_db.sql:/docker-entrypoint-initdb.d/01_init_db.sql # データベース、ユーザー作成用初期ファイル
flyway-clean: # 4で説明
<<: *flyway-template
command: clean
depends_on:
postgresql:
condition: service_healthy
flyway-migrate: # 4で説明
<<: *flyway-template
command: migrate
depends_on:
flyway-clean:
condition: service_completed_successfully
flyway-migrate-data: # 4で説明
<<: *flyway-template
command: -locations=filesystem:/flyway/local_sql migrate
depends_on:
flyway-migrate:
condition: service_completed_successfully
詳細な説明をしていきます。
- air を使ってホットリロードを対応するため、ローカルにあるコードをそのままコンテナにマウントして、変更をキャッチできるようにしています。
- このアプリケーションはDBが必要なので、
depends_on
にPostgreSQLが必ず立ち上がって、さらにHealthy状態を担保するように設定しています。 - PostgreSQLのHealthy状態のチェックは pg_isready というユーティリティを使っています。
- 前述のように Flyway を使ってDBのオブジェクト作成を行っていますので、
flyway-clean
、flyway-migrate
、flyway-migrate-data
serviceを作ってFlywayのコンテナ経由で実行するように設定しました。-
flyway-clean
はFlyway経由で作ったオブジェクトを更地にするserviceです。 -
flyway-migrate
はオブジェクトをマイグレーション(作成)するserviceです。 -
flyway-migrate-data
はオブジェクト作成後にローカルで必要な初期データをFlyway経由でInsertするためのserviceです。
-
-
flyway-xxx
serviceは共通のコンテナを使いますので、template機能を利用して共通化しています。 - ローカルでは自由にDBを壊したり、改造するのを許容するので、そこからのリカバリーは基本Flyway経由で行います。
続けて、Dockerfileを見てみましょう。
FROM public.ecr.aws/docker/library/golang:1.21.1-bullseye
WORKDIR /app/server
RUN go install github.com/cosmtrek/air@latest && \
# for remote development with vscode
go install golang.org/x/tools/gopls@latest && \
go install honnef.co/go/tools/cmd/staticcheck@latest && \
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.54.1 && \
# for debug
go install github.com/go-delve/delve/cmd/dlv@latest
# tools for ci
RUN go install github.com/vektra/mockery/v2@v2.23.1 && \
go install github.com/volatiletech/sqlboiler/v4@v4.14.1 && \
go install github.com/volatiletech/sqlboiler/v4/drivers/sqlboiler-psql@v4.14.1 && \
go install github.com/gzuidhof/tygo@v0.1.2 && \
go install github.com/k1LoW/tbls@v1.64.0
COPY server/go.mod .
COPY server/go.sum .
RUN go mod download
ENTRYPOINT [ "air" ]
ご覧の通りBuildに必要なパッケージや、CIに必要なパッケージを詰め込んだイメージファイルになります。
1点だけ、注目してほしいのは、ENTRYPOINT
です。airを起動するだけになっていますが、これは別途airの設定ファイル(airの設定ファイルのサンプルは割愛します)を読み込んでホットリロードできるようにするためです。
起動する
DB立ち上げとオブジェクト作成、初期データ作成
docker compose run --build --rm flyway-migrate-data
アプリケーションの立ち上げ
docker compose up --build --detach server-api
これでローカルでRESTful APIが立ち上がるようになります。
コンテナ中で開発する
せっかくDocker化対応したので、開発もコンテナ中に入ってやりたくなりませんか?
チームではVSCodeの devcontainers 拡張機能を導入してこの課題を解決しています。
VSCodeから立ち上がった server-api のコンテナにアタッチしてリモート開発を実施しています。
最後に
Docker化の対応前には、
- 環境構築に時間がかかる
- 環境構築中に不明なエラーでハマる
- Lint設定がバラバラでCIチェックに引っかかり、二度手間になる
- それぞれのローカル環境でGoや、周辺パッケージのバージョンが揃わなくて潜在的なバグを起こすリスクがある
などなどの課題が色々ありましたが、すべてクリアできたので、やって本当に良かったと思います。
しかし、まだ色々課題は残っています。
- GoLandを使うメンバーがいるので、devcontainers が使えずまだローカルで開発している
- devcontainers を使ってアタッチしてコンテナ中で開発していますが、毎回VSCodeのワークスペースモードで開き直さないと行けない
- 無邪気にコンテナを削除したら、開発するまでまた手動で設定し直さないと行けない(コンテナにアタッチし、ワークスペースモードで開き直す)
これらの課題もこれから解決に向けて改善を繰り返していこうと思います。
すべてをDocker化することでローカルのマシーンパワが必要なデメリットもありますが、チーム全員が揃った環境でストレスなしで開発できるようになったので、おすすめしたいと思います。
Discussion