ローカルRails6アプリのDocker環境への移行（ついでにMysql導入）

#はじめに
ローカル環境でrailsを使用し開発を進めていましたが、今後のデプロイを考慮し、環境をDockerによるコンテナで管理することとしました。
初めてでかなり時間がかかってしまったため、備忘録として残します。
同様の環境構築が必要な方の参考になれば幸いです。

#環境構築にあたっての目標
* ローカルの環境をコンテナ化し、本番環境構築時に容易にしたい。
* Dockerのイメージは、効率化を考慮し、できるだけ軽量化したい。
* bundleやyarnのモジュールを永続化することにより、起動にかかる時間を減らしておきたい。
* DBはsqliteを使用していたので、ついでにMysqlにしたい。

#環境
###コンテナの構成

* db
* web
* webpacker←webpack_dev_server実行用

###バージョン
* ruby 2.7.2
* Rails 6.0.3
* Mysql 8.0.22

##一覧
今回変更した箇所のみ記載しています。
ルートディレクトリはrailsアプリのルートディレクトリとしています。

```
.
├── config
│   ├── database.yml    #更新
│   └── webpacker.yml   #更新
├── docker
│   └── rails
│       └── Dockerfile  #新規作成
├── docker-compose.yml  #新規作成
├── .env                #新規作成
└── Gemfile             #更新
```

##Dockerfile

```dockerfile:docker/rails/Dockerfile
#軽量化のため、alpineを使用。
FROM ruby:2.7.2-alpine3.12

ENV TZ="Asia/Tokyo" \
    LANG="C.UTF-8" \
    APP_ROOT="/app" \
    ENTRYKIT_VERSION="0.4.0"

WORKDIR $APP_ROOT

#ENTRY KITの導入
RUN wget https://github.com/progrium/entrykit/releases/download/v${ENTRYKIT_VERSION}/entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
    && tar -xvzf entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
    && rm entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
    && mv entrykit /bin/entrykit \
    && chmod +x /bin/entrykit \
    && entrykit --symlink

RUN apk update \
&&  apk add --no-cache \
    alpine-sdk \
    bash \
    build-base \
	mysql-client \
    mysql-dev \
    nodejs \
    tzdata \
    yarn

COPY . $APP_ROOT

ENTRYPOINT [ \
  "prehook", "bundle install -j4 --quiet", "--", \
  "prehook", "yarn install --check-files --ignore-optional", "--"]
```

###イメージの軽量化

今後の作業の効率化のため、イメージの軽量化するために、alpineを使用しています。
約1.6GB→約400MBまで容量が減りました。

マルチステージビルドを使用し、nodejsのインストールを別にすることで、さらにイメージの軽量化ができそうです。
ある程度軽量化できたので、今回はここまでとしました。

###ENTRY KITの導入

ENTRY KITを使用し、コンテナ起動時に`yarn install`、`bundle install`を実行するようにしています。
調べた記事では、Dockerfile内で実行しているものが多かったですが、以下の問題があるため今回は見送りました。

* gem等の追加が必要となった時は、コンテナのbuildからやり直す必要がある。
* コンテナをdownすると、installしたgem等が保持されない。

そのためコンテナ起動時の実行かつ、モジュールを永続化することで、上記の問題を解決する構成としました。

##docker-compose.yml
```yaml:docker-compose.yml
version: '3'

services:
  db:
    image: mysql:8.0.22
    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci
    ports: 
      - '3306:3306'
    volumes:
      - db_data:/var/lib/mysql
    environment:
      MYSQL_DATABASE: ${DATABASE}
      MYSQL_ROOT_PASSWORD: ${ROOTPASS}
      MYSQL_USER: ${USERNAME}
      MYSQL_PASSWORD: ${USERPASS}

  web: &app_base
    build:
      context: .
      dockerfile: ./docker/rails/Dockerfile
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b 0.0.0.0"
    depends_on:
      - db
    ports:
      - "3000:3000"
    volumes:
      - .:/app
      - gem_modules:/app/vendor/bundle
      - node_modules:/app/node_modules
    tty: true            #binding.pry実行用
    stdin_open: true     #binding.pry実行用
    environment:
      WEBPACKER_DEV_SERVER_HOST: webpacker    #webpack_dev_server実行用のコンテナを指定
      BUNDLE_APP_CONFIG: ./.bundle
      NODE_ENV: development
      RAILS_ENV: development

  webpacker:             #webpack_dev_server実行用のコンテナ
    <<: *app_base
    command: bash -c "bundle exec bin/webpack-dev-server"
    depends_on:
      - web
    ports:
      - '3035:3035'
    tty: false           #binding.pry不要なのでfalseへ変更
    stdin_open: false    #binding.pry不要なのでfalseへ変更
    environment:
      BUNDLE_APP_CONFIG: ./.bundle
      WEBPACKER_DEV_SERVER_HOST: 0.0.0.0
      NODE_ENV: development
      RAILS_ENV: development

volumes:
  db_data:
  gem_modules:
  node_modules:
```

* DBのユーザ情報は別ファイルとしておきたかったので、変数化し`.env`に記載しています。
* コンテナをdownさせても、DB、モジュールのデータが失われないよう、永続化しています。

##config/database.yml
```diff:database.yml
 default: &default
-  adapter: sqlite3
+  adapter: mysql2
+  username: app
+  password: password
+  host: db    #サービス名を指定

 development:
   <<: *default
-  database: db/development.sqlite3
+  database: mysql_development
```

##config/webpacker.yml
```diff:webpacker.yml
 default: &default
   dev_server:
-    host: localhost
+    host: webpack    #サービス名を指定
```

##Gemfile
```diff:Gemfile
-gem 'sqlite3', '~> 1.4'
+gem 'mysql2', '0.5.3'
```

##.env
```text:.env
DATABASE=mysql_development
USERNAME=app
USERPASS=password
ROOTPASS=password
```

##疑問点・解消できなかったこと

###コンテナ初回起動時エラー（stack Error: getaddrinfo EAI_AGAIN nodejs.org）
コンテナ初回起動時の`yarn install`実行中に、エラーが発生することがあります。
調べた結果、原因はDNSで名前解決できていないことだそう。

解決方法と思われるホスト側のDNS変更や、`docker-compose.yml`でのDNSの指定等試しましたが、解決しませんでした。
2回目起動時はエラーが発生せず、コンテナの再起動を解決方法としている記事もあったため、このままとしておきます。

```
error /app/node_modules/node-sass: Command failed.
web_1        | Exit code: 1
web_1        | Command: node scripts/build.js
web_1        | Arguments: 
web_1        | Directory: /app/node_modules/node-sass
web_1        | Output:
web_1        | Building: /usr/bin/node /app/node_modules/node-gyp/bin/node-gyp.js rebuild --verbose --libsass_ext= --libsass_cflags= --libsass_ldflags= --libsass_library=
web_1        | gyp info it worked if it ends with ok
web_1        | gyp verb cli [
web_1        | gyp verb cli   '/usr/bin/node',
web_1        | gyp verb cli   '/app/node_modules/node-gyp/bin/node-gyp.js',
web_1        | gyp verb cli   'rebuild',
web_1        | gyp verb cli   '--verbose',
web_1        | gyp verb cli   '--libsass_ext=',
web_1        | gyp verb cli   '--libsass_cflags=',
web_1        | gyp verb cli   '--libsass_ldflags=',
web_1        | gyp verb cli   '--libsass_library='
web_1        | gyp verb cli ]
web_1        | gyp info using node-gyp@3.8.0
web_1        | gyp info using node@12.20.1 | linux | x64
web_1        | gyp verb command rebuild []
web_1        | gyp verb command clean []
web_1        | gyp verb clean removing "build" directory
web_1        | gyp verb command configure []
web_1        | gyp verb check python checking for Python executable "python2" in the PATH
web_1        | gyp verb `which` succeeded python2 /usr/bin/python2
web_1        | gyp verb check python version `/usr/bin/python2 -c "import sys; print "2.7.18
web_1        | gyp verb check python version .%s.%s" % sys.version_info[:3];"` returned: %j
web_1        | gyp verb get node dir no --target version specified, falling back to host node version: 12.20.1
web_1        | gyp verb command install [ '12.20.1' ]
web_1        | gyp verb install input version string "12.20.1"
web_1        | gyp verb install installing version: 12.20.1
web_1        | gyp verb install --ensure was passed, so won't reinstall if already installed
web_1        | gyp verb install version not already installed, continuing with install 12.20.1
web_1        | gyp verb ensuring nodedir is created /root/.node-gyp/12.20.1
web_1        | gyp verb created nodedir /root/.node-gyp
web_1        | gyp http GET https://nodejs.org/download/release/v12.20.1/node-v12.20.1-headers.tar.gz
web_1        | gyp WARN install got an error, rolling back install
web_1        | gyp verb command remove [ '12.20.1' ]
web_1        | gyp verb remove using node-gyp dir: /root/.node-gyp
web_1        | gyp verb remove removing target version: 12.20.1
web_1        | gyp verb remove removing development files for version: 12.20.1
web_1        | gyp ERR! configure error 
web_1        | gyp ERR! stack Error: getaddrinfo EAI_AGAIN nodejs.org
web_1        | gyp ERR! stack     at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:66:26)
web_1        | gyp ERR! System Linux 4.19.121-linuxkit
web_1        | gyp ERR! command "/usr/bin/node" "/app/node_modules/node-gyp/bin/node-gyp.js" "rebuild" "--verbose" "--libsass_ext=" "--libsass_cflags=" "--libsass_ldflags=" "--libsass_library="
web_1        | gyp ERR! cwd /app/node_modules/node-sass
web_1        | gyp ERR! node -v v12.20.1
web_1        | gyp ERR! node-gyp -v v3.8.0
web_1        | gyp ERR! not ok 
```

###マルチステージビルドでのイメージの軽量化
時間短縮等、更なる効率化が必要となった時に試してみようと思います。

#最後に
新規のRailsアプリをDockerで環境構築する記事はあったのですが、既存のRailsアプリをコンテナ化する記事はあまりなかったので苦労しました。
開発環境として一旦構築したので、テスト環境や本番環境の分け方・構築も検討して取り組んでいきます。

他にも以下の内容も取り組んでいきたいので、実施できれば記事にしようかと思います。

* webサーバとしてnginxの導入
* CI/CDの導入
* AWS上での環境構築

###1/26追記
このままではRspec×Capybara×ChromeDriverが正常に動かなかったので別の記事を書きました。。。
・[Docker環境でRspec×Capybara×ChromeDriverを動作させる - Qiita](https://qiita.com/ryohei_kh/items/2249c13d30648f50b9c8)

#参考にさせて頂いた記事
**・[開発しやすいRails on Docker環境の作り方 - Qiita](https://qiita.com/joker1007/items/9f54e763ae640f757cfb)**
**・[Rails newからproductionモードで動くようになるまで - Qiita](https://qiita.com/ham0215/items/c0f9a4a0897d805cf886)**
**・[Rails 6.0 × MySQL8でDocker環境構築（Alpineベース） - Qiita](https://qiita.com/fujisawatk/items/41176d0155cb738ebbf8)**
**・[Docker Composeのvolumesを使ってもっと効率的に - Qiita](https://qiita.com/tanaken0515/items/a564d2c46b2942b83ebb)**
**・[【Dockerfile全解説】Rails本番環境のための一番シンプルなDockerイメージを作る - Qiita](https://qiita.com/notozeki/items/ac1bf72cf44b7ed6a6ad)**
**・[【Docker】Rails開発で知っておきたい！gemの永続化による作業効率アップの話 | Enjoy IT Life](https://nishinatoshiharu.com/datavolume-for-gem/)**
**・[docker-composeでの環境構築で留意しておきたいところ - Qiita](https://qiita.com/undrthemt/items/47d382ce24f68e90f96a)**
**・[Rails + webpacker on Dockerの環境をdocker-composeで構築する - RoadMovie](https://tech-mr-myself.hatenablog.com/entry/2019/04/18/213320)**