4.0.3-debian-9-r0 release

This commit is contained in:
Bitnami Bot 2019-08-30 15:35:12 +00:00
parent 6ea7620e54
commit b2651a2956
21 changed files with 2371 additions and 0 deletions

View File

@ -0,0 +1,30 @@
FROM bitnami/minideb-extras-base:stretch-r346
LABEL maintainer "Bitnami <containers@bitnami.com>"
ENV BITNAMI_PKG_CHMOD="-R g+rwX" \
HOME="/" \
OS_ARCH="amd64" \
OS_FLAVOUR="debian-9" \
OS_NAME="linux"
# Install required system packages and dependencies
RUN install_packages libbsd0 libc6 libedit2 libgcc1 libicu57 liblzma5 libncurses5 libnss-wrapper libssl1.1 libstdc++6 libtinfo5 libuuid1 libxml2 libxslt1.1 locales zlib1g
RUN . ./libcomponent.sh && component_unpack "postgresql-repmgr" "4.0.3-0" --checksum 4b97f19154174f5b09cb9358b2266a265fb1c96aba192828884d91c8645c5bfb
RUN echo 'en_GB.UTF-8 UTF-8' >> /etc/locale.gen && locale-gen
RUN echo 'en_US.UTF-8 UTF-8' >> /etc/locale.gen && locale-gen
COPY rootfs /
RUN /postunpack.sh
ENV BITNAMI_APP_NAME="postgresql-repmgr" \
BITNAMI_IMAGE_VERSION="4.0.3-debian-9-r0" \
LANG="en_US.UTF-8" \
LANGUAGE="en_US:en" \
NAMI_PREFIX="/.nami" \
NSS_WRAPPER_LIB="/usr/lib/libnss_wrapper.so" \
PATH="/opt/bitnami/postgresql-repmgr/bin:/opt/bitnami/repmgr/bin:/opt/bitnami/postgresql/bin:$PATH"
EXPOSE 5432
USER 1001
ENTRYPOINT [ "/entrypoint.sh" ]
CMD [ "/run.sh" ]

View File

@ -0,0 +1,39 @@
version: '2'
services:
pg-0:
image: bitnami/postgresql-repmgr:4
ports:
- 5432
volumes:
- pg_0_data:/bitnami/postgresql
environment:
- POSTGRESQL_POSTGRES_PASSWORD=adminpassword
- POSTGRESQL_USERNAME=customuser
- POSTGRESQL_PASSWORD=custompassword
- POSTGRESQL_DATABASE=customdatabase
- REPMGR_PASSWORD=repmgrpassword
- REPMGR_PRIMARY_HOST=pg-0
- REPMGR_PARTNER_NODES=pg-0,pg-1
- REPMGR_NODE_NAME=pg-0
- REPMGR_NODE_NETWORK_NAME=pg-0
pg-1:
image: bitnami/postgresql-repmgr:4
ports:
- 5432
volumes:
- pg_1_data:/bitnami/postgresql
environment:
- POSTGRESQL_POSTGRES_PASSWORD=adminpassword
- POSTGRESQL_USERNAME=customuser
- POSTGRESQL_PASSWORD=custompassword
- POSTGRESQL_DATABASE=customdatabase
- REPMGR_PASSWORD=repmgrpassword
- REPMGR_PRIMARY_HOST=pg-0
- REPMGR_PARTNER_NODES=pg-0,pg-1
- REPMGR_NODE_NAME=pg-1
- REPMGR_NODE_NETWORK_NAME=pg-1
volumes:
pg_0_data:
driver: local
pg_1_data:
driver: local

View File

@ -0,0 +1,34 @@
#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail
#set -o xtrace
# shellcheck disable=SC1091
# Load libraries
. /liblog.sh
. /libbitnami.sh
. /libpostgresql.sh
. /librepmgr.sh
# Load PostgreSQL & repmgr environment variables
eval "$(repmgr_env)"
eval "$(postgresql_env)"
export MODULE=postgresql-repmgr
print_welcome_page
# Enable the nss_wrapper settings
postgresql_enable_nss_wrapper
if [[ "$*" = *"/run.sh"* ]]; then
info "** Starting PostgreSQL with Replication Manager setup **"
/setup.sh
touch "$POSTGRESQL_TMP_DIR"/.initialized
info "** PostgreSQL with Replication Manager setup finished! **"
fi
echo ""
exec "$@"

View File

@ -0,0 +1,20 @@
#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail
# set -o xtrace # Uncomment this line for debugging purpose
header="[REPMGR EVENT::$2]"
export header
echo "$header Node id: $1; Event type: $2; Success [1|0]: $3; Time: $4; Details: $5"
if [[ $3 -ne 1 ]];then
echo "$header The event failed! No need to do anything."
exit 1
fi
if [[ $1 -ne $(repmgr_get_node_id) ]]; then
echo "$header The event did not happen on me! No need to do anything."
exit 1
fi

View File

@ -0,0 +1,9 @@
#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail
# set -o xtrace # Uncomment this line for debugging purpose
# shellcheck disable=SC2154
echo "$header Locking primary..."

View File

@ -0,0 +1,14 @@
#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail
# set -o xtrace # Uncomment this line for debugging purpose
readonly query="SELECT upstream_node_id FROM repmgr.nodes WHERE node_id=$(repmgr_get_node_id)"
readonly new_upstream_node_id="$(echo "$query" | BITNAMI_DEBUG=true postgresql_execute "$REPMGR_DATABASE" "$POSTGRESQL_REPLICATION_USER" "$POSTGRESQL_REPLICATION_PASSWORD" "" "" "-tA")"
if [[ -n "$new_upstream_node_id" ]]; then
# shellcheck disable=SC2154
echo "$header Locking standby (new_upstream_node_id=$new_upstream_node_id)..."
echo "$new_upstream_node_id" > "$REPMGR_STANDBY_ROLE_LOCK_FILE_NAME"
fi

View File

@ -0,0 +1,10 @@
#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail
# set -o xtrace # Uncomment this line for debugging purpose
# shellcheck disable=SC2154
echo "$header Unlocking primary..."
rm -f "$REPMGR_PRIMARY_ROLE_LOCK_FILE_NAME"

View File

@ -0,0 +1,10 @@
#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail
# set -o xtrace # Uncomment this line for debugging purpose
# shellcheck disable=SC2154
echo "$header Unlocking standby..."
rm -f "$REPMGR_STANDBY_ROLE_LOCK_FILE_NAME"

View File

@ -0,0 +1,10 @@
#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail
# set -o xtrace # Uncomment this line for debugging purpose
. "$REPMGR_EVENTS_DIR/execs/includes/anotate_event_processing.sh"
. "$REPMGR_EVENTS_DIR/execs/includes/lock_primary.sh"
. "$REPMGR_EVENTS_DIR/execs/includes/unlock_standby.sh"

View File

@ -0,0 +1,11 @@
#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail
# set -o xtrace # Uncomment this line for debugging purpose
# shellcheck disable=SC1090
# shellcheck disable=SC1091
. "$REPMGR_EVENTS_DIR/execs/includes/anotate_event_processing.sh"
. "$REPMGR_EVENTS_DIR/execs/includes/lock_standby.sh"

View File

@ -0,0 +1,11 @@
#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail
# set -o xtrace # Uncomment this line for debugging purpose
# shellcheck disable=SC1090
# shellcheck disable=SC1091
. "$REPMGR_EVENTS_DIR/execs/includes/anotate_event_processing.sh"
. "$REPMGR_EVENTS_DIR/execs/includes/unlock_primary.sh"

View File

@ -0,0 +1,12 @@
#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail
# set -o xtrace # Uncomment this line for debugging purpose
# shellcheck disable=SC1090
# shellcheck disable=SC1091
. "$REPMGR_EVENTS_DIR/execs/includes/anotate_event_processing.sh"
. "$REPMGR_EVENTS_DIR/execs/includes/lock_primary.sh"
. "$REPMGR_EVENTS_DIR/execs/includes/unlock_standby.sh"

View File

@ -0,0 +1,25 @@
#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail
# set -o xtrace # Uncomment this line for debugging purpose
# shellcheck disable=SC1090
# shellcheck disable=SC1091
# Load libraries
. /librepmgr.sh
. /libpostgresql.sh
eval "$(repmgr_env)"
eval "$(postgresql_env)"
echo "[REPMGR EVENT] Node id: $1; Event type: $2; Success [1|0]: $3; Time: $4; Details: $5"
event_script="$REPMGR_EVENTS_DIR/execs/$2.sh"
echo "Looking for the script: $event_script"
if [[ -f "$event_script" ]]; then
echo "[REPMGR EVENT] will execute script '$event_script' for the event"
. "$event_script"
else
echo "[REPMGR EVENT] no script '$event_script' found. Skipping..."
fi

View File

@ -0,0 +1,788 @@
#!/bin/bash
#
# Bitnami PostgreSQL library
# shellcheck disable=SC1090
# shellcheck disable=SC1091
# Load Generic Libraries
. /libfile.sh
. /liblog.sh
. /libservice.sh
. /libvalidations.sh
########################
# Overwrite info, debug, warn and error functions (liblog.sh)
########################
postgresql_info() {
MODULE=postgresql info "${*}"
}
postgresql_debug() {
MODULE=postgresql debug "${*}"
}
postgresql_warn() {
MODULE=postgresql warn "${*}"
}
postgresql_error() {
MODULE=postgresql error "${*}"
}
########################
# Configure libnss_wrapper so PostgreSQL commands work with a random user.
# Globals:
# POSTGRESQL_*
# Arguments:
# None
# Returns:
# None
#########################
postgresql_enable_nss_wrapper() {
if ! getent passwd "$(id -u)" &> /dev/null && [ -e "$NSS_WRAPPER_LIB" ]; then
postgresql_debug "Configuring libnss_wrapper..."
export LD_PRELOAD="$NSS_WRAPPER_LIB"
# shellcheck disable=SC2155
export NSS_WRAPPER_PASSWD="$(mktemp)"
# shellcheck disable=SC2155
export NSS_WRAPPER_GROUP="$(mktemp)"
echo "postgres:x:$(id -u):$(id -g):PostgreSQL:$POSTGRESQL_DATA_DIR:/bin/false" > "$NSS_WRAPPER_PASSWD"
echo "postgres:x:$(id -g):" > "$NSS_WRAPPER_GROUP"
fi
}
########################
# Load global variables used on PostgreSQL configuration.
# Globals:
# POSTGRESQL_*
# Arguments:
# None
# Returns:
# Series of exports to be used as 'eval' arguments
#########################
postgresql_env() {
declare_env_alias() {
local -r alias="${1:?missing environment variable alias}"
local -r original="${2:?missing original environment variable}"
if printenv "${original}" > /dev/null; then
cat << EOF
export $alias="${!original}"
EOF
fi
}
# Alias created for official PostgreSQL image compatibility
[[ -z "${POSTGRESQL_DATABASE:-}" ]] && declare_env_alias POSTGRESQL_DATABASE POSTGRES_DB
[[ -z "${POSTGRESQL_USERNAME:-}" ]] && declare_env_alias POSTGRESQL_USERNAME POSTGRES_USER
[[ -z "${POSTGRESQL_DATA_DIR:-}" ]] && declare_env_alias POSTGRESQL_DATA_DIR PGDATA
local -r suffixes=(
"PASSWORD" "POSTGRES_PASSWORD" "INITDB_WAL_DIR" "INITDB_ARGS" "CLUSTER_APP_NAME"
"MASTER_HOST" "MASTER_PORT_NUMBER" "NUM_SYNCHRONOUS_REPLICAS"
"PORT_NUMBER" "REPLICATION_MODE" "REPLICATION_PASSWORD" "REPLICATION_USER" "FSYNC"
"SYNCHRONOUS_COMMIT_MODE" "PASSWORD_FILE" "POSTGRES_PASSWORD_FILE"
"REPLICATION_PASSWORD_FILE" "INIT_MAX_TIMEOUT"
)
for s in "${suffixes[@]}"; do
declare_env_alias "POSTGRESQL_${s}" "POSTGRES_${s}"
done
# Ensure the image is compatible with Helm chart 3.x.x series
local -r postgresql_data="${POSTGRESQL_DATA_DIR:-${PGDATA:-}}"
if [[ -n "${postgresql_data:-}" ]]; then
if [[ -d "${postgresql_data}/data" ]] || [[ "${postgresql_data}" = "/bitnami/postgresql" ]]; then
postgresql_warn "Data directory is set with a legacy value, adapting POSTGRESQL_DATA_DIR..."
postgresql_warn "POSTGRESQL_DATA_DIR set to \"${postgresql_data}/data\"!!"
cat << EOF
export POSTGRESQL_DATA_DIR="${postgresql_data}/data"
EOF
fi
fi
cat <<"EOF"
# Paths
export POSTGRESQL_VOLUME_DIR="${POSTGRESQL_VOLUME_DIR:-/bitnami/postgresql}"
export POSTGRESQL_DATA_DIR="${POSTGRESQL_DATA_DIR:-$POSTGRESQL_VOLUME_DIR/data}"
export POSTGRESQL_BASE_DIR="/opt/bitnami/postgresql"
export POSTGRESQL_CONF_DIR="$POSTGRESQL_BASE_DIR/conf"
export POSTGRESQL_MOUNTED_CONF_DIR="/bitnami/postgresql/conf"
export POSTGRESQL_CONF_FILE="$POSTGRESQL_CONF_DIR/postgresql.conf"
export POSTGRESQL_PGHBA_FILE="$POSTGRESQL_CONF_DIR/pg_hba.conf"
export POSTGRESQL_RECOVERY_FILE="$POSTGRESQL_DATA_DIR/recovery.conf"
export POSTGRESQL_LOG_DIR="$POSTGRESQL_BASE_DIR/logs"
export POSTGRESQL_LOG_FILE="$POSTGRESQL_LOG_DIR/postgresql.log"
export POSTGRESQL_TMP_DIR="$POSTGRESQL_BASE_DIR/tmp"
export POSTGRESQL_PID_FILE="$POSTGRESQL_TMP_DIR/postgresql.pid"
export POSTGRESQL_BIN_DIR="$POSTGRESQL_BASE_DIR/bin"
export POSTGRESQL_INITSCRIPTS_DIR=/docker-entrypoint-initdb.d
export PATH="$POSTGRESQL_BIN_DIR:$PATH"
# Users
export POSTGRESQL_DAEMON_USER="postgresql"
export POSTGRESQL_DAEMON_GROUP="postgresql"
# Settings
export POSTGRESQL_INIT_MAX_TIMEOUT=${POSTGRESQL_INIT_MAX_TIMEOUT:-60}
export POSTGRESQL_CLUSTER_APP_NAME=${POSTGRESQL_CLUSTER_APP_NAME:-walreceiver}
export POSTGRESQL_DATABASE="${POSTGRESQL_DATABASE:-postgres}"
export POSTGRESQL_INITDB_ARGS="${POSTGRESQL_INITDB_ARGS:-}"
export ALLOW_EMPTY_PASSWORD="${ALLOW_EMPTY_PASSWORD:-no}"
export POSTGRESQL_INITDB_WAL_DIR="${POSTGRESQL_INITDB_WAL_DIR:-}"
export POSTGRESQL_MASTER_HOST="${POSTGRESQL_MASTER_HOST:-}"
export POSTGRESQL_MASTER_PORT_NUMBER="${POSTGRESQL_MASTER_PORT_NUMBER:-5432}"
export POSTGRESQL_NUM_SYNCHRONOUS_REPLICAS="${POSTGRESQL_NUM_SYNCHRONOUS_REPLICAS:-0}"
export POSTGRESQL_PORT_NUMBER="${POSTGRESQL_PORT_NUMBER:-5432}"
export POSTGRESQL_REPLICATION_MODE="${POSTGRESQL_REPLICATION_MODE:-master}"
export POSTGRESQL_REPLICATION_USER="${POSTGRESQL_REPLICATION_USER:-}"
export POSTGRESQL_SYNCHRONOUS_COMMIT_MODE="${POSTGRESQL_SYNCHRONOUS_COMMIT_MODE:-on}"
export POSTGRESQL_FSYNC="${POSTGRESQL_FSYNC:-on}"
export POSTGRESQL_USERNAME="${POSTGRESQL_USERNAME:-postgres}"
EOF
if [[ -f "${POSTGRESQL_PASSWORD_FILE:-}" ]]; then
cat <<"EOF"
export POSTGRESQL_PASSWORD="$(< "${POSTGRESQL_PASSWORD_FILE}")"
EOF
else
cat <<"EOF"
export POSTGRESQL_PASSWORD="${POSTGRESQL_PASSWORD:-}"
EOF
fi
if [[ -f "${POSTGRESQL_REPLICATION_PASSWORD_FILE:-}" ]]; then
cat <<"EOF"
export POSTGRESQL_REPLICATION_PASSWORD="$(< "${POSTGRESQL_REPLICATION_PASSWORD_FILE}")"
EOF
else
cat <<"EOF"
export POSTGRESQL_REPLICATION_PASSWORD="${POSTGRESQL_REPLICATION_PASSWORD:-}"
EOF
fi
if [[ -f "${POSTGRESQL_POSTGRES_PASSWORD_FILE:-}" ]]; then
cat <<"EOF"
export POSTGRESQL_POSTGRES_PASSWORD="$(< "${POSTGRESQL_POSTGRES_PASSWORD_FILE}")"
EOF
else
cat <<"EOF"
export POSTGRESQL_POSTGRES_PASSWORD="${POSTGRESQL_POSTGRES_PASSWORD:-}"
EOF
fi
}
########################
# Validate settings in POSTGRESQL_* environment variables
# Globals:
# POSTGRESQL_*
# Arguments:
# None
# Returns:
# None
#########################
postgresql_validate() {
postgresql_info "Validating settings in POSTGRESQL_* env vars.."
# Auxiliary functions
empty_password_enabled_warn() {
postgresql_warn "You set the environment variable ALLOW_EMPTY_PASSWORD=${ALLOW_EMPTY_PASSWORD}. For safety reasons, do not use this flag in a production environment."
}
empty_password_error() {
postgresql_error "The $1 environment variable is empty or not set. Set the environment variable ALLOW_EMPTY_PASSWORD=yes to allow the container to be started with blank passwords. This is recommended only for development."
exit 1
}
if is_boolean_yes "$ALLOW_EMPTY_PASSWORD"; then
empty_password_enabled_warn
else
if [[ -z "$POSTGRESQL_PASSWORD" ]]; then
empty_password_error "POSTGRESQL_PASSWORD"
exit 1
fi
if (( ${#POSTGRESQL_PASSWORD} > 100 )); then
postgresql_error "The password cannot be longer than 100 characters. Set the environment variable POSTGRESQL_PASSWORD with a shorter value"
exit 1
fi
if [[ -n "$POSTGRESQL_USERNAME" ]] && [[ -z "$POSTGRESQL_PASSWORD" ]]; then
empty_password_error "POSTGRESQL_PASSWORD"
exit 1
fi
if [[ -n "$POSTGRESQL_USERNAME" ]] && [[ "$POSTGRESQL_USERNAME" != "postgres" ]] && [[ -n "$POSTGRESQL_PASSWORD" ]] && [[ -z "$POSTGRESQL_DATABASE" ]]; then
postgresql_error "In order to use a custom PostgreSQL user you need to set the environment variable POSTGRESQL_DATABASE as well"
exit 1
fi
fi
if [[ -n "$POSTGRESQL_REPLICATION_MODE" ]]; then
if [[ "$POSTGRESQL_REPLICATION_MODE" = "master" ]]; then
if (( POSTGRESQL_NUM_SYNCHRONOUS_REPLICAS < 0 )); then
postgresql_error "The number of synchronous replicas cannot be less than 0. Set the environment variable POSTGRESQL_NUM_SYNCHRONOUS_REPLICAS"
exit 1
fi
elif [[ "$POSTGRESQL_REPLICATION_MODE" = "slave" ]]; then
if [[ -z "$POSTGRESQL_MASTER_HOST" ]]; then
postgresql_error "Slave replication mode chosen without setting the environment variable POSTGRESQL_MASTER_HOST. Use it to indicate where the Master node is running"
exit 1
fi
if [[ -z "$POSTGRESQL_REPLICATION_USER" ]]; then
postgresql_error "Slave replication mode chosen without setting the environment variable POSTGRESQL_REPLICATION_USER. Make sure that the master also has this parameter set"
exit 1
fi
else
postgresql_error "Invalid replication mode. Available options are 'master/slave'"
exit 1
fi
# Common replication checks
if [[ -n "$POSTGRESQL_REPLICATION_USER" ]] && [[ -z "$POSTGRESQL_REPLICATION_PASSWORD" ]]; then
empty_password_error "POSTGRESQL_REPLICATION_PASSWORD"
exit 1
fi
else
if is_boolean_yes "$ALLOW_EMPTY_PASSWORD"; then
empty_password_enabled_warn
else
if [[ -z "$POSTGRESQL_PASSWORD" ]]; then
empty_password_error "POSTGRESQL_PASSWORD"
exit 1
fi
if [[ -n "$POSTGRESQL_USERNAME" ]] && [[ -z "$POSTGRESQL_PASSWORD" ]]; then
empty_password_error "POSTGRESQL_PASSWORD"
exit 1
fi
fi
fi
}
########################
# Create basic postgresql.conf file using the example provided in the share/ folder
# Globals:
# POSTGRESQL_*
# Arguments:
# None
# Returns:
# None
#########################
postgresql_create_config() {
postgresql_info "postgresql.conf file not detected. Generating it..."
cp "$POSTGRESQL_BASE_DIR/share/postgresql.conf.sample" "$POSTGRESQL_CONF_FILE"
# Update default value for 'include_dir' directive
# ref: https://github.com/postgres/postgres/commit/fb9c475597c245562a28d1e916b575ac4ec5c19f#diff-f5544d9b6d218cc9677524b454b41c60
if ! grep include_dir "$POSTGRESQL_CONF_FILE" > /dev/null; then
postgresql_error "include_dir line is not present in $POSTGRESQL_CONF_FILE. This may be due to a changes in a new version of PostgreSQL. Please check"
exit 1
fi
sed -i -E "/#include_dir/i include_dir = 'conf.d'" "$POSTGRESQL_CONF_FILE"
}
########################
# Create basic pg_hba.conf file
# Globals:
# POSTGRESQL_*
# Arguments:
# None
# Returns:
# None
#########################
postgresql_create_pghba() {
postgresql_info "pg_hba.conf file not detected. Generating it..."
cat << EOF > "$POSTGRESQL_PGHBA_FILE"
host all all 0.0.0.0/0 trust
host all all ::1/128 trust
EOF
}
########################
# Change pg_hba.conf so it allows local UNIX socket-based connections
# Globals:
# POSTGRESQL_*
# Arguments:
# None
# Returns:
# None
#########################
postgresql_allow_local_connection() {
cat << EOF >> "$POSTGRESQL_PGHBA_FILE"
local all all trust
EOF
}
########################
# Change pg_hba.conf so only password-based authentication is allowed
# Globals:
# POSTGRESQL_*
# Arguments:
# None
# Returns:
# None
#########################
postgresql_restrict_pghba() {
if [[ -n "$POSTGRESQL_PASSWORD" ]]; then
sed -i 's/trust/md5/g' "$POSTGRESQL_PGHBA_FILE"
fi
}
########################
# Change pg_hba.conf so it allows access from replication users
# Globals:
# POSTGRESQL_*
# Arguments:
# None
# Returns:
# None
#########################
postgresql_add_replication_to_pghba() {
local replication_auth="trust"
if [[ -n "$POSTGRESQL_REPLICATION_PASSWORD" ]]; then
replication_auth="md5"
fi
cat << EOF >> "$POSTGRESQL_PGHBA_FILE"
host replication all 0.0.0.0/0 ${replication_auth}
EOF
}
########################
# Change a PostgreSQL configuration file by setting a property
# Globals:
# POSTGRESQL_*
# Arguments:
# $1 - property
# $2 - value
# $3 - Path to configuration file (default: $POSTGRESQL_CONF_FILE)
# Returns:
# None
#########################
postgresql_set_property() {
local -r property="${1:?missing property}"
local -r value="${2:?missing value}"
local -r conf_file="${3:-$POSTGRESQL_CONF_FILE}"
sed -i "s?^#*\s*${property}\s*=.*?${property} = '${value}'?g" "$conf_file"
}
########################
# Create a user for master-slave replication
# Globals:
# POSTGRESQL_*
# Arguments:
# None
# Returns:
# None
#########################
postgresql_create_replication_user() {
local -r escaped_password="${POSTGRESQL_REPLICATION_PASSWORD//\'/\'\'}"
postgresql_info "Creating replication user $POSTGRESQL_REPLICATION_USER"
echo "CREATE ROLE \"$POSTGRESQL_REPLICATION_USER\" REPLICATION LOGIN ENCRYPTED PASSWORD '$escaped_password'" | postgresql_execute
}
########################
# Change postgresql.conf by setting replication parameters
# Globals:
# POSTGRESQL_*
# Arguments:
# None
# Returns:
# None
#########################
postgresql_configure_replication_parameters() {
postgresql_info "Configuring replication parameters"
postgresql_set_property "wal_level" "hot_standby"
postgresql_set_property "max_wal_size" "400MB"
postgresql_set_property "max_wal_senders" "16"
postgresql_set_property "wal_keep_segments" "12"
postgresql_set_property "hot_standby" "on"
if (( POSTGRESQL_NUM_SYNCHRONOUS_REPLICAS > 0 )); then
postgresql_set_property "synchronous_commit" "$POSTGRESQL_SYNCHRONOUS_COMMIT_MODE"
postgresql_set_property "synchronous_standby_names" "$POSTGRESQL_NUM_SYNCHRONOUS_REPLICAS ($POSTGRESQL_CLUSTER_APP_NAME)"
fi
}
########################
# Change postgresql.conf by setting fsync
# Globals:
# POSTGRESQL_*
# Arguments:
# None
# Returns:
# None
#########################
postgresql_configure_fsync() {
postgresql_info "Configuring fsync"
postgresql_set_property "fsync" "$POSTGRESQL_FSYNC"
}
########################
# Alter password of the postgres user
# Globals:
# POSTGRESQL_*
# Arguments:
# Password
# Returns:
# None
#########################
postgresql_alter_postgres_user() {
local -r escaped_password="${1//\'/\'\'}"
postgresql_info "Changing password of postgres"
echo "ALTER ROLE postgres WITH PASSWORD '$escaped_password';" | postgresql_execute
}
########################
# Create an admin user with all privileges in POSTGRESQL_DATABASE
# Globals:
# POSTGRESQL_*
# Arguments:
# None
# Returns:
# None
#########################
postgresql_create_admin_user() {
local -r escaped_password="${POSTGRESQL_PASSWORD//\'/\'\'}"
postgresql_info "Creating user ${POSTGRESQL_USERNAME}"
echo "CREATE ROLE \"${POSTGRESQL_USERNAME}\" WITH LOGIN CREATEDB PASSWORD '${escaped_password}';" | postgresql_execute
postgresql_info "Grating access to \"${POSTGRESQL_USERNAME}\" to the database \"${POSTGRESQL_DATABASE}\""
echo "GRANT ALL PRIVILEGES ON DATABASE \"${POSTGRESQL_DATABASE}\" TO \"${POSTGRESQL_USERNAME}\"\;" | postgresql_execute "" "postgres" "$POSTGRESQL_PASSWORD"
}
########################
# Create a database with name $POSTGRESQL_DATABASE
# Globals:
# POSTGRESQL_*
# Arguments:
# None
# Returns:
# None
#########################
postgresql_create_custom_database() {
echo "CREATE DATABASE \"$POSTGRESQL_DATABASE\"" | postgresql_execute "" "postgres" "" "localhost"
}
########################
# Change postgresql.conf to listen in 0.0.0.0
# Arguments:
# None
# Returns:
# None
#########################
postgresql_enable_remote_connections() {
postgresql_set_property "listen_addresses" "*"
}
########################
# Check if a given configuration file was mounted externally
# Globals:
# POSTGRESQL_*
# Arguments:
# $1 - Filename
# Returns:
# 1 if the file was mounted externally, 0 otherwise
#########################
postgresql_is_file_external() {
local -r filename=$1
if [[ -d "$POSTGRESQL_MOUNTED_CONF_DIR" ]] && [[ -f "$POSTGRESQL_MOUNTED_CONF_DIR"/"$filename" ]]; then
return 0
else
return 1
fi
}
########################
# Ensure PostgreSQL is initialized
# Globals:
# POSTGRESQL_*
# Arguments:
# None
# Returns:
# None
#########################
postgresql_initialize() {
postgresql_info "Initializing PostgreSQL database..."
# This fixes an issue where the trap would kill the entrypoint.sh, if a PID was left over from a previous run
# Exec replaces the process without creating a new one, and when the container is restarted it may have the same PID
rm -f "$POSTGRESQL_PID_FILE"
# User injected custom configuration
if [[ -d "$POSTGRESQL_MOUNTED_CONF_DIR" ]] && compgen -G "$POSTGRESQL_MOUNTED_CONF_DIR"/* > /dev/null; then
postgresql_debug "Copying files from $POSTGRESQL_MOUNTED_CONF_DIR to $POSTGRESQL_CONF_DIR"
cp -fr "$POSTGRESQL_MOUNTED_CONF_DIR"/. "$POSTGRESQL_CONF_DIR"
fi
local create_conf_file=yes
local create_pghba_file=yes
if postgresql_is_file_external "postgresql.conf"; then
postgresql_info "Custom configuration $POSTGRESQL_CONF_FILE detected"
create_conf_file=no
fi
if postgresql_is_file_external "pg_hba.conf"; then
postgresql_info "Custom configuration $POSTGRESQL_PGHBA_FILE detected"
create_pghba_file=no
fi
postgresql_debug "Ensuring expected directories/files exist..."
for dir in "$POSTGRESQL_TMP_DIR" "$POSTGRESQL_LOG_DIR"; do
ensure_dir_exists "$dir"
am_i_root && chown "$POSTGRESQL_DAEMON_USER:$POSTGRESQL_DAEMON_GROUP" "$dir"
done
is_boolean_yes "$create_conf_file" && postgresql_create_config
is_boolean_yes "$create_pghba_file" && postgresql_create_pghba && postgresql_allow_local_connection
if ! is_dir_empty "$POSTGRESQL_DATA_DIR"; then
postgresql_info "Deploying PostgreSQL with persisted data..."
local -r postmaster_path="$POSTGRESQL_DATA_DIR"/postmaster.pid
if [[ -f "$postmaster_path" ]]; then
postgresql_info "Cleaning stale postmaster.pid file"
rm "$postmaster_path"
fi
is_boolean_yes "$create_pghba_file" && postgresql_restrict_pghba
is_boolean_yes "$create_conf_file" && postgresql_configure_replication_parameters
is_boolean_yes "$create_conf_file" && postgresql_configure_fsync
[[ "$POSTGRESQL_REPLICATION_MODE" = "master" ]] && [[ -n "$POSTGRESQL_REPLICATION_USER" ]] && is_boolean_yes "$create_pghba_file" && postgresql_add_replication_to_pghba
else
ensure_dir_exists "$POSTGRESQL_DATA_DIR"
am_i_root && chown "$POSTGRESQL_DAEMON_USER:$POSTGRESQL_DAEMON_GROUP" "$POSTGRESQL_DATA_DIR"
if [[ "$POSTGRESQL_REPLICATION_MODE" = "master" ]]; then
postgresql_master_init_db
postgresql_start_bg
[[ -n "${POSTGRESQL_DATABASE}" ]] && [[ "$POSTGRESQL_DATABASE" != "postgres" ]] && postgresql_create_custom_database
if [[ "$POSTGRESQL_USERNAME" = "postgres" ]]; then
postgresql_alter_postgres_user "$POSTGRESQL_PASSWORD"
else
if [[ -n "$POSTGRESQL_POSTGRES_PASSWORD" ]]; then
postgresql_alter_postgres_user "$POSTGRESQL_POSTGRES_PASSWORD"
fi
postgresql_create_admin_user
fi
is_boolean_yes "$create_pghba_file" && postgresql_restrict_pghba
[[ -n "$POSTGRESQL_REPLICATION_USER" ]] && postgresql_create_replication_user
is_boolean_yes "$create_conf_file" && postgresql_configure_replication_parameters
is_boolean_yes "$create_conf_file" && postgresql_configure_fsync
[[ -n "$POSTGRESQL_REPLICATION_USER" ]] && is_boolean_yes "$create_pghba_file" && postgresql_add_replication_to_pghba
else
postgresql_slave_init_db
is_boolean_yes "$create_pghba_file" && postgresql_restrict_pghba
is_boolean_yes "$create_conf_file" && postgresql_configure_replication_parameters
is_boolean_yes "$create_conf_file" && postgresql_configure_fsync
postgresql_configure_recovery
fi
fi
# Delete conf files generated on first run
rm -f "$POSTGRESQL_DATA_DIR"/postgresql.conf "$POSTGRESQL_DATA_DIR"/pg_hba.conf
}
########################
# Run custom initialization scripts
# Globals:
# POSTGRESQL_*
# Arguments:
# None
# Returns:
# None
#########################
postgresql_custom_init_scripts() {
postgresql_info "Loading custom scripts..."
if [[ -n $(find "$POSTGRESQL_INITSCRIPTS_DIR/" -type f -regex ".*\.\(sh\|sql\|sql.gz\)") ]] && [[ ! -f "$POSTGRESQL_VOLUME_DIR/.user_scripts_initialized" ]] ; then
postgresql_info "Loading user's custom files from $POSTGRESQL_INITSCRIPTS_DIR ...";
postgresql_start_bg
find "$POSTGRESQL_INITSCRIPTS_DIR/" -type f -regex ".*\.\(sh\|sql\|sql.gz\)" | sort | while read -r f; do
case "$f" in
*.sh)
if [[ -x "$f" ]]; then
postgresql_debug "Executing $f"; "$f"
else
postgresql_debug "Sourcing $f"; . "$f"
fi
;;
*.sql) postgresql_debug "Executing $f"; postgresql_execute "$POSTGRESQL_DATABASE" "$POSTGRESQL_USERNAME" "$POSTGRESQL_PASSWORD" < "$f";;
*.sql.gz) postgresql_debug "Executing $f"; gunzip -c "$f" | postgresql_execute "$POSTGRESQL_DATABASE" "$POSTGRESQL_USERNAME" "$POSTGRESQL_PASSWORD";;
*) postgresql_debug "Ignoring $f" ;;
esac
done
touch "$POSTGRESQL_VOLUME_DIR"/.user_scripts_initialized
fi
}
########################
# Stop PostgreSQL
# Globals:
# POSTGRESQL_*
# Arguments:
# None
# Returns:
# None
#########################
postgresql_stop() {
postgresql_info "Stopping PostgreSQL..."
stop_service_using_pid "$POSTGRESQL_PID_FILE"
}
########################
# Execute an arbitrary query/queries against the running PostgreSQL service
# Stdin:
# Query/queries to execute
# Globals:
# BITNAMI_DEBUG
# POSTGRESQL_*
# Arguments:
# $1 - Database where to run the queries
# $2 - User to run queries
# $3 - Password
# $4 - Host
# $5 - Port
# $6 - Extra options (eg. -tA)
# Returns:
# None
postgresql_execute() {
local -r db="${1:-}"
local -r user="${2:-postgres}"
local -r pass="${3:-}"
local -r host="${4:-localhost}"
local -r port="${5:-5432}"
local -r opts="${6:-}"
local args=( "-h" "$host" "-p" "$port" "-U" "$user" )
local cmd=("$POSTGRESQL_BIN_DIR/psql")
[[ -n "$db" ]] && args+=( "-d" "$db" )
[[ -n "$opts" ]] && args+=( "$opts" )
if [[ "${BITNAMI_DEBUG:-false}" = true ]]; then
PGPASSWORD=$pass "${cmd[@]}" "${args[@]}"
elif [[ "${NO_ERRORS:-false}" = true ]]; then
PGPASSWORD=$pass "${cmd[@]}" "${args[@]}" 2>/dev/null
else
PGPASSWORD=$pass "${cmd[@]}" "${args[@]}" >/dev/null 2>&1
fi
}
########################
# Start PostgreSQL and wait until it is ready
# Globals:
# POSTGRESQL_*
# Arguments:
# None
# Returns:
# None
#########################
postgresql_start_bg() {
local -r pg_ctl_flags=("-w" "-D" "$POSTGRESQL_DATA_DIR" "-l" "$POSTGRESQL_LOG_FILE" "-o" "--config-file=$POSTGRESQL_CONF_FILE --external_pid_file=$POSTGRESQL_PID_FILE --hba_file=$POSTGRESQL_PGHBA_FILE")
postgresql_info "Starting PostgreSQL in background..."
is_postgresql_running && return
if [[ "${BITNAMI_DEBUG:-false}" = true ]]; then
"$POSTGRESQL_BIN_DIR"/pg_ctl "start" "${pg_ctl_flags[@]}"
else
"$POSTGRESQL_BIN_DIR"/pg_ctl "start" "${pg_ctl_flags[@]}" >/dev/null 2>&1
fi
local -r pg_isready_args=("-U" "postgres")
local counter=$POSTGRESQL_INIT_MAX_TIMEOUT
while ! "$POSTGRESQL_BIN_DIR"/pg_isready "${pg_isready_args[@]}" >/dev/null 2>&1; do
sleep 1
counter=$((counter - 1 ))
if (( counter <= 0 )); then
postgresql_error "PostgreSQL is not ready after $POSTGRESQL_INIT_MAX_TIMEOUT seconds"
exit 1
fi
done
}
########################
# Check if PostgreSQL is running
# Globals:
# POSTGRESQL_*
# Arguments:
# None
# Returns:
# Boolean
#########################
is_postgresql_running() {
local pid
pid="$(get_pid_from_file "$POSTGRESQL_PID_FILE")"
if [[ -z "$pid" ]]; then
false
else
is_service_running "$pid"
fi
}
########################
# Initialize master node database by running initdb
# Globals:
# POSTGRESQL_*
# Arguments:
# None
# Returns:
# Boolean
#########################
postgresql_master_init_db() {
local initdb_args=()
if [[ -n "${POSTGRESQL_INITDB_ARGS[*]}" ]]; then
initdb_args+=("${POSTGRESQL_INITDB_ARGS[@]}")
fi
if [[ -n "$POSTGRESQL_INITDB_WAL_DIR" ]]; then
ensure_dir_exists "$POSTGRESQL_INITDB_WAL_DIR"
am_i_root && chown "$POSTGRESQL_DAEMON_USER:$POSTGRESQL_DAEMON_GROUP" "$POSTGRESQL_INITDB_WAL_DIR"
initdb_args+=("--waldir" "$POSTGRESQL_INITDB_WAL_DIR")
fi
if [[ -n "${initdb_args[*]:-}" ]]; then
postgresql_info "Initializing PostgreSQL with ${initdb_args[*]} extra initdb arguments"
if [[ "${BITNAMI_DEBUG:-false}" = true ]]; then
"$POSTGRESQL_BIN_DIR/initdb" -E UTF8 -D "$POSTGRESQL_DATA_DIR" -U "postgres" "${initdb_args[@]}"
else
"$POSTGRESQL_BIN_DIR/initdb" -E UTF8 -D "$POSTGRESQL_DATA_DIR" -U "postgres" "${initdb_args[@]}" >/dev/null 2>&1
fi
elif [[ "${BITNAMI_DEBUG:-false}" = true ]]; then
"$POSTGRESQL_BIN_DIR/initdb" -E UTF8 -D "$POSTGRESQL_DATA_DIR" -U "postgres"
else
"$POSTGRESQL_BIN_DIR/initdb" -E UTF8 -D "$POSTGRESQL_DATA_DIR" -U "postgres" >/dev/null 2>&1
fi
}
########################
# Initialize slave node by running pg_basebackup
# Globals:
# POSTGRESQL_*
# Arguments:
# None
# Returns:
# Boolean
#########################
postgresql_slave_init_db() {
postgresql_info "Waiting for replication master to accept connections (${POSTGRESQL_INIT_MAX_TIMEOUT} timeout)..."
local -r check_args=("-U" "$POSTGRESQL_REPLICATION_USER" "-h" "$POSTGRESQL_MASTER_HOST" "-p" "$POSTGRESQL_MASTER_PORT_NUMBER" "-d" "postgres")
local -r check_cmd=("$POSTGRESQL_BIN_DIR"/pg_isready)
local ready_counter=$POSTGRESQL_INIT_MAX_TIMEOUT
while ! PGPASSWORD=$POSTGRESQL_REPLICATION_PASSWORD "${check_cmd[@]}" "${check_args[@]}";do
sleep 1
ready_counter=$(( ready_counter - 1 ))
if (( ready_counter <= 0 )); then
postgresql_error "PostgreSQL master is not ready after $POSTGRESQL_INIT_MAX_TIMEOUT seconds"
exit 1
fi
done
postgresql_info "Replicating the initial database"
local -r backup_args=("-D" "$POSTGRESQL_DATA_DIR" "-U" "$POSTGRESQL_REPLICATION_USER" "-h" "$POSTGRESQL_MASTER_HOST" "-p" "$POSTGRESQL_MASTER_PORT_NUMBER" "-X" "stream" "-w" "-v" "-P")
local -r backup_cmd=("$POSTGRESQL_BIN_DIR"/pg_basebackup)
local replication_counter=$POSTGRESQL_INIT_MAX_TIMEOUT
while ! PGPASSWORD=$POSTGRESQL_REPLICATION_PASSWORD "${backup_cmd[@]}" "${backup_args[@]}";do
postgresql_debug "Backup command failed. Sleeping and trying again"
sleep 1
replication_counter=$(( replication_counter - 1 ))
if (( replication_counter <= 0 )); then
postgresql_error "Slave replication failed after trying for $POSTGRESQL_INIT_MAX_TIMEOUT seconds"
exit 1
fi
done
chmod 0700 "$POSTGRESQL_DATA_DIR"
}
########################
# Create recovery.conf in slave node
# Globals:
# POSTGRESQL_*
# Arguments:
# None
# Returns:
# Boolean
#########################
postgresql_configure_recovery() {
postgresql_info "Setting up streaming replication slave..."
cp -f "$POSTGRESQL_BASE_DIR/share/recovery.conf.sample" "$POSTGRESQL_RECOVERY_FILE"
chmod 600 "$POSTGRESQL_RECOVERY_FILE"
postgresql_set_property "standby_mode" "on" "$POSTGRESQL_RECOVERY_FILE"
postgresql_set_property "primary_conninfo" "host=$POSTGRESQL_MASTER_HOST port=$POSTGRESQL_MASTER_PORT_NUMBER user=$POSTGRESQL_REPLICATION_USER password=$POSTGRESQL_REPLICATION_PASSWORD application_name=$POSTGRESQL_CLUSTER_APP_NAME" "$POSTGRESQL_RECOVERY_FILE"
postgresql_set_property "trigger_file" "/tmp/postgresql.trigger.$POSTGRESQL_MASTER_PORT_NUMBER" "$POSTGRESQL_RECOVERY_FILE"
}

View File

@ -0,0 +1,595 @@
#!/bin/bash
#
# Bitnami Postgresql Repmgr library
# shellcheck disable=SC1090
# shellcheck disable=SC1091
# Load Generic Libraries
. /liblog.sh
. /libfs.sh
. /libos.sh
. /libvalidations.sh
########################
# Overwrite info, debug, warn and error functions (liblog.sh)
########################
repmgr_info() {
MODULE=repmgr info "${*}"
}
repmgr_debug() {
MODULE=repmgr debug "${*}"
}
repmgr_warn() {
MODULE=repmgr warn "${*}"
}
repmgr_error() {
MODULE=repmgr error "${*}"
}
########################
# Loads global variables used on repmgr configuration.
# Globals:
# REPMGR_*
# Arguments:
# None
# Returns:
# Series of exports to be used as 'eval' arguments
#########################
repmgr_env() {
cat <<"EOF"
# Paths
export REPMGR_BASE_DIR="/opt/bitnami/repmgr"
export REPMGR_CONF_DIR="${REPMGR_BASE_DIR}/conf"
export REPMGR_TMP_DIR="${REPMGR_BASE_DIR}/tmp"
export REPMGR_EVENTS_DIR="${REPMGR_BASE_DIR}/events"
export REPMGR_PRIMARY_ROLE_LOCK_FILE_NAME="${REPMGR_TMP_DIR}/master.lock"
export REPMGR_STANDBY_ROLE_LOCK_FILE_NAME="${REPMGR_TMP_DIR}/standby.lock"
export REPMGR_BIN_DIR="${REPMGR_BASE_DIR}/bin"
export REPMGR_CONF_FILE="${REPMGR_CONF_DIR}/repmgr.conf"
export REPMGR_PID_FILE="${REPMGR_TMP_DIR}/repmgr.pid"
export PATH="${REPMGR_BIN_DIR}:$PATH"
# Settings
export REPMGR_NODE_ID="${REPMGR_NODE_ID:-}"
export REPMGR_NODE_NAME="${REPMGR_NODE_NAME:-$(hostname)}"
export REPMGR_NODE_NETWORK_NAME="${REPMGR_NODE_NETWORK_NAME:-}"
export REPMGR_NODE_PRIORITY="${REPMGR_NODE_PRIORITY:-100}"
export REPMGR_PORT_NUMBER="${REPMGR_PORT_NUMBER:-5432}"
export REPMGR_LOG_LEVEL="${REPMGR_LOG_LEVEL:-NOTICE}"
export REPMGR_START_OPTIONS="${REPMGR_START_OPTIONS:-}"
export REPMGR_CONNECT_TIMEOUT="${REPMGR_CONNECT_TIMEOUT:-5}"
export REPMGR_RECONNECT_ATTEMPTS="${REPMGR_RECONNECT_ATTEMPTS:-3}"
export REPMGR_RECONNECT_INTERVAL="${REPMGR_RECONNECT_INTERVAL:-5}"
export REPMGR_PARTNER_NODES="${REPMGR_PARTNER_NODES:-}"
export REPMGR_PRIMARY_HOST="${REPMGR_PRIMARY_HOST:-}"
export REPMGR_PRIMARY_PORT="${REPMGR_PRIMARY_PORT:-5432}"
export REPMGR_USE_REPLICATION_SLOTS="${REPMGR_USE_REPLICATION_SLOTS:-1}"
export REPMGR_STANDBY_ROLE_LOCK_FILE_NAME="${REPMGR_TMP_DIR}/standby.lock"
export REPMGR_MASTER_RESPONSE_TIMEOUT="${REPMGR_MASTER_RESPONSE_TIMEOUT:-20}"
export REPMGR_DEGRADED_MONITORING_TIMEOUT="${REPMGR_DEGRADED_MONITORING_TIMEOUT:-5}"
# These are internal
export REPMGR_SWITCH_ROLE="${REPMGR_SWITCH_ROLE:-no}"
export REPMGR_CURRENT_PRIMARY_HOST=""
# Aliases to setup PostgreSQL environment variables
export PGCONNECT_TIMEOUT="${PGCONNECT_TIMEOUT:-10}"
# Credentials
export REPMGR_USERNAME="${REPMGR_USERNAME:-repmgr}"
export REPMGR_DATABASE="${REPMGR_DATABASE:-repmgr}"
EOF
if [[ -f "${REPMGR_PASSWORD_FILE:-}" ]]; then
cat <<"EOF"
export REPMGR_PASSWORD="$(< "${REPMGR_PASSWORD_FILE}")"
EOF
else
cat <<"EOF"
export REPMGR_PASSWORD="${REPMGR_PASSWORD:-}"
EOF
fi
}
########################
# Get repmgr node id
# Globals:
# REPMGR_*
# Arguments:
# None
# Returns:
# String
#########################
repmgr_get_node_id() {
local num
if [[ "$REPMGR_NODE_ID" != "" ]]; then
echo "$REPMGR_NODE_ID"
else
num="${REPMGR_NODE_NAME##*-}"
if [[ "$num" != "" ]]; then
num=$((num+1000))
echo "$num"
fi
fi
}
########################
# Validate settings in REPMGR_* env. variables
# Globals:
# REPMGR_*
# Arguments:
# None
# Returns:
# None
#########################
repmgr_validate() {
repmgr_info "Validating settings in REPMGR_* env vars..."
# Auxiliary functions
print_error_exit() {
repmgr_error "$1"
exit 1
}
if [[ -z "$REPMGR_PARTNER_NODES" ]]; then
print_error_exit "The list of partner nodes cannot be empty. Set the environment variable REPMGR_PARTNER_NODES with a comma separated list of partner nodes."
fi
if [[ -z "$REPMGR_PRIMARY_HOST" ]]; then
print_error_exit "The initial primary host is required. Set the environment variable REPMGR_PRIMARY_HOST with the initial primary host."
fi
if [[ -z "$REPMGR_NODE_NAME" ]]; then
print_error_exit "The node name is required. Set the environment variable REPMGR_NODE_NAME with the node name."
elif [[ ! "$REPMGR_NODE_NAME" =~ ^[A-Za-z]+-[0-9]+$ ]]; then
print_error_exit "The node name does not follow the required format. Valid format: [A-Za-z]+-[0-9]+"
fi
if [[ -z "$(repmgr_get_node_id)" ]]; then
print_error_exit "The node id is required. Set the environment variable REPMGR_NODE_ID with the node id."
fi
if [[ -z "$REPMGR_NODE_NETWORK_NAME" ]]; then
print_error_exit "The node network name is required. Set the environment variable REPMGR_NODE_NETWORK_NAME with the node network name."
fi
# Credentials validations
if [[ -z "$REPMGR_USERNAME" ]] || [[ -z "$REPMGR_PASSWORD" ]]; then
print_error_exit "The repmgr credentials are mandatory. Set the environment variables REPMGR_USERNAME and REPMGR_PASSWORD with the repmgr credentials."
fi
}
########################
# Ask partner nodes which node is the primary
# Globals:
# REPMGR_*
# Arguments:
# Non
# Returns:
# String
#########################
repmgr_get_upstream_node() {
local primary_conninfo
local pretending_primary=""
if [[ -n "$REPMGR_PARTNER_NODES" ]]; then
repmgr_info "Querying all partner nodes for common upstream node..."
read -r -a nodes <<< "$(tr ',;' ' ' <<< "${REPMGR_PARTNER_NODES}")"
for node in "${nodes[@]}"; do
repmgr_debug "Checking node $node..."
local query="SELECT conninfo FROM repmgr.show_nodes WHERE (upstream_node_name IS NULL OR upstream_node_name = '') AND active=true"
if ! primary_conninfo="$(echo "$query" | NO_ERRORS=true postgresql_execute "$REPMGR_DATABASE" "$REPMGR_USERNAME" "$REPMGR_PASSWORD" "$node" "$REPMGR_PRIMARY_PORT" "-tA")"; then
repmgr_debug "Skipping: failed to get primary from the node $node!"
continue
elif [[ -z "$primary_conninfo" ]]; then
repmgr_debug "Skipping: failed to get information about primary nodes!"
continue
elif [[ "$(echo "$primary_conninfo" | wc -l)" -eq 1 ]]; then
local -r suggested_primary="$(echo "$primary_conninfo" | awk -F 'host=' '{print $2}' | awk '{print $1}')"
repmgr_debug "Pretending primary role node - ${suggested_primary}"
if [[ -n "$pretending_primary" ]]; then
if [[ "${pretending_primary}" != "${suggested_primary}" ]]; then
repmgr_warn "Conflict of pretending primary role nodes (previously: $pretending_primary, now: $suggested_primary)"
pretending_primary="" && break
fi
else
repmgr_debug "Pretending primary set to $suggested_primary!"
pretending_primary="$suggested_primary"
fi
else
repmgr_warn "There were more than one primary when getting primary from node $node"
pretending_primary="" && break
fi
done
fi
echo "$pretending_primary"
}
########################
# Gets the node that is currently set as primary node
# Globals:
# REPMGR_*
# Arguments:
# None
# Returns:
# String
#########################
repmgr_get_primary_node() {
local upstream_node
local primary_node=""
upstream_node="$(repmgr_get_upstream_node)"
[[ -n "$upstream_node" ]] && repmgr_info "Auto-detected primary node: '$upstream_node'"
if [[ -f "$REPMGR_PRIMARY_ROLE_LOCK_FILE_NAME" ]]; then
repmgr_info "This node was acting as a primary before restart!"
if [[ -z "$upstream_node" ]] || [[ "$upstream_node" = "$REPMGR_NODE_NETWORK_NAME" ]]; then
repmgr_info "Can not find new primary. Starting PostgreSQL normally..."
else
repmgr_info "Current master is $upstream_node. Cloning/rewinding it and acting as a standby node..."
rm -f "$REPMGR_PRIMARY_ROLE_LOCK_FILE_NAME"
export REPMGR_SWITCH_ROLE="yes"
primary_node="$upstream_node"
fi
else
if [[ -z "$upstream_node" ]]; then
[[ "$REPMGR_PRIMARY_HOST" != "$REPMGR_NODE_NETWORK_NAME" ]] && primary_node="$REPMGR_PRIMARY_HOST"
else
primary_node="$upstream_node"
fi
fi
[[ -n "$primary_node" ]] && repmgr_debug "Primary node: $primary_node"
echo "$primary_node"
}
########################
# Generates env vars for the node
# Globals:
# REPMGR_*
# Arguments:
# None
# Returns:
# Series of exports to be used as 'eval' arguments
#########################
repmgr_set_role() {
local role="standby"
local primary_node
primary_node="$(repmgr_get_primary_node)"
if [[ -z "$primary_node" ]]; then
repmgr_info "There are no nodes with primary role. Assuming the primary role..."
role="primary"
fi
cat <<EOF
export REPMGR_ROLE="$role"
export REPMGR_CURRENT_PRIMARY_HOST="$primary_node"
EOF
}
########################
# Change a Repmgr configuration file by setting a property
# Globals:
# REPMGR_*
# Arguments:
# $1 - property
# $2 - value
# $3 - Path to configuration file (default: $REPMGR_CONF_FILE)
# Returns:
# None
#########################
repmgr_set_property() {
local -r property="${1:?missing property}"
local -r value="${2:-}"
local -r conf_file="${3:-$REPMGR_CONF_FILE}"
sed -i "s?^#*\s*${property}\s*=.*?${property} = '${value}'?g" "$conf_file"
}
########################
# Create the repmgr user (with )
# Globals:
# REPMGR_*
# POSTGRESQL_*
# Arguments:
# None
# Returns:
# None
#########################
repmgr_create_repmgr_user() {
local postgres_password="$POSTGRESQL_PASSWORD"
local -r escaped_password="${REPMGR_PASSWORD//\'/\'\'}"
repmgr_info "Creating repmgr user: $REPMGR_USERNAME"
[[ "$POSTGRESQL_USERNAME" != "postgres" ]] && [[ -n "$POSTGRESQL_POSTGRES_PASSWORD" ]] && postgres_password="$POSTGRESQL_POSTGRES_PASSWORD"
# The repmgr user is created as superuser for simplicity (ref: https://repmgr.org/docs/4.3/quickstart-repmgr-user-database.html)
echo "CREATE ROLE \"${REPMGR_USERNAME}\" WITH LOGIN CREATEDB PASSWORD '${escaped_password}';" | postgresql_execute "" "postgres" "$postgres_password"
echo "ALTER USER ${REPMGR_USERNAME} WITH SUPERUSER;" | postgresql_execute "" "postgres" "$postgres_password"
# set the repmgr user's search path to include the 'repmgr' schema name (ref: https://repmgr.org/docs/4.3/quickstart-repmgr-user-database.html)
echo "ALTER USER ${REPMGR_USERNAME} SET search_path TO repmgr, \"\$user\", public;" | postgresql_execute "" "postgres" "$postgres_password"
}
########################
# Creates the repmgr database
# Globals:
# REPMGR_*
# POSTGRESQL_*
# Arguments:
# None
# Returns:
# None
#########################
repmgr_create_repmgr_db() {
local postgres_password="$POSTGRESQL_PASSWORD"
repmgr_info "Creating repmgr database: $REPMGR_DATABASE"
[[ "$POSTGRESQL_USERNAME" != "postgres" ]] && [[ -n "$POSTGRESQL_POSTGRES_PASSWORD" ]] && postgres_password="$POSTGRESQL_POSTGRES_PASSWORD"
echo "CREATE DATABASE $REPMGR_DATABASE;" | postgresql_execute "" "postgres" "$postgres_password"
}
########################
# Use a different PostgreSQL configuration file by pretending it's an injected custom configuration
# Globals:
# POSTGRESQL_MOUNTED_CONF_DIR
# Arguments:
# None
# Returns:
# None
#########################
repmgr_inject_postgresql_configuration() {
repmgr_debug "Injecting a new postgresql.conf file..."
postgresql_create_config
# ref: https://repmgr.org/docs/4.3/quickstart-postgresql-configuration.html
postgresql_set_property "shared_preload_libraries" "repmgr"
postgresql_set_property "max_wal_senders" "10"
postgresql_set_property "max_replication_slots" "10"
postgresql_set_property "wal_level" "hot_standby"
postgresql_set_property "archive_mode" "on"
postgresql_set_property "hot_standby" "on"
postgresql_set_property "archive_command" "/bin/true"
# Redirect logs to POSTGRESQL_LOG_FILE
postgresql_set_property "logging_collector" "on"
postgresql_set_property "log_directory" "$POSTGRESQL_LOG_DIR"
postgresql_set_property "log_filename" "postgresql.log"
cp "$POSTGRESQL_CONF_FILE" "$POSTGRESQL_MOUNTED_CONF_DIR/postgresql.conf"
}
########################
# Use a different pg_hba.conf file by pretending it's an injected custom configuration\
# Globals:
# REPMGR_*
# POSTGRESQL_*
# Arguments:
# None
# Returns:
# None
#########################
repmgr_inject_pghba_configuration() {
repmgr_debug "Injecting a new pg_hba.conf file..."
cat > "${POSTGRESQL_MOUNTED_CONF_DIR}/pg_hba.conf" << EOF
host all $REPMGR_USERNAME 0.0.0.0/0 trust
host $REPMGR_DATABASE $REPMGR_USERNAME 0.0.0.0/0 trust
host replication $REPMGR_USERNAME 0.0.0.0/0 trust
host all all 0.0.0.0/0 trust
host all all ::1/128 trust
local all all trust
EOF
}
########################
# Prepare PostgreSQL default configuration
# Globals:
# POSTGRESQL_MOUNTED_CONF_DIR
# Arguments:
# None
# Returns:
# None
#########################
repmgr_postgresql_configuration() {
repmgr_info "Preparing PostgreSQL configuration..."
# User injected custom configuration
if [[ -d "$POSTGRESQL_MOUNTED_CONF_DIR" ]] && compgen -G "$POSTGRESQL_MOUNTED_CONF_DIR"/* > /dev/null; then
repmgr_debug "User injected custom configuration detected!"
else
ensure_dir_exists "$POSTGRESQL_MOUNTED_CONF_DIR"
repmgr_inject_postgresql_configuration
repmgr_inject_pghba_configuration
fi
}
########################
# Generates repmgr config files
# Globals:
# REPMGR_*
# POSTGRESQL_*
# Arguments:
# None
# Returns:
# None
#########################
repmgr_generate_repmgr_config() {
repmgr_info "Preparing repmgr configuration..."
cat << EOF >> "$REPMGR_CONF_FILE"
event_notification_command='${REPMGR_EVENTS_DIR}/router.sh %n %e %s "%t" "%d"'
ssh_options=-o \"StrictHostKeyChecking no\" -v
use_replication_slots=$REPMGR_USE_REPLICATION_SLOTS
pg_bindir=$POSTGRESQL_BIN_DIR
# FIXME: these 2 parameter should work
node_id=$(repmgr_get_node_id)
node_name=$REPMGR_NODE_NAME
conninfo='user=$REPMGR_USERNAME password=$REPMGR_PASSWORD host=$REPMGR_NODE_NAME dbname=$REPMGR_DATABASE port=$REPMGR_PRIMARY_PORT connect_timeout=$REPMGR_CONNECT_TIMEOUT'
failover=automatic
promote_command='PGPASSWORD=$REPMGR_PASSWORD repmgr standby promote -f "$REPMGR_CONF_FILE" --log-level DEBUG --verbose'
follow_command='PGPASSWORD=$REPMGR_PASSWORD repmgr standby follow -f "$REPMGR_CONF_FILE" -W --log-level DEBUG --verbose'
reconnect_attempts=$REPMGR_RECONNECT_ATTEMPTS
reconnect_interval=$REPMGR_RECONNECT_INTERVAL
log_level=$REPMGR_LOG_LEVEL
priority=$REPMGR_NODE_PRIORITY
degraded_monitoring_timeout=$REPMGR_DEGRADED_MONITORING_TIMEOUT
data_directory=$POSTGRESQL_DATA_DIR
async_query_timeout=$REPMGR_MASTER_RESPONSE_TIMEOUT
EOF
}
########################
# Waits until the primary node responds
# Globals:
# REPMGR_*
# Arguments:
# None
# Returns:
# None
#########################
repmgr_wait_primary_node() {
local return_value=1
local -i timeout=300
local -i step=10
local -i max_tries=$(( timeout / step ))
local schemata
repmgr_info "Waiting for primary node..."
repmgr_debug "Wait for schema $REPMGR_DATABASE.repmgr on $REPMGR_CURRENT_PRIMARY_HOST:$REPMGR_PRIMARY_PORT, will try $max_tries times with $step delay seconds (TIMEOUT=$timeout)"
for ((i = 0 ; i <= timeout ; i+=step )); do
local query="SELECT 1 FROM information_schema.schemata WHERE catalog_name='$REPMGR_DATABASE' AND schema_name='repmgr'"
if ! schemata="$(echo "$query" | NO_ERRORS=true postgresql_execute "$REPMGR_DATABASE" "$REPMGR_USERNAME" "$REPMGR_PASSWORD" "$REPMGR_CURRENT_PRIMARY_HOST" "$REPMGR_PRIMARY_PORT" "-tA")"; then
repmgr_debug "Host $REPMGR_CURRENT_PRIMARY_HOST:$REPMGR_PRIMARY_PORT is not accessible"
else
if [[ $schemata -ne 1 ]]; then
repmgr_debug "Schema $REPMGR_DATABASE.repmgr is still not accessible"
else
repmgr_debug "Schema $REPMGR_DATABASE.repmgr exists!"
return_value=0 && break
fi
fi
sleep "$step"
done
return $return_value
}
########################
# Clones data from primary node
# Globals:
# REPMGR_*
# POSTGRESQL_*
# Arguments:
# None
# Returns:
# None
#########################
repmgr_clone_primary() {
repmgr_info "Cloning data from primary node..."
local -r flags=("-h" "$REPMGR_CURRENT_PRIMARY_HOST" "-p" "$REPMGR_PRIMARY_PORT" "-U" "$REPMGR_USERNAME" "-d" "$REPMGR_DATABASE" "-D" "$POSTGRESQL_DATA_DIR" "standby" "clone" "--fast-checkpoint" "--force")
PGPASSWORD="$REPMGR_PASSWORD" debug_execute "${REPMGR_BIN_DIR}/repmgr" "${flags[@]}"
}
########################
# Rejoin node
# Globals:
# REPMGR_*
# Arguments:
# None
# Returns:
# None
#########################
repmgr_rewind() {
repmgr_info "Rejoining node..."
repmgr_debug "Deleting old data..."
rm -rf "$POSTGRESQL_DATA_DIR" && ensure_dir_exists "$POSTGRESQL_DATA_DIR"
repmgr_debug "Cloning data from primary node..."
repmgr_clone_primary
}
########################
# Register a node as primary
# Globals:
# REPMGR_*
# Arguments:
# None
# Returns:
# None
#########################
repmgr_register_primary() {
repmgr_info "Registering Primary..."
local -r flags=("-f" "$REPMGR_CONF_FILE" "master" "register" "--force")
debug_execute "${REPMGR_BIN_DIR}/repmgr" "${flags[@]}"
}
########################
# Unregister secondary node
# Globals:
# REPMGR_*
# Arguments:
# None
# Returns:
# None
#########################
repmgr_unregister_standby() {
repmgr_info "Unregistering secondary node..."
echo "DELETE FROM repmgr.nodes WHERE conninfo LIKE '%host=$REPMGR_NODE_NETWORK_NAME%'" | postgresql_execute "$REPMGR_DATABASE" "$REPMGR_USERNAME" "$REPMGR_PASSWORD" "$REPMGR_CURRENT_PRIMARY_HOST" "$REPMGR_PRIMARY_PORT"
}
########################
# Resgister a node as secondary
# Globals:
# REPMGR_*
# Arguments:
# None
# Returns:
# None
#########################
repmgr_register_standby() {
repmgr_info "Registering Standby node..."
local -r flags=("-f" "$REPMGR_CONF_FILE" "standby" "register" "--force")
debug_execute "${REPMGR_BIN_DIR}/repmgr" "${flags[@]}"
}
########################
# Initialize repmgr service
# Globals:
# REPMGR_*
# Arguments:
# None
# Returns:
# None
#########################
repmgr_initialize() {
repmgr_debug "Node ID: $(repmgr_get_node_id), Rol: $REPMGR_ROLE, Primary Node: $REPMGR_CURRENT_PRIMARY_HOST"
repmgr_info "Initializing Repmgr..."
if [[ "$REPMGR_ROLE" = "standby" ]]; then
repmgr_wait_primary_node || exit 1
# TODO: better way to detect it's a 1st boot
if [[ ! -f "$POSTGRESQL_CONF_FILE" ]] || ! is_boolean_yes "$REPMGR_SWITCH_ROLE"; then
repmgr_clone_primary
else
repmgr_rewind
fi
fi
postgresql_initialize
# Allow remote connections, required to register primary and standby nodes
postgresql_enable_remote_connections
# Configure port and restrict access to PostgreSQL (MD5)
postgresql_set_property "port" "$POSTGRESQL_PORT_NUMBER"
postgresql_restrict_pghba
if [[ "$REPMGR_ROLE" = "primary" ]]; then
repmgr_create_repmgr_user
repmgr_create_repmgr_db
# Restart PostgreSQL
postgresql_stop
postgresql_start_bg
repmgr_register_primary
else
postgresql_start_bg
repmgr_unregister_standby
repmgr_register_standby
fi
}

View File

@ -0,0 +1,25 @@
#!/bin/bash
# shellcheck disable=SC1091
# Load libraries
. /libfs.sh
. /libpostgresql.sh
. /librepmgr.sh
# Load PostgreSQL & repmgr environment variables
eval "$(repmgr_env)"
eval "$(postgresql_env)"
for dir in "$POSTGRESQL_INITSCRIPTS_DIR" "$POSTGRESQL_TMP_DIR" "$POSTGRESQL_LOG_DIR" "$POSTGRESQL_CONF_DIR" "${POSTGRESQL_CONF_DIR}/conf.d" "$POSTGRESQL_VOLUME_DIR" "$REPMGR_CONF_DIR" "$REPMGR_TMP_DIR"; do
ensure_dir_exists "$dir"
chmod -R g+rwX "$dir"
done
# Copying events handlers
mv /events "$REPMGR_EVENTS_DIR"
chmod +x "$REPMGR_EVENTS_DIR"/router.sh "$REPMGR_EVENTS_DIR"/execs/*sh "$REPMGR_EVENTS_DIR"/execs/includes/*sh
# Redirect all logging to stdout
ln -sf /dev/stdout "$POSTGRESQL_LOG_FILE"

View File

@ -0,0 +1,28 @@
#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail
# set -o xtrace # Uncomment this line for debugging purpose
# shellcheck disable=SC1091
# Load libraries
. /liblog.sh
. /libpostgresql.sh
. /librepmgr.sh
# Load PostgreSQL & repmgr environment variables
eval "$(repmgr_env)"
eval "$(postgresql_env)"
readonly repmgr_flags=("--pid-file=$REPMGR_PID_FILE" "-f" "$REPMGR_CONF_FILE")
readonly repmgr_cmd=$(command -v repmgrd)
postgresql_start_bg
info "** Starting repmgrd **"
# TODO: properly test running the container as root
if am_i_root; then
exec gosu "$POSTGRESQL_DAEMON_USER" "${repmgr_cmd}" "${repmgr_flags[@]}"
else
exec "${repmgr_cmd}" "${repmgr_flags[@]}"
fi

View File

@ -0,0 +1,37 @@
#!/bin/bash
#
# Bitnami PostgreSQL setup
set -o errexit
set -o nounset
set -o pipefail
#set -o xtrace
# shellcheck disable=SC1090
# shellcheck disable=SC1091
# Load Libraries
. /libpostgresql.sh
. /librepmgr.sh
# Load PostgreSQL & repmgr environment variables
eval "$(repmgr_env)"
eval "$(postgresql_env)"
# Ensure PostgreSQL & repmgr environment variables settings are valid
repmgr_validate
postgresql_validate
# Set the environment variables for the node's role
eval "$(repmgr_set_role)"
# Ensure PostgreSQL is stopped when this script ends.
trap "postgresql_stop" EXIT
# Ensure 'daemon' user exists when running as 'root'
am_i_root && ensure_user_exists "$POSTGRESQL_DAEMON_USER" "$POSTGRESQL_DAEMON_GROUP"
# Prepare PostgreSQL default configuration
repmgr_postgresql_configuration
# Prepare repmgr configuration
repmgr_generate_repmgr_config
# Initialize PostgreSQL & repmgr
repmgr_initialize

View File

@ -0,0 +1,547 @@
# What is PostgreSQL with Replication Manager?
> [PostgreSQL](https://www.postgresql.org) is an open source object-relational database known for its reliability and data integrity. This solution includes [repmgr](https://repmgr.org), an open-source tool for managing replication and failover on PostgreSQL clusters.
# TL;DR;
```bash
$ docker run --name postgresql-repmgr bitnami/postgresql-repmgr:latest
```
## Docker Compose
```bash
$ curl -sSL https://raw.githubusercontent.com/bitnami/bitnami-docker-postgresql-repmgr/master/docker-compose.yml > docker-compose.yml
$ docker-compose up -d
```
# Why use Bitnami Images?
* Bitnami closely tracks upstream source changes and promptly publishes new versions of this image using our automated systems.
* With Bitnami images the latest bug fixes and features are available as soon as possible.
* Bitnami containers, virtual machines and cloud images use the same components and configuration approach - making it easy to switch between formats based on your project needs.
* All our images are based on [minideb](https://github.com/bitnami/minideb) a minimalist Debian based container image which gives you a small base container image and the familiarity of a leading linux distribution.
* All Bitnami images available in Docker Hub are signed with [Docker Content Trust (DTC)](https://docs.docker.com/engine/security/trust/content_trust/). You can use `DOCKER_CONTENT_TRUST=1` to verify the integrity of the images.
* Bitnami container images are released daily with the latest distribution packages available.
> This [CVE scan report](https://quay.io/repository/bitnami/postgresql-repmgr?tab=tags) contains a security report with all open CVEs. To get the list of actionable security issues, find the "latest" tag, click the vulnerability report link under the corresponding "Security scan" field and then select the "Only show fixable" filter on the next page.
# Why use a non-root container?
Non-root container images add an extra layer of security and are generally recommended for production environments. However, because they run as a non-root user, privileged tasks are typically off-limits. Learn more about non-root containers [in our docs](https://docs.bitnami.com/containers/how-to/work-with-non-root-containers/).
# How to deploy Postgresql-repmgr in Kubernetes?
Deploying Bitnami applications as Helm Charts is the easiest way to get started with our applications on Kubernetes. Read more about the installation in the [Bitnami PostgreSQL HA Chart GitHub repository](https://github.com/bitnami/charts/tree/master/bitnami/postgresql-ha).
Bitnami containers can be used with [Kubeapps](https://kubeapps.com/) for deployment and management of Helm Charts in clusters.
# Supported tags and respective `Dockerfile` links
> NOTE: Debian 8 images have been deprecated in favor of Debian 9 images. Bitnami will not longer publish new Docker images based on Debian 8.
Learn more about the Bitnami tagging policy and the difference between rolling tags and immutable tags [in our documentation page](https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/).
* [`4-centos-7`, `-centos-7-r0` (4/centos-7/Dockerfile)](https://github.com/bitnami/bitnami-docker-postgresql-repmgr/blob/-centos-7-r0/4/centos-7/Dockerfile)
* [`4-debian-9`, `4.0.3-debian-9-r0`, `4`, `4.0.3`, `4.0.3-r0`, `latest` (4/debian-9/Dockerfile)](https://github.com/bitnami/bitnami-docker-postgresql-repmgr/blob/4.0.3-debian-9-r0/4/debian-9/Dockerfile)
Subscribe to project updates by watching the [bitnami/postgresql-repmgr GitHub repo](https://github.com/bitnami/bitnami-docker-postgresql-repmgr).
# Get this image
The recommended way to get the Bitnami PostgreSQL with Replication Manager Docker Image is to pull the prebuilt image from the [Docker Hub Registry](https://hub.docker.com/r/bitnami/postgresql-repmgr).
```bash
$ docker pull bitnami/postgresql-repmgr:latest
```
To use a specific version, you can pull a versioned tag. You can view the [list of available versions](https://hub.docker.com/r/bitnami/postgresql-repmgr/tags/) in the Docker Hub Registry.
```bash
$ docker pull bitnami/postgresql-repmgr:[TAG]
```
If you wish, you can also build the image yourself.
```bash
$ docker build -t bitnami/postgresql-repmgr:latest 'https://github.com/bitnami/bitnami-docker-postgresql-repmgr.git#master:4/debian-9'
```
# Persisting your application
If you remove the container all your data will be lost, and the next time you run the image the database will be reinitialized. To avoid this loss of data, you should mount a volume that will persist even after the container is removed.
For persistence you should mount a directory at the `/bitnami/postgresql` path. If the mounted directory is empty, it will be initialized on the first run.
```bash
$ docker run \
-v /path/to/postgresql-repmgr-persistence:/bitnami/postgresql \
bitnami/postgresql-repmgr:latest
```
The [`docker-compose.yml`](https://github.com/bitnami/bitnami-docker-postgresql/blob/master/docker-compose.yml) file present in this repository already configures persistence.
# Connecting to other containers
Using [Docker container networking](https://docs.docker.com/engine/userguide/networking/), a PostgreSQL server running inside a container can easily be accessed by your application containers and vice-versa.
Containers attached to the same network can communicate with each other using the container name as the hostname.
## Using the Command Line
In this example, we will create a PostgreSQL client instance that will connect to the server instance that is running on the same docker network as the client.
### Step 1: Create a network
```bash
$ docker network create my-network --driver bridge
```
### Step 2: Launch the postgresql-repmgr container within your network
Use the `--network <NETWORK>` argument to the `docker run` command to attach the container to the `my-network` network.
```bash
$ docker run --detach --rm --name pg-0 \
--network my-network \
--env REPMGR_PARTNER_NODES=pg-0 \
--env REPMGR_NODE_NAME=pg-0 \
--env REPMGR_NODE_NETWORK_NAME=pg-0 \
--env REPMGR_PRIMARY_HOST=pg-0 \
--env REPMGR_PASSWORD=repmgrpass \
--env POSTGRESQL_PASSWORD=secretpass \
bitnami/postgresql-repmgr:latest
```
### Step 3: Launch your PostgreSQL client instance
Finally we create a new container instance to launch the PostgreSQL client and connect to the server created in the previous step:
```bash
$ docker run -it --rm \
--network my-network \
bitnami/postgresql:10 \
psql -h pg-0 -U postgres
```
## Using Docker Compose
When not specified, Docker Compose automatically sets up a new network and attaches all deployed services to that network. However, we will explicitly define a new `bridge` network named `my-network`. In this example we assume that you want to connect to the PostgreSQL server from your own custom application image which is identified in the following snippet by the service name `myapp`.
```yaml
version: '2'
networks:
my-network:
driver: bridge
services:
pg-0:
image: 'bitnami/postgresql-repmgr:latest'
networks:
- my-network
environment:
- POSTGRESQL_PASSWORD=custompassword
- REPMGR_PASSWORD=repmgrpassword
- REPMGR_PRIMARY_HOST=pg-0
- REPMGR_NODE_NETWORK_NAME=pg-0
- REPMGR_NODE_NAME=pg-0
- EPMGR_PARTNER_NODES=pg-0
myapp:
image: 'YOUR_APPLICATION_IMAGE'
networks:
- my-network
```
> **IMPORTANT**:
>
> 1. Please update the **YOUR_APPLICATION_IMAGE_** placeholder in the above snippet with your application image
> 2. In your application container, use the hostname `pg-0` to connect to the PostgreSQL server
Launch the containers using:
```bash
$ docker-compose up -d
```
# Configuration
## Initializing a new instance
When the container is executed for the first time, it will execute the files with extensions `.sh`, `.sql` and `.sql.gz` located at `/docker-entrypoint-initdb.d`.
In order to have your custom files inside the docker image you can mount them as a volume.
## Setting the root and repmgr passwords on first run
In the above commands you may have noticed the use of the `POSTGRESQL_PASSWORD` and `REPMGR_PASSWORD` environment variables. Passing the `POSTGRESQL_PASSWORD` environment variable when running the image for the first time will set the password of the `postgres` user to the value of `POSTGRESQL_PASSWORD` (or the content of the file specified in `POSTGRESQL_PASSWORD_FILE`). In the same way, passing the `REPMGR_PASSWORD` environment variable sets the password of the `repmgr` user to the value of `REPMGR_PASSWORD` (or the content of the file specified in `REPMGR_PASSWORD_FILE`).
```bash
$ docker run --name pg-0 --env REPMGR_PASSWORD=repmgrpass --env POSTGRESQL_PASSWORD=secretpass bitnami/postgresql-repmgr:latest
```
or by modifying the [`docker-compose.yml`](https://github.com/bitnami/bitnami-docker-postgresql-repmgr/blob/master/docker-compose.yml) file present in this repository:
```diff
...
services:
pg-0:
...
environment:
- - POSTGRESQL_PASSWORD=adminpassword
+ - POSTGRESQL_PASSWORD=password123
- - REPMGR_PASSWORD=repmgrpassword
+ - REPMGR_PASSWORD=password123
...
pg-1:
...
environment:
- - POSTGRESQL_PASSWORD=adminpassword
+ - POSTGRESQL_PASSWORD=password123
- - REPMGR_PASSWORD=repmgrpassword
+ - REPMGR_PASSWORD=password123
...
```
**Note!**
Both `postgres` and `repmgr` users are superusers and have full administrative access to the PostgreSQL database.
Refer to [Creating a database user on first run](#creating-a-database-user-on-first-run) if you want to set an unprivileged user and a password for the `postgres` user.
## Creating a database on first run
By passing the `POSTGRESQL_DATABASE` environment variable when running the image for the first time, a database will be created. This is useful if your application requires that a database already exists, saving you from having to manually create the database using the PostgreSQL client.
```bash
$ docker run --name pg-0 --env POSTGRESQL_DATABASE=my_database bitnami/postgresql-repmgr:latest
```
## Creating a database user on first run
You can also create a restricted database user that only has permissions for the database created with the [`POSTGRESQL_DATABASE`](#creating-a-database-on-first-run) environment variable. To do this, provide the `POSTGRESQL_USERNAME` environment variable.
```bash
$ docker run --name pg-0 --env POSTGRESQL_USERNAME=my_user --env POSTGRESQL_PASSWORD=password123 --env POSTGRESQL_DATABASE=my_database bitnami/postgresql-repmgr:latest
```
The [`docker-compose.yml`](https://github.com/bitnami/bitnami-docker-postgresql/blob/master/docker-compose.yml) file present in this repository already configures this setup.
**Note!**
When `POSTGRESQL_USERNAME` is specified, the `postgres` user is not assigned a password and as a result you cannot login remotely to the PostgreSQL server as the `postgres` user. If you still want to have access with the user `postgres`, please set the `POSTGRESQL_POSTGRES_PASSWORD` environment variable (or the content of the file specified in `POSTGRESQL_POSTGRES_PASSWORD_FILE`).
## Setting up a HA PostgreSQL cluster with streaming replication and repmgr
A HA PostgreSQL cluster with [Streaming replication](https://www.postgresql.org/docs/10/warm-standby.html#STREAMING-REPLICATION) and [repmgr](https://repmgr.org) can easily be setup with the Bitnami PostgreSQL with Replication Manager Docker Image using the following environment variables:
- `POSTGRESQL_PASSWORD`: Password for `postgres` user. No defaults.
- `POSTGRESQL_PASSWORD_FILE`: Path to a file that contains the `postgres` user password. This will override the value specified in `POSTGRESQL_PASSWORD`. No defaults.
- `REPMGR_USERNAME`: Username for `repmgr` user. Defaults to `repmgr`.
- `REPMGR_PASSWORD_FILE`: Path to a file that contains the `repmgr` user password. This will override the value specified in `REPMGR_PASSWORD`. No defaults.
- `REPMGR_PASSWORD`: Password for `repmgr` user. No defaults.
- `REPMGR_PRIMARY_HOST`: Hostname of the initial primary node. No defaults.
- `REPMGR_PARTNER_NODES`: Comma separated list of partner nodes in the cluster. No defaults.
- `REPMGR_NODE_NAME`: Node name. No defaults.
- `REPMGR_NODE_NETWORK_NAME`: Node hostname. No defaults.
In a HA PostgreSQL cluster you can have one primary and zero or more standby nodes. The primary node is in read-write mode, while the standby nodes are in read-only mode. For best performance its advisable to limit the reads to the standby nodes.
### Step 1: Create a network
```bash
$ docker network create my-network --driver bridge
```
### Step 2: Create the initial primary node
The first step is to start the initial primary node:
```bash
$ docker run --detach --name pg-0 \
--network my-network \
--env REPMGR_PARTNER_NODES=pg-0,pg-1 \
--env REPMGR_NODE_NAME=pg-0 \
--env REPMGR_NODE_NETWORK_NAME=pg-0 \
--env REPMGR_PRIMARY_HOST=pg-0 \
--env REPMGR_PASSWORD=repmgrpass \
--env POSTGRESQL_PASSWORD=secretpass \
bitnami/postgresql-repmgr:latest
```
### Step 3: Create a standby node
Next we start a standby node:
```bash
$ docker run --detach --name pg-1 \
--network my-network \
--env REPMGR_PARTNER_NODES=pg-0,pg-1 \
--env REPMGR_NODE_NAME=pg-1 \
--env REPMGR_NODE_NETWORK_NAME=pg-1 \
--env REPMGR_PRIMARY_HOST=pg-0 \
--env REPMGR_PASSWORD=repmgrpass \
--env POSTGRESQL_PASSWORD=secretpass \
bitnami/postgresql-repmgr:latest
```
With these three commands you now have a two node PostgreSQL primary-standby streaming replication cluster up and running. You can scale the cluster by adding/removing standby nodes without incurring any downtime.
> **Note**: The cluster replicates the primary in its entirety, which includes all users and databases.
If the master goes down, **repmgr** will ensure any of the standby nodes takes the primary role, guaranteeing high availability.
> **Note**: The configuration of the other nodes in the cluster needs to be updated so that they are aware of them. This would require you to restart the old nodes adapting the `REPMGR_PARTNER_NODES` environment variable.
With Docker Compose the HA PostgreSQL cluster can be setup using the [`docker-compose.yml`](https://github.com/bitnami/bitnami-docker-postgresql/blob/master/docker-compose.yml) file present in this repository:
```bash
$ curl -sSL https://raw.githubusercontent.com/bitnami/bitnami-docker-postgresql-repmgr/master/docker-compose.yml > docker-compose.yml
$ docker-compose up -d
```
## Configuration file
The image looks for a `postgresql.conf` file in `/opt/bitnami/postgresql/conf/`. You can mount a volume at `/bitnami/postgresql/conf/` and copy/edit the `postgresql.conf` file in the `/path/to/postgresql-persistence/conf/`. The default configurations will be populated to the `conf/` directory if it's empty.
```
/path/to/postgresql-persistence/conf/
└── postgresql.conf
0 directories, 1 file
```
As the PostgreSQL with Replication manager image is non-root, you need to set the proper permissions to the mounted directory in your host:
```bash
$ sudo chgrp -R root /path/to/postgresql-persistence/conf/
$ sudo chmod -R g+rwX /path/to/postgresql-persistence/conf/
```
### Step 1: Run the PostgreSQL image
Run the PostgreSQL image, mounting a directory from your host.
```bash
$ docker run --name pg-0 \
-v /path/to/postgresql-persistence/conf/:/bitnami/postgresql/conf/ \
bitnami/postgresql-repmgr:latest
```
or using Docker Compose:
```yaml
version: '2'
services:
pg-0:
image: bitnami/postgresql-repmgr:latest
ports:
- '5432:5432'
volumes:
- /path/to/postgresql-persistence/conf/:/bitnami/postgresql/conf/
pg-1:
image: bitnami/postgresql-repmgr:latest
ports:
- '5432:5432'
volumes:
- /path/to/postgresql-persistence/conf/:/bitnami/postgresql/conf/
```
### Step 2: Edit the configuration
Edit the configuration on your host using your favorite editor.
```bash
vi /path/to/postgresql-persistence/conf/postgresql.conf
```
### Step 3: Restart PostgreSQL
After changing the configuration, restart your PostgreSQL container for changes to take effect.
```bash
$ docker restart pg-0
```
or using Docker Compose:
```bash
$ docker-compose restart pg-0
$ docker-compose restart pg-1
```
Refer to the [server configuration](http://www.postgresql.org/docs/10/static/runtime-config.html) manual for the complete list of configuration options.
### Allow settings to be loaded from files other than the default `postgresql.conf`
Apart of using a custom `postgresql.conf`, you can include files ending in `.conf` from the `conf.d` directory in the volume at `/bitnami/postgresql/conf/`.
For this purpose, the default `postgresql.conf` contains the following section:
```
#------------------------------------------------------------------------------
# CONFIG FILE INCLUDES
#------------------------------------------------------------------------------
# These options allow settings to be loaded from files other than the
# default postgresql.conf.
include_dir = 'conf.d' # Include files ending in '.conf' from directory 'conf.d'
```
In your host, you should create the extended configuration file under the `conf.d` directory:
```bash
mkdir -p /path/to/postgresql-persistence/conf/conf.d/
vi /path/to/postgresql-persistence/conf/conf.d/extended.conf
```
If you are using your custom `postgresql.conf`, you should create (or uncomment) the above section in your config file, in this case the `/path/to/postgresql-persistence/conf/` structure should be something like
```
/path/to/postgresql-persistence/conf/
├── conf.d
│   └── extended.conf
└── postgresql.conf
1 directory, 2 files
```
## Environment variables aliases
Please see the list of environment variable available in the Bitnami PostgreSQL with Replication Manager container in the next table:
| Environment Variable | Default value |
| :----------------------------------- | :--------------------------------- |
| REPMGR_NODE_ID | `nil` |
| REPMGR_NODE_NAME | `nil` |
| REPMGR_NODE_NETWORK_NAME | `nil` |
| REPMGR_NODE_PRIORITY | `100` |
| REPMGR_PARTNER_NODES | `nil` |
| REPMGR_PRIMARY_HOST | `nil` |
| REPMGR_PRIMARY_PORT | `5432` |
| REPMGR_NODE_ID | `nil` |
| REPMGR_PORT_NUMBER | `5432` |
| REPMGR_LOG_LEVEL | `NOTICE` |
| REPMGR_START_OPTIONS | `nil` |
| REPMGR_CONNECT_TIMEOUT | `5` |
| REPMGR_RECONNECT_ATTEMPTS | `3` |
| REPMGR_RECONNECT_INTERVAL | `5` |
| REPMGR_USE_REPLICATION_SLOTS | `1` |
| REPMGR_MASTER_RESPONSE_TIMEOUT | `20` |
| REPMGR_DEGRADED_MONITORING_TIMEOUT | `5` |
| REPMGR_USERNAME | `repmgr` |
| REPMGR_DATABASE | `repmgr` |
| REPMGR_PASSWORD | `nil` |
| REPMGR_PASSWORD_FILE | `nil` |
| POSTGRESQL_USERNAME | `postgres` |
| POSTGRESQL_DATABASE | `nil` |
| POSTGRESQL_PASSWORD | `nil` |
| POSTGRESQL_PASSWORD_FILE | `nil` |
| POSTGRESQL_POSTGRES_PASSWORD | `nil` |
| POSTGRESQL_POSTGRES_PASSWORD_FILE | `nil` |
| POSTGRESQL_PORT_NUMBER | `5432` |
| POSTGRESQL_INITDB_ARGS | `nil` |
# Logging
The Bitnami PostgreSQL with Replication Manager Docker image sends the container logs to `stdout`. To view the logs:
```bash
$ docker logs pg-0
```
You can configure the containers [logging driver](https://docs.docker.com/engine/admin/logging/overview/) using the `--log-driver` option if you wish to consume the container logs differently. In the default configuration docker uses the `json-file` driver.
# Maintenance
## Upgrade this image
Bitnami provides up-to-date versions of PostgreSQL with Replication Manager, including security patches, soon after they are made upstream. We recommend that you follow these steps to upgrade your container.
### Step 1: Get the updated image
```bash
$ docker pull bitnami/postgresql-repmgr:latest
```
or if you're using Docker Compose, update the value of the image property to `bitnami/postgresql-repmgr:latest`.
### Step 2: Stop the running container
Stop the currently running container using the command
```bash
$ docker stop pg-0
```
or using Docker Compose:
```bash
$ docker-compose stop pg-0
$ docker-compose stop pg-1
```
Next, take a snapshot of the persistent volume `/path/to/postgresql-persistence` using:
```bash
$ rsync -a /path/to/postgresql-persistence /path/to/postgresql-persistence.bkp.$(date +%Y%m%d-%H.%M.%S)
```
### Step 3: Remove the currently running container
```bash
$ docker rm -v pg-0
```
or using Docker Compose:
```bash
$ docker-compose rm -v pg-0
$ docker-compose rm -v pg-1
```
### Step 4: Run the new image
Re-create your container from the new image.
```bash
$ docker run --name pg-0 bitnami/postgresql-repmgr:latest
```
or using Docker Compose:
```bash
$ docker-compose up pg-0
$ docker-compose up pg-1
```
# Contributing
We'd love for you to contribute to this container. You can request new features by creating an [issue](https://github.com/bitnami/bitnami-docker-postgresql-repmgr/issues), or submit a [pull request](https://github.com/bitnami/bitnami-docker-postgresql-repmgr/pulls) with your contribution.
# Issues
If you encountered a problem running this container, you can file an [issue](https://github.com/bitnami/bitnami-docker-postgresql-repmgr/issues). For us to provide better support, be sure to include the following information in your issue:
- Host OS and version
- Docker version (`docker version`)
- Output of `docker info`
- Version of this container
- The command you used to run the container, and any relevant output you saw (masking any sensitive information)
# License
Copyright 2019 Bitnami
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,39 @@
version: '2'
services:
pg-0:
image: bitnami/postgresql-repmgr:4
ports:
- 5432
volumes:
- pg_0_data:/bitnami/postgresql
environment:
- POSTGRESQL_POSTGRES_PASSWORD=adminpassword
- POSTGRESQL_USERNAME=customuser
- POSTGRESQL_PASSWORD=custompassword
- POSTGRESQL_DATABASE=customdatabase
- REPMGR_PASSWORD=repmgrpassword
- REPMGR_PRIMARY_HOST=pg-0
- REPMGR_PARTNER_NODES=pg-0,pg-1
- REPMGR_NODE_NAME=pg-0
- REPMGR_NODE_NETWORK_NAME=pg-0
pg-1:
image: bitnami/postgresql-repmgr:4
ports:
- 5432
volumes:
- pg_1_data:/bitnami/postgresql
environment:
- POSTGRESQL_POSTGRES_PASSWORD=adminpassword
- POSTGRESQL_USERNAME=customuser
- POSTGRESQL_PASSWORD=custompassword
- POSTGRESQL_DATABASE=customdatabase
- REPMGR_PASSWORD=repmgrpassword
- REPMGR_PRIMARY_HOST=pg-0
- REPMGR_PARTNER_NODES=pg-0,pg-1
- REPMGR_NODE_NAME=pg-1
- REPMGR_NODE_NETWORK_NAME=pg-1
volumes:
pg_0_data:
driver: local
pg_1_data:
driver: local

View File

@ -0,0 +1,77 @@
## This is test deployment for Kubernetes platforms.
## This is _not_ intended to be used in producction.
##
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: test-postgresql-repmgr
labels:
app.kubernetes.io/name: test-postgresql-repmgr
spec:
serviceName: test-postgresql-repmgr-headless
replicas: 1
selector:
matchLabels:
app: test-postgresql-repmgr
template:
metadata:
labels:
app: test-postgresql-repmgr
spec:
containers:
- image: bitnami/postgresql-repmgr
name: postgresql-repmgr
env:
- name: POSTGRESQL_POSTGRES_PASSWORD
value: "adminpassword"
- name: POSTGRESQL_USERNAME
value: "customuser"
- name: POSTGRESQL_PASSWORD
value: "custompassword"
- name: POSTGRESQL_DATABASE
value: "customdatabase"
- name: REPMGR_PASSWORD
value: "repmgrpassword"
- name: REPMGR_PRIMARY_HOST
value: "test-postgresql-repmgr-0"
- name: REPMGR_NODE_NAME
value: "test-postgresql-repmgr-0"
- name: REPMGR_NODE_NETWORK_NAME
value: "test-postgresql-repmgr-0"
- name: REPMGR_PARTNER_NODES
value: "test-postgresql-repmgr-0"
ports:
- name: postgresql
containerPort: 5432
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: test-postgresql-repmgr
labels:
app.kubernetes.io/name: test-postgresql-repmgr
spec:
type: ClusterIP
ports:
- port: 5432
protocol: TCP
targetPort: postgresql
selector:
app.kubernetes.io/name: test-postgresql-repmgr
---
apiVersion: v1
kind: Service
metadata:
name: test-postgresql-repmgr-headless
labels:
app.kubernetes.io/name: test-postgresql-repmgr
spec:
ClusterIP: None
type: ClusterIP
ports:
- port: 5432
protocol: TCP
targetPort: postgresql
selector:
app.kubernetes.io/name: test-postgresql-repmgr