From e546ff8807f93cbeffd41ae086ef6dd3fd6ee562 Mon Sep 17 00:00:00 2001 From: Dmitry Nourell Date: Sun, 8 Dec 2024 05:50:07 +0300 Subject: [PATCH] feat: rehaul to simplify container structure --- .dockerignore | 5 ++++ .gitignore | 3 ++ Dockerfile | 65 ++++++++++++++++++++++++++++++++++++++++++ docker/Dockerfile | 31 -------------------- docker/entrypoint.sh | 30 -------------------- docker/install.sh | 34 ---------------------- docker/walg-fetch | 5 ---- docker/walg-push | 55 ------------------------------------ walg-wrapper.sh | 67 ++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 140 insertions(+), 155 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile delete mode 100644 docker/Dockerfile delete mode 100755 docker/entrypoint.sh delete mode 100755 docker/install.sh delete mode 100755 docker/walg-fetch delete mode 100755 docker/walg-push create mode 100755 walg-wrapper.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e80ed28 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +# Don't include anything into context +* + +# Except for wal-g wrapper +!walg-wrapper.sh diff --git a/.gitignore b/.gitignore index a9600f8..c606446 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ .snapshots/* .env +.env.* + +docker-compose.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..55ba2db --- /dev/null +++ b/Dockerfile @@ -0,0 +1,65 @@ +# syntax=docker/dockerfile:1 +# check=skip=SecretsUsedInArgOrEnv;error=true +FROM postgres:16-bookworm + +# Install Peg dependencies +RUN <<-EOF + set -x + + # Install curl, ca-certifcates, and B4CKSP4CE Root CA + apt update + apt install -y curl ca-certificates + mkdir -p /usr/share/ca-certificates/bksp + curl -fSsl https://ca.bksp.in/root/bksp-root.crt -o /usr/share/ca-certificates/bksp/B4CKSP4CE_Root_CA.crt + echo "bksp/B4CKSP4CE_Root_CA.crt" | tee -a /etc/ca-certificates.conf + update-ca-certificates + + # Determine WALG download URL and digest depending on architecture + ARCH=$(uname -m) + if [ "$ARCH" = "aarch64" ]; then + WALG_URL="https://github.com/wal-g/wal-g/releases/download/v3.0.3/wal-g-pg-ubuntu20.04-aarch64" + WALG_SHA256="3aec9024959319468ac637ea4b2e215fe20511672669969077733ee5c3fd1466" + elif [ "$ARCH" = "x86_64" ]; then + WALG_URL="https://github.com/wal-g/wal-g/releases/download/v3.0.3/wal-g-pg-ubuntu-20.04-amd64" + WALG_SHA256="0b46652f23fb4d09fa08f3d536b72806e597c4e20d0a09d960d6337bc2368e8b" + else + echo "Unsupported architecture" + exit 1 + fi + + # Download wal-g and verify its checksum + curl -fsSL -o "/usr/local/bin/wal-g" "$WALG_URL" + echo "${WALG_SHA256} /usr/local/bin/wal-g" | sha256sum -c - + chmod +x /usr/local/bin/wal-g + + # Tidy up + apt clean + rm -rf /var/lib/apt/lists/* /var/cache/* /var/log/* +EOF + +# Define B4CKSP4CE-specific environment variables +ENV \ + # Prefer unix socket connection for wal-g + PGHOST=/var/run/postgresql \ + # Set Governance Object Lock for 10 years by default + S3_RETENTION_MODE="GOVERNANCE" \ + S3_RETENTION_PERIOD=315569520 \ + # Expect encryption key to be in Base64 + WALG_LIBSODIUM_KEY_TRANSFORM="base64" \ + # Set default compression method to zstd + WALG_COMPRESSION_METHOD="zstd" \ + # Use Yandex Cloud as default storage + AWS_ENDPOINT="https://storage.yandexcloud.net" + +# Enable pg_isready healthcheck +HEALTHCHECK --interval=10s --start-period=10s --timeout=5s --retries=5 CMD [ "pg_isready" ] + +# Copy wal-g wrapper, ensuring it is executable +COPY ./walg-wrapper.sh /usr/local/bin/walg-wrapper.sh +RUN chmod +x /usr/local/bin/walg-wrapper.sh + +# Drop privileges to postgres user +USER postgres + +# Append WAL configuration to default postgresql.conf +ENV POSTGRES_INITDB_ARGS="-c archive_mode=always -c archive_timeout=1h -c archive_command='walg-wrapper.sh wal-push /var/lib/postgresql/data/%p' -c restore_command='walg-wrapper.sh wal-fetch %f /var/lib/postgresql/data/%p'" diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index 3d8a681..0000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,31 +0,0 @@ -# syntax=docker/dockerfile:1 -# check=skip=SecretsUsedInArgOrEnv;error=true -FROM postgres:16-bookworm - -# Set default healthcheck to pg_isready -HEALTHCHECK --interval=10s --timeout=5s --start-period=5s --retries=5 CMD [ "pg_isready" ] - -# Use unix socket for WAL-G to reach PostgreSQL -ENV PGHOST=/var/run/postgresql - -# Sets Governance Object Lock for 10 years by default -ENV S3_RETENTION_MODE="GOVERNANCE" -ENV S3_RETENTION_PERIOD=315569520 - -# Expect encryption key to be in Base64 -ENV WALG_LIBSODIUM_KEY_TRANSFORM="base64" - -# Set default compression method to zstd -ENV WALG_COMPRESSION_METHOD="zstd" - -# Use Yandex Cloud as default storage -ENV AWS_ENDPOINT="https://storage.yandexcloud.net" - -# Do the preparation stuff -COPY ./install.sh ./entrypoint.sh / -COPY ./walg-push ./walg-fetch /usr/local/bin/ -RUN /install.sh - -# Drop to non-root user and run the entrypoint -USER postgres -CMD ["/entrypoint.sh"] diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh deleted file mode 100755 index c554c8e..0000000 --- a/docker/entrypoint.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash -set -e - -error_exit() { - echo "$1" >&2 - exit 1 -} - -# Backups are enabled by default. -# To opt out, set BACKUP_DISABLED to any value. -if [ -n "$BACKUP_DISABLED" ]; then - echo "Cool folks don't disable backups. I hope you know what you're doing." - exec docker-entrypoint.sh -else - # Check that AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are set - [ -z "$AWS_ACCESS_KEY_ID" ] && error_exit "AWS_ACCESS_KEY_ID must be set" - [ -z "$AWS_SECRET_ACCESS_KEY" ] && error_exit "AWS_SECRET_ACCESS_KEY must be set" - - # Check that either BACKUP_PREFIX or WALG_S3_PREFIX is set - [ -z "$BACKUP_PREFIX" ] && [ -z "$WALG_S3_PREFIX" ] && error_exit "BACKUP_PREFIX or WALG_S3_PREFIX must be set" - - # Reconstruct full WALG_S3_PREFIX if BACKUP_PREFIX is set - [ -n "$BACKUP_PREFIX" ] && WALG_S3_PREFIX="s3://bksp-backups/$BACKUP_PREFIX" - - exec docker-entrypoint.sh \ - -c archive_mode=always \ - -c archive_timeout=1h \ - -c archive_command='walg-push /var/lib/postgresql/data/%p' \ - -c restore_command='walg-fetch %f /var/lib/postgresql/data/%p' -fi diff --git a/docker/install.sh b/docker/install.sh deleted file mode 100755 index 2ae8ab4..0000000 --- a/docker/install.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash -set -e - -# Install dependencies -apt update -apt install -y curl ca-certificates - -# Install B4CKSP4CE Root CA -mkdir -p /usr/share/ca-certificates/bksp -curl -fSsl https://ca.bksp.in/root/bksp-root.crt | tee /usr/share/ca-certificates/bksp/B4CKSP4CE_Root_CA.crt -echo "bksp/B4CKSP4CE_Root_CA.crt" | tee -a /etc/ca-certificates.conf -update-ca-certificates - -# Whether to use aarch64 or amd64 -ARCH=$(uname -m) -if [ "$ARCH" = "aarch64" ]; then - WALG_URL="https://github.com/wal-g/wal-g/releases/download/v3.0.3/wal-g-pg-ubuntu20.04-aarch64" - WALG_SHA256="3aec9024959319468ac637ea4b2e215fe20511672669969077733ee5c3fd1466" -elif [ "$ARCH" = "x86_64" ]; then - WALG_URL="https://github.com/wal-g/wal-g/releases/download/v3.0.3/wal-g-pg-ubuntu-20.04-amd64" - WALG_SHA256="0b46652f23fb4d09fa08f3d536b72806e597c4e20d0a09d960d6337bc2368e8b" -else - echo "Unsupported architecture" - exit 1 -fi - -# Download wal-g -curl -fsSL -o "/usr/local/bin/wal-g" "$WALG_URL" -echo "${WALG_SHA256} /usr/local/bin/wal-g" | sha256sum -c - -chmod +x /usr/local/bin/wal-g - -# Clean-up stuff after installation -apt clean -rm -rf /var/lib/apt/lists/* /var/cache/* /var/log/* /install.sh diff --git a/docker/walg-fetch b/docker/walg-fetch deleted file mode 100755 index d5d127c..0000000 --- a/docker/walg-fetch +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash -PGUSER=$POSTGRES_USER -PGPASSWORD=$POSTGRES_PASS - -/usr/local/bin/wal-g wal-fetch $1 diff --git a/docker/walg-push b/docker/walg-push deleted file mode 100755 index 20ce33b..0000000 --- a/docker/walg-push +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env bash - -# Configuration -HEALTHCHECKS_UUID="${HEALTHCHECKS_UUID:-}" -HEALTHCHECKS_BASE_URL="https://hc.bksp.in/ping" -WALG_COMMAND="/usr/local/bin/wal-g" -PGUSER=$POSTGRES_USER -PGPASSWORD=$POSTGRES_PASS - -# Function to report status to Healthchecks -report_status() { - local status="$1" - local message="$2" - - # Skip if no Healthchecks UUID is set - [ -z "$HEALTHCHECKS_UUID" ] && return 0 - - case "$status" in - start) - curl --silent -m 10 --retry 5 "$HEALTHCHECKS_BASE_URL/$HEALTHCHECKS_UUID/start" || return 1 - ;; - success) - curl --silent -m 10 --retry 5 --data-raw "$message" "$HEALTHCHECKS_BASE_URL/$HEALTHCHECKS_UUID" || return 1 - ;; - failure) - # Include full stderr in the failure request - curl --silent -m 10 --retry 5 --data-raw "$message" "$HEALTHCHECKS_BASE_URL/$HEALTHCHECKS_UUID/fail" || return 1 - ;; - esac -} - -# Main archiving function -archive_wal() { - local wal_file="$1" - - report_status "start" - - # Execute WAL-G archive command and capture stderr - if output=$(2>&1 "$WALG_COMMAND" wal-push "$wal_file"); then - report_status "success" "WAL $wal_file archived successfully" - exit 0 - else - local error_msg="WAL archiving failed for $wal_file: $output" - report_status "failure" "$error_msg" - exit 1 - fi -} - -# Main script execution -if [ $# -ne 1 ]; then - echo "Usage: $0 " - exit 1 -fi - -archive_wal "$1" diff --git a/walg-wrapper.sh b/walg-wrapper.sh new file mode 100755 index 0000000..2be0317 --- /dev/null +++ b/walg-wrapper.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash + +set -euo pipefail # Add error handling and strict mode + +# Configuration +readonly HEALTHCHECKS_UUID="${HEALTHCHECKS_UUID:-}" +readonly HEALTHCHECKS_BASE_URL="${HEALTHCHECKS_BASE_URL:-https://hc.bksp.in/ping}" + +# Function for error handling +error_exit() { + echo "Error: $1" >&2 + exit 1 +} + +# Validate and set S3 prefix +[[ -z "${BACKUP_PREFIX-}" && -z "${WALG_S3_PREFIX-}" ]] && error_exit "BACKUP_PREFIX or WALG_S3_PREFIX must be set" +[[ -n "${BACKUP_PREFIX-}" ]] && export WALG_S3_PREFIX="s3://bksp-backups/$BACKUP_PREFIX" + +# Export PostgreSQL credentials +export PGUSER PGPASSWORD + +# Function to report status to Healthchecks +report_status() { + [[ -z "$HEALTHCHECKS_UUID" ]] && return 0 + + local status="$1" + local message="${2:-}" + local url="$HEALTHCHECKS_BASE_URL/$HEALTHCHECKS_UUID" + + case "$status" in + start) url+="/start" ;; + failure) url+="/fail" ;; + esac + + if [[ -n "$message" ]]; then + curl --silent -m 10 --retry 5 --data-raw "$message" "$url" + else + curl --silent -m 10 --retry 5 "$url" + fi +} + +# Main archiving function +archive_wal() { + local wal_file="$1" + report_status "start" + + if output=$(/usr/local/bin/wal-g wal-push "$wal_file" 2>&1); then + report_status "success" "WAL $wal_file archived successfully" + return 0 + fi + + report_status "failure" "WAL archiving failed for $wal_file: $output" + return 1 +} + +fetch_wal() { + exec /usr/local/bin/wal-g wal-fetch "$1" "$2" +} + +case "$1" in + wal-push) archive_wal "$2" ;; + wal-fetch) fetch_wal "$2" "$3" ;; + *) + echo "Usage: $0 (wal-push|wal-fetch) " >&2 + exit 1 + ;; +esac