RHEL 実践 #2 DB 運用: PostgreSQL on RHEL

読了 9分

RHEL 実践 #1 では nginx で Web 層を立ち上げました。今回はその後ろを支える データ層 に進みます。RHEL でデータベースを立ち上げる作業も、Web サーバと同じくパッケージ 1 つでは終わりません。ただ PostgreSQL には RHEL 固有のもう 1 段階が加わります。それが AppStream module でバージョンを選ぶ 過程です。

RHEL は同一パッケージの複数バージョンを module という単位でまとめて提供します。PostgreSQL のようにメジャーバージョンが速く上がるソフトウェアは、この module 構造を経てこそ目的のバージョンをきれいにインストールできます。今回はそのインストールから始めて、初期化・リモート接続・SELinux・バックアップまで運用 1 サイクルを辿ります。

AppStream module でバージョンを選ぶ #

まず、どの PostgreSQL バージョンが module として提供されているかを確認します。利用可能な stream (バージョンの流れ) とデフォルト値を一目で確認できます。

# 提供される postgresql module と stream の一覧
dnf module list postgresql

出力で [d] が付いた stream がデフォルト、[e] が付いた stream はすでに有効化されたものです。目的のバージョンがデフォルトと違う場合は、明示的に有効化してからインストールします。ここでは 16 バージョンを例にします。

# 16 stream を有効化
sudo dnf module enable -y postgresql:16

# サーバパッケージをインストール
sudo dnf install -y postgresql-server postgresql-contrib

postgresql-server がデータベース本体で、postgresql だけをインストールするとクライアント (psql など) だけが入ります。サーバを立ち上げるには必ず postgresql-server をインストールする必要があります。postgresql-contribpg_stat_statements のような実務でよく使う拡張モジュールを含むので、一緒にインストールしておくことをお勧めします。

一度 stream を有効化すると、以降のアップデートもそのバージョンの流れの中でだけ起こります。メジャーバージョンを上げるときは module を reset するか別の stream で enable する必要があるので、運用中のデータベースでは慎重に扱う必要があります。

初期化とサービス登録 #

インストール直後はデータディレクトリが空でサービスがすぐには立ち上がりません。RHEL パッケージは初期化専用のコマンドを提供します。

# データディレクトリの初期化
sudo postgresql-setup --initdb

このコマンドはデフォルトのデータディレクトリである /var/lib/pgsql/data に設定ファイルとシステムカタログを生成します。初期化が終わったら systemd に登録して即座に起動します。

# ブート時に自動起動 + 即時起動
sudo systemctl enable --now postgresql

# 状態確認
systemctl status postgresql

enable --now で一度に処理する流れは nginx のときと同じです。正常に立ち上がったら、まずローカルで接続を確認します。インストール時に postgres という OS ユーザが作られるので、このユーザに切り替えて接続するのが最も単純です。

# postgres ユーザに切り替えてから psql 接続
sudo -u postgres psql

psql プロンプトが出ればデータベース本体は正常です。\l でデータベース一覧、\q で終了します。

データディレクトリ #

デフォルトのデータディレクトリは /var/lib/pgsql/data です。主なファイルと位置を整理すると次のとおりです。

  • postgresql.conf: サーバ全体の設定 (接続アドレス、メモリ、ログなど)
  • pg_hba.conf: クライアント認証ルール (誰がどこからどの方式で接続するか)
  • base/global/: 実際のデータファイル
  • pg_wal/: トランザクションログ (WAL)

運用環境ではデータディレクトリを別ディスクに移すケースが多いですが、そのとき SELinux コンテキストを一緒に揃えないとサービスが立ち上がりません。この部分は下の SELinux 節で扱います。

リモート接続設定 #

デフォルトインストール状態の PostgreSQL はローカルからのみ接続を受け付けます。外部から接続するには 2 つのファイルを直す必要があります。postgresql.conf受信アドレス を開き、pg_hba.conf認証ルール を追加する 2 段階です。

まず postgresql.conflisten_addresses を修正します。デフォルト値は localhost で外部接続を塞ぎます。

# /var/lib/pgsql/data/postgresql.conf
listen_addresses = '*'        # すべてのインタフェースで受信
port = 5432

'*' はすべてのネットワークインタフェースで受け付けるという意味です。特定の IP だけ受けたい場合はそのアドレスを直接書きます。次に pg_hba.conf に認証ルールを追加します。このファイルは上から下へ読まれ、先に一致したルールが適用されます。

# /var/lib/pgsql/data/pg_hba.conf
# TYPE  DATABASE  USER  ADDRESS          METHOD
host    all       all   192.168.1.0/24   scram-sha-256

host は TCP/IP 接続を、scram-sha-256 は RHEL デフォルトの認証方式であるパスワードベース認証を意味します。過去の md5 より安全なので、新しく構成する環境では scram-sha-256 を使うのが適切です。ADDRESS には接続を許可するネットワーク帯域を書きます。2 つのファイルを直した後は、サービスに再読み込みさせてはじめて反映されます。

# 設定を再読み込み (ほとんどの設定は reload で十分)
sudo systemctl reload postgresql

listen_addresses のように一部の項目は reload では反映されず restart が必要です。変更した項目が reload で入るか確実でない場合は、sudo systemctl restart postgresql で再起動します。

firewalld で 5432 を開く #

PostgreSQL のデフォルトポートは 5432 です。RHEL は firewalld が有効化されていてこのポートが塞がっているので、恒久的に開放します。

# postgresql サービスを恒久許可 (5432/tcp)
sudo firewall-cmd --add-service=postgresql --permanent

# 反映
sudo firewall-cmd --reload

# 確認
sudo firewall-cmd --list-all

firewalld には postgresql という事前定義サービスがあるので、ポート番号を直接書く必要はありません。非標準ポートを使う場合は --add-port=5433/tcp のようにポートで直接開きます。--permanent で恒久ルールを作った後 --reload で反映する 2 段階の流れは、RHEL ファイアウォール作業の基本です。

SELinux が塞ぐ箇所 #

標準位置 (/var/lib/pgsql/data) で標準ポート (5432) で動作する限り、SELinux は静かです。しかし Web サーバのときとまったく同じく、非標準ディレクトリ非標準ポート に外れると即座に塞がれます。

非標準データディレクトリ #

データディレクトリを /data/pgsql のようなパスに移すと、そのディレクトリの SELinux コンテキストが PostgreSQL データタイプではないためサービスが立ち上がりません。postgresql_db_t タイプを恒久付与して適用します。

# /data/pgsql 以下を PostgreSQL データタイプとして恒久登録
sudo semanage fcontext -a -t postgresql_db_t "/data/pgsql(/.*)?"

# ディスクの実際のラベルに適用
sudo restorecon -Rv /data/pgsql

semanage fcontext でポリシーにルールを追加し、restorecon で実際のファイルに適用する 2 段階が核心です。これとは別に systemd unit がデフォルトディレクトリを指しているので、データディレクトリ自体を移すときは unit の Environment=PGDATA= の値も一緒に合わせる必要があります。

非標準ポート #

PostgreSQL を 5433 のように別のポートで動かすと、設定が合っていても SELinux がそのポートをデータベースポートと認めず塞ぎます。

# 現在 postgresql_port_t に登録されたポートを確認
sudo semanage port -l | grep postgresql

# 5433 を PostgreSQL ポートとして恒久登録
sudo semanage port -a -t postgresql_port_t -p tcp 5433

semanage がなければ policycoreutils-python-utils パッケージをインストールします。ポートを登録した後にサービスを再起動すると正常に立ち上がります。

ユーザと DB の作成 #

データベースを実際に使うには、アプリケーション用ユーザ (role) とデータベースを作る必要があります。postgres スーパーユーザで作業します。方法は 2 つで、シェルコマンドで作る方式と psql の中で SQL で作る方式です。

まずシェルからそのまま作る方式です。

# 対話的に role 作成 (パスワード入力プロンプト付き)
sudo -u postgres createuser --pwprompt appuser

# appuser を所有者とするデータベースを作成
sudo -u postgres createdb --owner=appuser appdb

同じ作業を psql の中で SQL で処理することもできます。権限を細かく与えたいときはこちらが便利です。

-- sudo -u postgres psql で接続した後
CREATE ROLE appuser WITH LOGIN PASSWORD 'change_this_password';
CREATE DATABASE appdb OWNER appuser;
GRANT ALL PRIVILEGES ON DATABASE appdb TO appuser;

LOGIN 属性があってはじめて接続可能なユーザになります。こうして作った appuser でリモートから接続するとき、先ほど pg_hba.conf に追加したルールと scram-sha-256 認証が合わさって、はじめて接続が成立します。

バックアップとリカバリ #

運用データベースでバックアップは選択ではなく基本です。PostgreSQL は論理バックアップツールとして pg_dumppg_dumpall を提供します。pg_dump は単一のデータベースを、pg_dumpall は role を含むクラスタ全体を取得します。

# 単一データベースをバックアップ (カスタムフォーマット、圧縮・並列リカバリに有利)
sudo -u postgres pg_dump -Fc appdb -f /var/backups/appdb.dump

# クラスタ全体をバックアップ (role・権限を含む、平文 SQL)
sudo -u postgres pg_dumpall -f /var/backups/all.sql

-Fc はカスタムフォーマットで、リカバリ時にテーブル単位の選択や並列処理が可能なため、単一データベースのバックアップに推奨されます。リカバリはフォーマットによってツールが異なります。カスタムフォーマットは pg_restore で、平文 SQL は psql で戻します。

# カスタムフォーマットのリカバリ (対象 DB は事前に作成)
sudo -u postgres createdb appdb_restore
sudo -u postgres pg_restore -d appdb_restore /var/backups/appdb.dump

# 平文 SQL のリカバリ
sudo -u postgres psql -f /var/backups/all.sql

これらのバックアップは特定時点のスナップショットなので、障害時には最後のバックアップ以降の変更は失われます。損失を最小化するには WAL アーカイビングベースのポイントインタイムリカバリ (PITR) を別途構成する必要がありますが、そのテーマは別の記事で扱うほど深いので、ここでは論理バックアップまでにとどめます。バックアップファイルは cron や systemd timer で定期実行し、同じホストではなく別のストレージに移しておくことで初めて意味をなします。

詰まったときの診断 #

データベースに接続できないときは、次の順序で絞り込んでいくとほとんどの原因が見えてきます。

  1. サービスが立ち上がっているか。 systemctl status postgresql、失敗時は journalctl -u postgresql で起動ログを確認します。
  2. ローカルで開くか。 sudo -u postgres psql で接続して、サーバ自体の問題とリモートアクセスの問題を切り分けます。
  3. ファイアウォールが開いているか。 firewall-cmd --list-all に postgresql または 5432 があるか。
  4. 認証が塞いでいるか。 リモートから psql -h ホスト -U appuser -d appdb で接続してみて、拒否されたら pg_hba.conf のルールと順序を確認します。
  5. SELinux が塞いでいるか。 sudo ausearch -m AVC -ts recent に postgresql 関連の denied があるか。

特に pg_hba.conf は上から下へ読まれ先に一致したルールが適用されるので、より広いルールが上にあると、下の意図したルールが隠れることがあります。認証エラーメッセージはどのルールで塞がれたかも PostgreSQL ログ (/var/lib/pgsql/data/log/) に残るので、接続拒否時はこのログを先に見るのが速いです。

運用ポイント #

今回の記事で押さえたこと:

  • バージョンは module で。 dnf module list postgresql で確認し dnf module enable postgresql:16 の後に postgresql-server をインストール。
  • 初期化が別段階。 postgresql-setup --initdb の後 systemctl enable --now postgresql、データは /var/lib/pgsql/data
  • リモート接続は 2 つのファイル。 postgresql.conflisten_addressespg_hba.confscram-sha-256 認証ルール、そして firewalld の 5432 開放。
  • SELinux が塞ぐ 2 箇所。 非標準データディレクトリ (semanage fcontext -t postgresql_db_t)、非標準ポート (semanage port -t postgresql_port_t)。
  • バックアップ。 単一 DB は pg_dump -Fc + pg_restore、クラスタ全体は pg_dumpall + psql、別のストレージで保管。
  • 診断順序。 サービス → ローカル psql → ファイアウォール → 認証 (pg_hba) → SELinux。

次回: コンテナワークロード #

Web 層とデータ層を RHEL の上に直接立ち上げたので、今回は同じワークロードを コンテナ にまとめる方式に進みます。

#3 コンテナワークロード: Podman、systemd (quadlet) では RHEL のデフォルトコンテナエンジンである Podman でコンテナを実行し、それを systemd サービスとして登録する quadlet 方式まで、コンテナを RHEL 運用体系の中に引き込む 1 サイクルを整理します。

X