開発環境(docker)にてpostgresのdataディレクトリのバージョンアップをお手軽にやる方法（docker volumes編）

# 前提条件
- 開発環境でお手軽にバージョンアップする話です。本番環境での実行は別の手段を用いてきちんと行ってください❗️
  - [神ツール](https://github.com/tianon/docker-postgres-upgrade)のREADMEにも書かれていますが、最悪開発環境のデータが壊れていしまう場合もありますので `なにをやっているかを理解した上で` 実行してください（要はDBが壊れても泣かない）
    - This is a PoC for using pg_upgrade inside Docker -- learn from it, adapt it for your needs; don't expect it to work as-is!
    - 意訳) これはdocker内でpg_upgradeを使う場合の概念実証(PoC)です。そこから学び、あなたがやりたいように修正してください。そのまま期待通り動くとは思わないで！
- dockerでpostgresを動かしている
- dataディレクトリはdocker volume上で永続化している。
- ネット上では pg_upgrade を使う方法が書かれているけど面倒そうだからやりたくないな…という強い気持ちを持っている👍

# TL;DR
結局tianonさんの作られた神ツール docker-postgres-upgrade を使うべし。

https://github.com/tianon/docker-postgres-upgrade

ただし、以下の手順だとdocker composeの管理外のvolume作成となりますので、volume設定時にexternalの指定になります。回避策はあると思いますが、面倒なのでこの手順では省いています。

```:この手順での最終的な docker-compose例
db: 
  image: postgres:14.4-alpine
...省略...
  volumes:
    - my-rails2_pg-data:/var/lib/postgresql/data
...省略...
volumes:
  my-rails2_pg-data2:
    external: true                      <-- これ！
...省略...
```

# 背景

以前、[ホストに物理ファイルとしてマウントしている場合のバージョンアップ手順](https://qiita.com/y-amadatsu/items/2d43ce49c77f1f1df434)の記事を書きましたが、docker volumesに保存するようになった[^1]のでその手順を記載します。

とはいえ、神ツールを使うのはいつもどおりです。

[^1]: [VolumeとBind Mountの比較など](https://matsuand.github.io/docs.docker.jp.onthefly/storage/volumes/)

# アップデートの手順
例として、PostgreSQL10系 -> 14系へアップデートしてみます。

## Volume名の確認

PostgreSQL10系（この例では10.14）のdataディレクトリが pg-data Volumeにあると仮定します。
例えばdocker-composeファイルは以下のようになっているイメージです。

```:docker-compose.yml
db: 
  image: postgres:10.14-alpine
...省略...
  volumes:
    - pg-data:/var/lib/postgresql/data
...省略...
volumes:
  pg-data:
...省略...
```

このときdocker volumes lsでVolume名を確認すると以下のような感じになると思います。

```bash
$ docker volume ls -f name=pg-data
DRIVER    VOLUME NAME
local     my-rails2_pg-data
```

ここでは `my-rails2_pg-data` であることを確認できました。

## バージョンアップ元のVolumeを複製する

コピー元が壊れてしまうのは大問題なので、バージョンアップ用にバージョンアップ元のデータを複製して、アップグレード元としたいと思います。
なおdockerにはvolumesをまるっとコピーする方法がないので下記の手順[^clone]でコピーします。ここでは、新しく 'pg-db-10' というvolume名にしました。

```bash
$ docker volume create --name pg-db-10

docker container run --rm -it \
           -v my-rails2_pg-data:/from \
           -v pg-db-10:/to \
           alpine ash -c "cd /from ; cp -av . /to"
```

[^clone]: https://stackoverflow.com/questions/67567986/copy-docker-volumes

## アップグレード

あとは神ツールをつかってバージョンアップするだけです。

繰り返しますが以下は
- 10系 -> 14系 へアップデート
- コピー元のVolume名: `pg-db-10`
- 新しいVolume名: `my-rails2_pg-data2` 
とした場合のコマンド例です。実際のバージョンアップ／Volume名にあわせて修正してください。

```bash
$ docker run --rm \
    --mount 'type=volume,src=pg-db-10,dst=/var/lib/postgresql/10/data' \
    --mount 'type=volume,src=my-rails2_pg-data2,dst=/var/lib/postgresql/14/data' \
    tianon/postgres-upgrade:10-to-14
```

するとこんな感じで処理がすすんで

```
The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.

The database cluster will be initialized with locale "en_US.utf8".
The default database encoding has accordingly been set to "UTF8".
The default text search configuration will be set to "english".
...省略
Setting next OID for new cluster                            ok
Sync data directory to disk                                 ok
Creating script to delete old cluster                       ok
Checking for extension updates                              ok

Upgrade Complete
----------------
Optimizer statistics are not transferred by pg_upgrade.
Once you start the new server, consider running:
    /usr/lib/postgresql/14/bin/vacuumdb --all --analyze-in-stages

Running this script will delete the old cluster's data files:
    ./delete_old_cluster.sh
```

わーい。できあがりです。

コマンドの最後に analyze_new_cluster.sh を実行するように言われますが、内部で vacuumdb しているだけなので必要に応じて実行してください。

## 確認

作成したVolumeを確認します。

```bash
$ docker volume ls -f name=pg-data
DRIVER    VOLUME NAME
local     my-rails2_pg-data
local     my-rails2_pg-data2
```

のように `my-rails2_pg-data2` が増えているので、あとは、docker-composeからさくっとマウントして動作確認すればOKです。


```:docker-compose.yml
db: 
  image: postgres:14.4-alpine           # 10.4 -> 14.4 に修正
...省略...
  volumes:
    - my-rails2_pg-data2:/var/lib/postgresql/data # pg-data -> pg-data２ に修正
...省略...
volumes:
  pg-data:                              # pg-dataはのこしたまま追加したほうが心理的安全性は高いですよね
  my-rails2_pg-data2:                   # docker compose外で作ったのでフルネームで指定
    external: true                      # docker compose外で作ったVolumeの指定。これがいやなら空DBで一度docker-composeで作ったりするとよいかも？
...省略...
```

お疲れさまでした。安定稼働したら作業コピーの不要なVolumeは削除してしまいます。 

```bash
$ docker volume rm pg-db-10
```

# 余談

DBを復元して動かしてみたところ他のコンテナからの接続時に以下のようなエラーが出ました。（hogeは適当に読み替えてください)

```
 FATAL:  no pg_hba.conf entry for host "172.hogehoge...", user "hoge", database "hogeghoge", no encryption
 (PG::ConnectionBad)
...省略
```

1４系へのバージョンアップについて接続系でなにかないかと調べてみると[postgres14からパスワードの暗号化方式が変わったみたい](https://www.kostolansky.sk/posts/upgrading-to-postgresql-14/)だったので、参考元の通り該当ユーザのパスワードを変更しなおしましたが、そもそも接続時の問題なので解決せず（とはいえ今回は必要な対応だったと思います）

結果、バージョンアップ前後で `pg_hba.conf` を見比べたしかに必要な記載が抜けていたので追記しました。以下postgresコンテナ内で実行です。

```bash
# echo 'host all all all md5' >>  /var/lib/postgresql/data/pg_hba.conf
# psql -U postgres
psql (14.4)
Type "help" for help.

postgres=# select pg_reload_conf();
 pg_reload_conf
----------------
 t
(1 row)

vagrant=> ^D\q　                 <-- ご参考までに。Macなら `Ctrl-d` で抜けます。
/ #
```

これで他のコンテナからも接続できるようになりました。