Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Publish Postgres with LOGIC #1

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .example.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
POSTGRES_CIDR=172.55.32.0/24
POSTGRES_SUBNET=172.55.0.0/16
POSTGRES_GATEWAY=172.55.32.254
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.env
59 changes: 57 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,62 @@
# Postgres with LOGIC

Production-ready Postgres with Docker that does not suck, with [LOGIC](https://withlogic.co).
This repository contains the configuration for production-grade Postgres with Docker, with [LOGIC](https://withlogic.co). It was first presented on 24 January 2024, at Docker Athens during the presentation [Production grade Postgres with Docker](https://www.youtube.com/watch?v=tJegTc-oLtk)[^1].

The configuration in this repository sets up a primary and secondary Postgres server with streaming replication. This means that the primary server accepts all write queries, which are in turn replicated to the secondary server. Therefore, read queries can be load balanced and performed on both servers.

## Requirements

- Docker Engine 25.0.0 or newer
- Docker Compose 2.24.9 or newer, for development

## Configuration

The setup of Postgres with LOGIC is configured with environment variables and Docker Secrets for sensitive data.

### Environment variables

- `POSTGRES_CIDR`: The CIDR block from which to allocate IPs in the Docker network and also allow replication from (default: `172.54.32.0/24`)
- `POSTGRES_GATEWAY`: The gateway to use in the Docker network (default: `172.54.32.254`)
- `POSTGRES_SUBNET`: The subnet to allocate for the Docker network (default: `172.54.0.0/16`)

For convenience, in development these environment variables can be set in a `.env` environment file. Example file available in [`.example.env`](./.example.env)

### Secrets

- `postgres-password`: The password for the `postgres` user of the database used for most database operations
- `replicator-password`: The password for the `replicator` role, used from the secondary server to replicate data

For convenience, in development all secrets are hardcoded as files and are available in the [`dev/secrets`](./dev/secrets) directory and do not need to be set.

## Development

To kick off and evaluate the setup locally, all you have to do is run

```console
docker compose up
```

After all containers start, you can validate the setup with the following steps:

1. Create a table on the primary server
```console
docker compose exec primary psql -U postgres -c "CREATE TABLE people (name varchar(40));"
```
2. Insert a couple of rows in the primary server
```console
docker compose exec primary psql -U postgres -c "INSERT INTO people VALUES ('grace');"
docker compose exec primary psql -U postgres -c "INSERT INTO people VALUES ('alan');"
```
3. Validate that data can be read from both servers
```console
docker compose exec primary psql -U postgres -c "SELECT * FROM people;"
docker compose exec secondary psql -U postgres -c "SELECT * FROM people;"
```

---

<p align="center">
<img src="https://github.com/withlogicco/postgres/assets/1188592/79797352-66a1-436e-82ac-a6ed33bc5aa8" />
<i>🦄 Built with <a href="https://withlogic.co/">LOGIC</a>. 🦄</i>
</p>

[^1]: Presentation on YouTube: https://www.youtube.com/watch?v=tJegTc-oLtk
51 changes: 51 additions & 0 deletions compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
x-base:
&base
secrets:
- postgres-password
- replicator-password
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/postgres-password
POSTGRES_CIDR: ${POSTGRES_CIDR:-172.54.32.0/24}
command: ["postgres", "-c", "log_statement=all"]

services:
primary:
<<: *base
build:
context: postgres
target: primary
volumes:
- primary_data:/var/lib/postgresql/data

secondary:
<<: *base
build:
context: postgres
target: secondary
restart: on-failure:3
volumes:
- secondary_data:/var/lib/postgresql/data

pgpool2:
build:
context: pgpool2
secrets:
- postgres-password

secrets:
postgres-password:
file: dev/secrets/postgres-password
replicator-password:
file: dev/secrets/replicator-password

networks:
default:
ipam:
config:
- subnet: ${POSTGRES_SUBNET:-172.54.0.0/16}
ip_range: ${POSTGRES_CIDR:-172.54.32.0/24}
gateway: ${POSTGRES_GATEWAY:-172.54.32.254}

volumes:
primary_data:
secondary_data:
1 change: 1 addition & 0 deletions dev/secrets/postgres-password
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
development_password
1 change: 1 addition & 0 deletions dev/secrets/replicator-password
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
development_replicator_password
19 changes: 19 additions & 0 deletions pgpool2/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM ubuntu:22.04 as pgpool

# https://www.pgpool.net/mediawiki/index.php/Apt_Repository

ENV DEBIAN_FRONTEND noninteractive

RUN apt-get update
RUN apt-get install -y wget gnupg
RUN echo "deb http://apt.postgresql.org/pub/repos/apt jammy-pgdg main" > /etc/apt/sources.list.d/pgdg.list
RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
RUN apt-get update
RUN apt-get install -y pgpool2 libpgpool2 postgresql-16-pgpool2
RUN apt-get install -y gettext-base

COPY pgpool2/ /etc/pgpool2/
COPY bin/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh

ENTRYPOINT [ "docker-entrypoint.sh" ]
CMD [ "pgpool", "-n" ]
14 changes: 14 additions & 0 deletions pgpool2/bin/docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#! /bin/bash

set -ex

export POSTGRES_PASSWORD=$(cat /run/secrets/postgres-password)
cat /etc/pgpool2/pgpool.conf.tpl | envsubst > /etc/pgpool2/pgpool.conf
cat /etc/pgpool2/pool_passwd.tpl | envsubst > /etc/pgpool2/pool_passwd
unset POSTGRES_PASSWORD

export POSTGRES_PASSWORD_MD5=$(md5sum /run/secrets/postgres-password | awk '{print $1}')
cat /etc/pgpool2/pcp.conf.tpl | envsubst > /etc/pgpool2/pcp.conf
unset POSTGRES_PASSWORD_MD5

exec "$@"
4 changes: 4 additions & 0 deletions pgpool2/pgpool2/pcp.conf.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# PCP Client Authentication Configuration File
# ============================================

postgres:${POSTGRES_PASSWORD_MD5}
101 changes: 101 additions & 0 deletions pgpool2/pgpool2/pgpool.conf.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# ----------------------------
# pgPool-II configuration file
# ----------------------------
#
# Docs: https://www.pgpool.net/docs/latest/en/html/configuring-pgpool.html#:~:text=conf%20is%20the%20main%20configuration,%24prefix%2Fetc%2Fpgpool.

#------------------------------------------------------------------------------
# BACKEND CLUSTERING MODE
#------------------------------------------------------------------------------

backend_clustering_mode = 'streaming_replication'


#------------------------------------------------------------------------------
# CONNECTIONS
#------------------------------------------------------------------------------

# - pgpool Connection Settings -

listen_addresses = '*'
port = 5432

# - pgpool Communication Manager Connection Settings -

pcp_listen_addresses = 'localhost'
pcp_port = 9898

# - Backend Connection Settings -

backend_hostname0 = 'primary'
backend_port0 = 5432
backend_weight0 = 1
backend_data_directory0 = '/var/lib/postgresql/data'
backend_flag0 = 'ALLOW_TO_FAILOVER'
backend_application_name0 = 'primary'

backend_hostname1 = 'secondary'
backend_port1 = 5432
backend_weight1 = 1
backend_data_directory1 = '/var/lib/postgresql/data'
backend_flag1 = 'ALLOW_TO_FAILOVER'
backend_application_name1 = 'secondary'

# - Authentication -

enable_pool_hba = on

# - SSL Connections -

#------------------------------------------------------------------------------
# LOGS
#------------------------------------------------------------------------------

# - Where to log -
log_per_node_statement = on


#------------------------------------------------------------------------------
# LOAD BALANCING MODE
#------------------------------------------------------------------------------

load_balance_mode = on


#------------------------------------------------------------------------------
# STREAMING REPLICATION MODE
#------------------------------------------------------------------------------

# - Streaming -

sr_check_period = 2
sr_check_user = 'postgres'
sr_check_password = '${POSTGRES_PASSWORD}'
sr_check_database = 'postgres'


#------------------------------------------------------------------------------
# HEALTH CHECK GLOBAL PARAMETERS
#------------------------------------------------------------------------------

health_check_period = 30
health_check_timeout = 20
health_check_user = 'postgres'
health_check_password = '${POSTGRES_PASSWORD}'
health_check_database = 'postgres'
health_check_max_retries = 3
health_check_retry_delay = 10

#------------------------------------------------------------------------------
# FAILOVER AND FAILBACK
#------------------------------------------------------------------------------
failover_command = 'PGPASSWORD=${POSTGRES_PASSWORD} psql --host secondary -U postgres -c "SELECT pg_promote();"'
failover_on_backend_error = on
failover_on_backend_shutdown = on
detach_false_primary = on

#------------------------------------------------------------------------------
# ONLINE RECOVERY
#------------------------------------------------------------------------------

auto_failback = on
9 changes: 9 additions & 0 deletions pgpool2/pgpool2/pool_hba.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# https://www.pgpool.net/docs/latest/en/html/auth-pool-hba-conf.html

# "local" is for Unix domain socket connections only
local all all trust
# IPv4 local connections:
host all all 127.0.0.1/32 trust
host all all ::1/128 trust

host all all all scram-sha-256
1 change: 1 addition & 0 deletions pgpool2/pgpool2/pool_passwd.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
postgres:TEXT${POSTGRES_PASSWORD}
16 changes: 16 additions & 0 deletions postgres/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM postgres:16.2 as base


FROM base as primary

COPY ./postgres/primary/docker-entrypoint-initdb.d/ docker-entrypoint-initdb.d/


FROM base as secondary

COPY ./postgres/secondary/bin/ /usr/local/bin/
ENTRYPOINT [ "docker-entrypoint-override.sh" ]

CMD ["postgres"]

FROM primary
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
set -ex

REPLICATOR_PASSWORD=$(cat /run/secrets/replicator-password)
psql -c "CREATE ROLE replicator REPLICATION LOGIN PASSWORD '$REPLICATOR_PASSWORD'"
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
set -ex

POSTGRES_CIDR=${POSTGRES_CIDR:-172.54.32.0/24}
PGDATA=${PGDATA:-/var/lib/postgresql/data}

echo "host replication replicator $POSTGRES_CIDR scram-sha-256" >> $PGDATA/pg_hba.conf
6 changes: 6 additions & 0 deletions postgres/secondary/bin/10-base-backup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
set -ex

export PGPASSWORD=$(cat /run/secrets/replicator-password)

pg_basebackup -w -h primary -D $PGDATA -U replicator -P -v -X stream
chmod -R 0750 $PGDATA
3 changes: 3 additions & 0 deletions postgres/secondary/bin/20-enable-standby-mode.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
set -ex

touch $PGDATA/standby.signal
6 changes: 6 additions & 0 deletions postgres/secondary/bin/30-configure-primary.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
set -ex

pg_ctl -w start
REPLICATOR_PASSWORD=$(cat /run/secrets/replicator-password)
psql -c "ALTER SYSTEM SET primary_conninfo = 'host=primary port=5432 user=replicator password=$REPLICATOR_PASSWORD application_name=secondary';"
pg_ctl -w stop
16 changes: 16 additions & 0 deletions postgres/secondary/bin/docker-entrypoint-override.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env bash

set -ex

if [ ! -f $PGDATA/PG_VERSION ]
then
echo "Secondary server is not initialized. Starting initialization procedure."
su postgres -c '10-base-backup.sh'
su postgres -c '20-enable-standby-mode.sh'
su postgres -c '30-configure-primary.sh'
echo "Initialization procedure completed successfully."
else
echo "Secondary server is already initialized."
fi

exec docker-entrypoint.sh "$@"