🐡

PostgreSQLのReplication構成をdocker-compose upで一撃で立ち上げたい

2022/02/15に公開

動機

ローカル環境でインテグレーションテストを行う際に、posrgresのレプリケーション構成を組んだprimaryとreplicaを手動でセットアップしなくて済むようdocker-compose upだけで立ち上がるようにしました。

TL;DR

https://github.com/seita-uc/postgresql-replication
こちらに上げてある構成をコピペすれば動きます。
以降はこちらのファイルの中で工夫が必要だった部分のみ解説していきます。

docker-compose.yaml

基本的にはreferenceに挙げているPostgreSQL Replication with Dockerという記事を参考にしています。

version: '3.8'
services:
  primary:
    image: postgres:13.5
    restart: always
    command: -c 'config_file=/etc/postgresql/postgresql.conf' -c 'hba_file=/etc/postgresql/pg_hba.conf'
    environment:
      - POSTGRES_USER=root
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=db
    ports:
      - 15432:5432
    volumes:
      - ./primary/data:/var/lib/postgresql/data
      - ./primary/postgresql.conf:/etc/postgresql/postgresql.conf
      - ./primary/pg_hba.conf:/etc/postgresql/pg_hba.conf
      - ./primary/init.sh:/docker-entrypoint-initdb.d/init.sh
    healthcheck:
      test: pg_isready -d db
      interval: 1s
      timeout: 1s
      retries: 5

  replica:
    image: postgres:13.5
    restart: always
    entrypoint: /entrypoint.sh
    environment:
      - POSTGRES_USER=root
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=db
    ports:
      - 25432:5432
    volumes:
      - ./replica/data:/var/lib/postgresql/data
      - ./replica/entrypoint.sh:/entrypoint.sh
      - ./replica/postgresql.conf:/etc/postgresql/postgresql.conf
      - ./replica/pg_hba.conf:/etc/postgresql/pg_hba.conf
    healthcheck:
      test: pg_isready -d db
      interval: 1s
      timeout: 1s
      retries: 5
    depends_on:
      primary:
        condition: service_healthy

レプリケーション関連の設定を記述したpostgresql.confpg_hba.conf/etc/postgresql/にmountし、-c 'config_file=/etc/postgresql/postgresql.conf' -c 'hba_file=/etc/postgresql/pg_hba.conf'というoptionをcommandとして渡すことで明示的に設定値を読み込ませています。このoptionがない場合defaultでは/var/lib/postgresql/data以下を参照します。optionに関してはファイルの場所が参考になります。

primary側のhealth_checkとreplica側のdepends_onの記述により、確実にprimaryが起動してからreplicaが起動するよう制御できます。

primary/init.sh

#!/bin/bash

set -e

psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
  CREATE USER replicator WITH REPLICATION ENCRYPTED PASSWORD 'password';
  SELECT * FROM pg_create_physical_replication_slot('replication_slot_slave1');
EOSQL

前述のdocker-compose.yamlでprimaryに対して./primary/init.sh:/docker-entrypoint-initdb.d/init.shと指定してmountしているシェルスクリプトです。postgresのdocker hubにある通り、docker-entrypoint-initdb.d/以下にある*.sql*.shファイルはdbの初期化後に呼び出されるため、ここではreplicationで使用するuserとreplication slotを作成しています。

replica/entrypoint.sh

#!/bin/bash

set -e

while ! psql -h primary -U $POSTGRES_USER -d $POSTGRES_DB -p 5432 -c "select 'it is running';" 2>&1 ; do \
	sleep 1s ; \
done

# load backup from primary instance
pg_basebackup -h primary -p 5432 -D $PGDATA -S replication_slot_slave1 --progress -X stream -U replicator -Fp -R || :

# start postgres
bash /usr/local/bin/docker-entrypoint.sh -c 'config_file=/etc/postgresql/postgresql.conf' -c 'hba_file=/etc/postgresql/pg_hba.conf'

前述のdocker-compose.yamlではreplicaのentrypointを上書きしてmountしたこちらのentrypount.shを実行しています。defaultのentrypoint(/usr/local/bin/docker-entrypoint.sh)を上書きしている理由は、defaultのentrypointでdbを初期化することでPG_DATA(/var/lib/postgresql/data)にファイルが生成され、pg_basebackupでprimaryからbackupをloadする際にnot emptyエラーが発生してしまうのを回避するためです。

pg_basebackup: error: directory "/var/lib/postgresql/data" exists but is not empty

回避策として、entrypoint.shでは、

pg_basebackup -h primary -p 5432 -D $PGDATA -S replication_slot_slave1 --progress -X stream -U replicator -Fp -R || :

を実行してバックアップを取得してから明示的に

bash /usr/local/bin/docker-entrypoint.sh -c 'config_file=/etc/postgresql/postgresql.conf' -c 'hba_file=/etc/postgresql/pg_hba.conf'

を実行しています。

また、2回目以降の起動時に落ちないよう、pg_basebackupの末尾に|| :をつけることでエラーを無視しています。

reference

Discussion