Add managed postgresql.svc.plus deployment

This commit is contained in:
Haitao Pan 2026-04-05 19:09:25 +08:00
parent e9ea0b1d3b
commit 36813d4bde
11 changed files with 451 additions and 2 deletions

View File

@ -1,8 +1,16 @@
- name: Deploy managed accounts.svc.plus service - name: Deploy managed accounts.svc.plus service
hosts: accounts hosts: "{{ accounts_service_hosts | default('accounts') }}"
gather_facts: false gather_facts: false
become: true become: true
vars: vars:
accounts_service_image_tag: "{{ lookup('ansible.builtin.env', 'ACCOUNTS_IMAGE_TAG') | default('70c6a3f8', true) }}" accounts_service_image_repo: >-
{{ lookup('ansible.builtin.env', 'ACCOUNTS_IMAGE_REPO')
| default('ghcr.io/x-evor/accounts', true) }}
accounts_service_image_tag: >-
{{ lookup('ansible.builtin.env', 'ACCOUNTS_IMAGE_TAG')
| default('70c6a3f8', true) }}
accounts_service_pull_image: >-
{{ lookup('ansible.builtin.env', 'ACCOUNTS_PULL_IMAGE')
| default(false, true) | bool }}
roles: roles:
- roles/vhosts/accounts_service - roles/vhosts/accounts_service

View File

@ -0,0 +1,25 @@
- name: Deploy managed postgresql.svc.plus service
hosts: "{{ postgresql_service_hosts | default('postgresql') }}"
gather_facts: false
become: true
vars:
postgresql_service_postgres_image_repo: >-
{{ lookup('ansible.builtin.env', 'POSTGRESQL_POSTGRES_IMAGE_REPO')
| default('postgres-extensions', true) }}
postgresql_service_postgres_image_tag: >-
{{ lookup('ansible.builtin.env', 'POSTGRESQL_POSTGRES_IMAGE_TAG')
| default('17', true) }}
postgresql_service_postgres_pull_image: >-
{{ lookup('ansible.builtin.env', 'POSTGRESQL_POSTGRES_PULL_IMAGE')
| default(false, true) | bool }}
postgresql_service_stunnel_image_repo: >-
{{ lookup('ansible.builtin.env', 'POSTGRESQL_STUNNEL_IMAGE_REPO')
| default('ghcr.io/x-evor/stunnel-server', true) }}
postgresql_service_stunnel_image_tag: >-
{{ lookup('ansible.builtin.env', 'POSTGRESQL_STUNNEL_IMAGE_TAG')
| default('2330d36', true) }}
postgresql_service_stunnel_pull_image: >-
{{ lookup('ansible.builtin.env', 'POSTGRESQL_STUNNEL_PULL_IMAGE')
| default(false, true) | bool }}
roles:
- roles/vhosts/postgresql_service

View File

@ -7,6 +7,9 @@ jp-xhttp-contabo.svc.plus ansible_host=46.250.251.132 ansible_user=roo
[accounts] [accounts]
acp-server.svc.plus ansible_host=46.250.251.132 ansible_user=root acp-server.svc.plus ansible_host=46.250.251.132 ansible_user=root
[postgresql]
acp-server.svc.plus ansible_host=46.250.251.132 ansible_user=root
[k3s] [k3s]
jp-k3s-vultr.svc.plus ansible_host=167.179.110.129 ansible_user=root jp-k3s-vultr.svc.plus ansible_host=167.179.110.129 ansible_user=root

View File

@ -0,0 +1,22 @@
# postgresql_service
Managed single-host deployment for `postgresql.svc.plus`.
This role reconciles the current production shape:
- local PostgreSQL container on `127.0.0.1:5432`
- public TLS tunnel via `stunnel` on `0.0.0.0:5433`
- shared Docker network `cn-toolkit-shared`
Primary entrypoint:
- `deploy_postgresql_svc_plus.yml`
Common overrides:
- `POSTGRESQL_POSTGRES_IMAGE_REPO`
- `POSTGRESQL_POSTGRES_IMAGE_TAG`
- `POSTGRESQL_POSTGRES_PULL_IMAGE`
- `POSTGRESQL_STUNNEL_IMAGE_REPO`
- `POSTGRESQL_STUNNEL_IMAGE_TAG`
- `POSTGRESQL_STUNNEL_PULL_IMAGE`

View File

@ -0,0 +1,83 @@
---
postgresql_service_base_dir: /opt/cloud-neutral/postgresql.svc.plus/managed
postgresql_service_shared_network: cn-toolkit-shared
postgresql_service_postgres_network: docker_postgres_network
postgresql_service_postgres_compose_dir: "{{ postgresql_service_base_dir }}/postgres"
postgresql_service_postgres_compose_file: "{{ postgresql_service_postgres_compose_dir }}/docker-compose.yml"
postgresql_service_postgres_env_file: "{{ postgresql_service_postgres_compose_dir }}/env/postgres.env"
postgresql_service_postgres_config_file: "{{ postgresql_service_postgres_compose_dir }}/config/postgresql.conf"
postgresql_service_postgres_legacy_env_file: /opt/cloud-neutral/postgresql.svc.plus/deploy/docker/.env
postgresql_service_postgres_init_scripts_dir: /opt/cloud-neutral/postgresql.svc.plus/deploy/docker/init-scripts
postgresql_service_postgres_data_path: /data
postgresql_service_postgres_container_name: postgresql-svc-plus
postgresql_service_postgres_image_repo: postgres-extensions
postgresql_service_postgres_image_tag: "17"
postgresql_service_postgres_major: "17"
postgresql_service_postgres_pull_image: false
postgresql_service_postgres_port: 5432
postgresql_service_postgres_health_user: postgres
postgresql_service_postgres_wait_retries: 30
postgresql_service_postgres_wait_delay: 2
postgresql_service_postgres_env_defaults:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ""
POSTGRES_DB: postgres
PG_LOCAL_PORT: "5432"
PG_MAJOR: "{{ postgresql_service_postgres_major }}"
PG_DATA_PATH: /data
postgresql_service_postgres_conf:
listen_addresses: "*"
port: 5432
max_connections: 100
superuser_reserved_connections: 3
shared_buffers: 256MB
effective_cache_size: 1GB
maintenance_work_mem: 64MB
work_mem: 16MB
wal_buffers: 16MB
min_wal_size: 1GB
max_wal_size: 4GB
checkpoint_completion_target: 0.9
wal_compression: "on"
random_page_cost: 1.1
effective_io_concurrency: 200
default_statistics_target: 100
log_destination: stderr
logging_collector: "on"
log_directory: log
log_filename: postgresql-%Y-%m-%d_%H%M%S.log
log_rotation_age: 1d
log_rotation_size: 100MB
log_line_prefix: "%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h "
log_timezone: UTC
log_checkpoints: "on"
log_connections: "on"
log_disconnections: "on"
log_duration: "off"
log_lock_waits: "on"
log_statement: none
log_temp_files: 0
log_min_duration_statement: 1000
datestyle: iso, mdy
timezone: UTC
lc_messages: en_US.utf8
lc_monetary: en_US.utf8
lc_numeric: en_US.utf8
lc_time: en_US.utf8
default_text_search_config: pg_catalog.english
postgresql_service_stunnel_compose_dir: "{{ postgresql_service_base_dir }}/stunnel"
postgresql_service_stunnel_compose_file: "{{ postgresql_service_stunnel_compose_dir }}/docker-compose.yml"
postgresql_service_stunnel_config_file: "{{ postgresql_service_stunnel_compose_dir }}/conf/stunnel.conf"
postgresql_service_stunnel_container_name: cn-toolkit-stunnel-server
postgresql_service_stunnel_image_repo: ghcr.io/x-evor/stunnel-server
postgresql_service_stunnel_image_tag: "2330d36"
postgresql_service_stunnel_pull_image: false
postgresql_service_stunnel_accept_port: 5433
postgresql_service_stunnel_service_name: postgres-tls-server
postgresql_service_stunnel_verify_level: 0
postgresql_service_stunnel_cert_file: /opt/cloud-neutral/stunnel-server/certs/server-cert.pem
postgresql_service_stunnel_key_file: /opt/cloud-neutral/stunnel-server/certs/server-key.pem

View File

@ -0,0 +1,198 @@
---
- name: Ensure postgresql service base directory exists
ansible.builtin.file:
path: "{{ postgresql_service_base_dir }}"
state: directory
owner: root
group: root
mode: "0755"
- name: Ensure managed postgresql directories exist
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: root
group: root
mode: "0755"
loop:
- "{{ postgresql_service_postgres_compose_dir }}"
- "{{ postgresql_service_postgres_compose_dir }}/env"
- "{{ postgresql_service_postgres_compose_dir }}/config"
- "{{ postgresql_service_stunnel_compose_dir }}"
- "{{ postgresql_service_stunnel_compose_dir }}/conf"
- "{{ postgresql_service_postgres_data_path }}"
- "{{ postgresql_service_postgres_init_scripts_dir }}"
- name: Ensure shared Docker network exists for postgresql service
ansible.builtin.command: docker network inspect "{{ postgresql_service_shared_network }}"
changed_when: false
- name: Ensure postgres Docker network exists for postgresql service
ansible.builtin.command: docker network inspect "{{ postgresql_service_postgres_network }}"
register: postgresql_service_postgres_network_inspect
changed_when: false
failed_when: false
- name: Create postgres Docker network when missing
ansible.builtin.command: docker network create "{{ postgresql_service_postgres_network }}"
when: postgresql_service_postgres_network_inspect.rc != 0
- name: Check for managed postgres env file
ansible.builtin.stat:
path: "{{ postgresql_service_postgres_env_file }}"
register: postgresql_service_postgres_env_stat
- name: Check for legacy postgres env file
ansible.builtin.stat:
path: "{{ postgresql_service_postgres_legacy_env_file }}"
register: postgresql_service_postgres_legacy_env_stat
- name: Seed managed postgres env file from legacy deployment
ansible.builtin.copy:
src: "{{ postgresql_service_postgres_legacy_env_file }}"
dest: "{{ postgresql_service_postgres_env_file }}"
remote_src: true
owner: root
group: root
mode: "0600"
when:
- not postgresql_service_postgres_env_stat.stat.exists
- postgresql_service_postgres_legacy_env_stat.stat.exists
- name: Render managed postgres env file from defaults
ansible.builtin.template:
src: postgres.env.j2
dest: "{{ postgresql_service_postgres_env_file }}"
owner: root
group: root
mode: "0600"
when:
- not postgresql_service_postgres_env_stat.stat.exists
- not postgresql_service_postgres_legacy_env_stat.stat.exists
- name: Ensure managed postgres data path is present in env file
ansible.builtin.lineinfile:
path: "{{ postgresql_service_postgres_env_file }}"
regexp: '^PG_DATA_PATH='
line: "PG_DATA_PATH={{ postgresql_service_postgres_data_path }}"
state: present
- name: Ensure managed postgres local port is present in env file
ansible.builtin.lineinfile:
path: "{{ postgresql_service_postgres_env_file }}"
regexp: '^PG_LOCAL_PORT='
line: "PG_LOCAL_PORT={{ postgresql_service_postgres_port }}"
state: present
- name: Ensure managed postgres major tag is present in env file
ansible.builtin.lineinfile:
path: "{{ postgresql_service_postgres_env_file }}"
regexp: '^PG_MAJOR='
line: "PG_MAJOR={{ postgresql_service_postgres_major }}"
state: present
- name: Render managed postgresql.conf
ansible.builtin.template:
src: postgresql.conf.j2
dest: "{{ postgresql_service_postgres_config_file }}"
owner: root
group: root
mode: "0644"
- name: Render managed postgres compose file
ansible.builtin.template:
src: postgres-compose.yml.j2
dest: "{{ postgresql_service_postgres_compose_file }}"
owner: root
group: root
mode: "0644"
- name: Check stunnel certificate file
ansible.builtin.stat:
path: "{{ postgresql_service_stunnel_cert_file }}"
register: postgresql_service_stunnel_cert_stat
- name: Check stunnel key file
ansible.builtin.stat:
path: "{{ postgresql_service_stunnel_key_file }}"
register: postgresql_service_stunnel_key_stat
- name: Fail when stunnel certificate files are missing
ansible.builtin.fail:
msg: >-
stunnel certificate material is missing. Expected
{{ postgresql_service_stunnel_cert_file }} and {{ postgresql_service_stunnel_key_file }}.
when:
- not postgresql_service_stunnel_cert_stat.stat.exists or not postgresql_service_stunnel_key_stat.stat.exists
- name: Render managed stunnel config
ansible.builtin.template:
src: stunnel.conf.j2
dest: "{{ postgresql_service_stunnel_config_file }}"
owner: root
group: root
mode: "0644"
- name: Render managed stunnel compose file
ansible.builtin.template:
src: stunnel-compose.yml.j2
dest: "{{ postgresql_service_stunnel_compose_file }}"
owner: root
group: root
mode: "0644"
- name: Pull postgres image when enabled
ansible.builtin.command: docker compose -f "{{ postgresql_service_postgres_compose_file }}" pull postgres
args:
chdir: "{{ postgresql_service_postgres_compose_dir }}"
when: postgresql_service_postgres_pull_image | bool
- name: Remove existing postgres container before managed recreate
ansible.builtin.shell: |
set -euo pipefail
ids="$(docker ps -aq --filter name=^/{{ postgresql_service_postgres_container_name }}$)"
if [ -n "${ids}" ]; then
docker rm -f ${ids}
fi
args:
executable: /bin/bash
register: postgresql_service_postgres_cleanup
changed_when: postgresql_service_postgres_cleanup.stdout | trim != ""
- name: Start managed postgres compose target
ansible.builtin.command: docker compose -f "{{ postgresql_service_postgres_compose_file }}" up -d --force-recreate --remove-orphans
args:
chdir: "{{ postgresql_service_postgres_compose_dir }}"
- name: Wait for postgres container health
ansible.builtin.command: >-
docker inspect --format={{ '{{' }}if .State.Health{{ '}}' }}{{ '{{' }}.State.Health.Status{{ '}}' }}{{ '{{' }}else{{ '}}' }}unknown{{ '{{' }}end{{ '}}' }}
{{ postgresql_service_postgres_container_name }}
register: postgresql_service_postgres_health
changed_when: false
retries: "{{ postgresql_service_postgres_wait_retries }}"
delay: "{{ postgresql_service_postgres_wait_delay }}"
until: postgresql_service_postgres_health.stdout | trim == 'healthy'
- name: Pull stunnel image when enabled
ansible.builtin.command: docker compose -f "{{ postgresql_service_stunnel_compose_file }}" pull stunnel
args:
chdir: "{{ postgresql_service_stunnel_compose_dir }}"
when: postgresql_service_stunnel_pull_image | bool
- name: Remove existing stunnel container before managed recreate
ansible.builtin.shell: |
set -euo pipefail
ids="$(docker ps -aq --filter name=^/{{ postgresql_service_stunnel_container_name }}$)"
if [ -n "${ids}" ]; then
docker rm -f ${ids}
fi
args:
executable: /bin/bash
register: postgresql_service_stunnel_cleanup
changed_when: postgresql_service_stunnel_cleanup.stdout | trim != ""
- name: Start managed stunnel compose target
ansible.builtin.command: docker compose -f "{{ postgresql_service_stunnel_compose_file }}" up -d --force-recreate --remove-orphans
args:
chdir: "{{ postgresql_service_stunnel_compose_dir }}"

View File

@ -0,0 +1,26 @@
services:
postgres:
image: {{ postgresql_service_postgres_image_repo }}:{{ postgresql_service_postgres_image_tag }}
container_name: {{ postgresql_service_postgres_container_name }}
restart: unless-stopped
env_file:
- {{ postgresql_service_postgres_env_file }}
ports:
- "127.0.0.1:{{ postgresql_service_postgres_port }}:5432"
volumes:
- {{ postgresql_service_postgres_data_path }}:/var/lib/postgresql/data
- {{ postgresql_service_postgres_init_scripts_dir }}:/docker-entrypoint-initdb.d:ro
- {{ postgresql_service_postgres_config_file }}:/etc/postgresql/postgresql.conf:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER:-{{ postgresql_service_postgres_health_user }}} -h 127.0.0.1"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
networks:
- postgres_network
networks:
postgres_network:
external: true
name: {{ postgresql_service_postgres_network }}

View File

@ -0,0 +1,3 @@
{% for key, value in postgresql_service_postgres_env_defaults.items() %}
{{ key }}={{ value }}
{% endfor %}

View File

@ -0,0 +1,47 @@
# Managed by Ansible: roles/vhosts/postgresql_service
listen_addresses = '{{ postgresql_service_postgres_conf.listen_addresses }}'
port = {{ postgresql_service_postgres_conf.port }}
max_connections = {{ postgresql_service_postgres_conf.max_connections }}
superuser_reserved_connections = {{ postgresql_service_postgres_conf.superuser_reserved_connections }}
shared_buffers = '{{ postgresql_service_postgres_conf.shared_buffers }}'
effective_cache_size = '{{ postgresql_service_postgres_conf.effective_cache_size }}'
maintenance_work_mem = '{{ postgresql_service_postgres_conf.maintenance_work_mem }}'
work_mem = '{{ postgresql_service_postgres_conf.work_mem }}'
wal_buffers = '{{ postgresql_service_postgres_conf.wal_buffers }}'
min_wal_size = '{{ postgresql_service_postgres_conf.min_wal_size }}'
max_wal_size = '{{ postgresql_service_postgres_conf.max_wal_size }}'
checkpoint_completion_target = {{ postgresql_service_postgres_conf.checkpoint_completion_target }}
wal_compression = {{ postgresql_service_postgres_conf.wal_compression }}
random_page_cost = {{ postgresql_service_postgres_conf.random_page_cost }}
effective_io_concurrency = {{ postgresql_service_postgres_conf.effective_io_concurrency }}
default_statistics_target = {{ postgresql_service_postgres_conf.default_statistics_target }}
log_destination = '{{ postgresql_service_postgres_conf.log_destination }}'
logging_collector = {{ postgresql_service_postgres_conf.logging_collector }}
log_directory = '{{ postgresql_service_postgres_conf.log_directory }}'
log_filename = '{{ postgresql_service_postgres_conf.log_filename }}'
log_rotation_age = '{{ postgresql_service_postgres_conf.log_rotation_age }}'
log_rotation_size = '{{ postgresql_service_postgres_conf.log_rotation_size }}'
log_line_prefix = '{{ postgresql_service_postgres_conf.log_line_prefix }}'
log_timezone = '{{ postgresql_service_postgres_conf.log_timezone }}'
log_checkpoints = {{ postgresql_service_postgres_conf.log_checkpoints }}
log_connections = {{ postgresql_service_postgres_conf.log_connections }}
log_disconnections = {{ postgresql_service_postgres_conf.log_disconnections }}
log_duration = {{ postgresql_service_postgres_conf.log_duration }}
log_lock_waits = {{ postgresql_service_postgres_conf.log_lock_waits }}
log_statement = '{{ postgresql_service_postgres_conf.log_statement }}'
log_temp_files = {{ postgresql_service_postgres_conf.log_temp_files }}
log_min_duration_statement = {{ postgresql_service_postgres_conf.log_min_duration_statement }}
datestyle = '{{ postgresql_service_postgres_conf.datestyle }}'
timezone = '{{ postgresql_service_postgres_conf.timezone }}'
lc_messages = '{{ postgresql_service_postgres_conf.lc_messages }}'
lc_monetary = '{{ postgresql_service_postgres_conf.lc_monetary }}'
lc_numeric = '{{ postgresql_service_postgres_conf.lc_numeric }}'
lc_time = '{{ postgresql_service_postgres_conf.lc_time }}'
default_text_search_config = '{{ postgresql_service_postgres_conf.default_text_search_config }}'

View File

@ -0,0 +1,25 @@
services:
stunnel:
image: {{ postgresql_service_stunnel_image_repo }}:{{ postgresql_service_stunnel_image_tag }}
container_name: {{ postgresql_service_stunnel_container_name }}
user: root
restart: unless-stopped
ports:
- "{{ postgresql_service_stunnel_accept_port }}:{{ postgresql_service_stunnel_accept_port }}"
extra_hosts:
- host.docker.internal:host-gateway
volumes:
- {{ postgresql_service_stunnel_config_file }}:/etc/stunnel/stunnel.conf:ro
- {{ postgresql_service_stunnel_cert_file }}:/etc/stunnel/certs/server-cert.pem:ro
- {{ postgresql_service_stunnel_key_file }}:/etc/stunnel/certs/server-key.pem:ro
networks:
- shared_stunnel
- postgres_network
networks:
shared_stunnel:
external: true
name: {{ postgresql_service_shared_network }}
postgres_network:
external: true
name: {{ postgresql_service_postgres_network }}

View File

@ -0,0 +1,9 @@
foreground = yes
debug = 5
[{{ postgresql_service_stunnel_service_name }}]
accept = 0.0.0.0:{{ postgresql_service_stunnel_accept_port }}
connect = {{ postgresql_service_postgres_container_name }}:5432
verify = {{ postgresql_service_stunnel_verify_level }}
cert = /etc/stunnel/certs/server-cert.pem
key = /etc/stunnel/certs/server-key.pem