feat: new postgresql role

This commit is contained in:
Lars Kaiser 2021-10-08 11:14:51 +02:00
parent 01d9d8532f
commit 34ff7cefd7
No known key found for this signature in database
GPG key ID: BB97304A16BC5DCF
7 changed files with 366 additions and 0 deletions

View file

@ -0,0 +1,64 @@
# `famedly.base.postgresql` ansible role for deploying PostgreSQL inside a docker container
This role supports both PostgreSQL listening on a
- UNIX socket on the host (mountable into other containers)
- a TCP socket on a configurable port on the host
at the same time.
It does not support changing the listening TCP port inside the container to anything other than the standard `5432`.
If both UNIX and TCP sockets are activated, the configuration of global options is done via UNIX socket.
Since the container sets the permissions of the UNIX socket to `0777`, we do not allow `trust` as auth method for local connections in `pg_hba.conf`.
## Requirements
- psycopg2
## Role Variables
See `defaults/main.yml`.
The `postgresql_superuser_password` variable is required and sets the password for the default user `postgres`.
In `postgresql_global_config_options` you can specify global config options in the form of `{ option: "listen_addresses", value: "*" }.
In the optional `postgresql_host_port` variable you can provide an unused port on the host which will be forwarded to the container port 5432.
By default `postgresql_connect_socket` is set to `true`, which will mount `postgresql_socket_path` into the container and thus provide the PostgreSQL UNIX socket on the host.
You can disable this by setting the variable to `false`, PostgreSQL will still listen on the UNIX socket inside the container.
### Alternative docker image
You can specify an alternative container image to use, for example if you want PostGIS support.
If this image is based on the default `docker.io/postgres` image, this role should work as expected.
However, this is not guaranteed.
This is an example for specifying the official PostGIS image:
```yaml
postgresql_version: "13"
postgis_version: "3.1"
postgresql_container_image_name: "docker.io/postgis/postgis"
postgresql_container_image_tag: "{{ postgresql_version }}-{{ postgis_version }}-{{ postgresql_container_distro }}"
```
You could also override the entire `postgresql_container_image` variable.
## Dependencies
Docker needs to be installed and configured.
## Example Playbook
```yaml
---
- name: Install PostgreSQL in a docker container
hosts: [ all ]
become: true
roles:
- famedly.base.postgresql
vars:
postgresql_host_port: "2345"
postgresql_superuser_password: "{{ vault_postgresql_superuser_password }}"
postgresql_connect_socket: "false"
```
## License
GNU Affero General Public License v3
## Author Information
Famedly GmbH, famedly.de

View file

@ -0,0 +1,31 @@
---
postgresql_user: postgresql
postgresql_base_path: /opt/postgresql
postgresql_data_path: "{{ postgresql_base_path }}/data"
postgresql_socket_path: "{{ postgresql_base_path }}/sockets"
postgresql_config_path: "{{ postgresql_base_path }}/config"
postgresql_connect_socket: true
postgresql_container_image_name: "docker.io/postgres"
postgresql_container_version: "13.4"
postgresql_container_distro: alpine
postgresql_container_image_tag: "{{ postgresql_container_version }}{{ '-' + postgresql_container_distro if postgresql_container_distro else '' }}"
postgresql_container_image: "{{ postgresql_container_image_name }}:{{ postgresql_container_image_tag }}"
postgresql_container_name: postgresql
postgresql_container_labels: {}
postgresql_container_ports: >-
{{
[] if postgresql_host_port is undefined else
[ postgresql_host_port | string + ':5432' ]
}}
postgresql_container_networks: []
postgresql_container_etc_hosts: {}
postgresql_container_pull: true
postgresql_container_recreate: false
postgresql_container_fd_soft_limit: "8192"
postgresql_container_fd_hard_limit: "8192"
postgresql_container_ulimits: ["nofile:{{ postgresql_container_fd_soft_limit }}:{{ postgresql_container_fd_hard_limit }}"]
postgresql_container_memory_reservation: "256M"
postgresql_container_memory: "512M"
postgresql_container_shm_size: "128M"

View file

@ -0,0 +1,8 @@
---
- name: Restart PostgreSQL docker container
community.docker.docker_container:
name: postgresql
state: started
restart: true
listen: postgresql_container_restart

View file

@ -0,0 +1,51 @@
---
- name: Create directories for PostgreSQL
file:
path: "{{ item }}"
state: directory
owner: "{{ postgresql_user_res.uid }}"
group: "{{ postgresql_user_res.group }}"
mode: 0755
loop:
- "{{ postgresql_base_path }}"
- "{{ postgresql_data_path }}"
- "{{ postgresql_socket_path }}"
- "{{ postgresql_config_path }}"
tags: [ 'prepare', 'prepare-postgresql' ]
- name: Template fake /etc/passwd for postgres
ansible.builtin.template:
src: postgresql-passwd.j2
dest: "{{ postgresql_config_path }}/postgresql-passwd"
owner: "{{ postgresql_user_res.uid }}"
group: "{{ postgresql_user_res.group }}"
mode: 0640
tags: [ 'prepare', 'prepare-postgresql' ]
- name: Initialize PostgreSQL container
community.docker.docker_container:
name: "{{ postgresql_container_name }}"
image: "{{ postgresql_container_image }}"
ports: "{{ postgresql_container_ports }}"
volumes: "{{ postgresql_container_init_volumes }}"
labels: "{{ postgresql_container_labels_complete }}"
networks: "{{ postgresql_container_networks }}"
etc_hosts: "{{ postgresql_container_etc_hosts }}"
user: "{{ postgresql_user_res.uid }}:{{ postgresql_user_res.group }}"
state: started
env:
POSTGRES_PASSWORD: "{{ postgresql_superuser_password }}"
register: postgresql_container
tags: [ 'prepare', 'prepare-postgresql' ]
- name: Wait for container to be initialized
wait_for:
path: "{{ postgresql_socket_path }}/.s.PGSQL.5432"
tags: [ 'prepare', 'prepare-postgresql' ]
- name: Stop initialized container
community.docker.docker_container:
name: "{{ postgresql_container_name }}"
state: absent
tags: [ 'prepare', 'prepare-postgresql' ]

View file

@ -0,0 +1,146 @@
---
- name: Create system user to run postgresql as
user:
name: "{{ postgresql_user }}"
state: present
system: yes
register: postgresql_user_res
tags: ['prepare', 'prepare-postgresql',
'deploy', 'deploy-postgresql']
- name: Ensure PostgreSQL container image is pulled
community.docker.docker_image:
name: "{{ postgresql_container_image }}"
force_source: "{{ postgresql_container_pull }}"
source: pull
state: present
tags: [ 'prepare', 'prepare-postgresql' ]
- name: Check if postgresql_data_path exists
stat:
path: "{{ postgresql_data_path }}"
register: stat_postgresql_data_path
tags: [ 'prepare', 'prepare-postgresql' ]
- name: Check if postgresql_data_path is empty
find:
paths: "{{ postgresql_data_path }}"
file_type: any
when: stat_postgresql_data_path.stat.exists
register: find_postgresql_data_path
tags: [ 'prepare', 'prepare-postgresql' ]
- name: Initialize
include_tasks: initialize.yml
when: (not stat_postgresql_data_path.stat.exists) or (find_postgresql_data_path is defined and find_postgresql_data_path.examined == 0)
- name: Template fake /etc/passwd for postgres
ansible.builtin.template:
src: postgresql-passwd.j2
dest: "{{ postgresql_config_path }}/postgresql-passwd"
owner: "{{ postgresql_user_res.uid }}"
group: "{{ postgresql_user_res.group }}"
mode: 0640
tags: [ 'prepare', 'prepare-postgresql' ]
- name: Insert pg_hba.conf header
ansible.builtin.lineinfile:
path: "{{ postgresql_config_path }}/pg_hba.conf"
insertbefore: BOF
line: "# Ansible managed"
create: true
mode: 0640
owner: "{{ postgresql_user_res.uid }}"
group: "{{ postgresql_user_res.group }}"
tags: [ 'prepare', 'prepare-postgresql' ]
- name: Allow peer login for postgres user on socket
community.postgresql.postgresql_pg_hba:
dest: "{{ postgresql_config_path }}/pg_hba.conf"
contype: local
users: postgres
method: peer
options: map=root_postgres
tags: [ 'prepare', 'prepare-postgresql' ]
- name: Insert pg_ident.conf header
ansible.builtin.lineinfile:
path: "{{ postgresql_config_path }}/pg_ident.conf"
insertbefore: BOF
line: "# Ansible managed"
create: true
mode: 0640
owner: "{{ postgresql_user_res.uid }}"
group: "{{ postgresql_user_res.group }}"
tags: [ 'prepare', 'prepare-postgresql' ]
- name: Insert pg_ident.conf root_postgres user map
ansible.builtin.lineinfile:
path: "{{ postgresql_config_path }}/pg_ident.conf"
insertafter: "# Ansible managed"
line: "root_postgres\troot\tpostgres" # noqa no-tabs
tags: [ 'prepare', 'prepare-postgresql' ]
- name: Ensure PostgreSQL container is started
community.docker.docker_container:
name: "{{ postgresql_container_name }}"
image: "{{ postgresql_container_image }}"
ports: "{{ postgresql_container_ports }}"
volumes: "{{ postgresql_container_volumes_complete }}"
labels: "{{ postgresql_container_labels_complete }}"
networks: "{{ postgresql_container_networks }}"
etc_hosts: "{{ postgresql_container_etc_hosts }}"
ulimits: "{{ postgresql_container_ulimits }}"
memory_reservation: "{{ postgresql_container_memory_reservation }}"
memory: "{{ postgresql_container_memory }}"
shm_size: "{{ postgresql_container_shm_size }}"
recreate: "{{ postgresql_container_recreate }}"
user: "{{ postgresql_user_res.uid }}:{{ postgresql_user_res.group }}"
restart_policy: unless-stopped
state: started
register: postgresql_container
tags: [ 'deploy', 'deploy-postgresql' ]
- name: Wait for container startup
wait_for:
path: "{{ postgresql_socket_path }}/.s.PGSQL.5432"
when: postgresql_connect_socket
tags: [ 'deploy', 'deploy-postgresql' ]
- name: Wait for container startup
wait_for:
port: "{{ postgresql_host_port }}"
when: not postgresql_connect_socket
tags: [ 'deploy', 'deploy-postgresql' ]
- name: Set superuser password
community.postgresql.postgresql_user:
name: postgres
password: "{{ postgresql_superuser_password }}"
login_host: "{{ postgresql_connection.login_host }}"
tags: [ 'deploy', 'deploy-postgresql' ]
- name: Set global configuration options
community.postgresql.postgresql_set:
name: "{{ item.option }}"
value: "{{ item.value }}"
login_host: "{{ postgresql_connection.login_host }}"
login_port: "{{ postgresql_connection.login_port }}"
login_password: "{{ postgresql_connection.login_password }}"
register: postgresql_set_result
loop: "{{ postgresql_global_config_options }}"
tags: [ 'deploy', 'deploy-postgresql' ]
# The above task sets global options, but only some of them require a restart
# The below task notifies the restart handler only in these cases, preventing unnecessary downtime
- name: Check and notify handler if restart is required
debug:
msg: "{{ item.item.option }} changed. Restart required: {{ item.restart_required }}"
when: item.changed # noqa no-handler
changed_when: item.restart_required
notify: postgresql_container_restart
loop: "{{ postgresql_set_result.results }}"
loop_control:
label: "{{ item.item.option }}"
tags: [ 'deploy', 'deploy-postgresql' ]

View file

@ -0,0 +1,29 @@
root:x:0:0:root:/root:/bin/ash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/mail:/sbin/nologin
news:x:9:13:news:/usr/lib/news:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
man:x:13:15:man:/usr/man:/sbin/nologin
postmaster:x:14:12:postmaster:/var/mail:/sbin/nologin
cron:x:16:16:cron:/var/spool/cron:/sbin/nologin
ftp:x:21:21::/var/lib/ftp:/sbin/nologin
sshd:x:22:22:sshd:/dev/null:/sbin/nologin
at:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin
squid:x:31:31:Squid:/var/cache/squid:/sbin/nologin
xfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin
games:x:35:35:games:/usr/games:/sbin/nologin
cyrus:x:85:12::/usr/cyrus:/sbin/nologin
vpopmail:x:89:89::/var/vpopmail:/sbin/nologin
ntp:x:123:123:NTP:/var/empty:/sbin/nologin
smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin
guest:x:405:100:guest:/dev/null:/sbin/nologin
nobody:x:65534:65534:nobody:/:/sbin/nologin
{{ postgresql_user_res.name }}:x:{{ postgresql_user_res.uid }}:{{ postgresql_user_res.group }}::{{ postgresql_user_res.home }}:{{ postgresql_user_res.shell }}
utmp:x:100:406:utmp:/home/utmp:/bin/false

View file

@ -0,0 +1,37 @@
---
postgresql_connection:
login_host: >-
{{
postgresql_socket_path if postgresql_connect_socket
else (postgresql_container.container.NetworkSettings.IPAddress if postgresql_host_port is undefined
else "127.0.0.1")
}}
login_port: >-
{{
postgresql_host_port if postgresql_host_port is defined and not postgresql_connect_socket
else "5432"
}}
login_password: "{{ postgresql_superuser_password }}"
postgresql_container_data_dir: /var/lib/postgresql/data
postgresql_container_socket_dir: /var/run/postgresql
postgresql_container_volumes_base:
- "{{ postgresql_data_path }}:{{ postgresql_container_data_dir }}:z"
- "{{ postgresql_config_path }}/pg_hba.conf:{{ postgresql_container_data_dir }}/pg_hba.conf:ro"
- "{{ postgresql_config_path }}/pg_ident.conf:{{ postgresql_container_data_dir }}/pg_ident.conf:ro"
- "{{ postgresql_config_path }}/postgresql-passwd:/etc/passwd:ro"
postgresql_container_volumes_complete: >-
{{ postgresql_container_volumes_base
+ ([postgresql_socket_path + ':' + postgresql_container_socket_dir + ':z']
if postgresql_connect_socket else [])
}}
postgresql_container_init_volumes:
- "{{ postgresql_data_path }}:{{ postgresql_container_data_dir }}:z"
- "{{ postgresql_socket_path }}:{{ postgresql_container_socket_dir }}:z"
- "{{ postgresql_config_path }}/postgresql-passwd:/etc/passwd:ro"
postgresql_container_labels_base:
version: "{{ postgresql_container_version }}"
postgresql_container_labels_complete: "{{ postgresql_container_labels_base | combine(postgresql_container_labels) }}"