既存アプリの開発環境にdockerを導入する【Rails6, MySQL8】

# はじめに

インフラの基本構成をAWSで整備できましたので、次の実装として
Docker導入をしました。

作成途中のアプリに初めてDockerを導入しましたので、記事に残します。

ポートフォリオ作成中の初学者のため、間違い等がございましたら
ご教示いただけますと幸いです。



# 使用技術

・Ruby: 2.7.3
・Rails: 6.1.4 
・MySQL: 8.0.25



# 基礎学習

実装前に下記教材をざあーと学習しました。

はじめにUdemy教材に手をつけましたが、開発環境にdockerを導入するのみなら
YouTube動画　＋　Qiita記事　で問題ないと思われます。

なので、山浦さんのYoutube動画「docker-compose編」まで完了したら
入門Docker、小島さんのUdemy動画を補助教材に使うのが
個人的にはオススメです。

https://www.udemy.com/course/docker-k/

https://y-ohgi.com/introduction-docker/

https://www.youtube.com/watch?v=lZD1MIHwMBY&list=PLs3eD4QT7ow6O5wyRRnRCvB7JMuq_D3-D


# 準備

・Mac版のDockerDesktopをインストール、起動させる


# 手順

今回の実装は、以下の通り

1:　Dockerfileの作成
2:　entrypoint.shの作成
3:　docker-compose.ymlの作成
4:　config/database.ymlの編集
5:　ターミナルでコマンド実行
6:　ブラウザで確認
000: MySQLへの接続



# 1: Dockerfileの作成

まず、dockerイメージをビルドする際に必要なファイルを作成する。

```Dockerfile:Dockerfile
# 既存アプリのrubyバージョンと合わせる
FROM ruby:2.7.3

# Node.jsとyarnのインストール
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
  && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
  && apt-get update -qq \
  && apt-get install -y nodejs yarn

# Dockerコンテナに作業ディレクトリを作成する
WORKDIR /自分のアプリ名

# ホスト側のGemfileとGemfile.lockをDockerコンテナ側にコピーする
COPY Gemfile ./Gemfile
COPY Gemfile.lock ./Gemfile.lock

# bundlerをインストールした後、gemをインストールする
RUN gem install bundler
RUN bundle install

# ホストのアプリケーションをdockerコンテナにコピー
COPY . /自分のアプリ名

# dockerコンテナ起動時にentrypoint.shが実行されるように設定
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

# dockerの3000番ポートを解放する
EXPOSE 3000

# Railsサーバーの実行コマンド
# (バインドを0.0.0.0とし、アクセスするipアドレスの制限をなくす)
CMD ["rails", "server", "-b", "0.0.0.0"]

```


参考：コンテナに使用したimageのOSを調べる

`apt-getコマンド`は`OSがdebian系統`であれば使用できるコマンドらしい。
なので、これを機会にimageのOSが気になり調べてみた。
MacOSでこれまで強くOSを意識したことがなかったけど、ちょっと勉強になった。

```sh:ターミナル
# 参考まで

% docker run --rm ruby:2.7.3 sh -c "cat /etc/*-release"
　　　　　　　　　　　↑　DockerHubにあがってる公式イメージ

〜〜返答〜〜

Unable to find image 'ruby:2.7.3' locally
2.7.3: Pulling from library/ruby
0bc3020d05f1: Already exists 
a110e5871660: Already exists 
83d3c0fa203a: Already exists 
a8fd09c11b02: Already exists 
14feb89c4a52: Already exists 
958d2475f181: Already exists 
379448f8a7e9: Already exists 
3944488d15f8: Already exists 
Digest: sha256:ee2b19ced1d44600963c52691b9b6975e209286e7168230d03afe7894f9c81a0
Status: Downloaded newer image for ruby:2.7.3
PRETTY_NAME="Debian GNU/Linux 10 (buster)"
NAME="Debian GNU/Linux"
VERSION_ID="10"
VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
```

# 2: entrypoint.shの作成

DockerfileのENTRYPOINTで当ファイルを参照するように設定しているので
ここも記述していく。

コンテナ起動後の実行される動作を設定するファイル。
(なのでdocker起動時に引数を渡すなら設定は必須ではないはず…)

docker公式ページ内のrailsを実装するコードで紹介されていたので、今回は実装した。

https://docs.docker.com/samples/rails/

```sh:entrypoint.sh

# bin/bashが起動する
set -e

# railsエラーを招くプロセスを削除する
rm -f /myapp/tmp/pids/server.pid

# DockerfileのCMDに動作が移る設定
exec "$@"

```


# 3: docker-compose.ymlの作成

次に、複数コンテナを起動させる為のdocker-composeに必要なファイルを作成する。

本来、dockerは１プロセス１コンテナと決まっている為、webアプリのように
webサーバーとdbサーバーを用いる構成の場合、相互を繋ぐネットワークを構成したり
別途の作業が必要で面倒。

ただ、docker-composeを使用すれば、webサーバーとdbサーバーに関する
それぞれのコンテナを作成して、良い感じに繋いでくれるので
設定が容易になる。

```yml:docker-compose.yml
# composefileのフォーマットを決める（https://docs.docker.com/compose/compose-file/）
version: '3'

# dbとwebで２つのコンテナを作成する
services:

  db:
    # イメージはmysqlのバージョン8.0を選択
    image: mysql:8.0
    # mysql8.0より認証方式が変更された為、従来設定を戻すコマンドを入れる
    command: --default-authentication-plugin=mysql_native_password
    # ホストとコンテナのデータが同期するようボリュームを設定
    volumes: 
      - ./自分のアプリ名/db/mysql_data:/var/lib/mysql
    # 環境変数を用いてrootのパスワードを設定
    environment:
      MYSQL_ROOT_PASSWORD: Rails.application.credentials.db_development[:password] 

  web:
    # カレントディレクトリでビルドさせる設定
    build: .
    # server.pidをrmコマンドで消しておく
    # サーバーを起動する
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    # ホストのアプリケーションをdockerコンテナと同期させる
    volumes:
      - .:/自分のアプリ名
    # railsとdockerの3000番ポートを解放する
    ports:
      - "3000:3000"
    # dbとの依存関係を持たせる
    depends_on:
      - db

```

補足：

volumesで設定している`./自分のアプリ名/db/mysql_data`は
ホスト側に新しくディレクトリを作成されるものなので
`任意で、パスやディレクトリ名`を決める。

`rm -f tmp/pids/server.pid`は
サーバーにプロセスが残っていると起動できないという
rails特有のエラーを発生させない為に入れている。



# 4: config/database.ymlの編集

ここでは、操作しているシェルの設定ファイルに
環境変数を書き込み、直接database.ymlに書き込まないようにしている。

なお、ターミナルからEDITOR="vi" bin/rails credentials:editコマンドを打って
機密情報をcredentials.yml側に記述する方法を採用しているが
この方法の用途は`本番環境`と説明する情報が多い。

`開発環境`なので、この方法が望ましいか分からないが
本番環境と同様に機密情報はGitHubに載ってしまうような
情報流出のリスクを防ぐためにcredentials.yml側に記述する方法で
設定しておく事にする。

加えて、先ほどdocker-compose.ymlで設定したdbが
hostになるように記述する。


```sh:ターミナル

% EDITOR="vi" bin/rails credentials:edit


~~上記コマンドでvi起動~~
（ iキーを押して、insertモードで下記を入力する）


db_development:
  password: MySQLのrootユーザーに設定しているパスワード


```

```yml:config/database.yml

default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: Rails.application.credentials.db_development[:password] 
  socket: /tmp/mysql.sock
  host: db

development:
  <<: *default
  database: アプリ名_development

test:
  <<: *default
  database: アプリ名_test

以下省略

```

以上でファイル設定は完了。



# 5: ターミナルからコマンド実行

まずは、ローカルからターミナルを実行して
webとdbの２つのコンテナを起動させる。

**`docker-compose upコマンド`**は、
・ `dockerイメージを生成するdocker-compose buildコマンド`
・ `コンテナを起動させるdocker-compose runコマンド`
を**`1コマンドで実行する`**もの。

```sh:ターミナル

#　Dockerイメージを生成後、同イメージをもとにコンテナを作成/起動させる
## -dオプション：　バックグラウンドで起動
% docker-compose up -d

# 起動しているかwebコンテナの名称を確かめる
## コンテナはwebとdbの２つが起動している
% docker-compose ps -a

# 起動中のwebコンテナに接続する
## bashシェルを用いてインタラクティブモードで接続している
% docker container exec -it webコンテナ名 bash

```

ここまでのコマンド実行では、
仮想環境にあるコンテナにはデータベースが存在しない。

その為、webコンテナ(Rails)内に入った状態で、
DBに関わるのrailsコマンドを実行していく。

```shell:webコンテナ内

# データベースの　新規作成/マイグレーション/テスト用データの投入　を行う
rails db:create
rails db:migrate
rails db:seed

```

# 6: ブラウザで確認

![スクリーンショット 2022-01-07 13.08.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1380401/48c3a0d5-b4d6-aad3-c22d-cf944596ec40.png)

あとはブラウザで「localhost:3000」を入力して表示されるかを確認する。

ローカルで、rails sコマンドを実行しなくとも
dockerが仮想環境上でアプリを表示してくれているはず！



# 000: MySQLへの接続

データベースは以前まで使用していたデータベースとは
別の場所（コンテナ内）にある。

その為、bashを指定してインタラクティブモードで
作成したデータベースのコンテナにログインする。

なお、コンテナへのログイン後にmysqlの接続で入力するパスワードは
docker-compose.ymlで設定したパスワードをそのままコピペする。

```sh:ターミナル

# 起動中のDBコンテナ名を確認
% docker-compose ps

# 起動中のDBコンテナにbashシェルでインタラクティブモードとしてログイン
% docker container exec -it データベースのコンテナ名 bash

# rootユーザーでログインすることを宣言
root@1234567890:/# mysql -u root -p

# パスワードをdocker-compose.ymlからコピペ
Enter password: Rails.application.credentials.db_development[:password]

```



# 補足情報

上記実装では、Dockerのwebコンテナ内で
RSpecコマンドをしてもこれまで通りテストをパスできません。
(dbコンテナではなく、webコンテナである事に注意)

厳密にいうと、部分的にRSpecのmodelテスト等はパスするが
chromeなどのブラウザ機能をコンテナに入れれていないため
RSpecのsystemテストがパスできません。

加えて、local内でもrspecコマンドで実行してもテストは通りません。
理由は、今回編集したdatabase.ymlの接続先hostをdb(dockerのdbコンテナ)としているため
接続が許されるweb(dockerのwebコンテナ)からしかアクセスできない設定しているから…
だと思われます。

↑　初学者のため、仮説です。

そのため、私はCI/CDパイプラインを構築できるCircleCIを導入し
CircleCI内で自動テストを起動させる実装をしました。

下記記事でまとめておりますので、宜しければ参考にしてください。

https://qiita.com/Shuhei_Nakada/items/a1cfae77e3463a5b9ee8



# 参考記事

https://qiita.com/takuma_0625/items/2a25dce50ad309993fa3


https://docs.docker.com/samples/rails/


https://www.youtube.com/watch?v=ltDdZAJli8c&list=PLs3eD4QT7ow6O5wyRRnRCvB7JMuq_D3-D&index=9

https://hub.docker.com/



# 終わりに

本番環境（awsのEC2）では、applicationサーバーにnginxを使用しているんですが
開発環境も同じ実装にすべきか悩み中です。

開発環境はアクセス数が高くなって負荷が掛かるとは思えないから
applicationサーバーをかます必要があるのかな…と感じつつも
Qiita記事でdockerでnginx仕様の実装も見られるので、
意味のない実装ではないんだろうと想像しています。

最後までお読みいただき、ありがとうございました。