はじめに
最近よく聞く悩み
最近身の回りでベータ版に相当するアプリケーションを開発をしている人たちから、以下のような悩みを聞きます。
- ローカルでは動いた機能が、本番リリースしたら動かなかった ...
- 全員 master ブランチで開発してるけど、これでいいの ... ?
- Docker とかよく聞くけど、使ったほうがいいの ... ?
最近はプログラミング学習サービスが豊富で、誰でもアプリケーションのコーディングを始められます。
しかし、実際にアプリケーションを開発しようとすると、すぐに上記のような悩みにぶつかります。
Git、Heroku、CircleCI、Docker などの単語は聞いたことがあっても、結局何をどう使うのが適切かを理解するハードルはちょっと高いです。
また、こういった状況を解決するための環境整備は、実装のための時間と比べて後回しにされてしまいがちです。
しかし、こういった環境整備の実施は非常にコスパが良いです。
「なぜか動かない」といった本来不要な悩みの解決に使う時間を削減し、アプリケーションの実装に割く時間を増やすことができます。
また、開発者の心理的にもめちゃくちゃ楽になります。
ということで、「ベータ版程度の開発であってもとりあえずこの辺は整備しておくべし」という項目をまとめました。
この記事の対象者
この記事は、以下に当てはまるような方を対象としています。
- プログラミングを独学で勉強し、Web アプリケーションを作り始められる
- ちゃんとしたチームでアプリケーションを開発した経験はない
- 自動テストとかはよく分からない
対象フェーズ
プロダクト開発では、アルファ版、ベータ版、正式版などとプロダクトを育てていくことがあります。
Wikipedia の アルファ版、ベータ版 の項を見ると ...
アルファ版は、開発初期において性能や使い勝手などを評価するためのテスターや開発者向けの版である。
また
ベータ版(ベータばん、β版)とは、正式版をリリース(公開)する前にユーザーに試用してもらうためのサンプルのソフトウェアである。
と書かれています。
この記事は、「正式版をリリース(公開)する前にユーザーに試用してもらうためのサンプル」であるベータ版のアプリケーションの開発を対象としています。
本番環境が長時間ダウンしても問題ないようなアルファ版サービスや、より高い品質を求められるような正式版サービスは対象外です。
全体像
以後で説明していく内容をまとめると、下図のような環境が出来上がります。
この図の内容を、以下の順で説明していきます。
- ローカル環境
- バージョン管理
- 環境構成
- CI / CD
※ この環境はあくまで一例ではあり、状況によって最適化できる部分はたくさんあります。
ローカル環境
コーディング用の端末としては、Windows や Mac の PC を使うことがほとんどでしょう。
まずはその環境をどう整備すべきかを説明していきます。
PC の OS やツールは統一すること
開発者の PC が Windows、Mac と人によって異なると、環境構築手順も 2 倍必要になります。
PC の OS が統一されていれば、1 つの手順でみんな構築できます。
可能な限り、PC の OS は統一しましょう。
Vagrant や Docker を使えば大丈夫と思うかもしれませんが、そのインストール手順が異なったり、デフォルトの設定の違いで動かないといった問題が発生することは非常に多いです。
Docker を使っていても Windows と Mac の両方では動かないことがしばしばあります。
DB クライアント (Sequel Pro など) のようなツールも、できれば統一しておいたほうがいいです。
**ツールの違いで挙動が違う ... ということは普通に起こりえます。**その悩みを最初から回避しておきましょう。
ローカルの DB は Docker を使うべし
アプリケーション実装時、ローカルに DB が必要になることが多いです。
ローカルで使用する DB は、Homebrew などでインストールせずに Docker を使うことをオススメします。
Docker を使用することで、チーム内で DB のバージョンや設定の統一が容易になります。
また、メンバーが変わるたびにインストールやセットアップで手間取ることも減ります。
何かあった際の構築し直しも非常に簡単です。
ここから、例として Docker を用いた MySQL の環境構築を説明していきます。
Docker で MySQL を構築するには、以下のディレクトリ構成で 3 つのファイルを作成するだけです。
.
├── conf
│ └── charset.cnf
├── docker-compose.yml
└── docker-entrypoint-initdb.d
└── ddl.sql
各ファイルの内容は以下の通りです。
version: '3'
services:
mydb:
image: mysql:5.7.19
ports:
- 3306:3306
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_USER: user
MYSQL_PASSWORD: password
MYSQL_DATABASE: mydb
volumes:
- ${PWD}/conf/charset.cnf:/etc/mysql/conf.d/charset.cnf
- ${PWD}/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_general_ci
[client]
default-character-set=utf8mb4
CREATE TABLE users
id INT,
name VARCHAR(10);
あとは docker-compose.yaml ファイルのあるディレクトリで以下のコマンドを実行するだけです。
$ docker-compose up
これで、ローカルで起動したアプリケーションや Sequel Pro などの DB クライアントツールから localhost:3306 にアクセスすれば、DB に接続できます。
Docker でローカルに DB を構築する際のポイントは 3 つあります。
- Docker Compose を使う
- 文字コードを設定する
- 初期化用の SQL を用意する
Docker Compose を使う
上記の例では、Docker をそのまま使った場合に毎回打ち込む必要がある長いオプションリストを YAML にまとめる目的で Docker Compose を使っています。
以下のコマンドを実行したりシェルスクリプトを作成しても同じことができますが、Docker Compose を使う方が分かりやすいでしょう。
$ docker run \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=root \
-e MYSQL_USER=user \
-e MYSQL_PASSWORD=password \
-e MYSQL_DATABASE=mydb \
-v ${PWD}/conf/charset.cnf:/etc/mysql/conf.d/charset.cnf \
-v ${PWD}/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d \
mysql:5.7.19
文字コードを設定する
Docker であってもなくても、DB をインストールした際はまず文字コードに気をつけた方がいいです。
MySQL を Docker で構築する場合、上記のような charset.cnf ファイルを作成してコンテナ内に配置してあげることになります。
docker-compose.yaml の
- ${PWD}/conf/charset.cnf:/etc/mysql/conf.d/charset.cnf
の部分が、 ローカルにある charset.cnf をコンテナ内と共有する設定になります。
初期化用の SQL を用意する
MySQL の公式 Docker イメージでは、コンテナ内の docker-entrypoint-initdb.d ディレクトリに配置した SQL が、起動時に実行されます。1
DDL などはここに配置してあげれば OK ということです。
docker-compose.yaml の
- ${PWD}/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
の部分が、ローカルにある SQL をコンテナ内と共有する設定になります。
プログラミング言語の実行環境は無理に Docker にしない
プログラミング言語の実行環境まで Docker を利用することで、チーム内でのバージョンの統一やメンバー追加時の環境構築は容易になります。
しかし、プログラミング言語の実行環境まで Docker にしようとすると、ハマりどころが結構あります。
例えば、Windows のファイルのパーミッションのせいで動かない、ライブラリがアプリケーション起動のたびにダウロードし直しになる、開発用のコマンドを実行したいときはどうしたらいいか分からない、などなど ...
Docker の知識と利用するプログラミング言語の知識がある程度あれば大丈夫ですが、慣れないうちは結構苦労します。
「同じ PC で複数アプリケーションの開発を並行して進めているため、言語のバージョンがぶつかって困る」といった状況でなければ、プログラミング言語の実行環境の Docker 化は避けたほうが楽な場合もあります。2
ただし、PC にインストールするプログラミング言語のバージョンはしっかりチーム内で統一してください。
(余談) Heroku 上の環境も Docker にする必要はない
本番環境として Heroku を使う場合、Heroku 上で Docker を使うべきかという話があります。
結論としては、基本的に Heroku 上で Docker を使う必要はありません。
Docker on Heroku にしてしまうと、そのための Dockerfile の記述が必要になったり、自分たちでセキュリティを気にしなくてはならない範囲が広がったりしてしまいます。
そもそも Heroku は自動的にコンテナを作成してくれる Buildpack を利用して動いており、Docker on Heroku では、Buildpack が自動化していることを自分たちで再実装していることになります。
「Buildpack で構築される環境で提供されないミドルウェアなどが必要」といった理由がなければ、Heroku 上で Docker を利用する必要はありません。
バージョン管理
バージョン管理としては、Git が採用されることが多いでしょう。
Git を採用しても、最初はよく分からずに master 一本で開発してしまったりします。
後述する CI / CD パイプライン構築のためにも、master 一本ではない使い方を始めましょう。
とりあえず develop ブランチを用意する
後述しますが、アプリケーションの実行環境としては、ローカルと本番以外に、本番と類似した環境を最低でももう 1 つ用意することになります。
Git のブランチは、環境と一致させておくと分かりやすいです。
例えば、
- master ブランチは production 環境と一致させる
- develop ブランチは develop 環境と一致させる
- コーディングは feature/xxx ブランチで実施する
といった構成にすべきです。
図にすると、以下のようになります。
Git Flow との違い
このような Git ブランチの使用のルールを「ブランチ戦略」と言うことがあります。
ブランチ戦略としては、A successful Git branching model で紹介されている Git Flow が最も有名です。3
上図では、Git Flow から release ブランチと hotfix ブランチを除外しています。
release ブランチや hotfix ブランチは Git に慣れるまでは混乱を生みやすいため、ひとまずは上図ぐらいシンプルにしておくと良いでしょう。
feature ブランチの命名規則
feature ブランチには、命名規則を設ける場合があります。
例えば、
- 追加機能の名前をつける
- 追加機能と対応するチケット番号と紐付ける
といった命名が考えられます。個人的には、ベータ版開発のフェーズでは、ここはあまりこだわらないことをオススメします。
master ブランチ・develop ブランチや動いているコードを正しいものとして、feature ブランチがぐちゃぐちゃになるのは多少許容してもいいのではないでしょうか。
GitHub の設定を変更する
上図のようなブランチ戦略で運用するというルールを作っても、ミスによって master ブランチにコミットしてしまうことがあるかもしれません。
GitHub などの設定により、master ブランチを保護するようにしておきましょう。(参考: Configuring protected branches)
また、デフォルトのブランチを develop に変更しておくと便利です。Setting the default branch
環境構成
ベータ版程度のアプリケーション開発では、ローカルと本番の 2 環境しかないといったことがありえます。
一度リリースして終わりなら問題ないかもしれませんが、多くの場合、リリースしたアプリケーションを改修したくなるはずです。
ユーザに大きな影響を与えずに開発を進めるためには、ローカルと本番に加え、最低でももう 1 つは環境が必要です。
逆に、ベータ版程度のアプリケーションなら、ローカル・本番ともう 1 環境あれば十分です。
なぜ 3 つ目の環境が必要か
3 つ目の環境が必要な理由は、「ローカルと本番では何もかもが違うから」です。
言語・ライブラリのバージョンの違い、OS の違い、CPU アーキテクチャの違い、並列稼働の有無の違い、何か 1 つでもあれば、アプリケーションの動作は保証できません。
本番環境にリリースする前に、本番と同様の環境でテストするのは必須です。4
環境構築の基本
環境構築は、必ず再現できるようにしましょう。
理想的には Terraform などで Infrastructure as Code を実施すべきですが、その学習コストが発生するのであれば、まずはメモや画面キャプチャでも OK です。
実行したコマンド、画面から入力した設定などは、全てメモするくらいでちょうどいいです。
また、「ぐちゃぐちゃ設定を変えて、なんとか動いた」と出来上がった環境には、以後誰も手を加えられません。
何をどんな順番で設定したのか、しっかりメモを残しましょう。
また、設定変更したい場合は 1 から環境を作り直す Immutable Infrastructure のような考え方を取り入れられると、変更に強い環境が出来上がりやすいです。
並列稼働には注意
少し話は変わりますが、可用性の向上などを目的として、本番環境ではアプリケーションを複数台並列稼働させる場合があります。
例えば、下図のような構成になります。
このような構成を実現するには、いくつか注意しなければならないことがあります。
- セッションが適切に管理されていること
- バッチ処理の場合、並列実行が許容される内容であること (参考: Understanding concurrency)
これらを実現できない場合、アプリケーションの並列稼働は諦める必要があります。
また、こういった理由で本番のみでしか発生しないエラー発生を避けるため、本番と同等の環境では、並列稼働の有無も統一しておきましょう。
セッションについては別の観点もあるので、もう少し掘り下げます。
セッション管理のパターン
セッション管理の方法は、並列稼働の可否と、アプリケーション再起動時のセッション切れに影響します。
管理方法と影響は、以下のようにまとめられます。
セッション管理の方法 | 並列稼働 | 再起動時のセッション切れ |
---|---|---|
特に設定しない | × | × |
セッションアフィニティ | ◯ | × |
Redis などで外部管理する | ◯ | ◯ |
セッションを使わない | ◯ | ◯ |
特に設定せずにセッションを利用する場合、並列稼働させてしまうとまともに動作してくれません。
また、アプリケーション再起動時に利用していたユーザは、セッション切れにより異常な挙動を味わう可能性があります。
特に Heroku では、自動的に毎日アプリケーションが再起動するようになっているので、適切な対応をしないと定期的にユーザに異常な挙動を味わわせることになります。 (参考: Automatic dyno restarts)
さらには、Redis などで外部管理するかセッションを使わないかしない限り、日中の無停止リリースも非常に難しくなります。
とはいえ、これらはベータ版サービスでは少しリッチな悩みかもしれません。
こういった挙動があり得ることを把握したうえで、ひとまず無視する手もありだと思います。
CI / CD
ここまでで、ローカル環境、バージョン管理、環境構成について説明してきましたが、最後に、下図の構成の全体をつなぐ CI / CD について説明します。
CI / CD とは
ざっくり言うと、GitHub などへのコードのプッシュをトリガとして、自動テスト・自動ビルドを実行するのが CI (Continuous Integration)、さらに本番リリース直前の状態まで自動で進むのが CD (Continuous Delivery) であり、本番リリースまで自動で進むのが Continous Deployment です。
正直言って、ベータ版サービスの開発では自動テストはほとんどしていないことが多いのではないかと思います。
自動テストしていない状態では CI / CD の効果は減少してしまいますが、
- 自動化によりリリースのミスを防ぐ
- リリースの心理ハードルを下げ、小さなリリースと学習を可能にする
- 新しいライブラリ等が本番同等の環境で動くかをすぐに確認できるようになる
といった目的で、develop 環境、production 環境へのデプロイ自動化だけでも実施しておくと良いでしょう。
CI / CD のツール
図では最も有名な CI / CD ツールである Jenkins のアイコンを使っていますが、CI / CD のツールはいろいろあります。
SaaS であれば CircleCI などが有名ですし、GitLab を使っていれば GitLab CI などがあります。
また、Heroku であれば、Pipelines を利用することもできるでしょう。
CI / CD は可能な限り初期に構築するべきです。
まず最初に一通りの環境を用意して CI / CD も疎通させて、そこからアプリケーションを開発していくくらいがベストです。
テスト
CI をオススメしつつテスト自動化しないことを許すというのは、結構怒られそうな発言です。
ただ、ベータ版のアプリケーションで、しかも開発慣れしていないメンバーで、自動テストを記述するのは結構ハードルが高いと思います。
ベータ版のアプリケーションであれば、最悪テストしきれていないことを許容する手もありだと思います。
ただし、
- テストしていないパターンは動作する保証がないこと
- どこか 1 行でも直したら、予想もしない他のどこかが壊れているかもしれないこと
は、絶対に意識しておくべきでしょう。
その上で、本番リリースの前後では、特に重要な機能の正常系だけでも手動でテストしましょう。5
その場合、何を持ってテスト通過とするのか、再現性のある手順を書いておくと良いでしょう。
まとめ
いろいろ書いてきましたが、この構成を作るだけで、開発はかなり快適になります。
ベータ版アプリケーションの開発でここまでやっていれば、「ちょっとはちゃんとしているチームです」と言えると思います !