k8s上のPostgreSQLバージョンアップ

PostgreSQLのアップグレードには、公式ツール pg_upgradeを利用できます。
ただし、pg_upgradeを実行する前提に以下の条件があり、 公式コンテナイメージでは実行できません。

  1. 新旧バージョンの実行バイナリ
  2. 旧バージョンのデータディレクトリ
  3. 新バージョンの初期化済みデータディレクトリ

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には旧バージョンのデータディレクトリを指定します。
$PGDATANEWinitdbで初期化したディレクトリを用います。
これらの環境変数は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のデータ格納には永続ディスクを利用する必要がありますが、永続ディスクの設定に不備があると再起動時にデータ消失します。

バージョンアップの際には、データの配置レイアウトが変わることもあり、すべてのデータを保持していることは確認すべき重要ポイントです。

稼働後の再起動でデータ消失すると致命的な事態になるため、移行時の動作確認の際にコンテナ再起動を実施しておくべきでしょう。

⁋ 2018/03/11↻ 2024/12/18
中馬崇尋
Chuma Takahiro