LoginSignup
14
12

More than 5 years have passed since last update.

【Go 1.12】Goの開発環境テンプレート【DDD/Docker/Makefile/CircleCI】

Posted at

はじめに

Go 1.12を使ってAPIサーバを開発するに当たって、開発環境のテンプレートのようなものを作ってみたので紹介します。
APIサーバとMySQLのみの簡易的な構成になっているので、サービスの要件やチームの技術スタックによって適宜アレンジすると良さげです。

まとめ

こんな感じのことをやりました。

  1. ドメイン駆動設計を参考にしたレイヤ設計をアーキテクチャとして導入
  2. Dockerを開発環境と本番環境で利用できるように
  3. Makefileに開発で使うコマンドをまとめる
  4. CircleCIでテストおよびマイグレーション・デプロイを自動化

解説

それでは今回作った開発環境の簡単な解説をしていきたいと思います。
コードはGitHubに置いているので適宜参照してください。

1. ドメイン駆動設計を参考にしたレイヤ設計をアーキテクチャとして導入

まずは、APIサーバのソフトウェアアーキテクチャにどのようなものを採用したかについて話します。
今回使った設計はpospomeさんのDevFestでのセッション内容を大いに参考にしています。
ですので、さっき話しますと言ったばかりですが、本記事では詳しい説明は割愛します。
僕のDDDやレイヤ設計への理解を深めてくれたセッションで、本当に感謝しています。
少しアレンジしていたり、詳しい実装が紹介されていないところは僕が勝手に補完して実装していたりしますが、大枠は同じだと思うので細かいところはGitHubを見ていただければと思います。

2. Dockerを開発環境と本番環境で利用できるように

次に、ローカル開発環境と本番環境で使うDockerまわりの解説をしていきます。

ディレクトリ構成

まずはDockerまわりの設定ファイルをどのように管理しているかについて説明します。
プロジェクトルートにdockerディレクトリを作り、この中にDocker関連のファイルを置いています。
dockerディレクトリ中のファイル構成は↓のような感じです。

docker/
├── docker-compose.yml       # ローカル開発用コンテナサービス群を立ち上げる
├── docker-compose.test.yml  # テスト用コンテナサービス群を立ち上げる
├── .env.default             # .envにコピーして使う
├── development
│   └── Dockerfile           # 開発用のAPIサーバコンテナ
├── migration
│   └── Dockerfile           # MySQLをマイグレーションするコンテナ
├── mysql
│   ├── Dockerfile           # MySQLコンテナ
│   ├── conf.d
│   │   └── my.cnf
│   └── data
└── production
    └── Dockerfile           # 本番用のAPIサーバコンテナ

作りたいDockerコンテナごとにディレクトリを作成し、その中にDockerfileを置くようにしています。
こうすることでDockerfileのファイル名を守れる(Dockerfile.prdとかしなくていい)ようになってディレクトリ構成がキレイになるので、良いかなと思っています。

プロジェクトルートにdocker-compose.ymlDockerfileを置くプロジェクトが多いと思いますが、それと比べて本記事のディレクトリ構成を取るデメリットとしては、docker-compose upしたりするときのコマンドが複雑になる(ディレクトリを移動したり-fオプションでファイル名を指定したり)ことが挙げられます。
その点については、後述のMakefileで改善したいと思います。

各コンテナの簡単な解説

ここでは、Dockerfileで定義した各コンテナの用途と使い分けについて説明していきます。
等幅フォントになっているところにGitHubへのリンクが貼られていたりするので、適宜参照してください。

developmentproduction

developmentはローカル開発環境で用いるAPIサーバ用のコンテナで、テストでも用いられ、docker-compose.ymldocker-compose.test.ymlで立ち上げます。
productionは本番環境で用いるAPIサーバ用のコンテナで、基本的にCIでイメージをビルドしてデプロイするときに用いられます。

分けた理由としては、主にローカル開発環境と本番環境では求められるコンテナの特性が異なるためです。
例えばローカル開発では、ファイルの変更に追従してもらうためにローカルのファイル群がコンテナにマウントされることが多いため、Dockerfile内でライブラリの依存を解決する必要はありません。
また、ファイルの変更をホットリロードしてローカルサーバを立ち上げ直してほしいため、ホットリロードできるライブラリを使いたかったりもします。
逆に本番環境では、ファイル群をマウントすることはないため、Dockerfile内で依存を解決する必要があったり、シングルバイナリで動作するというGoの特性を活かすためにビルドしたバイナリを起動するだけのコンテナで良かったりもします(goがインストールされてなくても問題ない)。
そのため、マルチステージビルドを使ってバイナリをビルドするステージと、そのバイナリを起動するステージに分けています。

mysqlmigration

mysqlはローカル開発環境で用いるMySQLサーバ用のコンテナで、テストでも用いられ、docker-compose.ymldocker-compose.test.ymlで立ち上げます。
文字コードにutf8mb4を使うために、公式のMySQLイメージそのままではなく、別途設定ファイルを読み込むDockerfileを作っています。

migrationはローカル開発環境に立ち上がっているMySQLサーバのDBスキーママイグレーションを行うコンテナで、docker-compose.ymlで立ち上げます。
また、migrationは、本番で稼働しているAmazon RDSなどのDBサーバをマイグレーションするECS Taskのコンテナとしても使われる想定をしているため、Dockerfileを分けています。
本番環境のDBを異なる方法でマイグレーションする場合はマイグレーションの機能自体をローカル開発用のAPIサーバコンテナに入れてしまっても良いかもしれません。

3. Makefileに開発で使うコマンドをまとめる

続いて、前節で作ったDockerまわりを利用するためのコマンド群をMakefileにまとめることで、開発効率化を図りつつ、dockerディレクトリにまとめることで煩雑になってしまったコマンドを簡略化したいと思います。
コマンドの使い方はファイル内に書いています。
ちなみに、↓のようなMakefileの特性(?)を利用して作っています。

  • make local startのようにジョブを複数指定すると順番に実行してくれる特性を利用して、1つ目のジョブでは実行するコマンドを変数に代入し、2つ目のジョブでそのコマンドにサブコマンドをつけて実行する、ということを実現
  • testでしか代入しない変数などを使ってローカル開発(local)とテスト(test)のコマンドを抽象化
  • cd docker; ...のように書くとdockerディレクトリに移動してからコマンドを実行してくれる

4. CircleCIでテストおよびマイグレーション・デプロイを自動化

このような設定ファイルを使うことで、テストやマイグレーション・デプロイを自動化します。

各ジョブの簡単な解説

ここでは、設定ファイルに定義した各ジョブの用途を簡単に説明していきます。

cachetest

このあたりは特に説明する必要はない気がしますが、よくあるテストを実行するジョブと、テストに用いるコンテナイメージのビルドをキャッシュしているジョブです。
イメージキャッシュのキーに何を使うかが悩みどころになりそうですが、今回はテストに使うコンテナを定義するDockerfileとテスト用コンテナを立ち上げるdocker-compose.test.yml、そして依存関係をlockしているgo.sumchecksumを取れば十分だと思います。
MySQLコンテナはコンテナが立ち上がってもMySQLサーバがConnection readyになっていない場合があるので、jwilder/dockerizeを使って待機しています。

migratedeploy

今回はECS Taskを用いて本番環境で稼働しているAmazon RDSなどをスキーママイグレーションすることを想定しているため、migrateジョブではマイグレーション用のイメージをビルドしてTaskを実行したりします。
そのとき、直前のマージコミットの中にマイグレーションファイルの変更が含まれていたらマイグレーションを実行するようにしています。
また、マイグレーションとAPI変更を同じPull Requestで行っても問題ないように、このmigrateジョブはdeployジョブよりも先に実行します。

deployジョブでは、APIサーバの本番用のイメージをビルドし、Amazon ECSのServiceなどにデプロイします。
また、先にマイグレーションが行われているはずですが、migrateジョブのECS Taskが成功していることを確認してからAPIサーバのデプロイを行いたいため、migrateジョブとdeployジョブの間にapproveジョブを挟んでいます。
CircleCIでは、Workflowのジョブにtype: approvalを指定すると、CircleCIのワークフロー画面からApproveしないとWorkflowを先に進めない、というジョブを作ることができます。
これを利用して、成功を確認してからデプロイを進める、ということを実現しています。
Approveするのを忘れることが想定されるため、Slackなどにリマインダーなどを送ることを検討してもいいかもしれません。

おわりに

いかがでしたでしたか?参考になれば幸いです。

最初はこのくらいの最小構成から始めて、制作物の要件変更やチーム開発の状況に合わせて最適化していくのが良いのではないでしょうか?
こうしたほうがいいんじゃないか、とかあればコメントとかPull Requestをいただけると嬉しいです。

14
12
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
14
12