freeleaps-ops/apps/pg-brain-split-recover/repmgr-split-brain-recovery.sh

341 lines
11 KiB
Bash
Raw Normal View History

#!/bin/bash
# filepath: repmgr-split-brain-recovery.sh
set -e
NAMESPACE="freeleaps-prod"
STATEFULSET="freeleaps-prod-gitea-postgresql-ha-postgresql"
HEADLESS_SVC="${STATEFULSET}-headless.${NAMESPACE}.svc.freeleaps.cluster"
REPMGR_USER="repmgr"
REPMGR_PASSWORD="WGZ47gbUTLvo"
POSTGRES_PASSWORD="X9H2*9M2ZWYmuZ"
REPMGR_DB="repmgr"
POSTGRES_USER="postgres"
BACKUP_DIR="/tmp/pg_backup_$(date +%Y%m%d_%H%M%S)"
LOCAL_BACKUP_DIR="./pg_backups_$(date +%Y%m%d_%H%M%S)"
echo "===== PostgreSQL Repmgr Split-Brain Recovery ====="
echo "This script will attempt to fix the repmgr split-brain issue"
echo ""
# Create local backup directory
mkdir -p $LOCAL_BACKUP_DIR
# Function to run commands in a pod
run_in_pod() {
local pod=$1
local cmd=$2
kubectl exec -n $NAMESPACE $pod -- bash -c "$cmd"
}
# Function to get PostgreSQL WAL position
get_wal_position() {
local pod=$1
run_in_pod $pod "PGPASSWORD=$REPMGR_PASSWORD psql -U $REPMGR_USER -d $REPMGR_DB -t -c \"SELECT pg_current_wal_lsn();\""
}
# Function to check if node is primary
is_primary() {
local pod=$1
local result=$(run_in_pod $pod "PGPASSWORD=$REPMGR_PASSWORD psql -U $REPMGR_USER -d $REPMGR_DB -t -c \"SELECT pg_is_in_recovery();\"")
if [[ $result == *"f"* ]]; then
return 0 # is primary
else
return 1 # is standby
fi
}
# Function to backup databases from a pod
backup_databases() {
local pod=$1
local backup_path="$BACKUP_DIR/$pod"
echo "Creating backup directory in the pod..."
run_in_pod $pod "mkdir -p $backup_path"
echo "Getting list of databases..."
local databases=$(run_in_pod $pod "PGPASSWORD=$POSTGRES_PASSWORD psql -U $POSTGRES_USER -t -c \"SELECT datname FROM pg_database WHERE datname NOT IN ('template0', 'template1', 'postgres')\" | tr -d ' '")
echo "Backing up databases: $databases"
for db in $databases; do
echo "Backing up database: $db"
run_in_pod $pod "PGPASSWORD=$POSTGRES_PASSWORD pg_dump -U $POSTGRES_USER -Fc $db > $backup_path/${db}.dump"
done
# Also backup global objects (roles, tablespaces)
echo "Backing up global objects..."
run_in_pod $pod "PGPASSWORD=$POSTGRES_PASSWORD pg_dumpall -U $POSTGRES_USER --globals-only > $backup_path/globals.sql"
# Backup PostgreSQL configuration
echo "Backing up PostgreSQL configuration..."
run_in_pod $pod "cp /bitnami/postgresql/conf/postgresql.conf $backup_path/ 2>/dev/null || true"
run_in_pod $pod "cp /bitnami/postgresql/conf/pg_hba.conf $backup_path/ 2>/dev/null || true"
# Copy repmgr configuration
echo "Backing up repmgr configuration..."
run_in_pod $pod "cp /etc/repmgr.conf $backup_path/ 2>/dev/null || true"
# Tar the backup files
echo "Creating archive of the backup..."
run_in_pod $pod "tar -czf ${backup_path}.tar.gz -C $(dirname $backup_path) $(basename $backup_path)"
# Copy backup to local machine
echo "Copying backup to local machine..."
kubectl cp $NAMESPACE/$pod:${backup_path}.tar.gz $LOCAL_BACKUP_DIR/${pod}_backup.tar.gz
# Cleanup backup in the pod
echo "Cleaning up backup files in the pod..."
run_in_pod $pod "rm -rf $backup_path ${backup_path}.tar.gz"
}
echo "Step 0: Checking current status of the cluster..."
for i in 0 1 2; do
POD="${STATEFULSET}-${i}"
echo -n "Node ${i} ($POD): "
# Check if node is running as primary
if is_primary $POD; then
PRIMARY_STATE="running as primary"
echo "$PRIMARY_STATE"
else
echo "running as standby"
fi
# Get WAL position
WAL_POS=$(get_wal_position $POD 2>/dev/null || echo "N/A")
if [ "$WAL_POS" != "N/A" ]; then
echo " - WAL position: $WAL_POS"
# Store WAL positions for comparison
declare "WAL_POS_${i}=$WAL_POS"
fi
done
echo ""
echo "Step 1: Backing up all databases from each node..."
for i in 0 1 2; do
POD="${STATEFULSET}-${i}"
echo "Backing up data from node $i ($POD)..."
backup_databases $POD
done
echo "All backups completed and stored in: $LOCAL_BACKUP_DIR"
echo ""
echo "Determining most advanced node based on WAL position..."
# Get the primary nodes from each pod - there might be more than one in split-brain
for i in 0 1 2; do
POD="${STATEFULSET}-${i}"
# Get node information
NODE_INFO=$(run_in_pod $POD "PGPASSWORD=$REPMGR_PASSWORD psql -U $REPMGR_USER -d $REPMGR_DB -t -c \"SELECT node_id, node_name, type, active FROM repmgr.nodes WHERE node_name = '$POD';\"" 2>/dev/null || echo "")
if [ -n "$NODE_INFO" ]; then
echo "Node ${i} info: $NODE_INFO"
# Store if this node thinks it's a primary
if [[ $NODE_INFO == *"primary"* ]]; then
echo "Node ${i} is configured as a primary"
declare "NODE_${i}_IS_PRIMARY=true"
else
declare "NODE_${i}_IS_PRIMARY=false"
fi
# Check if node is actually running as primary using pg_is_in_recovery()
if is_primary $POD; then
echo "Node ${i} is running as primary (pg_is_in_recovery=false)"
declare "NODE_${i}_RUNNING_AS_PRIMARY=true"
else
declare "NODE_${i}_RUNNING_AS_PRIMARY=false"
fi
else
echo "Could not get info for node ${i}"
declare "NODE_${i}_IS_PRIMARY=false"
declare "NODE_${i}_RUNNING_AS_PRIMARY=false"
fi
done
echo ""
echo "Analyzing WAL positions to determine the most advanced node..."
# Compare WAL positions
if [ -n "${WAL_POS_0}" ] && [ -n "${WAL_POS_1}" ] && [ -n "${WAL_POS_2}" ]; then
# We have all WAL positions, find the most advanced
if run_in_pod ${STATEFULSET}-0 "PGPASSWORD=$REPMGR_PASSWORD psql -U $REPMGR_USER -d $REPMGR_DB -t -c \"SELECT '${WAL_POS_0}' > '${WAL_POS_1}' AND '${WAL_POS_0}' > '${WAL_POS_2}';\"" | grep -q 't'; then
NEW_PRIMARY=0
elif run_in_pod ${STATEFULSET}-0 "PGPASSWORD=$REPMGR_PASSWORD psql -U $REPMGR_USER -d $REPMGR_DB -t -c \"SELECT '${WAL_POS_1}' > '${WAL_POS_2}';\"" | grep -q 't'; then
NEW_PRIMARY=1
else
NEW_PRIMARY=2
fi
elif [ -n "${WAL_POS_0}" ] && [ -n "${WAL_POS_1}" ]; then
# Only nodes 0 and 1 have WAL positions
if run_in_pod ${STATEFULSET}-0 "PGPASSWORD=$REPMGR_PASSWORD psql -U $REPMGR_USER -d $REPMGR_DB -t -c \"SELECT '${WAL_POS_0}' > '${WAL_POS_1}';\"" | grep -q 't'; then
NEW_PRIMARY=0
else
NEW_PRIMARY=1
fi
elif [ -n "${WAL_POS_0}" ] && [ -n "${WAL_POS_2}" ]; then
# Only nodes 0 and 2 have WAL positions
if run_in_pod ${STATEFULSET}-0 "PGPASSWORD=$REPMGR_PASSWORD psql -U $REPMGR_USER -d $REPMGR_DB -t -c \"SELECT '${WAL_POS_0}' > '${WAL_POS_2}';\"" | grep -q 't'; then
NEW_PRIMARY=0
else
NEW_PRIMARY=2
fi
elif [ -n "${WAL_POS_1}" ] && [ -n "${WAL_POS_2}" ]; then
# Only nodes 1 and 2 have WAL positions
if run_in_pod ${STATEFULSET}-1 "PGPASSWORD=$REPMGR_PASSWORD psql -U $REPMGR_USER -d $REPMGR_DB -t -c \"SELECT '${WAL_POS_1}' > '${WAL_POS_2}';\"" | grep -q 't'; then
NEW_PRIMARY=1
else
NEW_PRIMARY=2
fi
elif [ -n "${WAL_POS_0}" ]; then
NEW_PRIMARY=0
elif [ -n "${WAL_POS_1}" ]; then
NEW_PRIMARY=1
elif [ -n "${WAL_POS_2}" ]; then
NEW_PRIMARY=2
else
echo "Could not determine most advanced node. Using node 0 as default primary."
NEW_PRIMARY=0
fi
echo "Selected node ${NEW_PRIMARY} as the new primary based on WAL position."
# Fix the bad substitution by using proper indirection
eval WAL_POS_VALUE=\$WAL_POS_${NEW_PRIMARY}
if [ -n "$WAL_POS_VALUE" ]; then
echo "WAL position: $WAL_POS_VALUE"
fi
echo ""
# Confirm with user
read -p "Backups completed. Do you want to proceed with fixing the split-brain issue? (y/n): " CONFIRM
if [[ "$CONFIRM" != "y" ]]; then
echo "Operation cancelled. Backups are still available at $LOCAL_BACKUP_DIR"
exit 1
fi
echo ""
echo "Step 2: Registering node ${NEW_PRIMARY} as primary..."
PRIMARY_POD="${STATEFULSET}-${NEW_PRIMARY}"
# Create a temporary script to run repmgr commands
run_in_pod $PRIMARY_POD "cat > /tmp/register_primary.sh << EOF
#!/bin/bash
export PGUSER='$REPMGR_USER'
export PGPASSWORD='$REPMGR_PASSWORD'
export PGDATABASE='$REPMGR_DB'
export PATH=\$PATH:/opt/bitnami/repmgr/bin:/opt/bitnami/postgresql/bin
# Try to find repmgr
repmgr_bin=\$(find /opt/bitnami -name repmgr -type f | head -1)
if [ -z \"\$repmgr_bin\" ]; then
echo \"Could not find repmgr binary\"
exit 1
fi
\$repmgr_bin -f /etc/repmgr.conf primary register --force
EOF
chmod +x /tmp/register_primary.sh"
# Run the script directly
run_in_pod $PRIMARY_POD "bash /tmp/register_primary.sh"
# Stop PostgreSQL on other nodes
for i in 0 1 2; do
if [ $i -ne $NEW_PRIMARY ]; then
STANDBY_POD="${STATEFULSET}-${i}"
echo "Step 3: Stopping PostgreSQL on standby node ${i}..."
run_in_pod $STANDBY_POD "/opt/bitnami/scripts/postgresql-repmgr/stop.sh"
echo "Step 4: Cloning primary data to standby node ${i}..."
# Create a temporary script for cloning the standby that doesn't rely on specific user
run_in_pod $STANDBY_POD "cat > /tmp/clone_standby.sh << EOF
#!/bin/bash
export PGUSER='$REPMGR_USER'
export PGPASSWORD='$REPMGR_PASSWORD'
export PGDATABASE='$REPMGR_DB'
export PATH=\$PATH:/opt/bitnami/repmgr/bin:/opt/bitnami/postgresql/bin
# Remove existing data
rm -rf /bitnami/postgresql/data/*
# Try to find repmgr
repmgr_bin=\$(find /opt/bitnami -name repmgr -type f | head -1)
if [ -z \"\$repmgr_bin\" ]; then
echo \"Could not find repmgr binary\"
exit 1
fi
\$repmgr_bin -h ${PRIMARY_POD}.${HEADLESS_SVC} -p 5432 standby clone --force
EOF
chmod +x /tmp/clone_standby.sh"
# Run the clone script directly
run_in_pod $STANDBY_POD "bash /tmp/clone_standby.sh"
echo "Step 5: Starting PostgreSQL on standby node ${i}..."
run_in_pod $STANDBY_POD "/opt/bitnami/scripts/postgresql-repmgr/start.sh"
echo "Step 6: Registering node ${i} as standby..."
# Create a temporary script for registering the standby
run_in_pod $STANDBY_POD "cat > /tmp/register_standby.sh << EOF
#!/bin/bash
export PGUSER='$REPMGR_USER'
export PGPASSWORD='$REPMGR_PASSWORD'
export PGDATABASE='$REPMGR_DB'
export PATH=\$PATH:/opt/bitnami/repmgr/bin:/opt/bitnami/postgresql/bin
# Try to find repmgr
repmgr_bin=\$(find /opt/bitnami -name repmgr -type f | head -1)
if [ -z \"\$repmgr_bin\" ]; then
echo \"Could not find repmgr binary\"
exit 1
fi
\$repmgr_bin -f /etc/repmgr.conf standby register --force
EOF
chmod +x /tmp/register_standby.sh"
# Run the register script directly
run_in_pod $STANDBY_POD "bash /tmp/register_standby.sh"
fi
done
echo ""
echo "Step 7: Checking final cluster status..."
# Create a temporary script for checking cluster status
run_in_pod $PRIMARY_POD "cat > /tmp/cluster_status.sh << EOF
#!/bin/bash
export PGUSER='$REPMGR_USER'
export PGPASSWORD='$REPMGR_PASSWORD'
export PGDATABASE='$REPMGR_DB'
export PATH=\$PATH:/opt/bitnami/repmgr/bin:/opt/bitnami/postgresql/bin
# Try to find repmgr
repmgr_bin=\$(find /opt/bitnami -name repmgr -type f | head -1)
if [ -z \"\$repmgr_bin\" ]; then
echo \"Could not find repmgr binary\"
exit 1
fi
\$repmgr_bin -f /etc/repmgr.conf cluster show
EOF
chmod +x /tmp/cluster_status.sh"
# Run the cluster status script directly
FINAL_STATUS=$(run_in_pod $PRIMARY_POD "bash /tmp/cluster_status.sh")
echo "$FINAL_STATUS"
# Clean up temporary scripts
for i in 0 1 2; do
POD="${STATEFULSET}-${i}"
run_in_pod $POD "rm -f /tmp/register_primary.sh /tmp/clone_standby.sh /tmp/register_standby.sh /tmp/cluster_status.sh" || true
done
echo ""
echo "Split-brain recovery completed."
echo "Your database backups are available at: $LOCAL_BACKUP_DIR"
echo "Please verify that the cluster is now in a consistent state."