🕶️

GoアプリケーションをDocker化した話

2023/12/19に公開

初めに

株式会社アルファドライブにてバックエンド開発をやっております @くも と申します。今日は自社で開発しているToB向けSaaS型GoアプリケーションをDocker化した話を共有したいと思います。

構成

ざっくりした構成ですが、フロントエンドはNext.jsでバックエンドはGoを採用しており、RESTful APIで繋がっています。

背景

大きく2つあります。

  • チームに新メンバーがジョインする時のセットアップ時間を削減したい
  • 個人の環境に依存せず、全員同じ環境で開発できるようにしたい

やったこと

  • すべてのコンテナ関連の設定を一つの docker-compose.yml にまとめて docker-compose コマンドでコントロールする
  • air を導入してホットリロードにも対応する
  • DBと接続するアプリケーションなので、DBの作成、マイグレーション、リフレッシュは Flyway を使って対応する
  • docker-compose.ymldepends_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

詳細な説明をしていきます。

  1. air を使ってホットリロードを対応するため、ローカルにあるコードをそのままコンテナにマウントして、変更をキャッチできるようにしています。
  2. このアプリケーションはDBが必要なので、depends_on にPostgreSQLが必ず立ち上がって、さらにHealthy状態を担保するように設定しています。
  3. PostgreSQLのHealthy状態のチェックは pg_isready というユーティリティを使っています。
  4. 前述のように Flyway を使ってDBのオブジェクト作成を行っていますので、flyway-cleanflyway-migrateflyway-migrate-data serviceを作ってFlywayのコンテナ経由で実行するように設定しました。
    • flyway-clean はFlyway経由で作ったオブジェクトを更地にするserviceです。
    • flyway-migrate はオブジェクトをマイグレーション(作成)するserviceです。
    • flyway-migrate-data はオブジェクト作成後にローカルで必要な初期データをFlyway経由でInsertするためのserviceです。
  5. flyway-xxx serviceは共通のコンテナを使いますので、template機能を利用して共通化しています。
  6. ローカルでは自由に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