RailsでPostgreSQLのバックアップファイルを作成する

ステージング環境データベースのバックアップを取得してローカル環境に流したいということがあり本題のことをやるに至りました。

利用技術のバージョンは以下です。今回DBはPostgreSQLに限りたいと思います

|技術名称 |バージョン|
|:-----------|---------:|
| Ruby       | 3.2.2    |
| Rails      | 7.0.4.3  |
| PostgreSQL | 14.6     |



## 全体の流れ
0. 環境構築(今回Railsを動かしているイメージにpostgresql-client-14が単純には入らなかったのでその解説)
0. バックアップファイルの作成
0. バックアップしたファイルをS3にアップロード

といった流れで行います。

## 1. 環境構築
PostgreSQLでバックアップファイルを作成するために `pg_dump`コマンドを利用します。

Dockerコンテナ内で開発を行っている場合には、DBのコンテナとRailsのコンテナが異なっており、Railsコンテナにはpostgresql-clientがなく、pg_dumpコマンドを利用できないといったことがあるかも知れません。

pg_dumpコマンドがすでに利用できる環境の方は、この章を飛ばしていただいて構いません。ぜひ次の章へ [バックアップファイルを作成](#バックアップファイルを作成)

### postgresql-clientのインストール
pg_dumpコマンドを使える様にするために、RailsのDockerfileの`apt install`を実行している箇所に、以下のように`postgresql-client`を追加します。

```Dockerfile
RUN apt update -qq && \
    apt install -y build-essential vim libvips locales tzdata \
    postgresql-client-14 #←これ追加
```

注意点としては、必ず実際に利用している **PostgreSQLのバージョンに合わせたバージョンをインストールしてください。**
バージョンが異なる場合には、`pg_dump`ができない場合があります。

しかし、2023年4月時点では、PostgreSQLの14系をインストールするには、1つ工夫が必要です。
上記の様に`postgresql-client-14`を追加するだけでは、「14系が見つからない」というエラーが発生してインストールができません。


### postgresql-client-14をインストールするために

結論`apt install -y postgresql-client-14`をする前に以下の4行ほど追加しておきます。
```Dockerfile
FROM ruby:3.2.2 AS base
# ...この辺りにもみなさん数行あるだろうけど関係ないので省略

# ここから追加
RUN curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | \
    gpg --dearmor -o /usr/share/keyrings/postgresql-keyring.gpg && \
    echo "deb [signed-by=/usr/share/keyrings/postgresql-keyring.gpg] http://apt.postgresql.org/pub/repos/apt/ bullseye-pgdg main" | \
    tee /etc/apt/sources.list.d/postgresql.list
# 追加はここまで

RUN apt update -qq && \
    apt install -y build-essential vim libvips locales tzdata \
    postgresql-client-14

```

追加した4行についてざっくり解説します。
1行目は、curlコマンドでhttpリクエスト送信してPostgreSQLリポジトリの公開鍵をダウンロードしています。
2行目は、gpgコマンドで先にダウンロードした鍵をデコードして`/usr/share/keyrings/postgresql-keyring.gpg`ファイルに保存しています。
3,4行目は、PostgreSQLパッケージのAPTリポジトリをechoで出力して、その出力をteeコマンドで `/etc/apt/sources.list.d/postgresql.list`ファイルに書き込んでいます。

参考: https://linuxways.net/debian/how-to-install-postgresql-14-on-debian-11/

これでpg_dumpコマンドに必要なpostgresql-clientのインストールができたかと思います。

## バックアップファイルの作成
今回、バックアップの処理はサービスクラスで行う様にしました。
これは私がRakeタスクでバックアップ取りたいと思い、「Rakeタスクからは対応するサービスクラスを呼び出すだけに留める」という方針で開発しているため、サービスクラスに書いております。

全体のコードをとりあえず載せておきます。長いので折りたたみ式です。
<details>
<summary>全体のコード</summary>

```ruby
class Tasks::Tools::DbBackupService
  def initialize
    @filename = "backup.sql"
  end

  def execute
    backup
    upload
    Rails.logger.debug `rm -f tmp/#{@filename}`
    ServiceResponse.success(message: "バックアップが完了しました")
  end

  private

  def backup
    db_config = Rails.configuration.database_configuration[Rails.env]

    cmd = `PGPASSWORD="#{db_config["password"]}" \
            pg_dump -F p -v \
            -U #{db_config["username"]} \
            -h #{db_config["host"]} \
            -d #{db_config["database"]} \
            -p #{db_config["port"]} \
            -f tmp/#{@filename}`
  end

  def upload
    s3_client.put_object(
      bucket: Rails.application.credentials.aws[:s3_user][:bucket],
      body: File.open("tmp/#{@filename}"),
      key: @filename
    )
  end

  def s3_client
    Aws::S3::Client.new(
      region: Rails.application.credentials.aws[:s3_user][:region],
      credentials: Aws::Credentials.new(
        Rails.application.credentials.aws[:s3_user][:access_key_id],
        Rails.application.credentials.aws[:s3_user][:secret_access_key]
      )
    )
  end
end

```
</details>

特に重要な箇所のbackupメソッドだけ簡単に紹介します。
まずは以下がコードです。
```ruby
  def backup
    db_config = Rails.configuration.database_configuration[Rails.env]

    cmd = `PGPASSWORD="#{db_config["password"]}" \
            pg_dump -F p -v \
            -U #{db_config["username"]} \
            -h #{db_config["host"]} \
            -d #{db_config["database"]} \
            -p #{db_config["port"]} \
            -f tmp/#{@filename}`
     Rails.logger.debug cmd
  end
```

`db_config`変数には起動している環境のDB情報が入っています。
その情報を用いてpg_dumpを行います。
pg_dumpで指定しているオプションは 
- `-F p`で平文のSQLスクリプトファイルの出力フォーマットを指定
- `-v`で、詳細なオブジェクトコメント、開始時刻、終了時刻をダンプファイルに、進行状況メッセージを標準エラーに出力させます(ログを表示したくない場合はこのオプションは利用しないのがいいかなと)
- `-f`でバックアップをファイルの置き場を指定しています。
  - 今回はtmpフォルダに`backup.sql`のようなファイル名で置いています。
- 先頭の`PGPASSWORD`入力を省くため、パスワードの環境変数です。値を埋め込んでいます

pg_dumpコマンドのオプションについての公式サイトはこちら
 https://www.postgresql.jp/document/14/html/app-pgdump.html#PG-DUMP-OPTIONS

これでバックアップの完了です。
今はtmp/フォルダにバックアップしたファイルが設定されています。
ローカル環境であればそれでいいですが、ステージング環境などのtmpフォルダに置かれてもローカルに持ってくるのが大変なので次にS3にアップします。

## バックアップしたファイルをS3にアップロード
以下のGemを利用してS3との接続をします。使用したバージョンは `1.121.0`です。
```Ruby
gem "aws-sdk-s3"
```

以下でtmpフォルダあるバックアップしたファイルをアップロードしています。
```ruby
  def upload
    s3_client.put_object(
      bucket: Rails.application.credentials.aws[:s3_user][:bucket],
      body: File.open("tmp/#{@filename}"),
      key: @filename
    )
  end

  def s3_client
    Aws::S3::Client.new(
      region: Rails.application.credentials.aws[:s3_user][:region],
      credentials: Aws::Credentials.new(
        Rails.application.credentials.aws[:s3_user][:access_key_id],
        Rails.application.credentials.aws[:s3_user][:secret_access_key]
      )
    )
  end
```
こちらはしっかりと、他人から閲覧できないS3バケットに置くようにしましょう。

また、私はアップロードが完了したら以下の様なコードで、tmpフォルダにあるバックアップファイルは削除しています。
```ruby
Rails.logger.debug `rm -f tmp/#{@filename}`
```
tmpフォルダに置きっぱなしも危険なので消しておきましょうね。

## 終わりに
以上で、初めに紹介した流れの３ステップが完了です。
個人的には、今回の本題ではないですが、postgresql-client-14のインストールが大変でした。
もし詰まっている方がいたら、この記事が参考になればと思います。