diff --git a/.config/make/docker.mak b/.config/make/docker.mak index 3975949e57..9b2b19fe14 100644 --- a/.config/make/docker.mak +++ b/.config/make/docker.mak @@ -5,8 +5,8 @@ DOCKER_REGISTRY ?= autobase # Sanitize the tag by replacing slashes with hyphens for Docker compatibility SANITIZED_TAG := $(subst /,-,$(TAG)) -.PHONY: docker-lint docker-lint-console-ui docker-lint-console-api docker-lint-console-db docker-lint-console -docker-lint: docker-lint-automation docker-lint-console-ui docker-lint-console-api docker-lint-console-db docker-lint-console ## Lint all Dockerfiles +.PHONY: docker-lint docker-lint-console-ui docker-lint-console-api docker-lint-console-db docker-lint-console docker-lint-patroni +docker-lint: docker-lint-automation docker-lint-console-ui docker-lint-console-api docker-lint-console-db docker-lint-console docker-lint-patroni ## Lint all Dockerfiles docker-lint-automation: ## Lint automation Dockerfile @echo "Lint automation container Dockerfile" @@ -33,8 +33,13 @@ docker-lint-console: ## Lint console Dockerfile (all services) docker run --rm -i -v $(PWD)/console/Dockerfile:/Dockerfile \ hadolint/hadolint hadolint --ignore DL3002 --ignore DL3008 --ignore DL3059 --ignore DL4001 /Dockerfile -.PHONY: docker-build docker-build-console-ui docker-build-console-api docker-build-console-db docker-build-console -docker-build: docker-build-automation docker-build-console-ui docker-build-console-api docker-build-console-db docker-build-console ## Build for all Docker images +docker-lint-patroni: ## Lint patroni Dockerfile + @echo "Lint Patroni container Dockerfile" + docker run --rm -i -v $(PWD)/docker/patroni/Dockerfile:/Dockerfile \ + hadolint/hadolint hadolint --ignore DL3002 --ignore DL3008 --ignore DL3059 --ignore DL4001 /Dockerfile + +.PHONY: docker-build docker-build-console-ui docker-build-console-api docker-build-console-db docker-build-console docker-build-patroni +docker-build: docker-build-automation docker-build-console-ui docker-build-console-api docker-build-console-db docker-build-console docker-build-patroni ## Build for all Docker images docker-build-automation: ## Build automation image @echo "Build automation docker image with tag $(TAG) (sanitized as $(SANITIZED_TAG))"; @@ -56,6 +61,10 @@ docker-build-console: ## Build console image (all services) @echo "Build console docker image with tag $(TAG) (sanitized as $(SANITIZED_TAG))" docker build --no-cache --platform linux/amd64 --tag console:$(SANITIZED_TAG) --file console/Dockerfile . +docker-build-patroni: ## Build patroni image + @echo "Build Patroni docker image with tag $(TAG) (sanitized as $(SANITIZED_TAG))" + docker build --no-cache --platform linux/amd64 --tag patroni:$(SANITIZED_TAG) --file docker/patroni/Dockerfile . + .PHONY: docker-push docker-push-console-ui docker-push-console-api docker-push-console-db docker-push-console docker-push: docker-push-automation docker-push-console-ui docker-push-console-api docker-push-console-db docker-push-console ## Push all images to Dockerhub (example: make docker-push TAG=my_tag DOCKER_REGISTRY=my_repo DOCKER_REGISTRY_USER="my_username" DOCKER_REGISTRY_PASSWORD="my_password") @@ -89,6 +98,12 @@ docker-push-console: ## Push console image to Dockerhub (all services) docker tag console:$(SANITIZED_TAG) $(DOCKER_REGISTRY)/console:$(SANITIZED_TAG) docker push $(DOCKER_REGISTRY)/console:$(SANITIZED_TAG) +docker-push-patroni: ## Push patroni image to Dockerhub + @echo "Push Patroni docker image with tag $(TAG) (sanitized as $(SANITIZED_TAG))" + echo "$(DOCKER_REGISTRY_PASSWORD)" | docker login --username "$(DOCKER_REGISTRY_USER)" --password-stdin + docker tag patroni:$(SANITIZED_TAG) $(DOCKER_REGISTRY)/patroni:$(SANITIZED_TAG) + docker push $(DOCKER_REGISTRY)/patroni:$(SANITIZED_TAG) + .PHONY: docker-tests docker-tests: ## Run tests for docker $(MAKE) docker-lint diff --git a/README.md b/README.md index 113427b8c7..921c7c294b 100644 --- a/README.md +++ b/README.md @@ -101,8 +101,6 @@ It requires the installation of a consul in client mode on each application serv ## Compatibility -RedHat and Debian based distros (x86_64) - ###### Supported Linux Distributions: - **Debian**: 11, 12, 13 @@ -112,6 +110,8 @@ RedHat and Debian based distros (x86_64) - **Rocky Linux**: 8, 9, 10 - **AlmaLinux**: 8, 9, 10 +Architecture: x86_64/amd64, arm64/aarch64 + ###### PostgreSQL versions: all supported PostgreSQL versions diff --git a/automation/Dockerfile b/automation/Dockerfile index 559ee498eb..a1f1138992 100644 --- a/automation/Dockerfile +++ b/automation/Dockerfile @@ -1,5 +1,5 @@ FROM debian:bookworm-slim -LABEL maintainer="Vitaliy Kukharik vitabaks@gmail.com" +LABEL maintainer="Autobase " USER root diff --git a/automation/playbooks/add_balancer.yml b/automation/playbooks/add_balancer.yml index d329bf1e70..2575632b7b 100644 --- a/automation/playbooks/add_balancer.yml +++ b/automation/playbooks/add_balancer.yml @@ -123,6 +123,12 @@ - role: vitabaks.autobase.hostname - role: vitabaks.autobase.resolv_conf + - role: vitabaks.autobase.docker + when: + - installation_method | default('packages') == "docker" + - docker_install | default(true) | bool + tags: docker, docker_install + - role: vitabaks.autobase.haproxy when: with_haproxy_load_balancing | default(false) | bool diff --git a/automation/playbooks/add_pgnode.yml b/automation/playbooks/add_pgnode.yml index 48bf6b8ab8..f1feb1d6db 100644 --- a/automation/playbooks/add_pgnode.yml +++ b/automation/playbooks/add_pgnode.yml @@ -166,6 +166,12 @@ - role: vitabaks.autobase.copy - role: vitabaks.autobase.cron + - role: vitabaks.autobase.docker + when: + - installation_method | default('packages') == "docker" + - docker_install | default(true) | bool + tags: docker, docker_install + - name: vitabaks.autobase.add_pgnode | Configure pgBackRest hosts: pgbackrest:postgres_cluster become: true @@ -205,7 +211,7 @@ when: pg_probackup_install | default(false) | bool - role: vitabaks.autobase.pgbouncer - when: pgbouncer_install | default(false) | bool + when: pgbouncer_install | default(true) | bool - role: vitabaks.autobase.pgpass diff --git a/automation/playbooks/deploy_pgcluster.yml b/automation/playbooks/deploy_pgcluster.yml index e0d0cada30..2eb3816821 100644 --- a/automation/playbooks/deploy_pgcluster.yml +++ b/automation/playbooks/deploy_pgcluster.yml @@ -108,6 +108,36 @@ - ansible_os_family == "Debian" - "'gnupg' not in ansible_facts.packages or 'apt-transport-https' not in ansible_facts.packages" + # Ansible requires the iproute package for network facts to be populated + - name: Make sure that the iproute is installed + ansible.builtin.package: + name: iproute + state: present + register: package_status + until: package_status is success + delay: 5 + retries: 3 + when: ansible_os_family == "RedHat" + + - name: Make sure that the iproute is installed + ansible.builtin.apt: + name: iproute2 + state: present + register: apt_status + until: apt_status is success + delay: 5 + retries: 3 + when: ansible_os_family == "Debian" + + - name: Make sure that the postgres user exists (for Docker mode) + ansible.builtin.user: + name: postgres + shell: /bin/bash + state: present + when: + - installation_method | default('packages') == "docker" + - inventory_hostname in groups['postgres_cluster'] + # (optional) Command or script to be executed before the Postgres cluster deployment. - block: - name: Print pre-deploy command @@ -186,6 +216,12 @@ tls_cert_regenerate: "{{ patroni_tls_cert_regenerate | default(false) }}" # Do not generate new certificates if they already exist. when: tls_cert_generate | default(true) | bool + - role: vitabaks.autobase.docker + when: + - installation_method | default('packages') == "docker" + - docker_install | default(true) | bool + tags: docker, docker_install + - name: vitabaks.autobase.deploy_pgcluster | Deploy etcd cluster ansible.builtin.import_playbook: etcd_cluster.yml when: not dcs_exists | default(false) | bool and dcs_type | default('etcd') == "etcd" @@ -312,7 +348,7 @@ - role: vitabaks.autobase.cron - role: vitabaks.autobase.pgbouncer - when: pgbouncer_install | default(false) | bool + when: pgbouncer_install | default(true) | bool - role: vitabaks.autobase.pgpass diff --git a/automation/roles/common/defaults/main.yml b/automation/roles/common/defaults/main.yml index 528d4e4a2a..4d42bc02e9 100644 --- a/automation/roles/common/defaults/main.yml +++ b/automation/roles/common/defaults/main.yml @@ -228,6 +228,7 @@ postgresql_data_dir: "\ {% else %}\ {{ postgresql_home_dir }}/{{ postgresql_version }}/{{ postgresql_cluster_name }}\ {% endif %}" +postgresql_base_dir: "{{ postgresql_data_dir | regex_replace('/$', '') | dirname | dirname }}" # You can specify custom WAL dir path. Example: "/pgwal/{{ postgresql_version }}/pg_wal" postgresql_wal_dir: "" # if defined, symlink will be created [optional] postgresql_conf_dir: "\ @@ -1139,10 +1140,10 @@ confd_package_repo: "https://github.com/kelseyhightower/confd/releases/download/ packages_from_file: [] # - ".{{ pkg_type }}" -# ---------------------------------------------------------------------------------------------------- +# ---------------------------------------------------------- # Redefine the installation method (optional) -# ---------------------------------------------------------------------------------------------------- -installation_method: "packages" # "packages" = install via deb/rpm packages; "docker" = use container images (TODO). +# ---------------------------------------------------------- +installation_method: "packages" # "packages" = install via deb/rpm packages; "docker" = use container images. # The Patroni package will be installed from the deb/rpm package by default. # You also have the option of choosing an installation method using the pip package. @@ -1166,6 +1167,100 @@ patroni_pip_requirements_repo: [] # - "https:///.tar.gz" pip_package_repo: "https://bootstrap.pypa.io/get-pip.py" # latest version pip3 for python3 (or use "pip-.tar.gz"). +############################################################ +# Docker containers (if installation_method: "docker") +############################################################ + +docker_install: true # Set to false if you prefer to install Docker manually. +docker_add_repo: true # Set to false if you prefer to manage the Docker repository yourself. + +postgresql_container: + name: "postgres" + image: "autobase/patroni:{{ postgresql_container.tag }}" # image with Patroni, PostgreSQL, extensions, and backup tools. + tag: "postgresql-{{ postgresql_version }}" # major or minor version, e.g. 17 or 17.5 + options: + - "--network host" + - "--shm-size=4g" + envs: + - "PGDATA={{ postgresql_data_dir }}" + volumes: + - "/etc/patroni:/etc/patroni" + - "{{ patroni_log_dir }}:{{ patroni_log_dir }}" + - "/etc/postgresql:/etc/postgresql" + - "{{ postgresql_base_dir }}:{{ postgresql_base_dir }}" + - "{{ postgresql_log_dir }}:{{ postgresql_log_dir }}" + - "{{ postgresql_unix_socket_dir }}:{{ postgresql_unix_socket_dir }}" + - "{{ tls_dir }}:{{ tls_dir }}" + run_command: | + bash -c '/usr/bin/patroni --version && \ + mkdir -p {{ postgresql_conf_dir }} {{ postgresql_data_dir }}; \ + chown -R postgres:postgres \ + {{ postgresql_conf_dir }} \ + {{ postgresql_data_dir }} \ + {{ postgresql_log_dir }} \ + {{ postgresql_unix_socket_dir }}; \ + exec su - postgres -c "/usr/bin/patroni /etc/patroni/patroni.yml"' + +# if dcs_type: "etcd" and dcs_exists: false +etcd_container: + name: "etcd" + image: "bitnami/etcd:{{ etcd_container.tag }}" + tag: "{{ etcd_version }}" + options: + - "--network host" + - "--env-file {{ etcd_conf_dir }}/etcd.conf" + envs: [] + volumes: + - "{{ etcd_data_dir }}:{{ etcd_data_dir }}" + - "{{ etcd_tls_dir }}:{{ etcd_tls_dir }}" + run_command: "" + +# if dcs_type: "consul" and dcs_exists: false +consul_container: + name: "consul" + image: "bitnami/consul:{{ consul_container.tag }}" + tag: "{{ consul_version }}" + options: + - "--network host" + envs: [] + volumes: + - "{{ consul_config_path }}:{{ consul_config_path }}" + - "{{ consul_configd_path }}:{{ consul_configd_path }}" + - "{{ consul_data_path }}:{{ consul_data_path }}" + - "{{ consul_run_path }}:{{ consul_run_path }}" + - "{{ consul_tls_dir }}:{{ consul_tls_dir }}" + run_command: | + consul agent \ + -config-file={{ consul_config_path }}/config.json \ + -config-dir={{ consul_configd_path }} \ + -pid-file={{ consul_run_path }}/consul.pid + +# if pgbouncer_install: true +pgbouncer_container: + name: "pgbouncer" + image: "bitnami/pgbouncer:{{ pgbouncer_container.tag }}" + tag: "{{ pgbouncer_version | default('latest') }}" + options: + - "--network host" + envs: [] + volumes: + - "{{ pgbouncer_conf_dir }}:{{ pgbouncer_conf_dir }}" + - "{{ pgbouncer_log_dir }}:{{ pgbouncer_log_dir }}" + - "{{ pgbouncer_tls_dir }}:{{ pgbouncer_tls_dir }}" + +# if with_haproxy_load_balancing: true +haproxy_container: + name: "haproxy" + image: "bitnami/haproxy:{{ haproxy_container.tag }}" + tag: "{{ haproxy_version | default('latest') }}" + options: + - "--network host" + envs: [] + volumes: + - "/etc/haproxy:/etc/haproxy" + - "/run/haproxy:/run/haproxy" + run_command: "haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /run/haproxy/haproxy.pid" + ############################################################ # Other variables ############################################################ diff --git a/automation/roles/consul/templates/consul_systemd-docker.service.j2 b/automation/roles/consul/templates/consul_systemd-docker.service.j2 new file mode 100644 index 0000000000..cd28b0cba2 --- /dev/null +++ b/automation/roles/consul/templates/consul_systemd-docker.service.j2 @@ -0,0 +1,37 @@ +[Unit] +Description=Consul docker wrapper +Requires=docker.service +After=docker.service + +[Service] +Type=simple +TimeoutSec=30 +RestartSec=15 +Restart=always + +# Delete consul container (if any) +ExecStartPre=-/usr/bin/docker rm -f {{ consul_container.name | default('consul') }} + +# Start consul container +ExecStart=/usr/bin/docker run -d \ + --name {{ consul_container.name | default('consul') }} \ + {% for option in consul_container.options | default([]) %} + {{ option }} \ + {% endfor %} + {% for env in consul_container.envs | default([]) %} + --env {{ env }} \ + {% endfor %} + {% for volume in consul_container.volumes | default([]) %} + --volume {{ volume }} \ + {% endfor %} + {{ consul_container.image | default('bitnami/consul:latest') }} {{ consul_container.run_command | default('') }} + +# Stop consul container +ExecStop=-/usr/bin/docker stop {{ consul_container.name | default('consul') }} +ExecStopPost=-/usr/bin/docker rm -f {{ consul_container.name | default('consul') }} + +# Send HUP to reload config +ExecReload=/usr/bin/docker exec {{ consul_container.name | default('consul') }} kill -SIGHUP 1 + +[Install] +WantedBy=multi-user.target diff --git a/automation/roles/docker/LICENSE b/automation/roles/docker/LICENSE new file mode 100644 index 0000000000..4275cf3c10 --- /dev/null +++ b/automation/roles/docker/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2017 Jeff Geerling + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/automation/roles/docker/README.md b/automation/roles/docker/README.md new file mode 100644 index 0000000000..c5ecae119e --- /dev/null +++ b/automation/roles/docker/README.md @@ -0,0 +1,138 @@ +# Ansible Role: Docker + +An Ansible Role that installs [Docker](https://www.docker.com) on Linux. Based on: [geerlingguy/ansible-role-docker](https://github.com/geerlingguy/ansible-role-docker) + +## Requirements + +None. + +## Role Variables + +Available variables are listed below, along with default values (see `defaults/main.yml`): + +```yaml +# Edition can be one of: 'ce' (Community Edition) or 'ee' (Enterprise Edition). +docker_edition: 'ce' +docker_packages: + - "docker-{{ docker_edition }}" + - "docker-{{ docker_edition }}-cli" + - "docker-{{ docker_edition }}-rootless-extras" +docker_packages_state: present +``` + +The `docker_edition` should be either `ce` (Community Edition) or `ee` (Enterprise Edition). +You can also specify a specific version of Docker to install using the distribution-specific format: +Red Hat/CentOS: `docker-{{ docker_edition }}-` (Note: you have to add this to all packages); +Debian/Ubuntu: `docker-{{ docker_edition }}=` (Note: you have to add this to all packages). + +You can control whether the package is installed, uninstalled, or at the latest version by setting `docker_packages_state` to `present`, `absent`, or `latest`, respectively. Note that the Docker daemon will be automatically restarted if the Docker package is updated. This is a side effect of flushing all handlers (running any of the handlers that have been notified by this and any other role up to this point in the play). + +```yaml +docker_obsolete_packages: + - docker + - docker.io + - docker-engine + - docker-doc + - docker-compose + - docker-compose-v2 + - podman-docker + - containerd + - runc +``` + +`docker_obsolete_packages` for different os-family: + +- [`RedHat.yaml`](./vars/RedHat.yml) +- [`Debian.yaml`](./vars/Debian.yml) + +A list of packages to be uninstalled prior to running this role. See [Docker's installation instructions](https://docs.docker.com/engine/install/debian/#uninstall-old-versions) for an up-to-date list of old packages that should be removed. + +```yaml +docker_service_manage: true +docker_service_state: started +docker_service_enabled: true +docker_restart_handler_state: restarted +``` + +Variables to control the state of the `docker` service, and whether it should start on boot. If you're installing Docker inside a Docker container without systemd or sysvinit, you should set `docker_service_manage` to `false`. + +```yaml +docker_install_compose_plugin: true +docker_compose_package: docker-compose-plugin +docker_compose_package_state: present +``` + +Docker Compose Plugin installation options. These differ from the below in that docker-compose is installed as a docker plugin (and used with `docker compose`) instead of a standalone binary. + +```yaml +docker_install_compose: false +docker_compose_version: "v2.32.1" +docker_compose_arch: "{{ ansible_architecture }}" +docker_compose_url: "https://github.com/docker/compose/releases/download/{{ docker_compose_version }}/docker-compose-linux-{{ docker_compose_arch }}" +docker_compose_path: /usr/local/bin/docker-compose +``` + +Docker Compose installation options. + +```yaml +docker_add_repo: true +``` + +Controls whether this role will add the official Docker repository. Set to `false` if you want to use the default docker packages for your system or manage the package repository on your own. + +```yaml +docker_repo_url: https://download.docker.com/linux +``` + +The main Docker repo URL, common between Debian and RHEL systems. + +```yaml +docker_apt_release_channel: stable +docker_apt_arch: "{{ 'arm64' if ansible_architecture == 'aarch64' else 'amd64' }}" +docker_apt_repository: "deb [arch={{ docker_apt_arch }}{{' signed-by=/etc/apt/keyrings/docker.asc' if add_repository_key is not failed}}] {{ docker_repo_url }}/{{ ansible_distribution | lower }} {{ ansible_distribution_release }} {{ docker_apt_release_channel }}" +docker_apt_ignore_key_error: True +docker_apt_gpg_key: "{{ docker_repo_url }}/{{ ansible_distribution | lower }}/gpg" +docker_apt_filename: "docker" +``` + +(Used only for Debian/Ubuntu.) You can switch the channel to `nightly` if you want to use the Nightly release. + +You can change `docker_apt_gpg_key` to a different url if you are behind a firewall or provide a trustworthy mirror. +Usually in combination with changing `docker_apt_repository` as well. `docker_apt_filename` controls the name of the source list file created in `sources.list.d`. If you are upgrading from an older (<7.0.0) version of this role, you should change this to the name of the existing file (e.g. `download_docker_com_linux_debian` on Debian) to avoid conflicting lists. + +```yaml +docker_yum_repo_url: "{{ docker_repo_url }}/{{ (ansible_distribution == 'Fedora') | ternary('fedora', 'centos') }}/docker-{{ docker_edition }}.repo" +docker_yum_repo_enable_nightly: '0' +docker_yum_repo_enable_test: '0' +docker_yum_gpg_key: "{{ docker_repo_url }}/centos/gpg" +``` + +(Used only for RedHat/CentOS.) You can enable the Nightly or Test repo by setting the respective vars to `1`. + +You can change `docker_yum_gpg_key` to a different url if you are behind a firewall or provide a trustworthy mirror. +Usually in combination with changing `docker_yum_repository` as well. + +```yaml +docker_users: + - user1 + - user2 +``` + +A list of system users to be added to the `docker` group (so they can use Docker on the server). + +```yaml +docker_daemon_options: + storage-driver: "overlay2" + log-opts: + max-size: "100m" +``` + +Custom `dockerd` options can be configured through this dictionary representing the json file `/etc/docker/daemon.json`. + +## License + +MIT / BSD + +## Author Information + +This role was created in 2017 by [Jeff Geerling](https://www.jeffgeerling.com/), author of [Ansible for DevOps](https://www.ansiblefordevops.com/). diff --git a/automation/roles/docker/defaults/main.yml b/automation/roles/docker/defaults/main.yml new file mode 100644 index 0000000000..aa75fe3699 --- /dev/null +++ b/automation/roles/docker/defaults/main.yml @@ -0,0 +1,69 @@ +# yamllint disable rule:line-length +--- +# Edition can be one of: 'ce' (Community Edition) or 'ee' (Enterprise Edition). +docker_edition: 'ce' +docker_packages: + - "docker-{{ docker_edition }}" + - "docker-{{ docker_edition }}-cli" + - "docker-{{ docker_edition }}-rootless-extras" + - "containerd.io" + - docker-buildx-plugin +docker_packages_state: present +docker_obsolete_packages: + - docker + - docker.io + - docker-engine + - docker-doc + - docker-compose + - docker-compose-v2 + - podman-docker + - containerd + - runc + +# Service options. +docker_service_manage: true +docker_service_state: started +docker_service_enabled: true +docker_restart_handler_state: restarted + +# Docker Compose Plugin options. +docker_install_compose_plugin: true +docker_compose_package: docker-compose-plugin +docker_compose_package_state: present + +# Docker Compose options. +docker_install_compose: false +docker_compose_version: "v2.32.1" +docker_compose_arch: "{{ ansible_architecture }}" +docker_compose_url: "https://github.com/docker/compose/releases/download/{{ docker_compose_version }}/docker-compose-linux-{{ docker_compose_arch }}" +docker_compose_path: /usr/local/bin/docker-compose + +# Enable repo setup +# docker_add_repo: true # Defined in roles/common/defaults/main.yml. Commented out here to prevent conflicts. + +# Docker repo URL. +docker_repo_url: https://download.docker.com/linux + +# Used only for Debian/Ubuntu/Pop!_OS/Linux Mint. Switch 'stable' to 'nightly' if needed. +docker_apt_release_channel: stable +# docker_apt_ansible_distribution is a workaround for Ubuntu variants which can't be identified as such by Ansible, +# and is only necessary until Docker officially supports them. +docker_apt_ansible_distribution: "{{ 'ubuntu' if ansible_distribution in ['Pop!_OS', 'Linux Mint'] else ansible_distribution }}" +docker_apt_arch: "{{ 'arm64' if ansible_architecture == 'aarch64' else 'armhf' if ansible_architecture == 'armv7l' else 'amd64' }}" +docker_apt_repository: "deb [arch={{ docker_apt_arch }} signed-by=/etc/apt/keyrings/docker.asc] {{ docker_repo_url }}/{{ docker_apt_ansible_distribution | lower }} {{ ansible_distribution_release }} {{ docker_apt_release_channel }}" +docker_apt_ignore_key_error: true +docker_apt_gpg_key: "{{ docker_repo_url }}/{{ docker_apt_ansible_distribution | lower }}/gpg" +docker_apt_gpg_key_checksum: "sha256:1500c1f56fa9e26b9b8f42452a553675796ade0807cdce11975eb98170b3a570" +docker_apt_filename: "docker" + +# Used only for RedHat/CentOS/Fedora. +docker_yum_repo_url: "{{ docker_repo_url }}/{{ (ansible_distribution == 'Fedora') | ternary('fedora', 'centos') }}/docker-{{ docker_edition }}.repo" +docker_yum_repo_enable_nightly: '0' +docker_yum_repo_enable_test: '0' +docker_yum_gpg_key: "{{ docker_repo_url }}/centos/gpg" + +# A list of users who will be added to the docker group. +docker_users: [] + +# Docker daemon options as a dict +docker_daemon_options: {} diff --git a/automation/roles/docker/handlers/main.yml b/automation/roles/docker/handlers/main.yml new file mode 100644 index 0000000000..ef0c7437e1 --- /dev/null +++ b/automation/roles/docker/handlers/main.yml @@ -0,0 +1,7 @@ +--- +- name: restart docker + ansible.builtin.service: + name: docker + state: "{{ docker_restart_handler_state }}" + ignore_errors: "{{ ansible_check_mode }}" + when: docker_service_manage | bool diff --git a/automation/roles/docker/tasks/docker-compose.yml b/automation/roles/docker/tasks/docker-compose.yml new file mode 100644 index 0000000000..b9d062e951 --- /dev/null +++ b/automation/roles/docker/tasks/docker-compose.yml @@ -0,0 +1,31 @@ +--- +- name: Check current docker-compose version. + ansible.builtin.command: "{{ docker_compose_path }} --version" + register: docker_compose_vsn + check_mode: false + changed_when: false + failed_when: false + +- ansible.builtin.set_fact: + docker_compose_current_version: "{{ docker_compose_vsn.stdout | regex_search('(\\d+(\\.\\d+)+)') }}" + when: > + docker_compose_vsn.stdout is defined + and (docker_compose_vsn.stdout | length > 0) + +- name: Delete existing docker-compose version if it's different. + ansible.builtin.file: + path: "{{ docker_compose_path }}" + state: absent + when: > + docker_compose_current_version is defined + and (docker_compose_version | regex_replace('v', '')) not in docker_compose_current_version + +- name: Install Docker Compose (if configured). + ansible.builtin.get_url: + url: "{{ docker_compose_url }}" + dest: "{{ docker_compose_path }}" + mode: "0755" + when: > + (docker_compose_current_version is not defined) + or (docker_compose_current_version | length == 0) + or (docker_compose_current_version is version((docker_compose_version | regex_replace('v', '')), '<')) diff --git a/automation/roles/docker/tasks/docker-users.yml b/automation/roles/docker/tasks/docker-users.yml new file mode 100644 index 0000000000..61f416327c --- /dev/null +++ b/automation/roles/docker/tasks/docker-users.yml @@ -0,0 +1,10 @@ +--- +- name: Ensure docker users are added to the docker group. + ansible.builtin.user: + name: "{{ item }}" + groups: docker + append: true + with_items: "{{ docker_users }}" + +- name: Reset ssh connection to apply user changes. + ansible.builtin.meta: reset_connection diff --git a/automation/roles/docker/tasks/main.yml b/automation/roles/docker/tasks/main.yml new file mode 100644 index 0000000000..da4d00b6c4 --- /dev/null +++ b/automation/roles/docker/tasks/main.yml @@ -0,0 +1,106 @@ +--- +- name: Load OS-specific vars. + ansible.builtin.include_vars: "{{ lookup('first_found', params) }}" + vars: + params: + files: + - '{{ ansible_distribution }}.yml' + - '{{ ansible_os_family }}.yml' + - main.yml + paths: + - 'vars' + +- ansible.builtin.include_tasks: setup-RedHat.yml + when: ansible_os_family == 'RedHat' + +- ansible.builtin.include_tasks: setup-Debian.yml + when: ansible_os_family == 'Debian' + +- name: Install Docker packages. + ansible.builtin.package: + name: "{{ docker_packages }}" + state: "{{ docker_packages_state }}" + notify: restart docker + ignore_errors: "{{ ansible_check_mode }}" + when: ansible_version.full is version_compare('2.12', '<') or + ansible_os_family not in ['RedHat', 'Debian'] + +- name: Install Docker packages (with downgrade option). + ansible.builtin.package: + name: "{{ docker_packages }}" + state: "{{ docker_packages_state }}" + allow_downgrade: true + notify: restart docker + ignore_errors: "{{ ansible_check_mode }}" + when: + - ansible_version.full is version_compare('2.12', '>=') + - ansible_os_family in ['RedHat', 'Debian'] + +- name: Install docker-compose plugin. + ansible.builtin.package: + name: "{{ docker_compose_package }}" + state: "{{ docker_compose_package_state }}" + notify: restart docker + ignore_errors: "{{ ansible_check_mode }}" + when: + - docker_install_compose_plugin | bool + - ansible_version.full is version_compare('2.12', '<') or ansible_os_family not in ['RedHat', 'Debian'] + +- name: Install docker-compose-plugin (with downgrade option). + ansible.builtin.package: + name: "{{ docker_compose_package }}" + state: "{{ docker_compose_package_state }}" + allow_downgrade: true + notify: restart docker + ignore_errors: "{{ ansible_check_mode }}" + when: + - docker_install_compose_plugin | bool + - ansible_version.full is version_compare('2.12', '>=') + - ansible_os_family in ['RedHat', 'Debian'] + +- name: Ensure /etc/docker/ directory exists. + ansible.builtin.file: + path: /etc/docker + state: directory + mode: "0755" + when: docker_daemon_options.keys() | length > 0 + +- name: Configure Docker daemon options. + ansible.builtin.copy: + content: "{{ docker_daemon_options | to_nice_json }}" + dest: /etc/docker/daemon.json + mode: "0644" + when: docker_daemon_options.keys() | length > 0 + notify: restart docker + +- name: Ensure Docker is started and enabled at boot. + ansible.builtin.service: + name: docker + state: "{{ docker_service_state }}" + enabled: "{{ docker_service_enabled }}" + ignore_errors: "{{ ansible_check_mode }}" + when: docker_service_manage | bool + +- name: Ensure handlers are notified now to avoid firewall conflicts. + ansible.builtin.meta: flush_handlers + +- ansible.builtin.include_tasks: docker-compose.yml + when: docker_install_compose | bool + +- name: Get docker group info using getent. + ansible.builtin.getent: + database: group + key: docker + split: ':' + when: docker_users | length > 0 + +- name: Check if there are any users to add to the docker group. + ansible.builtin.set_fact: + at_least_one_user_to_modify: true + when: + - docker_users | length > 0 + - item not in ansible_facts.getent_group["docker"][2] + with_items: "{{ docker_users }}" + +- ansible.builtin.include_tasks: docker-users.yml + when: at_least_one_user_to_modify is defined diff --git a/automation/roles/docker/tasks/setup-Debian.yml b/automation/roles/docker/tasks/setup-Debian.yml new file mode 100644 index 0000000000..c3faa2eabf --- /dev/null +++ b/automation/roles/docker/tasks/setup-Debian.yml @@ -0,0 +1,69 @@ +--- +- name: Ensure apt key is not present in trusted.gpg.d + ansible.builtin.file: + path: /etc/apt/trusted.gpg.d/docker.asc + state: absent + +- name: Ensure old apt source list is not present in /etc/apt/sources.list.d + ansible.builtin.file: + path: "/etc/apt/sources.list.d/download_docker_com_linux_{{ docker_apt_ansible_distribution | lower }}.list" + state: absent + +- name: Ensure the repo referencing the previous trusted.gpg.d key is not present + ansible.builtin.apt_repository: + repo: "deb [arch={{ docker_apt_arch }} signed-by=/etc/apt/trusted.gpg.d/docker.asc] {{ docker_repo_url }}/{{ docker_apt_ansible_distribution | lower }} {{ ansible_distribution_release }} {{ docker_apt_release_channel }}" # yamllint disable rule:line-length + state: absent + filename: "{{ docker_apt_filename }}" + update_cache: true + when: docker_add_repo | bool + +- # See https://docs.docker.com/engine/install/debian/#uninstall-old-versions + name: Ensure old versions of Docker are not installed. + ansible.builtin.package: + name: "{{ docker_obsolete_packages }}" + state: absent + +- name: Ensure dependencies are installed. + ansible.builtin.apt: + name: + - apt-transport-https + - ca-certificates + state: present + when: docker_add_repo | bool + +- name: Ensure directory exists for /etc/apt/keyrings + ansible.builtin.file: + path: /etc/apt/keyrings + state: directory + mode: "0755" + +- name: Add Docker apt key. + ansible.builtin.get_url: + url: "{{ docker_apt_gpg_key }}" + dest: /etc/apt/keyrings/docker.asc + mode: "0644" + force: false + checksum: "{{ docker_apt_gpg_key_checksum | default(omit) }}" + register: add_repository_key + ignore_errors: "{{ docker_apt_ignore_key_error }}" + when: docker_add_repo | bool + +- name: Ensure curl is present (on older systems without SNI). + ansible.builtin.package: + name: curl + state: present + when: add_repository_key is failed and docker_add_repo | bool + +- name: Add Docker apt key (alternative for older systems without SNI). + ansible.builtin.shell: | + set -o pipefail + curl -sSL {{ docker_apt_gpg_key }} | apt-key add - + when: add_repository_key is failed and docker_add_repo | bool + +- name: Add Docker repository. + ansible.builtin.apt_repository: + repo: "{{ docker_apt_repository }}" + state: present + filename: "{{ docker_apt_filename }}" + update_cache: true + when: docker_add_repo | bool diff --git a/automation/roles/docker/tasks/setup-RedHat.yml b/automation/roles/docker/tasks/setup-RedHat.yml new file mode 100644 index 0000000000..34a1022139 --- /dev/null +++ b/automation/roles/docker/tasks/setup-RedHat.yml @@ -0,0 +1,58 @@ +--- +- name: Ensure old versions of Docker are not installed. + ansible.builtin.package: + name: "{{ docker_obsolete_packages }}" + state: absent + +- name: Add Docker GPG key. + ansible.builtin.rpm_key: + key: "{{ docker_yum_gpg_key }}" + state: present + when: docker_add_repo | bool + +- name: Add Docker repository. + ansible.builtin.get_url: + url: "{{ docker_yum_repo_url }}" + dest: '/etc/yum.repos.d/docker-{{ docker_edition }}.repo' + owner: root + group: root + mode: "0644" + when: docker_add_repo | bool + +- name: Configure Docker Nightly repo. + community.general.ini_file: + dest: '/etc/yum.repos.d/docker-{{ docker_edition }}.repo' + section: 'docker-{{ docker_edition }}-nightly' + option: enabled + value: '{{ docker_yum_repo_enable_nightly }}' + mode: "0644" + no_extra_spaces: true + when: docker_add_repo | bool + +- name: Configure Docker Test repo. + community.general.ini_file: + dest: '/etc/yum.repos.d/docker-{{ docker_edition }}.repo' + section: 'docker-{{ docker_edition }}-test' + option: enabled + value: '{{ docker_yum_repo_enable_test }}' + mode: "0644" + no_extra_spaces: true + when: docker_add_repo | bool + +- name: Configure containerd on RHEL 8. + block: + - name: Ensure runc is not installed. + ansible.builtin.package: + name: runc + state: absent + + - name: Ensure container-selinux is installed. + ansible.builtin.package: + name: container-selinux + state: present + + - name: Ensure containerd.io is installed. + ansible.builtin.package: + name: containerd.io + state: present + when: ansible_distribution_major_version | int == 8 diff --git a/automation/roles/docker/vars/Alpine.yml b/automation/roles/docker/vars/Alpine.yml new file mode 100755 index 0000000000..5fea58db6b --- /dev/null +++ b/automation/roles/docker/vars/Alpine.yml @@ -0,0 +1,3 @@ +--- +docker_packages: "docker" +docker_compose_package: docker-cli-compose diff --git a/automation/roles/docker/vars/Archlinux.yml b/automation/roles/docker/vars/Archlinux.yml new file mode 100644 index 0000000000..f68d962a7f --- /dev/null +++ b/automation/roles/docker/vars/Archlinux.yml @@ -0,0 +1,3 @@ +--- +docker_packages: "docker" +docker_compose_package: docker-compose diff --git a/automation/roles/docker/vars/Debian.yml b/automation/roles/docker/vars/Debian.yml new file mode 100644 index 0000000000..9f90a11e74 --- /dev/null +++ b/automation/roles/docker/vars/Debian.yml @@ -0,0 +1,14 @@ +--- +# Used only for Debian/Ubuntu (Debian OS-Family) +# https://docs.docker.com/engine/install/debian/#uninstall-old-versions + +docker_obsolete_packages: + - docker + - docker.io + - docker-engine + - docker-doc + - docker-compose + - docker-compose-v2 + - podman-docker + - containerd + - runc diff --git a/automation/roles/docker/vars/RedHat.yml b/automation/roles/docker/vars/RedHat.yml new file mode 100644 index 0000000000..d8063fb0a4 --- /dev/null +++ b/automation/roles/docker/vars/RedHat.yml @@ -0,0 +1,14 @@ +--- +# Used only for Fedora/Rocky (RedHat OS-Family) +# https://docs.docker.com/engine/install/fedora/#uninstall-old-versions +# https://docs.docker.com/engine/install/centos/#uninstall-old-versions + +docker_obsolete_packages: + - docker + - docker-client + - docker-client-latest + - docker-common + - docker-latest + - docker-latest-logrotate + - docker-logrotate + - docker-engine diff --git a/automation/roles/docker/vars/main.yml b/automation/roles/docker/vars/main.yml new file mode 100755 index 0000000000..805232bfe7 --- /dev/null +++ b/automation/roles/docker/vars/main.yml @@ -0,0 +1,2 @@ +--- +# Empty file diff --git a/automation/roles/etcd/templates/etcd-docker.service.j2 b/automation/roles/etcd/templates/etcd-docker.service.j2 new file mode 100644 index 0000000000..7dd2f44f4f --- /dev/null +++ b/automation/roles/etcd/templates/etcd-docker.service.j2 @@ -0,0 +1,33 @@ +[Unit] +Description=etcd docker wrapper +Requires=docker.service +After=docker.service + +[Service] +Type=simple +TimeoutSec=30 +RestartSec=15 +Restart=always + +# Delete etcd container (if any) +ExecStartPre=-/usr/bin/docker rm -f {{ etcd_container.name | default('etcd') }} + +# Start etcd container +ExecStart=/usr/bin/docker run -d --name {{ etcd_container.name | default('etcd') }} \ + {% for option in etcd_container.options | default([]) %} + {{ option }} \ + {% endfor %} + {% for env in etcd_container.envs | default([]) %} + --env {{ env }} \ + {% endfor %} + {% for volume in etcd_container.volumes | default([]) %} + --volume {{ volume }} \ + {% endfor %} + {{ etcd_container.image | default('bitnami/etcd:3.5') }} {{ etcd_container.run_command | default('') }} + +# Stop etcd container +ExecStop=-/usr/bin/docker stop {{ etcd_container.name | default('etcd') }} +ExecStopPost=-/usr/bin/docker rm -f {{ etcd_container.name | default('etcd') }} + +[Install] +WantedBy=multi-user.target diff --git a/automation/roles/haproxy/templates/haproxy-docker.service.j2 b/automation/roles/haproxy/templates/haproxy-docker.service.j2 new file mode 100644 index 0000000000..d91efaebfe --- /dev/null +++ b/automation/roles/haproxy/templates/haproxy-docker.service.j2 @@ -0,0 +1,36 @@ +[Unit] +Description=HAProxy docker wrapper +Requires=docker.service +After=docker.service + +[Service] +Type=simple +TimeoutSec=30 +RestartSec=15 +Restart=always + +# Delete HAProxy container (if any) +ExecStartPre=-/usr/bin/docker rm -f {{ haproxy_container.name | default('haproxy') }} + +# Start HAProxy container +ExecStart=/usr/bin/docker run -d --name {{ haproxy_container.name | default('haproxy') }} \ + {% for option in haproxy_container.options | default([]) %} + {{ option }} \ + {% endfor %} + {% for env in haproxy_container.envs | default([]) %} + --env {{ env }} \ + {% endfor %} + {% for volume in haproxy_container.volumes | default([]) %} + --volume {{ volume }} \ + {% endfor %} + {{ haproxy_container.image | default('bitnami/haproxy:latest') }} {{ haproxy_container.run_command | default('') }} + +# Stop HAProxy container +ExecStop=-/usr/bin/docker stop {{ haproxy_container.name | default('haproxy') }} +ExecStopPost=-/usr/bin/docker rm -f {{ haproxy_container.name | default('haproxy') }} + +# Send HUP to reload config +ExecReload=/usr/bin/docker exec {{ haproxy_container.name | default('haproxy') }} kill -USR2 1 + +[Install] +WantedBy=multi-user.target diff --git a/automation/roles/patroni/tasks/docker.yml b/automation/roles/patroni/tasks/docker.yml new file mode 100644 index 0000000000..ae8b98873d --- /dev/null +++ b/automation/roles/patroni/tasks/docker.yml @@ -0,0 +1,53 @@ +--- +# if installation_method: "docker" + +- name: "Pull patroni image: {{ docker_image_name }}" + community.docker.docker_image_pull: + name: "{{ docker_image_name }}" + vars: + docker_image_name: "{{ postgresql_container.image | default('autobase/patroni:postgresql-' ~ postgresql_version) }}" + environment: "{{ proxy_env | default({}) }}" + +# CLI wrappers +- name: Create docker wrapper scripts for postgresql + ansible.builtin.copy: + # These wrapper scripts are intended to ensure compatibility of Ansible roles + # with both package-based and Docker-based PostgreSQL setups. + # This is not a full list of PostgreSQL tools — only commonly used ones. + content: | + #!/bin/bash + docker exec -it {{ postgresql_container.name | default('patroni') }} \ + su - postgres -c '{{ item }} "$@"' + dest: "/usr/local/bin/{{ item }}" + owner: root + group: root + mode: "0755" + loop: "{{ postgresql_wrappers | default(default_postgresql_wrappers) }}" # Allows overriding the list via postgresql_wrappers. + loop_control: + label: "/usr/local/bin/{{ item }}" + vars: + default_postgresql_wrappers: + - psql + - pg_ctl + - pg_isready + - pg_dump + - pg_dumpall + - pg_lsclusters + - pg_upgrade + - pg_controldata + - initdb + - vacuumdb + - pgbench + tags: docker, docker_wrappers + +- name: Create Docker wrapper script for patronictl + ansible.builtin.copy: + content: | + #!/bin/bash + docker exec -e PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml -it {{ postgresql_container.name | default('patroni') }} \ + su - postgres -c 'patronictl "$@"' + dest: /usr/local/bin/patronictl + owner: root + group: root + mode: "0755" + tags: docker, docker_wrappers diff --git a/automation/roles/patroni/tasks/install.yml b/automation/roles/patroni/tasks/install.yml new file mode 100644 index 0000000000..954cd510a7 --- /dev/null +++ b/automation/roles/patroni/tasks/install.yml @@ -0,0 +1,191 @@ +--- +# pip install +- ansible.builtin.import_tasks: pip.yml + vars: + pip_package: "python{{ python_version | default('3') }}-pip" + when: + - installation_method == "packages" + - patroni_installation_method == "pip" + - pip_package not in system_packages + tags: patroni, patroni_install, pip + +# Patroni install +- block: # installation_method: "packages" and patroni_installation_method: "pip" + - name: Install setuptools + ansible.builtin.pip: + name: setuptools<66.0.0 # https://github.com/pypa/setuptools/issues/3772 + state: latest + executable: pip3 + extra_args: "--trusted-host=pypi.python.org --trusted-host=pypi.org --trusted-host=files.pythonhosted.org" + umask: "0022" + environment: + PATH: "{{ ansible_env.PATH }}:/usr/local/bin:/usr/bin" + PIP_BREAK_SYSTEM_PACKAGES: "1" + when: patroni_pip_requirements_repo | length < 1 + + # Use master requirements if patroni_install_version is 'latest' or explicitly forced via patroni_latest_requirements + - name: Download Patroni requirements.txt + ansible.builtin.get_url: + url: >- + https://raw.githubusercontent.com/patroni/patroni/{{ + 'refs/heads/master' if patroni_install_version == 'latest' or patroni_latest_requirements | default(false) + else 'refs/tags/v' ~ (patroni_install_version | regex_replace('^v', '')) + }}/requirements.txt + dest: /tmp/requirements.txt + timeout: 60 + validate_certs: false + when: patroni_pip_requirements_repo | length < 1 + + - name: Install requirements + ansible.builtin.pip: + requirements: /tmp/requirements.txt + executable: pip3 + extra_args: "--trusted-host=pypi.python.org --trusted-host=pypi.org --trusted-host=files.pythonhosted.org" + umask: "0022" + environment: + PATH: "{{ ansible_env.PATH }}:{{ postgresql_bin_dir }}:/usr/local/bin:/usr/bin" + PIP_BREAK_SYSTEM_PACKAGES: "1" + when: patroni_pip_requirements_repo | length < 1 + + - name: Install patroni + ansible.builtin.pip: + name: patroni + state: latest + executable: pip3 + extra_args: "--trusted-host=pypi.python.org --trusted-host=pypi.org --trusted-host=files.pythonhosted.org" + umask: "0022" + environment: + PATH: "{{ ansible_env.PATH }}:/usr/local/bin:/usr/bin" + PIP_BREAK_SYSTEM_PACKAGES: "1" + when: patroni_pip_package_repo | length < 1 and patroni_install_version == "latest" + + - name: "Install patroni {{ patroni_install_version }}" + ansible.builtin.pip: + name: "patroni=={{ patroni_install_version }}" + executable: pip3 + extra_args: "--trusted-host=pypi.python.org --trusted-host=pypi.org --trusted-host=files.pythonhosted.org" + umask: "0022" + environment: + PATH: "{{ ansible_env.PATH }}:/usr/local/bin:/usr/bin" + PIP_BREAK_SYSTEM_PACKAGES: "1" + when: patroni_pip_package_repo | length < 1 and patroni_install_version != "latest" + when: installation_method == "packages" and patroni_installation_method == "pip" + environment: "{{ proxy_env | default({}) }}" + tags: patroni, patroni_install + +- block: # when "patroni_pip_requirements_repo" and "patroni_pip_package_repo" is defined + - name: Download patroni requirements + ansible.builtin.get_url: + url: "{{ item }}" + dest: /tmp/ + timeout: 120 + validate_certs: false + loop: "{{ patroni_pip_requirements_repo }}" + when: patroni_pip_requirements_repo | length > 0 + + - name: Download patroni package + ansible.builtin.get_url: + url: "{{ item }}" + dest: /tmp/ + timeout: 60 + validate_certs: false + loop: "{{ patroni_pip_package_repo | list }}" + when: patroni_pip_package_repo | length > 0 + + - name: Install requirements + ansible.builtin.pip: + name: "file:///tmp/{{ item }}" + executable: pip3 + extra_args: "--no-index --find-links=file:///tmp --ignore-installed" + umask: "0022" + loop: "{{ patroni_pip_requirements_repo | map('basename') | list }}" + environment: + PATH: "{{ ansible_env.PATH }}:/usr/local/bin:/usr/bin" + PIP_BREAK_SYSTEM_PACKAGES: "1" + when: patroni_pip_requirements_repo | length > 0 + + - name: Install patroni + ansible.builtin.pip: + name: "file:///tmp/{{ item }}" + executable: pip3 + extra_args: "--no-index --find-links=file:///tmp --ignore-installed" + umask: "0022" + loop: "{{ patroni_pip_package_repo | map('basename') | list }}" + environment: + PATH: "{{ ansible_env.PATH }}:/usr/local/bin:/usr/bin" + PIP_BREAK_SYSTEM_PACKAGES: "1" + when: patroni_pip_package_repo | length > 0 + when: installation_method == "packages" and patroni_installation_method == "pip" + tags: patroni, patroni_install + +- block: # installation_method: "packages" and patroni_installation_method: "rpm/deb" + # Debian + - name: Install patroni package + ansible.builtin.apt: + name: "{{ patroni_packages | flatten }}" + state: present + register: apt_status + until: apt_status is success + delay: 5 + retries: 3 + when: ansible_os_family == "Debian" and patroni_deb_package_repo | length < 1 + + # RedHat + - name: Install patroni package + ansible.builtin.dnf: + name: "{{ patroni_packages | flatten }}" + state: present + disable_gpg_check: true + register: dnf_status + until: dnf_status is success + delay: 5 + retries: 3 + when: ansible_os_family == "RedHat" and patroni_rpm_package_repo | length < 1 + + # when patroni_deb_package_repo or patroni_rpm_package_repo URL is defined + # Debian + - name: Download patroni deb package + ansible.builtin.get_url: + url: "{{ item }}" + dest: /tmp/ + timeout: 60 + validate_certs: false + loop: "{{ patroni_deb_package_repo | list }}" + when: ansible_os_family == "Debian" and patroni_deb_package_repo | length > 0 + + - name: Install patroni from deb package + ansible.builtin.apt: + force_apt_get: true + deb: "/tmp/{{ item }}" + state: present + loop: "{{ patroni_deb_package_repo | map('basename') | list }}" + register: deb_package_status + until: deb_package_status is success + delay: 5 + retries: 3 + when: ansible_os_family == "Debian" and patroni_deb_package_repo | length > 0 + + # RedHat + - name: Download patroni rpm package + ansible.builtin.get_url: + url: "{{ item }}" + dest: /tmp/ + timeout: 60 + validate_certs: false + loop: "{{ patroni_rpm_package_repo | list }}" + when: ansible_os_family == "RedHat" and patroni_rpm_package_repo | length > 0 + + - name: Install patroni from rpm package + ansible.builtin.dnf: + name: "/tmp/{{ item }}" + state: present + disable_gpg_check: true + loop: "{{ patroni_rpm_package_repo | map('basename') | list }}" + register: rpm_package_status + until: rpm_package_status is success + delay: 5 + retries: 3 + when: ansible_os_family == "RedHat" and patroni_rpm_package_repo | length > 0 + environment: "{{ proxy_env | default({}) }}" + when: installation_method == "packages" and (patroni_installation_method == "rpm" or patroni_installation_method == "deb") + tags: patroni, patroni_install diff --git a/automation/roles/patroni/tasks/main.yml b/automation/roles/patroni/tasks/main.yml index b58c26e276..29257e1586 100644 --- a/automation/roles/patroni/tasks/main.yml +++ b/automation/roles/patroni/tasks/main.yml @@ -2,195 +2,15 @@ - name: Make sure handlers are flushed immediately ansible.builtin.meta: flush_handlers -# pip install -- ansible.builtin.import_tasks: pip.yml - when: - - patroni_installation_method == "pip" - - pip_package not in system_packages - vars: - pip_package: "python{{ python_version | default('3') }}-pip" - tags: patroni, patroni_install, pip - -# Patroni install -- block: # installation_method: "packages" and patroni_installation_method: "pip" - - name: Install setuptools - ansible.builtin.pip: - name: setuptools<66.0.0 # https://github.com/pypa/setuptools/issues/3772 - state: latest - executable: pip3 - extra_args: "--trusted-host=pypi.python.org --trusted-host=pypi.org --trusted-host=files.pythonhosted.org" - umask: "0022" - environment: - PATH: "{{ ansible_env.PATH }}:/usr/local/bin:/usr/bin" - PIP_BREAK_SYSTEM_PACKAGES: "1" - when: patroni_pip_requirements_repo | length < 1 - - # Use master requirements if patroni_install_version is 'latest' or explicitly forced via patroni_latest_requirements - - name: Download Patroni requirements.txt - ansible.builtin.get_url: - url: >- - https://raw.githubusercontent.com/patroni/patroni/{{ - 'refs/heads/master' if patroni_install_version == 'latest' or patroni_latest_requirements | default(false) - else 'refs/tags/v' ~ (patroni_install_version | regex_replace('^v', '')) - }}/requirements.txt - dest: /tmp/requirements.txt - timeout: 60 - validate_certs: false - when: patroni_pip_requirements_repo | length < 1 - - - name: Install requirements - ansible.builtin.pip: - requirements: /tmp/requirements.txt - executable: pip3 - extra_args: "--trusted-host=pypi.python.org --trusted-host=pypi.org --trusted-host=files.pythonhosted.org" - umask: "0022" - environment: - PATH: "{{ ansible_env.PATH }}:{{ postgresql_bin_dir }}:/usr/local/bin:/usr/bin" - PIP_BREAK_SYSTEM_PACKAGES: "1" - when: patroni_pip_requirements_repo | length < 1 - - - name: Install patroni - ansible.builtin.pip: - name: patroni - state: latest - executable: pip3 - extra_args: "--trusted-host=pypi.python.org --trusted-host=pypi.org --trusted-host=files.pythonhosted.org" - umask: "0022" - environment: - PATH: "{{ ansible_env.PATH }}:/usr/local/bin:/usr/bin" - PIP_BREAK_SYSTEM_PACKAGES: "1" - when: patroni_pip_package_repo | length < 1 and patroni_install_version == "latest" - - - name: "Install patroni {{ patroni_install_version }}" - ansible.builtin.pip: - name: "patroni=={{ patroni_install_version }}" - executable: pip3 - extra_args: "--trusted-host=pypi.python.org --trusted-host=pypi.org --trusted-host=files.pythonhosted.org" - umask: "0022" - environment: - PATH: "{{ ansible_env.PATH }}:/usr/local/bin:/usr/bin" - PIP_BREAK_SYSTEM_PACKAGES: "1" - when: patroni_pip_package_repo | length < 1 and patroni_install_version != "latest" - when: installation_method == "packages" and patroni_installation_method == "pip" - environment: "{{ proxy_env | default({}) }}" +# if installation_method: "packages" +- ansible.builtin.import_tasks: install.yml + when: installation_method == "packages" tags: patroni, patroni_install -- block: # when "patroni_pip_requirements_repo" and "patroni_pip_package_repo" is defined - - name: Download patroni requirements - ansible.builtin.get_url: - url: "{{ item }}" - dest: /tmp/ - timeout: 120 - validate_certs: false - loop: "{{ patroni_pip_requirements_repo }}" - when: patroni_pip_requirements_repo | length > 0 - - - name: Download patroni package - ansible.builtin.get_url: - url: "{{ item }}" - dest: /tmp/ - timeout: 60 - validate_certs: false - loop: "{{ patroni_pip_package_repo | list }}" - when: patroni_pip_package_repo | length > 0 - - - name: Install requirements - ansible.builtin.pip: - name: "file:///tmp/{{ item }}" - executable: pip3 - extra_args: "--no-index --find-links=file:///tmp --ignore-installed" - umask: "0022" - loop: "{{ patroni_pip_requirements_repo | map('basename') | list }}" - environment: - PATH: "{{ ansible_env.PATH }}:/usr/local/bin:/usr/bin" - PIP_BREAK_SYSTEM_PACKAGES: "1" - when: patroni_pip_requirements_repo | length > 0 - - - name: Install patroni - ansible.builtin.pip: - name: "file:///tmp/{{ item }}" - executable: pip3 - extra_args: "--no-index --find-links=file:///tmp --ignore-installed" - umask: "0022" - loop: "{{ patroni_pip_package_repo | map('basename') | list }}" - environment: - PATH: "{{ ansible_env.PATH }}:/usr/local/bin:/usr/bin" - PIP_BREAK_SYSTEM_PACKAGES: "1" - when: patroni_pip_package_repo | length > 0 - when: installation_method == "packages" and patroni_installation_method == "pip" - tags: patroni, patroni_install - -- block: # installation_method: "packages" and patroni_installation_method: "rpm/deb" - # Debian - - name: Install patroni package - ansible.builtin.apt: - name: "{{ patroni_packages | flatten }}" - state: present - register: apt_status - until: apt_status is success - delay: 5 - retries: 3 - when: ansible_os_family == "Debian" and patroni_deb_package_repo | length < 1 - - # RedHat - - name: Install patroni package - ansible.builtin.dnf: - name: "{{ patroni_packages | flatten }}" - state: present - disable_gpg_check: true - register: dnf_status - until: dnf_status is success - delay: 5 - retries: 3 - when: ansible_os_family == "RedHat" and patroni_rpm_package_repo | length < 1 - - # when patroni_deb_package_repo or patroni_rpm_package_repo URL is defined - # Debian - - name: Download patroni deb package - ansible.builtin.get_url: - url: "{{ item }}" - dest: /tmp/ - timeout: 60 - validate_certs: false - loop: "{{ patroni_deb_package_repo | list }}" - when: ansible_os_family == "Debian" and patroni_deb_package_repo | length > 0 - - - name: Install patroni from deb package - ansible.builtin.apt: - force_apt_get: true - deb: "/tmp/{{ item }}" - state: present - loop: "{{ patroni_deb_package_repo | map('basename') | list }}" - register: deb_package_status - until: deb_package_status is success - delay: 5 - retries: 3 - when: ansible_os_family == "Debian" and patroni_deb_package_repo | length > 0 - - # RedHat - - name: Download patroni rpm package - ansible.builtin.get_url: - url: "{{ item }}" - dest: /tmp/ - timeout: 60 - validate_certs: false - loop: "{{ patroni_rpm_package_repo | list }}" - when: ansible_os_family == "RedHat" and patroni_rpm_package_repo | length > 0 - - - name: Install patroni from rpm package - ansible.builtin.dnf: - name: "/tmp/{{ item }}" - state: present - disable_gpg_check: true - loop: "{{ patroni_rpm_package_repo | map('basename') | list }}" - register: rpm_package_status - until: rpm_package_status is success - delay: 5 - retries: 3 - when: ansible_os_family == "RedHat" and patroni_rpm_package_repo | length > 0 - environment: "{{ proxy_env | default({}) }}" - when: installation_method == "packages" and (patroni_installation_method == "rpm" or patroni_installation_method == "deb") - tags: patroni, patroni_install +# if installation_method: "docker" +- ansible.builtin.import_tasks: docker.yml + when: installation_method == "docker" + tags: patroni, docker # Patroni configure - name: Create conf directory @@ -328,11 +148,13 @@ - name: Copy systemd service file "/etc/systemd/system/patroni.service" ansible.builtin.template: - src: templates/patroni.service.j2 + src: "templates/{{ patroni_service_template }}" dest: /etc/systemd/system/patroni.service owner: postgres group: postgres mode: "0644" + vars: + patroni_service_template: "{{ 'patroni-docker.service.j2' if installation_method == 'docker' else 'patroni.service.j2' }}" tags: patroni, patroni_conf, patroni_service - name: Prepare PostgreSQL | create statistics directory (if not already exists) @@ -387,39 +209,40 @@ state: directory mode: "0700" - # for Debian based distros only + # for Debian based distros only (if installation_method: "packages") # patroni bootstrap failure is possible if the PostgreSQL config files are missing - - name: Prepare PostgreSQL | make sure PostgreSQL config directory exists - ansible.builtin.file: - path: /etc/postgresql - state: directory - owner: postgres - group: postgres - recurse: true - when: ansible_os_family == "Debian" and - postgresql_packages|join(" ") is not search("postgrespro") + - block: + - name: Prepare PostgreSQL | make sure PostgreSQL config directory exists + ansible.builtin.file: + path: /etc/postgresql + state: directory + owner: postgres + group: postgres + recurse: true + when: postgresql_packages|join(" ") is not search("postgrespro") - - name: Prepare PostgreSQL | make sure PostgreSQL config files exists - ansible.builtin.stat: - path: "{{ postgresql_conf_dir }}/postgresql.conf" - register: postgresql_conf_file - when: ansible_os_family == "Debian" and - postgresql_packages|join(" ") is not search("postgrespro") + - name: Prepare PostgreSQL | make sure PostgreSQL config files exists + ansible.builtin.stat: + path: "{{ postgresql_conf_dir }}/postgresql.conf" + register: postgresql_conf_file + when: postgresql_packages|join(" ") is not search("postgrespro") - - name: Prepare PostgreSQL | generate default PostgreSQL config files - become: true - become_user: postgres - ansible.builtin.command: > - /usr/bin/pg_createcluster {{ postgresql_version }} {{ postgresql_cluster_name }} - -d {{ postgresql_data_dir }} - -p {{ postgresql_port }} - -e {{ postgresql_encoding }} - --locale {{ postgresql_locale }} - register: pg_createcluster_result - failed_when: pg_createcluster_result.rc != 0 - when: (ansible_os_family == "Debian" and - postgresql_packages|join(" ") is not search("postgrespro")) and - not postgresql_conf_file.stat.exists + - name: Prepare PostgreSQL | generate default PostgreSQL config files + become: true + become_user: postgres + ansible.builtin.command: > + /usr/bin/pg_createcluster {{ postgresql_version }} {{ postgresql_cluster_name }} + -d {{ postgresql_data_dir }} + -p {{ postgresql_port }} + -e {{ postgresql_encoding }} + --locale {{ postgresql_locale }} + register: pg_createcluster_result + failed_when: pg_createcluster_result.rc != 0 + when: (postgresql_packages|join(" ") is not search("postgrespro")) and + not postgresql_conf_file.stat.exists + when: + - ansible_os_family == "Debian" + - installation_method == "packages" # When performing PITR, we do not clear the directory if pgbackrest is used to be able to use the '--delta restore' option. # In all other cases, we must clear the data directory before restore. @@ -1014,8 +837,9 @@ when: postgresql_wal_dir is defined and postgresql_wal_dir | length > 0 tags: patroni, custom_wal_dir, point_in_time_recovery -# disable postgresql from autostart -- block: # "Debian" +# Disable postgresql service from autostart (if installation_method: "packages") +- block: + # "Debian" - name: Turning off postgresql autostart from config "start.conf" (will be managed by patroni) ansible.builtin.copy: dest: "{{ postgresql_conf_dir }}/start.conf" @@ -1023,36 +847,39 @@ owner: postgres group: postgres mode: "0644" + when: ansible_os_family == "Debian" - name: Disable "postgresql@{{ postgresql_version }}-{{ postgresql_cluster_name }}" service ansible.builtin.systemd: name: "postgresql@{{ postgresql_version }}-{{ postgresql_cluster_name }}" enabled: false daemon_reload: true - when: ansible_os_family == "Debian" and - postgresql_packages|join(" ") is not search("postgrespro") - tags: patroni, postgresql_disable + when: + - ansible_os_family == "Debian" + - postgresql_packages|join(" ") is not search("postgrespro") -# "RedHat" -- name: Disable "postgresql-{{ postgresql_version }}" service (will be managed by patroni) - ansible.builtin.systemd: - name: "postgresql-{{ postgresql_version }}" - enabled: false - daemon_reload: true - when: ansible_os_family == "RedHat" and - postgresql_packages|join(" ") is not search("postgrespro") - tags: patroni, postgresql_disable + # "RedHat" + - name: Disable "postgresql-{{ postgresql_version }}" service (will be managed by patroni) + ansible.builtin.systemd: + name: "postgresql-{{ postgresql_version }}" + enabled: false + daemon_reload: true + when: + - ansible_os_family == "RedHat" + - postgresql_packages|join(" ") is not search("postgrespro") + tags: patroni, postgresql_disable -# PostgresPro -- name: Disable "postgrespro-std-{{ postgresql_version }}" service (will be managed by patroni) - ansible.builtin.systemd: - name: "postgrespro-std-{{ postgresql_version }}" - enabled: false - daemon_reload: true - when: postgresql_packages|join(" ") is search("postgrespro-std") + # PostgresPro + - name: Disable "postgrespro-std-{{ postgresql_version }}" service (will be managed by patroni) + ansible.builtin.systemd: + name: "postgrespro-std-{{ postgresql_version }}" + enabled: false + daemon_reload: true + when: postgresql_packages|join(" ") is search("postgrespro-std") + when: installation_method == "packages" tags: patroni, postgresql_disable -# PATRONICTL_CONFIG_FILE (patroni v1.6.1 and higher) +# PATRONICTL_CONFIG_FILE - name: Add PATRONICTL_CONFIG_FILE environment variable into /etc/environment ansible.builtin.lineinfile: dest: "/etc/environment" @@ -1063,4 +890,5 @@ group: root mode: "0644" ignore_errors: true + when: installation_method == "packages" tags: patroni, patroni_env diff --git a/automation/roles/patroni/templates/patroni-docker.service.j2 b/automation/roles/patroni/templates/patroni-docker.service.j2 new file mode 100644 index 0000000000..a218a21e29 --- /dev/null +++ b/automation/roles/patroni/templates/patroni-docker.service.j2 @@ -0,0 +1,39 @@ +[Unit] +Description=Patroni docker wrapper +Requires=docker.service +After=docker.service + +[Service] +Type=simple +TimeoutSec=60 +RestartSec=15 +Restart=always + +# Delete patroni container (if any) +ExecStartPre=-/usr/bin/docker rm -f {{ postgresql_container.name | default('patroni') }} + +# Start patroni container +ExecStart=/usr/bin/docker run -d --name {{ postgresql_container.name | default('patroni') }} \ + {% for option in postgresql_container.options | default([]) %} + {{ option }} \ + {% endfor %} + {% for env in postgresql_container.envs | default([]) %} + --env {{ env }} \ + {% endfor %} + {% for volume in postgresql_container.volumes | default([]) %} + --volume {{ volume }} \ + {% endfor %} + {% if postgresql_stats_temp_directory_path | default('') != 'none' and postgresql_version | int <= 14 %} + --volume {{ postgresql_stats_temp_directory_path }}:{{ postgresql_stats_temp_directory_path }} \ + {% endif %} + {{ postgresql_container.image | default('autobase/patroni:postgresql-' ~ postgresql_version) }} {{ postgresql_container.run_command | default('') }} + +# Stop patroni container +ExecStop=-/usr/bin/docker stop {{ postgresql_container.name | default('patroni') }} +ExecStopPost=-/usr/bin/docker rm -f {{ postgresql_container.name | default('patroni') }} + +# Send HUP to reload from patroni.yml +ExecReload=/usr/bin/docker exec {{ postgresql_container.name | default('patroni') }} kill -SIGHUP 1 + +[Install] +WantedBy=multi-user.target diff --git a/automation/roles/pgbouncer/templates/pgbouncer-docker.service.j2 b/automation/roles/pgbouncer/templates/pgbouncer-docker.service.j2 new file mode 100644 index 0000000000..8ab770588c --- /dev/null +++ b/automation/roles/pgbouncer/templates/pgbouncer-docker.service.j2 @@ -0,0 +1,38 @@ +[Unit] +Description=PgBouncer docker wrapper +Requires=docker.service +After=docker.service + +[Service] +Type=simple +TimeoutSec=30 +RestartSec=15 +Restart=always + +# Delete PgBouncer container (if any) +ExecStartPre=-/usr/bin/docker rm -f {{ pgbouncer_container.name | default('pgbouncer') }} + +# Start PgBouncer container +ExecStart=/usr/bin/docker run -d --name {{ pgbouncer_container.name | default('pgbouncer') }}{{ '-%d' % (idx + 1) if idx > 0 else '' }} \ + {% for option in pgbouncer_container.options | default([]) %} + {{ option }} \ + {% endfor %} + {% for env in pgbouncer_container.envs | default([]) %} + --env {{ env }} \ + {% endfor %} + {% for volume in pgbouncer_container.volumes | default([]) %} + --volume {{ volume }} \ + {% endfor %} + --volume /var/run/pgbouncer{{ '-%d' % (idx + 1) if idx > 0 else '' }}:/var/run/pgbouncer{{ '-%d' % (idx + 1) if idx > 0 else '' }} \ + {{ pgbouncer_container.image | default('bitnami/pgbouncer:latest') }} \ + pgbouncer -d {{ pgbouncer_conf_dir }}/pgbouncer{{ '-%d' % (idx + 1) if idx > 0 else '' }}.ini + +# Stop PgBouncer container +ExecStop=-/usr/bin/docker stop {{ pgbouncer_container.name | default('pgbouncer') }} +ExecStopPost=-/usr/bin/docker rm -f {{ pgbouncer_container.name | default('pgbouncer') }} + +# Send HUP to reload config +ExecReload=/usr/bin/docker exec {{ pgbouncer_container.name | default('pgbouncer') }} kill -SIGHUP 1 + +[Install] +WantedBy=multi-user.target diff --git a/automation/tags.md b/automation/tags.md index 537a0d1cfa..f93853e676 100644 --- a/automation/tags.md +++ b/automation/tags.md @@ -110,3 +110,6 @@ - tls - tls_cert_generate - tls_cert_copy +- docker + - docker_install + - docker_wrappers diff --git a/console/Dockerfile b/console/Dockerfile index afdde6ffa3..1bfa4cc70c 100644 --- a/console/Dockerfile +++ b/console/Dockerfile @@ -15,7 +15,7 @@ RUN yarn install --frozen-lockfile --network-timeout 1000000 && yarn vite build # Build the console image FROM nginx:1.29-bookworm -LABEL maintainer="Vitaliy Kukharik vitabaks@gmail.com" +LABEL maintainer="Autobase " COPY --from=api-builder /go/src/pg-console/pg-console /usr/local/bin/ COPY console/db/migrations /etc/db/migrations diff --git a/console/db/Dockerfile b/console/db/Dockerfile index f9b5a3350c..9e0214d52c 100644 --- a/console/db/Dockerfile +++ b/console/db/Dockerfile @@ -1,4 +1,5 @@ -FROM debian:12-slim +FROM debian:bookworm-slim +LABEL maintainer="Autobase " ARG POSTGRES_VERSION ENV POSTGRES_VERSION=${POSTGRES_VERSION:-16} diff --git a/console/service/Dockerfile b/console/service/Dockerfile index 2e7f3de90a..499603010a 100644 --- a/console/service/Dockerfile +++ b/console/service/Dockerfile @@ -6,7 +6,7 @@ COPY console/service/ . RUN make build_in_docker FROM debian:bookworm-slim -LABEL maintainer="Vitaliy Kukharik vitabaks@gmail.com" +LABEL maintainer="Autobase " COPY --from=builder /go/src/pg-console/pg-console /usr/local/bin/ COPY console/db/migrations /etc/db/migrations diff --git a/console/ui/Dockerfile b/console/ui/Dockerfile index 8ac72cbafe..d1beeb5636 100644 --- a/console/ui/Dockerfile +++ b/console/ui/Dockerfile @@ -7,7 +7,7 @@ COPY console/ui/ . RUN yarn install --frozen-lockfile --network-timeout 1000000 && yarn vite build FROM nginx:1.29-bookworm AS runtime -LABEL maintainer="Vitaliy Kukharik vitabaks@gmail.com" +LABEL maintainer="Autobase " WORKDIR /usr/share/nginx/html diff --git a/docker/patroni/Dockerfile b/docker/patroni/Dockerfile new file mode 100644 index 0000000000..9bad1c16be --- /dev/null +++ b/docker/patroni/Dockerfile @@ -0,0 +1,89 @@ +ARG POSTGRES_VERSION=17 +ARG DISTRO_VERSION=bookworm + +FROM postgres:${POSTGRES_VERSION}-${DISTRO_VERSION} +LABEL maintainer="Autobase " + +ARG POSTGRES_VERSION +ENV POSTGRES_VERSION=${POSTGRES_VERSION:-17} + +ARG WALG_VERSION +ENV WALG_VERSION=${WALG_VERSION:-3.0.7} + +ARG PGVECTORSCALE_VERSION +ENV PGVECTORSCALE_VERSION=${PGVECTORSCALE_VERSION:-0.7.1} + +# Set SHELL to /bin/bash +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get clean && rm -rf /var/lib/apt/lists/partial \ + && apt-get update -o Acquire::CompressionTypes::Order::=gz \ + && apt-get install --no-install-recommends -y \ + gnupg apt-transport-https ca-certificates lsb-release zstd openssh-client wget curl unzip \ + # patroni + && apt-get install --no-install-recommends -y patroni python3-etcd python3-consul python3-psycopg2 \ + # timescaledb extension + && if [ "${POSTGRES_VERSION}" -gt "11" ]; then \ + wget --quiet -O - https://packagecloud.io/timescale/timescaledb/gpgkey | gpg --dearmor -o /etc/apt/trusted.gpg.d/timescaledb.gpg \ + && echo "deb https://packagecloud.io/timescale/timescaledb/debian/ $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/timescaledb.list \ + && apt-get update -o Acquire::CompressionTypes::Order::=gz \ + && apt-get install --no-install-recommends -y timescaledb-2-postgresql-"${POSTGRES_VERSION}"; \ + fi \ + # citus extension + && if [ "${POSTGRES_VERSION}" -gt "10" ]; then \ + if [ "${POSTGRES_VERSION}" = "11" ]; then CITUS_VERSION="10.0"; \ + elif [ "${POSTGRES_VERSION}" = "12" ]; then CITUS_VERSION="10.2"; \ + elif [ "${POSTGRES_VERSION}" = "13" ]; then CITUS_VERSION="11.3"; \ + elif [ "${POSTGRES_VERSION}" = "14" ]; then CITUS_VERSION="12.1"; \ + elif [ "${POSTGRES_VERSION}" = "15" ]; then CITUS_VERSION="13.0"; \ + elif [ "${POSTGRES_VERSION}" = "16" ]; then CITUS_VERSION="13.0"; \ + elif [ "${POSTGRES_VERSION}" = "17" ]; then CITUS_VERSION="13.0"; \ + fi \ + && curl -s https://install.citusdata.com/community/deb.sh | bash \ + && apt-get install --no-install-recommends -y postgresql-"${POSTGRES_VERSION}"-citus-"${CITUS_VERSION}"; \ + fi \ + # pg_stat_kcache extension + && apt-get install --no-install-recommends -y postgresql-"${POSTGRES_VERSION}"-pg-stat-kcache \ + # pg_wait_sampling extension + && apt-get install --no-install-recommends -y postgresql-"${POSTGRES_VERSION}"-pg-wait-sampling \ + # pg_repack extension + && apt-get install --no-install-recommends -y postgresql-"${POSTGRES_VERSION}"-repack \ + # pg_cron extension + && apt-get install --no-install-recommends -y postgresql-"${POSTGRES_VERSION}"-cron \ + # pgaudit extension + && apt-get install --no-install-recommends -y postgresql-"${POSTGRES_VERSION}"-pgaudit \ + # pgvector extension + && apt-get install --no-install-recommends -y postgresql-"${POSTGRES_VERSION}"-pgvector \ + # pgvectorscale extension + && if [ "${POSTGRES_VERSION}" -gt "12" ]; then \ + wget --quiet https://github.com/timescale/pgvectorscale/releases/download/"${PGVECTORSCALE_VERSION}"/pgvectorscale-"${PGVECTORSCALE_VERSION}"-pg"${POSTGRES_VERSION}"-"$(dpkg --print-architecture)".zip \ + && unzip pgvectorscale-"${PGVECTORSCALE_VERSION}"-pg"${POSTGRES_VERSION}"-"$(dpkg --print-architecture)".zip \ + && dpkg -i pgvectorscale-postgresql-"${POSTGRES_VERSION}"_"${PGVECTORSCALE_VERSION}"-Linux_"$(dpkg --print-architecture)".deb \ + && rm -f pgvectorscale-*.{deb,zip}; \ + fi \ + # postgis extension + && apt-get install --no-install-recommends -y postgresql-"${POSTGRES_VERSION}"-postgis-3 \ + # pgrouting extension + && apt-get install --no-install-recommends -y postgresql-"${POSTGRES_VERSION}"-pgrouting \ + # partman extension + && apt-get install --no-install-recommends -y postgresql-"${POSTGRES_VERSION}"-partman \ + # pgBackRest + && apt-get install --no-install-recommends -y pgbackrest \ + # WAL-G + && wget --quiet https://github.com/wal-g/wal-g/releases/download/v"${WALG_VERSION}"/wal-g-pg-ubuntu-20.04-"$(dpkg --print-architecture)".tar.gz \ + && tar -zxvf wal-g-pg-ubuntu-20.04-"$(dpkg --print-architecture)".tar.gz \ + && mv wal-g-pg-ubuntu-20.04-"$(dpkg --print-architecture)" /usr/local/bin/wal-g \ + && chmod +x /usr/local/bin/wal-g \ + && rm wal-g-pg-ubuntu-20.04-"$(dpkg --print-architecture)".tar.gz \ + && wal-g --version \ + # Clean up + && apt-get autoremove -y --purge gnupg wget unzip \ + && apt-get clean -y autoclean \ + && rm -rf /var/lib/apt/lists/* \ + && rm -rf /var/lib/postgresql/* + +ENTRYPOINT [] + +CMD ["/usr/bin/patroni", "/etc/patroni/patroni.yml"]