PostgreSQLのアップグレードには、公式ツール
pg_upgradeを利用できます。
ただし、pg_upgrade
を実行する前提に以下の条件があり、
公式コンテナイメージでは実行できません。
- 新旧バージョンの実行バイナリ
- 旧バージョンのデータディレクトリ
- 新バージョンの初期化済みデータディレクトリ
tianon/docker-postgres-upgradeを利用するとpg_upgrade
を実行可能な環境を利用できます。
pg_upgradeの手順
tianon/docker-postgres-upgrade:xx-to-yy
(xx, yyは適切なバージョン)イメージを用いて作業用コンテナを起動します。
(既存のデータディレクトリをマウントすること)
GKE用のマニフェストの例は以下のとおりです。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: pg-migrate
spec:
serviceName: pg-migrate
selector:
matchLabels:
name: pg-migrate
template:
spec:
containers:
- name: pg-migrate
image: tianon/postgres-upgrade:14-to-15
# コマンドを手動実行するため、何も実行させない
command: ["tail"]
args: ["-f", "/dev/null"]
volumeMounts:
- name: postgres-str
mountPath: /var/lib/postgresql/data
volumes:
- name: postgres-str
gcePersistentDisk:
pdName: pg-data
fsType: ext4
この例ではPogtgreSQLコンテナの定義を省略していますが、移行ツールのコンテナーはサイドカー構成で同一podに追加できます。
kubernetesの外部ストレージの多くはwriteできるpod接続を1つに制約していますが、pod内のコンテナは同時にwriteできます。ただしpg_upgrade
はPostgreSQLインスタンスが起動していると実行できません。
コンテナ内のコマンド実行の流れは以下のとおりです。
なお、以下の流れはコンテナのスクリプトに実装されていますが、pg_upgrade
の標準的な手順であるためここでは手動実行しています。
root@pg:~# mkdir /var/lib/postgresql/data/13
root@pg:~# chown -R postgres:postgres /var/lib/postgresql
root@pg:~# su postgres
postgres@pg:~$ export PGDATAOLD=/var/lib/postgresql/data/12
postgres@pg:~$ export PGDATANEW=/var/lib/postgresql/data/13
postgres@pg:~$ PGDATA=$PGDATANEW initdb
# configを旧バージョンに沿って修正。データディスクのバックアップ
postgres@pg:~$ pg_upgrade
このプロセスはrootで実行できず、公式コンテナの前提ではpostgresユーザーで実行します。rootに設定されている環境変数を引き継ぐことがポイントです。
initdb
は初期データをセットアップするプロセスで、既存のDBインスタンスが稼働していても実行できます。
pg_upgrade
は旧バージョンのデータをコンバートします。DBインスタンスを停止しておく必要があります。
実行バイナリについては、コンテナイメージ内にセットアップ済みであるため、とくに追加のセットアップは必要ありません。
$PGDATAOLD
には旧バージョンのデータディレクトリを指定します。
$PGDATANEW
はinitdb
で初期化したディレクトリを用います。
これらの環境変数はpg_upgrade
が直接参照するものです。
この過程で2バージョンのデータが存在する構成になるため、ディスクの空きが50%以上必要になります。
移行が完了したら作業用のコンテナを終了し、新バージョンのpostgresコンテナイメージ(公式イメージなど)を用いて起動します。
旧バージョンのデータディレクトリも不要化するでしょう(バックアップの考慮は別途必要)。
万一失敗した場合、$PGDATAOLDのディレクトリはこのプロセスで変更されないため、旧構成でそのまま起動できます。
pg_upgradeのエラー
新旧のDB設定が異なる場合、pg_upgrade
がエラー終了します。
encodingやlc_collateの不一致に遭遇した場合には、initdb
に適切なオプションを指定する必要があります。
configは手動で移行
pg_upgrade
はデータをアップグレードしますが、configは既存のファイルを利用しません。
たとえばpg_hba.conf
はローカルアクセスのみ許容する設定になっているため、構成に合わせて設定が必要でしょう。
ディスククローンを活用したリハーサル
クラウドプロバイダが物理ディスクのクローン機能を提供している場合、実際にバージョンアップする前に一時的なコンテナを追加してリハーサルを実行できます。
バージョンアップのプロセスは未知の挙動を含みます。とくにロジカルレプリケーションなどの複雑な構成の場合、pg_upgrade
が移行しないオブジェクトもあり不用意な操作で構成が破損するため、あらかじめ挙動を確認しておくことが有用です。
複数バージョンの実行バイナリを同梱するイメージ
tianon/postgres-upgrade
イメージは広範なバージョンの組み合わせをサポートしていますが、バージョンが合わない場合には自作する必要が生じます。
Dockerfileから抜粋すると、ポイントは以下の点でしょう。
FROM postgres:13
RUN sed -i 's/$/ 12/' /etc/apt/sources.list.d/pgdg.list
RUN apt-get update && apt-get install -y --no-install-recommends \
postgresql-12=12.8-1.pgdg100+1 \
&& rm -rf /var/lib/apt/lists/*
ENV PGBINOLD /usr/lib/postgresql/12/bin
ENV PGBINNEW /usr/lib/postgresql/13/bin
作業用コンテナ構成
kubernetes上でデータベースを運用するケースでは、バージョンアップの際に新しいpod(コンテナ)を1系統追加して新旧コンテナを並走させることで、ダウンタイムを減らし、トラブル時の切り戻しを手軽に実行できます。
移行プロセスの大まかな流れは下図のとおりです。
トラフィック切り替えの方法
基本的には、更新前にwriteアクセスを止める必要があるため、一般的には旧コンテナを停止し、image
指定を変更して新コンテナを起動することになるでしょう。
スナップショットなどの物理バックアップを取得しておくことで、リトライも可能です。
podのポートには名前をつけられる(spec.template.spec.ports.name)ため、新バージョンのコンテナに現行バージョンと異なる名前をつけることも可能です。
一方、アプリケーションはサービスを介してDB接続しているため、サービスの転送先の名前を切り替えることで(spec.selector.name)、トラフィックを新バージョンのDBコンテナに向けることが可能です。
移行プロセスが完了するまで旧バージョンのコンテナを起動しておけば、万一トラブルに遭遇した際にもサービスの転送先名称を元に戻すことで切り戻せます。
注意:動作確認の際に再起動テストも実施すべき
コンテナならではの注意点として、コンテナは揮発性で再起動時にデータが消える点があります。
DBのデータ格納には永続ディスクを利用する必要がありますが、永続ディスクの設定に不備があると再起動時にデータ消失します。
バージョンアップの際には、データの配置レイアウトが変わることもあり、すべてのデータを保持していることは確認すべき重要ポイントです。
稼働後の再起動でデータ消失すると致命的な事態になるため、移行時の動作確認の際にコンテナ再起動を実施しておくべきでしょう。
Chuma Takahiro