diff --git a/defaults/main.yml b/defaults/main.yml new file mode 100644 index 0000000..ce3a03d --- /dev/null +++ b/defaults/main.yml @@ -0,0 +1,41 @@ +--- +postgres_ha_service_name: 'postgres-ha' +postgres_ha_service_path: '/docker/{{ postgres_ha_service_name }}' +postgres_ha_compose_file: '{{ postgres_ha_service_path }}/docker-compose.yml' + +postgres_ha_cont_name: '{{ postgres_ha_service_name }}' +postgres_ha_cont_data_vol: '{{ postgres_ha_service_path }}/data' +postgres_ha_cont_image: 'postgres:15.1-alpine' +postgres_ha_cont_port: 5432 +postgres_ha_cont_uid: 70 +postgres_ha_host_uid: '{{ 100000 + hackmd_db_cont_uid | int }}' +postgres_ha_cont_run_size: '512K' +postgres_ha_cont_tmp_size: '256K' + +# Admin Auth +postgres_ha_admin_user: 'postgres' +postgres_ha_admin_pass: 'changeMeIfYouCare' +# Replication Auth +postgres_ha_replica_user: 'replicator' +postgres_ha_replica_pass: 'changeMeIfYouCare' +postgres_ha_replica_slot: 'main' +#postgres_ha_replica_host: 'db-master.example.org' +#postgres_ha_replica_port: 5432 +#postgres_ha_replica_allowed_addresses: ['10.0.0.0/8'] + +# Mandatory Configuration +#postgres_ha_is_master: true + +# Optional Configuration +postgres_ha_db_name: 'postgres' + +# Additional Databases +postgres_ha_databases: [] +# - name: 'dbtest' +# user: 'dbuser' +# pass: 'dbpass' + +# general container management +compose_recreate: 'smart' +compose_state: 'present' +compose_restart: false diff --git a/tasks/config.yml b/tasks/config.yml new file mode 100644 index 0000000..d25ec42 --- /dev/null +++ b/tasks/config.yml @@ -0,0 +1,19 @@ +--- +- name: 'Create service and container folders' + file: + dest: '{{ item }}' + state: 'directory' + owner: 'dockremap' + group: 'docker' + mode: 0775 + with_items: + - '{{ postgres_ha_service_path }}' + - '{{ postgres_ha_cont_data_vol }}' + +- name: 'Create compose file' + template: + src: 'docker-compose.yml.j2' + dest: '{{ postgres_ha_compose_file }}' + owner: 'dockremap' + group: 'docker' + mode: 0644 diff --git a/tasks/databases.yml b/tasks/databases.yml new file mode 100644 index 0000000..be68d68 --- /dev/null +++ b/tasks/databases.yml @@ -0,0 +1,25 @@ +--- +- name: 'Create user: {{ db.get("user", db.name) }}' + command: | + docker exec {{ postgres_ha_cont_name }} + psql + --username={{ postgres_ha_admin_user }} + --port={{ postgres_ha_cont_port }} + --command + "DO $$ + BEGIN + CREATE USER \"{{ db.get("user", db.name) }}\" PASSWORD '{{ db.pass | mandatory }}'; + EXCEPTION WHEN DUPLICATE_OBJECT THEN + RAISE NOTICE 'not creating role {{ db.get("user", db.name) }} -- it already exists'; + END + $$;" + +- name: 'Create database: {{ db.name | mandatory }}' + shell: | + echo "SELECT + 'CREATE DATABASE \"{{ db.name }}\" WITH OWNER \"{{ db.get("user", db.name) }}\";' + WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '{{ db.name }}')\\\gexec" | + docker exec -i {{ postgres_ha_cont_name }} \ + psql + --username={{ postgres_ha_admin_user }} + --port={{ postgres_ha_cont_port }} diff --git a/tasks/hba.yml b/tasks/hba.yml new file mode 100644 index 0000000..935ed7b --- /dev/null +++ b/tasks/hba.yml @@ -0,0 +1,17 @@ +--- +- name: 'Allow VPN connections in HBA config' + register: postgres_ha_hba + blockinfile: + path: '{{ postgres_ha_cont_data_vol }}/pgdata/pg_hba.conf' + marker: '# {mark} ANSIBLE MANAGED REPLICATORS' + block: | + {% for addr in postgres_ha_replica_allowed_addresses %} + host replication replicator {{ addr }} md5 + {% endfor %} + +- name: 'Restart database container' + when: postgres_ha_hba.changed + args: + chdir: '{{ postgres_ha_service_path }}' + command: | + docker-compose restart db diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100644 index 0000000..6e725bc --- /dev/null +++ b/tasks/main.yml @@ -0,0 +1,22 @@ +--- +- import_tasks: config.yml + +- import_tasks: start.yml + when: postgres_ha_is_master + +- import_tasks: hba.yml + when: postgres_ha_is_master + +- import_tasks: users.yml + when: postgres_ha_is_master + +- import_tasks: replica.yml + when: not postgres_ha_is_master + +- import_tasks: start.yml + when: not postgres_ha_is_master + +- include_tasks: databases.yml + with_items: '{{ postgres_ha_databases }}' + loop_control: + loop_var: db diff --git a/tasks/replica.yml b/tasks/replica.yml new file mode 100644 index 0000000..4569b7d --- /dev/null +++ b/tasks/replica.yml @@ -0,0 +1,25 @@ +--- +- name: 'Check if replica DB config exists' + stat: + path: '{{ postgres_ha_cont_data_vol }}/pgdata/postgresql.auto.conf' + register: postgres_ha_auto_conf + +- name: 'Initialize DB with base backup' + when: not postgres_ha_auto_conf.stat.exists + args: + chdir: '{{ postgres_ha_service_path }}' + command: | + docker-compose run --rm + --env=PGPASSWORD={{ postgres_ha_replica_pass }} + --name {{ postgres_ha_service_name }}-backup + -- db + pg_basebackup + --host={{ postgres_ha_replica_host }} + --port={{ postgres_ha_replica_port }} + --username={{ postgres_ha_replica_user }} + --slot={{ postgres_ha_replica_slot }} + --verbose + --create-slot + --pgdata=/var/lib/postgresql/data/pgdata + --write-recovery-conf + --checkpoint=fast diff --git a/tasks/start.yml b/tasks/start.yml new file mode 100644 index 0000000..79d87a2 --- /dev/null +++ b/tasks/start.yml @@ -0,0 +1,8 @@ +--- +- name: 'Create database container' + docker_compose: + project_src: '{{ postgres_ha_service_path }}' + state: '{{ compose_state }}' + restarted: '{{ compose_restart }}' + recreate: '{{ compose_recreate }}' + pull: true diff --git a/tasks/users.yml b/tasks/users.yml new file mode 100644 index 0000000..dc66ace --- /dev/null +++ b/tasks/users.yml @@ -0,0 +1,16 @@ +--- +- name: 'Create user replication user' + command: | + docker exec {{ postgres_ha_cont_name }} + psql + --username={{ postgres_ha_admin_user }} + --port={{ postgres_ha_cont_port }} + --command + "DO $$ + BEGIN + CREATE ROLE {{ postgres_ha_replica_user }} + WITH REPLICATION LOGIN ENCRYPTED PASSWORD '{{ postgres_ha_replica_pass }}'; + EXCEPTION WHEN DUPLICATE_OBJECT THEN + RAISE NOTICE 'not creating role {{ postgres_ha_replica_user }} -- it already exists'; + END + $$;" diff --git a/templates/docker-compose.yml.j2 b/templates/docker-compose.yml.j2 new file mode 100644 index 0000000..b5e515b --- /dev/null +++ b/templates/docker-compose.yml.j2 @@ -0,0 +1,33 @@ +--- +version: '3.7' +services: + db: + container_name: '{{ postgres_ha_cont_name }}' + image: '{{ postgres_ha_cont_image }}' + restart: always + environment: + POSTGRES_DB: '{{ postgres_ha_db_name | mandatory }}' + POSTGRES_USER: '{{ postgres_ha_admin_user }}' + POSTGRES_PASSWORD: '{{ postgres_ha_admin_pass }}' +{% if not postgres_ha_is_master %} + PGPASSWORD: '{{ postgres_ha_replica_pass }}' +{% endif %} + # This fixes chmod errors on DB startup due to volume + userns-remap + PGDATA: '/var/lib/postgresql/data/pgdata' + ports: + - '{{ postgres_ha_cont_port }}:{{ postgres_ha_cont_port }}' + tmpfs: + - '/run/postgresql:size={{ postgres_ha_cont_run_size }}' + - '/tmp:size={{ postgres_ha_cont_tmp_size }}' + volumes: + - '{{ postgres_ha_cont_data_vol }}:/var/lib/postgresql/data' + command: | + -p {{ postgres_ha_cont_port }} + healthcheck: + interval: 30s + retries: 5 + test: [ + 'CMD', 'pg_isready', + '-p', '{{ postgres_ha_cont_port }}', + '-U', '{{ postgres_ha_admin_user }}' + ]