PostgreSQL（Docker）にRails（Docker）が接続できなくなったから調べてみた。（could not translate host name "db" to address: Name or service not known）

# この記事で解決したい課題
今まで「[Quickstart: Compose and Rails | Docker Documentation](https://docs.docker.com/compose/rails/)」を参考にRails on Docker開発をしてきたのですが、
2020.05.10現在同じように`rails new`を進めていると`rails db:create`で「<b>could not translate host name "db" to address: Name or service not known</b>」とエラーが起きた。

ん？今までできていたのになんでだろう？ということで調査をしてみました。

# 課題の詳細
上のサイトを参考にRuby2.7.1のイメージでRails6.0.3を動かそうとしてますと。

```yaml:docker-compose.yml
version: '3'
services:
  db:
    image: postgres:12.2-alpine
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/app
    ports:
      - "3000:3000"
    depends_on:
      - db
```

```yaml:config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  username: postgres
  password:
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

development:
  <<: *default
  database: app_development

test:
  <<: *default
  database: app_test

production:
  <<: *default
  database: app_production
  username: app
  password: <%= ENV['APP_DATABASE_PASSWORD'] %>
```
`host`、`username`、`password`を追記するだけのはず。これで今までうまくいっていた。

でもこうなる。

```
$ docker-compose --rm run web rails db:create

Creating network "sample_default" with the default driver
Creating sample_db_1 ... done
could not translate host name "db" to address: Name or service not known
Couldn't create 'app_development' database. Please check your configuration.
rails aborted!
PG::ConnectionBad: could not translate host name "db" to address: Name or service not known
/usr/local/bundle/gems/pg-1.2.3/lib/pg.rb:58:in `initialize'
/usr/local/bundle/gems/pg-1.2.3/lib/pg.rb:58:in `new'
/usr/local/bundle/gems/pg-1.2.3/lib/pg.rb:58:in `connect'
/usr/local/bundle/gems/activerecord-6.0.3/lib/active_record/connection_adapters/postgresql_adapter.rb:46:in `postgresql_connection'
/usr/local/bundle/gems/activerecord-6.0.3/lib/active_record/connection_adapters/abstract/connection_pool.rb:887:in `new_connection'
/usr/local/bundle/gems/activerecord-6.0.3/lib/active_record/connection_adapters/abstract/connection_pool.rb:931:in `checkout_new_connection'
/usr/local/bundle/gems/activerecord-6.0.3/lib/active_record/connection_adapters/abstract/connection_pool.rb:910:in `try_to_checkout_new_connection'
/usr/local/bundle/gems/activerecord-6.0.3/lib/active_record/connection_adapters/abstract/connection_pool.rb:871:in `acquire_connection'
/usr/local/bundle/gems/activerecord-6.0.3/lib/active_record/connection_adapters/abstract/connection_pool.rb:593:in `checkout'
/usr/local/bundle/gems/activerecord-6.0.3/lib/active_record/connection_adapters/abstract/connection_pool.rb:437:in `connection'
/usr/local/bundle/gems/activerecord-6.0.3/lib/active_record/connection_adapters/abstract/connection_pool.rb:1119:in `retrieve_connection'
/usr/local/bundle/gems/activerecord-6.0.3/lib/active_record/connection_handling.rb:221:in `retrieve_connection'
/usr/local/bundle/gems/activerecord-6.0.3/lib/active_record/connection_handling.rb:189:in `connection'
/usr/local/bundle/gems/activerecord-6.0.3/lib/active_record/tasks/postgresql_database_tasks.rb:12:in `connection'
/usr/local/bundle/gems/activerecord-6.0.3/lib/active_record/tasks/postgresql_database_tasks.rb:21:in `create'
/usr/local/bundle/gems/activerecord-6.0.3/lib/active_record/tasks/database_tasks.rb:126:in `create'
/usr/local/bundle/gems/activerecord-6.0.3/lib/active_record/tasks/database_tasks.rb:185:in `block in create_current'
/usr/local/bundle/gems/activerecord-6.0.3/lib/active_record/tasks/database_tasks.rb:479:in `block (2 levels) in each_current_configuration'
/usr/local/bundle/gems/activerecord-6.0.3/lib/active_record/tasks/database_tasks.rb:476:in `each'
/usr/local/bundle/gems/activerecord-6.0.3/lib/active_record/tasks/database_tasks.rb:476:in `block in each_current_configuration'
/usr/local/bundle/gems/activerecord-6.0.3/lib/active_record/tasks/database_tasks.rb:475:in `each'
/usr/local/bundle/gems/activerecord-6.0.3/lib/active_record/tasks/database_tasks.rb:475:in `each_current_configuration'
/usr/local/bundle/gems/activerecord-6.0.3/lib/active_record/tasks/database_tasks.rb:184:in `create_current'
/usr/local/bundle/gems/activerecord-6.0.3/lib/active_record/railties/databases.rake:39:in `block (2 levels) in <main>'
```

一体どうした。

# 課題の解決
## 解決1
ひとつめの結論としては、postgresコンテナに環境変数`POSTGRES_PASSWORD`を設定しないといけないです。
そして、その値を`config/database.yml`の`password`に定義してあげないといけないです。

```diff:docker-compose.yml
  version: '3'
  services:
    db:
      image: postgres:12.2-alpine
      volumes:
        - ./tmp/db:/var/lib/postgresql/data
+     environment:
+       - POSTGRES_PASSWORD=password
    web:
      build: .
      command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
      volumes:
        - .:/app
      ports:
        - "3000:3000"
      depends_on:
        - db
```

```diff:config/database.yml
  default: &default
    adapter: postgresql
    encoding: unicode
    host: db
    username: postgres
-   password:
+   password: password
    pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

  development:
    <<: *default
    database: app_development

  test:
    <<: *default
    database: app_test

  production:
    <<: *default
    database: app_production
    username: app
    password: <%= ENV['APP_DATABASE_PASSWORD'] %>
```
dbコンテナを起動させたままの場合は一度downして`db:create`してください。

## 解決2
二つ目の結論としては、`POSTGRES_HOST_AUTH_METHOD=trust`の環境変数を与えてあげます。
PostgreSQLが全てのアクセスを認証レスで許可してしまうので非推奨となっていますが、ローカル開発中はこれでも問題ない気がします。

```diff:docker-compose.yml
  version: '3'
  services:
    db:
      image: postgres:12.2-alpine
      volumes:
        - ./tmp/db:/var/lib/postgresql/data
+     environment:
+       - POSTGRES_HOST_AUTH_METHOD=trust
    web:
      build: .
      command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
      volumes:
        - .:/app
      ports:
        - "3000:3000"
      depends_on:
        - db
```
こちらの場合は`config/database.yml`は変更しなくてOKです。
dbコンテナを起動させている場合は一度downしてから`db:create`してください。

# 調査の軌跡
ここからは、解決に向けた調査の軌跡を記録しておきます。お時間がある方はお付き合いください！

まずは目の前に表示されたエラーメッセージを凝視します。

### ホスト名が違う？

```
could not translate host name "db" to address: Name or service not known
Couldn't create 'app_development' database. Please check your configuration.
rails aborted!
PG::ConnectionBad: could not translate host name "db" to address: Name or service not known
```
`db`という名前のホストが見つからないと言ってます。そのせいでコネクションできない、設定を見直せって言ってます。

今、Docker Composeを使ってwebコンテナとdbコンテナを立ち上げています。
この２つのコンテナは同じ`docker-compose.yml`で定義しているので同じDockerネットワーク内に存在します。
そして、ホスト名は`docker-compose.yml`の`services`の名前（今回は`db`と`web`）になるはずです。

`config/database.yml`の`host`にも`db`と記述していますので、どうやらこの辺りのタイポとかではなさそうです。

### コンテナちゃんと動いてたのかな...？
なんだかよくわからない。どこがいけないのかもよくわからない。
とりあえずコンテナのステータスでも確認してみます。

```
$ docker-compose ps -a

Name               Command                         State      Ports
----------------------------------------------------------------------------------------
sample_db_1        docker-entrypoint.sh postgres   Exit 1
sample_web_run_1   rails db:create                 Exit 1     111111111
```
あれ、なんか違和感。webコンテナはrunでコマンド実行が終わったから停止しているのはわかるだけど、なんでdbコンテナまで停止してるんだっけ...`depends_on`で起動させられてるから起動しっぱなしになるはず...

気になったのでコンテナのログを確認。

```
$ docker-compose logs

Attaching to sample_db_1
db_1   | Error: Database is uninitialized and superuser password is not specified.
db_1   |        You must specify POSTGRES_PASSWORD to a non-empty value for the
db_1   |        superuser. For example, "-e POSTGRES_PASSWORD=password" on "docker run".
db_1   |
db_1   |        You may also use "POSTGRES_HOST_AUTH_METHOD=trust" to allow all
db_1   |        connections without a password. This is *not* recommended.
db_1   |
db_1   |        See PostgreSQL documentation about "trust":
db_1   |        https://www.postgresql.org/docs/current/auth-trust.html
```
**なんかDBコンテナでエラー出てた！多分こいつだ！**

エラーメッセージを読むと

- スーパーユーザーのパスワードが定義されていないよ！`POSTGRES_PASSWORD`の環境変数に値を入れて！
- 非推奨だけど、`POSTGRES_HOST_AUTH_METHOD=trust`の環境変数を使えばパスワードなしでも使えるよ！

ということらしい。

ここまでくれば、**結論**に書いた対処に行き着く。

### READMEにそんなこと書いてたっけなぁ...
前は`POSTGRES_PASSWORD`を環境変数に与えなくてもできた。
[DockerHubのDescription](https://hub.docker.com/_/postgres)を読んでみる。

> **POSTGRES_PASSWORD**
This environment variable is required for you to use the PostgreSQL image. **It must not be empty or undefined.** This environment variable sets the superuser password for PostgreSQL. The default superuser is defined by the POSTGRES_USER environment variable.

空文字や定義なしはダメだ、って書いてある。
いや、前できたんだよ。絶対。

このドキュメンテーションは[GitHub](https://github.com/docker-library/docs/blob/master/postgres/README.md)で管理されている（長すぎるらしくてDockerHubのDescriptionに収まらないらしく、全文はGitHub見てってなってた。）

いつかまではこれ定義なしでもいけたんじゃないだろうか？Commitを遡ってみると...

https://github.com/docker-library/docs/commit/2e59fc9a6aee9125eac1a29283205333da4760fa#diff-febe75211617514f6ecb81d53ec56250R133

```diff
- This environment variable is normally required for you to use the PostgreSQL image. This environment variable sets the superuser password for PostgreSQL. The default superuser is defined by the `POSTGRES_USER` environment variable.
+ This environment variable is required for you to use the PostgreSQL image. It must not be empty or undefined. This environment variable sets the superuser password for PostgreSQL. The default superuser is defined by the `POSTGRES_USER` environment variable.
```
いた！やっぱり`POSTGRES_PASSWORD`の存在性が途中で**MUST**になっている。

### じゃあどっかでDockerイメージ変わっているのか
ということでDockerfileとかも読んでみる。
今回利用した`postgres:12.2-alpine`のソースコードは[こちら](https://github.com/docker-library/postgres/tree/master/12/alpine)。

[Dockerfile](https://github.com/docker-library/postgres/blob/master/12/alpine/Dockerfile)は特に関係なさそう。**password**で検索しても引っかからないし。
`ENTRYPOINT`で[docker-entrypoint.sh](https://github.com/docker-library/postgres/blob/master/12/alpine/docker-entrypoint.sh)を実行しているみたいなのでそちらを見てみる。

https://github.com/docker-library/postgres/blob/master/12/alpine/docker-entrypoint.sh#L109

あ、さっきコンテナのログに出てたエラーメッセージだ！

```sh:docker-entrypoint.sh
...
if [ -z "$POSTGRES_PASSWORD" ] && [ 'trust' != "$POSTGRES_HOST_AUTH_METHOD" ]; then
  # The - option suppresses leading tabs but *not* spaces. :)
  cat >&2 <<-'EOE'
  Error: Database is uninitialized and superuser password is not specified.
         You must specify POSTGRES_PASSWORD to a non-empty value for the
         superuser. For example, "-e POSTGRES_PASSWORD=password" on "docker run".
         You may also use "POSTGRES_HOST_AUTH_METHOD=trust" to allow all
         connections without a password. This is *not* recommended.
         See PostgreSQL documentation about "trust":
         https://www.postgresql.org/docs/current/auth-trust.html
  EOE
  exit 1
fi
...
```
環境変数`POSTGRES_PASSWORD`の文字列が0（参考: [Bash の if 文（test文）のオプションを整理してみた - Qiita](https://qiita.com/wakayama-y/items/a9b7380263da77e51711#%E6%96%87%E5%AD%97%E5%88%97%E9%95%B7%E3%81%AE%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3)）、かつ、環境変数`POSTGRES_HOST_AUTH_METHOD`が`trust`以外の場合、このメッセージを出力して`exit 1`でPostgreSQLの起動の前に終了するようになっているようです。

こちらもCommitを辿ってみると、

https://github.com/docker-library/postgres/commit/42ce7437ee68150ee29f5272428aa4fc657dc6dc#diff-40178ea00ee6cba1430d5ce8cca2afaaL106

で見つけることができました。（**2020年1月10日**にマージされているみたいです。）
このコミットまでは`POSTGRES_PASSWORD`がなくてもWarningを出力するだけでPostgreSQL自体は起動できていたみたいです。

# まとめ
そんなこんなでDB作成時点で少しつまづいちゃいました。
自作でないイメージやライブラリを利用するときは、ちゃんと改版も追えるようになっておくと慌てなくてすむなという学びを得ました。