自宅KubernetesでPostgreSQLを18へアップグレードした記録
はじめに
自宅クラスタで運用している PostgreSQL を 15 から 18 にバージョンアップした手順をまとめました。 pg_upgrade を利用してダウンタイムを抑えつつ移行した際の気づきを記録しています。
方針
バックアップを取得して新しいクラスタにリストアする方法でも移行できますが、データ量が多いと復元に時間がかかりダウンタイムも伸びてしまいます。 自宅用途のデータベースなのでデータボリュームは大きくありませんが、短時間で切り替えられる方法を確認するために pg_upgrade を選びました。 今回は手動でコマンドを実行しています。
手順
作業用 Pod を用意する
移行作業用の Job を作成し、既存のデータディレクトリをマウントします。
apiVersion: batch/v1
kind: Job
metadata:
name: pg-upgrade-shell
namespace: app
spec:
template:
spec:
restartPolicy: Never
containers:
- name: debian
image: debian:bookworm
command: ["bash", "-lc", "sleep infinity"]
volumeMounts:
- name: pgdata
mountPath: /var/lib/postgresql
subPath: pgdata
volumes:
- name: pgdata
persistentVolumeClaim:
claimName: pgdata-pvc
kubectl apply -f pg-upgrade-shell.yaml
kubectl exec -it job/pg-upgrade-shell -n app -- bash
PostgreSQL バイナリを準備する
pg_upgrade には旧バージョンと新バージョンの両方のバイナリが必要です。
そのため、作業用コンテナに PostgreSQL 15 と 18 をインストールします。
インストール時に /var/lib/postgresql 配下へ不要なデータが作られてしまったため、本来は別ディレクトリへマウントしておくべきでした。
set -eux
apt-get update
apt-get install -y wget gnupg lsb-release ca-certificates
wget -qO - https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor -o /usr/share/keyrings/postgresql.gpg
echo "deb [signed-by=/usr/share/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" \
> /etc/apt/sources.list.d/pgdg.list
apt-get update
apt-get install -y postgresql-15 postgresql-18
rm -rf /var/lib/postgresql/18
作業ユーザーとロケールを整える
pg_upgrade は root では実行できないため、postgres ユーザーで作業します。
共有ボリュームの所有者が UID 999 なので、最後に 999 へ戻します。
コンテナ起動時に runAsUser, runAsGroup, fsGroup を 999 に設定しておけば、この作業は省略できるかもしれません。
mkdir /var/lib/postgresql18
chown -R postgresql:postgresql /var/lib/postgresql
chown -R postgresql:postgresql /var/lib/postgresql18
su postgres
export OLD_BIN=/usr/lib/postgresql/15/bin
export NEW_BIN=/usr/lib/postgresql/18/bin
export OLD_PGDATA=/var/lib/postgresql
export NEW_PGDATA=/var/lib/postgresql18
export OLDPORT=55001
export NEWPORT=55002
ロケールが必要になるため、en_US.UTF-8 を生成しておきます。
apt-get update
apt-get install -y locales
sed -i 's/^# *\(en_US.UTF-8 UTF-8\)/\1/' /etc/locale.gen
locale-gen
update-locale LANG=en_US.UTF-8
locale -a | grep -i en_US
旧クラスタを停止する
StatefulSet のレプリカを 0 に設定して停止します。 Argo CD で自動同期している場合は、一時的に同期を無効化します。 Longhorn を利用しているので、合わせてバックアップも取得しました。
PostgreSQL 15 が適切に停止できていないとアップグレードできず、The source cluster was not shut down cleanly, state reported as: "in production" というエラーになります。
その場合は強制停止を行います。
"$OLD_BIN/pg_controldata" "$OLD_PGDATA" | grep -E 'Database cluster state|Latest checkpoint'
# in production のままなら以下で開始と停止を実行します
mkdir ./tmp
"$OLD_BIN/pg_ctl" -D "$OLD_PGDATA" \
-o "-c port=$OLDPORT -c unix_socket_directories=./tmp -c listen_addresses=''" start
"$OLD_BIN/pg_ctl" -D "$OLD_PGDATA" stop -m fast
新クラスタを初期化してチェックする
PostgreSQL 18 ではデフォルトでチェックサムが有効になります。
旧クラスタで有効にしていなかったため、今回は無効にした状態で移行しました。
ユーザーは POSTGRES_USER 環境変数で指定していたユーザーを利用します。
指定しないと database user "mainuser" is not the install user というエラーになるので注意してください。
$NEW_BIN/initdb -D "$NEW_PGDATA" --no-data-checksums --encoding=UTF8 --locale=C.UTF-8 -U mainuser
チェック用のコマンドで問題がないか確認し、問題がなければ本番の移行を実行します。
export SOCKDIR="$NEW_PGDATA"
"$NEW_BIN/pg_upgrade" \
-b "$OLD_BIN" -B "$NEW_BIN" \
-d "$OLD_PGDATA" -D "$NEW_PGDATA" \
-p "$OLDPORT" -P "$NEWPORT" \
-s "$SOCKDIR" \
-U mainuser \
-o "-c unix_socket_directories=$SOCKDIR -c unix_socket_permissions=0700 -c listen_addresses=''" \
-O "-c unix_socket_directories=$SOCKDIR -c unix_socket_permissions=0700 -c listen_addresses=''" \
--check
"$NEW_BIN/pg_upgrade" \
-b "$OLD_BIN" -B "$NEW_BIN" \
-d "$OLD_PGDATA" -D "$NEW_PGDATA" \
-p "$OLDPORT" -P "$NEWPORT" \
-s "$SOCKDIR" \
-U mainuser \
-o "-c unix_socket_directories=$SOCKDIR -c unix_socket_permissions=0700 -c listen_addresses=''" \
-O "-c unix_socket_directories=$SOCKDIR -c unix_socket_permissions=0700 -c listen_addresses=''" \
--copy --jobs "$(nproc)"
ディレクトリ構成を整える
移行が完了したら、ディレクトリ構成を /var/lib/postgresql/{version}/docker/ に統一するために整理します。
mkdir /var/lib/18/docker
mv /var/lib/postgresql/* /var/lib/18/docker
mv /var/lib/18 /var/lib/postgresql/
ネットワーク経由の接続を有効にする
ローカルホストからは接続できましたが、ネットワーク越しには接続できなかったため設定を調整しました。
# kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}\t{.spec.podCIDR}\n{end}' で確認
echo 'host all all 10.1.0.0/16 scram-sha-256' >> /var/lib/postgresql/18/docker/pg_hba.conf
cat <<'EOF' >> /var/lib/postgresql/18/docker/postgresql.conf
listen_addresses = '*'
EOF
/usr/lib/postgresql/18/bin/pg_ctl -D /var/lib/postgresql/18/docker reload
作業が終わったら、PostgreSQL ユーザーの所有者を元の 999 に戻します。
chown -R 999:999 /var/lib/postgresql
StatefulSet のマウント先も更新します。
volumeMounts:
- name: pgdata
# mountPath: /var/lib/postgresql/data # 変更前
mountPath: /var/lib/postgresql
subPath: pgdata
最後に StatefulSet のレプリカを 1 に戻し、PostgreSQL のバージョンを 18 に更新します。
最後に
想定より手順が多く、準備に時間がかかりました。 ダウンタイムを許容できる環境であれば、バックアップとリストアによる移行のほうがシンプルかもしれません。