DockerとRuby on Railsを使った基本的なトラブルシューティング

Ruby on Railsに再度親しむために、Open Food Networkでボランティアを始めたところです。彼らはオープンソースで、非営利の、食品店のためのオンラインマーケットプレイスです。独立した農家をハブや流通業者と結びつけるためのソフトウェアを提供しており、そのすべてがRuby on Railsで動いています。

多くのウェブアプリがそうであるように、彼らは物事を簡単にするためにDockerをセットアップしています。Dockerは、特定のソフトウェアパッケージがインストールされていなかったり、間違ったバージョンがインストールされていたりして、他の開発作業を悩ませる「私のコンピュータで動作する」という問題を解消するのに役立ちます。

Dockerは、OSの特定のバージョンを実行するシステムイメージをコンピュータに「ドッキング」させるソフトウェアの一部で、よく知られていません。Dockerは、ビルド時に特定のパッケージをインストールするようにカスタマイズすることができます。その上でRSpecを使ったテストを行ったり、サーバーを立ち上げて開発版のサイトを操作して、自分のコードが期待通りの効果を発揮するかどうかを確認したりすることができるのです。

Dockerにコマンドを渡す必要があるため、このセットアップでは、最初はトラブルシューティングが少し難しいと感じていました。単純にbashなどのターミナルで実行することはできません。また、コマンドを実行するためには、サーバーが稼働している必要があります。これは、多くのフラストレーションにつながる可能性があります。

## DBに接続する
私の最初の大きなハードルは、データベースに接続し、「ボンネットの下」で起こっていることをより直接的に確認することでした。幸運なことに、Dockerはデフォルトでポートをlocalhostに転送するので、あたかも自分のシステムで動いているかのようにアクセスすることができます。

もしそうではなく、何らかの理由でDockerイメージがlocalhostに転送されない場合でも、Dockerイメージのipをかなり簡単に見つけることができます。まず、ローカルで動作しているすべてのイメージを見てみます。

`docker ps`

これにより、現在実行中のイメージのリストが得られ、以下のようになります。

```
CONTAINER ID   IMAGE                     COMMAND                  CREATED        STATUS          PORTS                                       NAMES
697b8685d509   openfoodnetwork_web       "bash -c 'wait-for-i…"   12 hours ago   Up 10 seconds   0.0.0.0:3000->3000/tcp, :::3000->3000/tcp   openfoodnetwork_web_1
8ec13ea014c2   openfoodnetwork_worker    "bash -c 'wait-for-i…"   12 hours ago   Up 11 seconds                                               openfoodnetwork_worker_1
9171e4c62f72   postgres:10.19            "docker-entrypoint.s…"   12 hours ago   Up 31 minutes   0.0.0.0:5432->5432/tcp, :::5432->5432/tcp   openfoodnetwork_db_1
5015b1895607   openfoodnetwork_webpack   "./bin/webpack-dev-s…"   12 hours ago   Up 11 seconds   0.0.0.0:3035->3035/tcp, :::3035->3035/tcp   openfoodnetwork_webpack_1
9067a38ec683   redis                     "docker-entrypoint.s…"   12 hours ago   Up 11 seconds   6379/tcp                                    openfoodnetwork_redis_1
```

ここから、以下のコマンドで、任意のイメージのIPアドレスを調べることができます。

```
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' *container_id*
```

そこから、お気に入りのデータベースビューワーに詳細を差し込むことができます。私の個人的なお気に入りは[Dbeaver](https://dbeaver.io/)で、定期的に更新され、データベースへの接続が非常に簡単になります。

## 必要なRSpecだけを実行する
もうひとつの問題は、Open Food Networkに付属している膨大なテストスイートです。テスト・スイート全体を実行するのに20～30分かかることもあり、ちょっとした変更をするたびに実行するのは不可能です。それよりも、必要なテストだけを実行するほうがずっとよいでしょう。

これを行うには、特定の行番号を指定してrspecを実行します。次のコマンドは、`enterprise_relationship_spec.rb`の258行目から始まるspecを実行するものです。

```
bundle exec rspec spec/models/enterprise_relationship_spec.rb:258
```

Dockerイメージの中で実行するには、2つのオプションがあります。

#### 1. スクリプトを使う
RSpecを実行するたびに、Dockerイメージをセットアップし、破棄するスクリプトを作成することができます。Open Food Networkでは、このスクリプトを公開しているので、コピーして使ってみてください。

上記のスクリプトを使うには、先ほどのコマンドの前に、`docker/run`を追加するだけです。

```
docker/run bundle exec rspec spec/models/enterprise_relationship_spec.rb:258
```

#### 2. インタラクティブターミナルを起動する
また、ローカルホストのターミナルと同じような対話型ターミナルでコンテナを起動することもできます。
```
docker exec -it $(docker-compose -f docker-compose.yml ps -q web) /bin/bash
```
対話型端末を手に入れたら、ローカルホストの端末を使っているように普通にコマンドを実行できます。
```
bundle exec rspec spec/models/enterprise_relationship_spec.rb:258
```
## ライブサーバーで`binding.pry`を使う
Railsのコードの任意の場所で`binding.pry`を使用すると、コードの実行を停止してインタラクティブなターミナルセッションを開始することができます。ローカルマシンでrailsサーバを実行している場合、サーバを起動したターミナルウィンドウで対話するだけでよいのです。

しかし、Dockerイメージの場合は少し事情が異なります。`binding.pry`を使用すると、サーバーの実行は停止しますが、そのサーバーと対話することはできません。そのため、コマンドを実行して何が起こっているかを確認するためには、そのイメージにアタッチする必要があります。

まず、ウェブサーバーを動かしているイメージを見つける必要があります。そのためには、実行中のDockerイメージをリストアップする必要があります。
```
docker ps
```
ここでも、実行中のイメージの一覧を取得します。
```
CONTAINER ID   IMAGE                     COMMAND                  CREATED        STATUS          PORTS                                       NAMES
697b8685d509   openfoodnetwork_web       "bash -c 'wait-for-i…"   12 hours ago   Up 10 seconds   0.0.0.0:3000->3000/tcp, :::3000->3000/tcp   openfoodnetwork_web_1
8ec13ea014c2   openfoodnetwork_worker    "bash -c 'wait-for-i…"   12 hours ago   Up 11 seconds                                               openfoodnetwork_worker_1
9171e4c62f72   postgres:10.19            "docker-entrypoint.s…"   12 hours ago   Up 31 minutes   0.0.0.0:5432->5432/tcp, :::5432->5432/tcp   openfoodnetwork_db_1
5015b1895607   openfoodnetwork_webpack   "./bin/webpack-dev-s…"   12 hours ago   Up 11 seconds   0.0.0.0:3035->3035/tcp, :::3035->3035/tcp   openfoodnetwork_webpack_1
9067a38ec683   redis                     "docker-entrypoint.s…"   12 hours ago   Up 11 seconds   6379/tcp                                    openfoodnetwork_redis_1
```
今回は、実行中のウェブサーバーにアタッチすることにします。私の場合、`openfoodnetwork_web`です。
```
docker attach 697b8685d509
```
そこから、ローカル変数に問い合わせたり、レコードを引き出したりして、何が起こっているかを確認し、問題のトラブルシューティングを行うことができます。上で紹介した2つの方法のいずれかを使ってspecを実行している場合、Rspecコマンドの実行に使用したターミナルウィンドウでインタラクティブターミナルが正常に開始されることをお伝えしておきます。

## まとめ
以上が、Dockerで動作するRailsアプリケーションのトラブルシューティングを試みた際に学んだ基本的なヒントの一部です。もちろん、このようなアプリケーションをセットアップしてトラブルシューティングする方法は数多くありますが、私が最も便利だと感じた方法はこれです。

皆さんは、Dockerで動作するRailsアプリケーションのトラブルシューティングを行う際に、どのような方法があるかご存知でしょうか？