既存アプリ(Rails6)にDocker導入

# はじめに
最近オリジナルアプリにDocker開発環境を導入したので、導入までに学習した所をこの記事にまとめたいと思います。

# Dockerとは

コンテナ技術を使ったアプリケーションの実行環境の構築や運用ができるオープンソースソフトウェアのこと。
コンテナ技術により、コンテナ上の環境は個別のサーバーのように扱うことができる。そのためホスト上とは別の環境を作ることができる。ホストOSとリソースを共有しているため、高速かつ軽量な仮想化を実現している。

# Dockerの利点
インフラ環境をコードに落とし込み、そのコードをもとに開発環境を作れる。そのため、簡単に環境構築を行うことができる。さらにそのコードを共有すれば他の開発者と同じ環境を作れる。開発環境と同じ本番環境を作ることもできるので、開発環境と本番環境の違いによる不具合をなくすこともできる。
DockerではDockerfileと呼ばれるファイルに、インフラの構成情報を記述していく。

# Dockerfileとは
アプリの開発環境やインフラの構成情報をコードとして記述したもの。Dockerfileにかかれたコードをもとにイメージを作成し、コンテナを起動する。コンテナ内がDockerfileをもとに作られた環境になっている。このコンテナ内で開発を行っていく。
イメージがコンテナの設計図でコンテナがイメージからできた実体(インスタンス)のようなイメージ。
イメージはDocker Hub上でたくさん公開されている。

# 今回Dockerを開発環境に導入する理由
現在、オリジナルアプリケーションでGitHub Actionsを使いCI/CDを導入しています。
CI(継続的インテグレーション)は自動テストでコードの品質を担保してくれますが、開発環境とテストを実行するステージング環境が現状だと異なってしまっています。その部分を同じ環境にしてコードの信頼性を上げたかったので、Dockerを導入します。

# Docker導入

### 実行環境
・ macOS
・ Docker 20.10.7
・ docker-compose 1.29.2
・ Ruby 2.6.7
・ Rails 6.1.4

## ①Dockerfile作成
Dockerfileはベースイメージからコマンドを重ねるように環境を構築していく。

```dockerfile:Dockerfile
# ベースイメージ
FROM ruby:2.6.7
# Railsを使うために必要なコンポーネントを環境内にインストールする
RUN apt-get update -qq \
  && apt-get install -y nodejs yarn postgresql-client
# myappディレクトリを作成し、移動する
WORKDIR /myapp
# gemfileとgemfile.lockをコピーして既存アプリと同じgemをインストールする
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
# 既存アプリのコードをコンテナ内のmyappディレクトリ以下にコピーする
COPY . /myapp
# docker run時にserver.pidファイルを削除する設定
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
# コンテナ起動時に公開することを想定されているポートを記述、あったほうが親切
EXPOSE 3000
# docker run時にrailsサーバーを起動
CMD ["rails", "server", "-b", "0.0.0.0"]
```

以下がserver.pidを削除するシェルスクリプト。

```sh:entrypoint.sh
#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

```

## ②docker-compose.ymlファイルを作成
DBはPostgreSQLを使用する。
Docker Containerは1コンテナでは1プロセスを動かす設計となっているため、DBはアプリケーションサーバとは別のコンテナとして作成し管理する必要がある。
そのため、複数のコンテナをまとめて管理できるdocker-compose.ymlファイルを作成していく。

```yml:docker-compose.yml
# composeファイルのバージョン
version: "3.9"
# servicesの下に使うコンテナを記述
services:
  db:
    image: postgres
    # volumesにより(ホスト：コンテナ)のデータを同期する
    # コンテナを消してもホスト上にデータが同期されるからDBのデータは残る
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: password
  web:
    # カレントディレクトリにあるDockerfileをwebコンテナのイメージに使用
    build: .
    # docker-compose コマンドで発動。server.pidファイル削除し、railsサーバー起動。
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    # カレントディレクトリのファイルとコンテナ内のファイルを同期している
    # コードを変更すればコンテナ内のコードも変更される
    volumes:
      - .:/myapp
    # "ホストのポート番号:コンテナ内のポート番号"
    # コンテナの3000番ポートでrailsサーバが起動しているので、
　　　　　　 # ローカルの3000番ポートと繋ぎローカルでも確認できるようにする。
    ports:
      - "3000:3000"
    # コンテナの起動順序と依存関係を記述する。
    # この場合、コンテナの起動順はdb→web、しかし完全にdbが起動してからwebを起動するのではない
    depends_on:
      - db
```

## ③Dockerfileイメージを作成
docker-compose.ymlファイルの`build: .`からカレントディレクトリのDockerfileを探しwebコンテナのもとになるイメージを作成する。

```zsh
% docker-compose build
```

## ④database.ymlファイル変更
dbコンテナと通信できるようにdatabase.ymlを変更。

```yml:database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  # docker-compose.ymlでdepends_onにdbを記述しているのでhost:dbとすればdbコンテナに繋げられる
  host: db
  username: postgres
  password: password
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000

development:
  <<: *default
  database: myapp_development

test:
  <<: *default
  database: myapp_test
```

## ⑤docker-compose upでコンテナを起動
Dockerfileから作成したイメージとDocker Hubから取得してきたpostgresイメージをもとにコンテナを起動。

```zsh
% docker-compose up
```

この状態で、localhost:3000にアクセスするとdbがないエラーが出てくるためコンテナに入りDB作成。続けてテーブルも作成。

```zsh
% docker-compose run web rails db:create
% docker-compose run web rails db:migrate
```

ここまででlocalhost:3000にアクセスしてアプリが正常に動いていれば導入完了

## 参考
[既存のRailsアプリにDockerを導入する手順](https://qiita.com/kenz-dev/items/b9e716204e0cd0cea447)
[既存のRails6アプリをDocker化しつつCircleCIでシステムスペックも実行できる環境を作る](https://qiita.com/kenz-dev/items/e3d970b59bf106cab19e)
[Quickstart: Compose and Rails](https://docs.docker.com/samples/rails/)