{"id":12188,"date":"2019-01-07T07:48:41","date_gmt":"2019-01-07T06:48:41","guid":{"rendered":"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/"},"modified":"2019-01-07T07:48:41","modified_gmt":"2019-01-07T06:48:41","slug":"using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes","status":"publish","type":"post","link":"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/","title":{"rendered":"Using Ansible to bring up a three node Patroni cluster in minutes"},"content":{"rendered":"<p>Automation is key today, nobody wants to do the same tasks over and over and again. Cloud without automation is not even possible. There are several tools around that help with automation and one of the most popular is <a href=\"https:\/\/www.ansible.com\/\" target=\"_blank\" rel=\"noopener noreferrer\">Ansible<\/a>. We already have <a href=\"https:\/\/www.dbi-services.com\/blog\/?s=ansible\" target=\"_blank\" rel=\"noopener noreferrer\">several posts<\/a> about Ansible on our blog platform but this one will be rather long. Setting up PostgreSQL high available architectures is our daily business and we as well try to automate as much as possible. We do not only automate to save time, even more important we automate to avoid human errors. What we will share with this post is how you could use Ansible to bring up a three node <a href=\"https:\/\/patroni.readthedocs.io\/en\/latest\/README.html\" target=\"_blank\" rel=\"noopener noreferrer\">Patroni<\/a> cluster from scratch.<\/p>\n<p><!--more--><\/p>\n<p>Disclaimer: Please see what we show here as a kind of template. You might need to adjust several bits to fit into your environment, other bits for sure can be solved more elegant by using advanced features of Ansible. Anyway, using this template you should be able to bring up one PostgreSQL master instance, two replicas, Patroni and <a href=\"http:\/\/www.haproxy.org\/\" target=\"_blank\" rel=\"noopener noreferrer\">HAProxy<\/a> in minutes on CentOS 7. This should work the same for Red Hat 7 but if you want to do the same on Debian based systems or SUSE you for sure need to adjust some of the Ansible tasks. This post does not explain how Ansible works nor does it explain what Patroni or HAProxy is. <\/p>\n<p>The starting point is a CentOS minimal installation with just the postgres user and group created and sudo permissions for postgres. That&#8217;s it:<\/p>\n<pre class=\"brush: bash; gutter: true; first-line: 1\">\npostgres@patroni1 ~]$ id -a\nuid=1000(postgres) gid=1000(postgres) groups=1000(postgres) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023\n[postgres@patroni1 ~]$ lsb_release -a\nLSB Version:\t:core-4.1-amd64:core-4.1-noarch:cxx-4.1-amd64:cxx-4.1-noarch:desktop-4.1-amd64:desktop-4.1-noarch:languages-4.1-amd64:languages-4.1-noarch:printing-4.1-amd64:printing-4.1-noarch\nDistributor ID:\tCentOS\nDescription:\tCentOS Linux release 7.5.1804 (Core) \nRelease:\t7.5.1804\nCodename:\tCore\n[postgres@patroni1 ~]$ sudo cat \/etc\/sudoers | grep postgres\npostgres\tALL=(ALL)\tNOPASSWD: ALL\n[postgres@patroni1 ~]$ \n<\/pre>\n<p>This is the Ansible directory structure on my workstation:<\/p>\n<pre class=\"brush: bash; gutter: true; first-line: 1\">\ndwe@dwe:~\/Documents\/dbiProjects\/dbi_dmk_postgres\/dmk\/ansible$ tree\n.\n\u251c\u2500\u2500 _commands.sh\n\u251c\u2500\u2500 _init_dirs.sh\n\u251c\u2500\u2500 patroni\n\u2514\u2500\u2500 roles\n    \u251c\u2500\u2500 common\n    \u2502\u00a0\u00a0 \u251c\u2500\u2500 files\n    \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 PostgreSQL-DMK-17-09.4.zip\n    \u2502\u00a0\u00a0 \u251c\u2500\u2500 handlers\n    \u2502\u00a0\u00a0 \u251c\u2500\u2500 meta\n    \u2502\u00a0\u00a0 \u251c\u2500\u2500 tasks\n    \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 main.yml\n    \u2502\u00a0\u00a0 \u251c\u2500\u2500 templates\n    \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 compile.sh.j2\n    \u2502\u00a0\u00a0 \u2514\u2500\u2500 vars\n    \u2514\u2500\u2500 patroni\n        \u251c\u2500\u2500 files\n        \u2502\u00a0\u00a0 \u251c\u2500\u2500 etcd.service\n        \u2502\u00a0\u00a0 \u2514\u2500\u2500 patroni.service\n        \u251c\u2500\u2500 handlers\n        \u251c\u2500\u2500 meta\n        \u251c\u2500\u2500 site.retry\n        \u251c\u2500\u2500 site.yml\n        \u251c\u2500\u2500 tasks\n        \u2502\u00a0\u00a0 \u2514\u2500\u2500 main.yml\n        \u251c\u2500\u2500 templates\n        \u2502\u00a0\u00a0 \u251c\u2500\u2500 etcd.conf.j2\n        \u2502\u00a0\u00a0 \u251c\u2500\u2500 haproxy.cfg.j2\n        \u2502\u00a0\u00a0 \u251c\u2500\u2500 hosts.j2\n        \u2502\u00a0\u00a0 \u251c\u2500\u2500 keepalived.conf.j2\n        \u2502\u00a0\u00a0 \u2514\u2500\u2500 patroni.yml.j2\n        \u2514\u2500\u2500 vars\n<\/pre>\n<p>You can use the _init_dirs.sh script to create that but it is pretty much the Ansible default anyway:<\/p>\n<pre class=\"brush: bash; gutter: true; first-line: 1\">\ndwe@dwe:~\/Documents\/dbiProjects\/dbi_dmk_postgres\/dmk\/ansible$ cat _init_dirs.sh \n#!\/bin\/bash\ntouch patroni\nmkdir roles\/\nmkdir roles\/common\nmkdir roles\/common\/tasks\nmkdir roles\/common\/handlers\nmkdir roles\/common\/templates\nmkdir roles\/common\/files\nmkdir roles\/common\/vars\nmkdir roles\/common\/meta\nmkdir roles\/patroni\nmkdir roles\/patroni\/tasks\nmkdir roles\/patroni\/handlers\nmkdir roles\/patroni\/templates\nmkdir roles\/patroni\/files\nmkdir roles\/patroni\/vars\nmkdir roles\/patroni\/meta\n<\/pre>\n<p>What you always need with Ansible is the <a href=\"https:\/\/docs.ansible.com\/ansible\/latest\/user_guide\/intro_inventory.html\" target=\"_blank\" rel=\"noopener noreferrer\">inventory<\/a> and in our case it looks like this:<\/p>\n<pre class=\"brush: bash; gutter: true; first-line: 1\">\ndwe@dwe:~\/Documents\/dbiProjects\/dbi_dmk_postgres\/dmk\/ansible$ cat patroni \n[patroni-servers]\n192.168.22.240 keepalived_role=MASTER keepalived_priority=102 ansible_hostname=patroni1 ansible_hostname_fqdn=patroni1.it.dbi-services.com\n192.168.22.241 keepalived_role=SLAVE keepalived_priority=101 ansible_hostname=patroni2 ansible_hostname_fqdn=patroni2.it.dbi-services.com\n192.168.22.242 keepalived_role=SLAVE keepalived_priority=100 ansible_hostname=patroni3 ansible_hostname_fqdn=patroni3.it.dbi-services.com\n\n[patroni-servers:vars]\npostgresql_version=11.1\npostgresql_major_version=11\ndmk_postgresql_version=11\/db_1\netcd_vserion=3.3.10\npostgres_user=postgres\npostgres_group=postgres\ndmk_version=17-09.4\ncluster_name=PG1\nblank=' '\nvirtual_ip=192.168.22.245\n<\/pre>\n<p>As you can see there are three machines and several variables defined. The *dmk* stuff if for our <a href=\"https:\/\/www.dbi-services.com\/offering\/products\/dmk-management-kit\/\" target=\"_blank\" rel=\"noopener noreferrer\">management kit<\/a>, just ignore\/delete that for your environment.<\/p>\n<p>We have two roles, one common and one for Patroni. The common role is responsible for doing the common stuff and can be used for single instance PostgreSQL deployments as well so lets start with this one:<\/p>\n<pre class=\"brush: bash; gutter: true; first-line: 1\">\ndwe@dwe:~\/Documents\/dbiProjects\/dbi_dmk_postgres\/dmk\/ansible$ cat roles\/common\/tasks\/main.yml \n- name: Install all dependencies for PostgreSQL \n  yum: name={{item}} state=present\n  with_items:\n   - gcc\n   - openldap-devel\n   - python-devel\n   - readline-devel\n   - redhat-lsb\n   - bison\n   - flex\n   - perl-ExtUtils-Embed\n   - zlib-devel\n   - crypto-utils\n   - openssl-devel\n   - pam-devel\n   - libxml2-devel\n   - libxslt-devel\n   - openssh-clients\n   - bzip2\n   - net-tools\n   - wget\n   - screen\n   - unzip\n   - sysstat\n   - xorg-x11-xauth\n   - systemd-devel\n   - bash-completion\n\n- name: Remove iwl packages\n  yum: name={{item}} state=removed\n  with_items:\n   - iwl*\n\n- name: upgrade all packages\n  yum:\n    name: '*'\n    state: latest\n\n- file:\n    path: \/u01\/app\/{{ postgres_user }}\/local\n    state: directory\n    mode: 0700\n    recurse: yes\n    owner: \"{{ postgres_user }}\"\n    group: \"{{ postgres_group }}\"\n\n- file:\n    path: \/u01\n    owner: \"{{ postgres_user }}\"\n    group: \"{{ postgres_user }}\"\n    mode: 0700\n\n- file:\n    path: \/u01\/app\n    owner: \"{{ postgres_user }}\"\n    group: \"{{ postgres_group }}\"\n    mode: 0700\n\n- file:\n    path: \/u01\/app\/{{ postgres_user }}\n    owner: \"{{ postgres_user }}\"\n    group: \"{{ postgres_group }}\"\n    mode: 0700\n\n- file:\n    path: \/u02\/pgdata\/\n    state: directory\n    mode: 0700\n    owner: \"{{ postgres_user }}\"\n    group: \"{{ postgres_group }}\"\n\n- file:\n    path: \/u02\/pgdata\/{{ postgresql_major_version }}\n    state: directory\n    mode: 0700\n    owner: \"{{ postgres_user }}\"\n    group: \"{{ postgres_group }}\"\n\n- file:\n    path: \/u02\/pgdata\/{{ postgresql_major_version }}\/{{ cluster_name }}\n    state: directory\n    mode: 0700\n    owner: \"{{ postgres_user }}\"\n    group: \"{{ postgres_group }}\"\n\n- file:\n    path: \/u99\/pgdata\/\n    state: directory\n    mode: 0700\n    owner: \"{{ postgres_user }}\"\n    group: \"{{ postgres_group }}\"\n\n- file:\n    path: \/etc\/pgtab\n    state: touch\n    owner: \"{{ postgres_user }}\"\n    group: \"{{ postgres_group }}\"\n    mode: 0600\n\n- name: check if PostgreSQL source code exists\n  stat: \n    path: \/home\/{{ postgres_user }}\/source.tar.bz2\n  register: source_available\n\n- name: Download the PostgreSQL source code if it is not already there\n  get_url:\n    url: https:\/\/ftp.postgresql.org\/pub\/source\/v{{ postgresql_version }}\/postgresql-{{ postgresql_version }}.tar.bz2\n    dest: \/home\/{{ postgres_user }}\/source.tar.bz2\n    mode: 0775\n  when: source_available.stat.exists == false\n\n- name: Check if PostgreSQL is already installed\n  stat:\n    path: \/u01\/app\/{{ postgres_user }}\/product\/{{ dmk_postgresql_version }}\/bin\/postgres\n  register: postrgresql_is_installed\n\n- name: extract the sources when PostgreSQL is not already installed\n  shell: cd \/home\/{{ postgres_user }}; tar -axf source.tar.bz2\n  become: yes\n  become_user: \"{{ postgres_user }}\"\n  when: postrgresql_is_installed.stat.exists == false\n\n- template:\n    src: compile.sh.j2\n    dest: \/home\/{{ postgres_user }}\/postgresql-{{ postgresql_version }}\/compile.sh\n    owner: \"{{ postgres_user }}\"\n    group: \"{{ postgres_group }}\"\n    mode: 0700\n\n- name: Install PostgreSQL from source code\n  shell: cd \/home\/{{ postgres_user }}\/postgresql-{{ postgresql_version }}; .\/compile.sh\n  become: yes\n  become_user: \"{{ postgres_user }}\"\n  when: postrgresql_is_installed.stat.exists == false\n\n- name: check if DMK for PostgreSQL source code exists\n  stat:\n    path: \/u01\/app\/{{ postgres_user }}\/local\/PostgreSQL-DMK-{{ dmk_version }}.zip\n  register: dmk_source_available\n\n- name: check if DMK for PostgreSQL is extracted\n  stat:\n    path: \/u01\/app\/{{ postgres_user }}\/local\/dmk\/bin\/dmk.bash\n  register: dmk_extracted\n\n- name: Copy DMK source distribution\n  copy:\n    src: PostgreSQL-DMK-{{ dmk_version }}.zip\n    dest: \/u01\/app\/{{ postgres_user }}\/local\/PostgreSQL-DMK-{{ dmk_version }}.zip\n    owner: \"{{ postgres_user }}\"\n    group: \"{{ postgres_group }}\"\n    mode: 0700\n  when: dmk_source_available.stat.exists == false\n\n- name: extract DMK\n  shell: cd \/u01\/app\/{{ postgres_user }}\/local; unzip PostgreSQL-DMK-{{ dmk_version }}.zip\n  become: yes\n  become_user: \"{{ postgres_user }}\"\n  when: dmk_extracted.stat.exists == false\n\n- name: check if DMK is installed\n  stat:\n    path: \/home\/{{ postgres_user }}\/.DMK_HOME\n  register: dmk_installed\n\n- lineinfile:\n    path: \/etc\/pgtab\n    line: 'pg{{ postgresql_version }}:\/u01\/app\/{{ postgres_user }}\/product\/{{ dmk_postgresql_version }}:dummy:9999:D'\n    create: no\n  when: dmk_installed.stat.exists == false\n\n- name: Execute DMK for the first time\n  shell: \/u01\/app\/{{ postgres_user }}\/local\/dmk\/bin\/dmk.bash; cat \/u01\/app\/{{ postgres_user }}\/local\/dmk\/templates\/profile\/dmk.postgres.profile &gt;&gt; \/home\/{{ postgres_user }}\/.bash_profile\n  become: yes\n  become_user: \"{{ postgres_user }}\"\n  when: dmk_installed.stat.exists == false\n<\/pre>\n<p>This should be more or less self explaining so we will only summarize what it does:<\/p>\n<ul>\n<li>Install required packages for compiling PostgreSQL from source<\/li>\n<li>Remove the iwl* packages<\/li>\n<li>Update all packages to the latest release<\/li>\n<li>Create the directory structure<\/li>\n<li>Download the PostgreSQL source code, compile and install<\/li>\n<li>Install our DMK<\/li>\n<\/ul>\n<p>As said, this role can be included in any other PostgreSQL setup as it only does basic stuff. There is one template used here, which is compile.sh.j2:<\/p>\n<pre class=\"brush: bash; gutter: true; first-line: 1\">\ndwe@dwe:~\/Documents\/dbiProjects\/dbi_dmk_postgres\/dmk\/ansible$ cat roles\/common\/templates\/compile.sh.j2 \nPGHOME=\/u01\/app\/postgres\/product\/{{ dmk_postgresql_version }}\nSEGSIZE=2\nBLOCKSIZE=8\n\n.\/configure --prefix=${PGHOME} \\\n            --exec-prefix=${PGHOME} \\\n            --bindir=${PGHOME}\/bin \\\n            --libdir=${PGHOME}\/lib \\\n            --sysconfdir=${PGHOME}\/etc \\\n            --includedir=${PGHOME}\/include \\\n            --datarootdir=${PGHOME}\/share \\\n            --datadir=${PGHOME}\/share \\\n            --with-pgport=5432 \\\n            --with-perl \\\n            --with-python \\\n            --with-openssl \\\n            --with-pam \\\n            --with-ldap \\\n            --with-libxml \\\n            --with-libxslt \\\n            --with-segsize=${SEGSIZE} \\\n            --with-blocksize=${BLOCKSIZE} \\\n\t    --with-systemd \"\nmake -j 2 all\nmake install\ncd contrib\nmake -j 2 install\n<\/pre>\n<p>This one is our standard way of bringing PostgreSQL onto the system and the only parameter is the PostgreSQL version we use for the directory name. No magic, simple stuff and that&#8217;s it for the common role.<\/p>\n<p>Coming to the Patroni role. Here is it:<\/p>\n<pre class=\"brush: bash; gutter: true; first-line: 1\">\ndwe@dwe:~\/Documents\/dbiProjects\/dbi_dmk_postgres\/dmk\/ansible$ cat roles\/patroni\/tasks\/main.yml \n---\n\n- name: check if epel rpm already is there\n  stat:\n    path: \/root\/epel-release-latest-7.noarch.rpm\n  register: epel_rpm_available\n\n- name: Download the EPEL rpm\n  get_url:\n    url: http:\/\/dl.fedoraproject.org\/pub\/epel\/epel-release-latest-7.noarch.rpm\n    dest: \/root\/epel-release-latest-7.noarch.rpm\n    mode: 0440\n  when: epel_rpm_available.stat.exists == false\n\n- name: check if epel repository is already installed\n  stat:\n    path: \/etc\/yum.repos.d\/epel.repo\n  register: epel_installed\n\n\n- name: Install the EPEL rpm\n  shell: yum localinstall -y \/root\/epel-release-latest-7.noarch.rpm\n  args: \n    warn: false\n  when: epel_installed.stat.exists == false\n\n- name: Install all dependencies for Patroni\n  yum: name={{item}} state=present\n  with_items:\n   - python-pip\n   - PyYAML\n   - bind-utils\n   - keepalived\n   - haproxy\n\n# create the hosts file\n- template:\n    src: hosts.j2\n    dest: \/etc\/hosts\n    owner: root\n    group: root\n    mode: 0644\n\n- name: Create the file to load the watchdog module\n  file:\n    path: \/etc\/modules-load.d\/softdog.conf\n    state: touch\n\n- name: Add the watchdog module\n  shell: modprobe softdog\n\n- name: Change ownershhip of the watchdog device\n  shell: chown postgres \/dev\/watchdog\n  args:\n    warn: false\n\n- name: check if etcd sources already exist\n  stat:\n    path: \/home\/{{ postgres_user }}\/etcd-v{{ etcd_vserion }}-linux-amd64.tar.gz\n  register: etcd_source_available\n\n- name: Download etcd\n  get_url:\n    url: https:\/\/github.com\/etcd-io\/etcd\/releases\/download\/v{{ etcd_vserion }}\/etcd-v{{ etcd_vserion }}-linux-amd64.tar.gz\n    dest: \/home\/{{ postgres_user }}\/etcd-v{{ etcd_vserion }}-linux-amd64.tar.gz\n    mode: 0755\n  when: etcd_source_available.stat.exists == false\n\n- name: check if etcd is available in DMK\n  stat:\n    path: \/u01\/app\/{{ postgres_user }}\/local\/dmk\/bin\/etcd\n  register: etcd_copied_to_dmk\n\n- name: extract etcd\n  shell: cd \/home\/{{ postgres_user }}\/; tar -axf etcd-v{{ etcd_vserion }}-linux-amd64.tar.gz\n  become: yes\n  become_user: \"{{ postgres_user }}\"\n  when: etcd_copied_to_dmk.stat.exists == false\n\n- name: copy etcd to DMK\n  shell: cp \/home\/{{ postgres_user }}\/etcd-v{{ etcd_vserion }}-linux-amd64\/etcd* \/u01\/app\/{{ postgres_user }}\/local\/dmk\/bin\/\n  become: yes\n  become_user: \"{{ postgres_user }}\"\n  when: etcd_copied_to_dmk.stat.exists == false\n\n- template:\n    src: etcd.conf.j2\n    dest: \/u01\/app\/{{ postgres_user }}\/local\/dmk\/etc\/etcd.conf\n    owner: \"{{ postgres_user }}\"\n    group: \"{{ postgres_group }}\"\n    mode: 0700\n\n- name: Copy the etcd systemd service file\n  copy:\n    src: etcd.service\n    dest: \/etc\/systemd\/system\/etcd.service\n    owner: root\n    group: root\n    mode: 0755\n\n- file:\n    path: \/u02\/pgdata\/etcd\n    state: directory\n    mode: 0700\n    recurse: yes\n    owner: \"{{ postgres_user }}\"\n    group: \"{{ postgres_group }}\"\n\n- name: force systemd to reread configs\n  systemd:\n    daemon_reload: yes\n\n- name: Enable the systemd etcd service\n  systemd:\n    name: etcd\n    enabled: yes\n\n- name: Start the systemd etcd service\n  shell: systemctl start etcd.service\n\n- name: check if patroni is alraedy installed\n  stat:\n    path: \/home\/{{ postgres_user }}\/.local\/bin\/patroni\n  register: patroni_is_installed\n\n- name: install and upgrade pip\n  shell: pip install --upgrade pip\n  when: patroni_is_installed.stat.exists == false\n \n- name: install and upgrade setuptools\n  become: yes\n  become_user: \"{{ postgres_user }}\"\n  shell: pip install --upgrade --user setuptools\n  when: patroni_is_installed.stat.exists == false\n\n- name: install psycopg2-binary\n  become: yes\n  become_user: \"{{ postgres_user }}\"\n  shell: pip install --user psycopg2-binary\n  when: patroni_is_installed.stat.exists == false\n\n- name: install patroni\n  become: yes\n  become_user: \"{{ postgres_user }}\"\n  shell: pip install --user patroni[etcd]\n  when: patroni_is_installed.stat.exists == false\n\n- file:\n    src: \/home\/{{ postgres_user }}\/.local\/bin\/patroni\n    dest: \/u01\/app\/{{ postgres_user }}\/local\/dmk\/bin\/patroni\n    owner: \"{{ postgres_user }}\"\n    group: \"{{ postgres_user }}\"\n    state: link\n\n- file:\n    src: \/home\/{{ postgres_user }}\/.local\/bin\/patronictl\n    dest: \/u01\/app\/{{ postgres_user }}\/local\/dmk\/bin\/patronictl\n    owner: \"{{ postgres_user }}\"\n    group: \"{{ postgres_user }}\"\n    state: link\n\n- template:\n    src: patroni.yml.j2\n    dest: \/u01\/app\/{{ postgres_user }}\/local\/dmk\/etc\/patroni.yml\n    owner: \"{{ postgres_user }}\"\n    group: \"{{ postgres_group }}\"\n    mode: 0600\n\n- name: Copy the patroni systemd service file\n  copy:\n    src: patroni.service\n    dest: \/etc\/systemd\/system\/patroni.service\n    owner: root\n    group: root\n    mode: 0755\n\n- name: force systemd to reread configs \n  systemd:\n    daemon_reload: yes\n\n- name: Enable the systemd etcd service\n  systemd:\n    name: patroni\n    enabled: yes\n\n# add the instance to \/etc\/pgtab so DMK is aware of if\n- lineinfile:\n    path: \/etc\/pgtab\n    line: '{{ cluster_name }}:\/u01\/app\/{{ postgres_user }}\/product\/{{ dmk_postgresql_version }}:\/u02\/pgdata\/{{ postgresql_major_version }}\/{{ cluster_name }}:5432:N'\n\n- template:\n    src: haproxy.cfg.j2\n    dest: \/etc\/haproxy\/haproxy.cfg\n    owner: \"{{ postgres_user }}\"\n    group: \"{{ postgres_group }}\"\n    mode: 0600\n\n- name: Enable the systemd haproxy service\n  systemd:\n    name: haproxy\n    enabled: yes\n\n# we need to set this so haproxy can be started\n- name: Set selinux context for ha proxy\n  shell: setsebool -P haproxy_connect_any=1\n\n- template:\n    src: keepalived.conf.j2\n    dest: \/etc\/keepalived\/keepalived.conf\n    owner: \"{{ postgres_user }}\"\n    group: \"{{ postgres_group }}\"\n    mode: 0600\n  with_items:\n    - { role: \"{{ hostvars[inventory_hostname].keepalived_role }}\" , priority: \"{{ hostvars[inventory_hostname].keepalived_priority }}\" }\n<\/pre>\n<p>What it does:<\/p>\n<ul>\n<li>Install <a href=\"https:\/\/fedoraproject.org\/wiki\/EPEL\" target=\"_blank\" rel=\"noopener noreferrer\">the Extra Packages for Enterprise Linux (EPEL)<\/a><\/li>\n<li>Install the dependencies for Patroni, HAProxy<\/li>\n<li>Create the \/etc\/hosts file<\/li>\n<li>Enable the watchdog service<\/li>\n<li>Download and install <a href=\"https:\/\/github.com\/etcd-io\/etcd\" target=\"_blank\" rel=\"noopener noreferrer\">etcd<\/a><\/li>\n<li>Integrate etcd into systemd<\/li>\n<li>Install Patroni, create the configuration files and integrate it into systemd<\/li>\n<li>Install and configure HAProxy<\/li>\n<\/ul>\n<p>This role uses several templates. The first one is used to create \/etc\/hosts:<\/p>\n<pre class=\"brush: bash; gutter: true; first-line: 1\">\ndwe@dwe:~\/Documents\/dbiProjects\/dbi_dmk_postgres\/dmk\/ansible$ cat roles\/patroni\/templates\/hosts.j2 \n#jinja2: trim_blocks:False\n127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4\n::1         localhost localhost.localdomain localhost6 localhost6.localdomain6\n\n{%- for h in ansible_play_hosts %}\n{{ hostvars[h]['ansible_enp0s8']['ipv4']['address'] }} {{ hostvars[h]['ansible_hostname'] }}\n{% endfor %}\n<\/pre>\n<p>The second one is used to create the etcd configuration:<\/p>\n<pre class=\"brush: bash; gutter: true; first-line: 1\">\ndwe@dwe:~\/Documents\/dbiProjects\/dbi_dmk_postgres\/dmk\/ansible$ cat roles\/patroni\/templates\/etcd.conf.j2 \nname: {{ ansible_hostname }}\ndata-dir: \/u02\/pgdata\/etcd\ninitial-advertise-peer-urls: http:\/\/{{ hostvars[inventory_hostname]['ansible_enp0s8']['ipv4']['address'] }}:2380\nlisten-peer-urls: http:\/\/{{ hostvars[inventory_hostname]['ansible_enp0s8']['ipv4']['address'] }}:2380\nlisten-client-urls: http:\/\/{{ hostvars[inventory_hostname]['ansible_enp0s8']['ipv4']['address'] }}:2379,http:\/\/localhost:2379\nadvertise-client-urls: http:\/\/{{ hostvars[inventory_hostname]['ansible_enp0s8']['ipv4']['address'] }}:2379\ninitial-cluster:{{ blank }} {%- for h in ansible_play_hosts %}\n{{ hostvars[h]['ansible_hostname'] }}=http:\/\/{{ hostvars[h]['ansible_enp0s8']['ipv4']['address'] }}:2380{% if not loop.last %},{% endif %}\n{% endfor %}\n<\/pre>\n<p>The third one creates the Patroni configuration:<\/p>\n<pre class=\"brush: bash; gutter: true; first-line: 1\">\ndwe@dwe:~\/Documents\/dbiProjects\/dbi_dmk_postgres\/dmk\/ansible$ cat roles\/patroni\/templates\/patroni.yml.j2 \nscope: {{ cluster_name }}\n#namespace: \/service\/\nname: {{ ansible_hostname }}\n\nrestapi:\n  listen: {{ hostvars[inventory_hostname]['ansible_enp0s8']['ipv4']['address'] }}:8008\n  connect_address: {{ hostvars[inventory_hostname]['ansible_enp0s8']['ipv4']['address'] }}:8008\n#  certfile: \/etc\/ssl\/certs\/ssl-cert-snakeoil.pem\n#  keyfile: \/etc\/ssl\/private\/ssl-cert-snakeoil.key\n#  authentication:\n#    username: username\n#    password: password\n\n# ctl:\n#   insecure: false # Allow connections to SSL sites without certs\n#   certfile: \/etc\/ssl\/certs\/ssl-cert-snakeoil.pem\n#   cacert: \/etc\/ssl\/certs\/ssl-cacert-snakeoil.pem\n\netcd:\n  host: 127.0.0.1:2379\n\nbootstrap:\n  # this section will be written into Etcd:\/\/\/config after initializing new cluster\n  # and all other cluster members will use it as a `global configuration`\n  dcs:\n    ttl: 30\n    loop_wait: 10\n    retry_timeout: 10\n    maximum_lag_on_failover: 1048576\n    postgresql:\n      use_pg_rewind: true\n      use_slots: true\n      parameters:\n        wal_level: 'hot_standby'\n        hot_standby: \"on\"\n        wal_keep_segments: 8\n        max_replication_slots: 10\n        wal_log_hints: \"on\"\n        listen_addresses: '*'\n        port: 5432\n        logging_collector: 'on'\n        log_truncate_on_rotation: 'on'\n        log_filename: 'postgresql-%a.log'\n        log_rotation_age: '1440'\n        log_line_prefix: '%m - %l - %p - %h - %u@%d - %x'\n        log_directory: 'pg_log'\n        log_min_messages: 'WARNING'\n        log_autovacuum_min_duration: '60s'\n        log_min_error_statement: 'NOTICE'\n        log_min_duration_statement: '30s'\n        log_checkpoints: 'on'\n        log_statement: 'ddl'\n        log_lock_waits: 'on'\n        log_temp_files: '0'\n        log_timezone: 'Europe\/Zurich'\n        log_connections: 'on'\n        log_disconnections: 'on'\n        log_duration: 'on'\n        client_min_messages: 'WARNING'\n        wal_level: 'replica'\n        hot_standby_feedback: 'on'\n        max_wal_senders: '10'\n        shared_buffers: '128MB'\n        work_mem: '8MB'\n        effective_cache_size: '512MB'\n        maintenance_work_mem: '64MB'\n        wal_compression: 'off'\n        max_wal_senders: '20'\n        shared_preload_libraries: 'pg_stat_statements'\n        autovacuum_max_workers: '6'\n        autovacuum_vacuum_scale_factor: '0.1'\n        autovacuum_vacuum_threshold: '50'\n        archive_mode: 'on'\n        archive_command: '\/bin\/true'\n        wal_log_hints: 'on'\n#      recovery_conf:\n#        restore_command: cp ..\/wal_archive\/%f %p\n\n  # some desired options for 'initdb'\n  initdb:  # Note: It needs to be a list (some options need values, others are switches)\n  - encoding: UTF8\n  - data-checksums\n\n  pg_hba:  # Add following lines to pg_hba.conf after running 'initdb'\n  - host replication replicator 192.168.22.0\/24 md5\n  - host all all 192.168.22.0\/24 md5\n#  - hostssl all all 0.0.0.0\/0 md5\n\n  # Additional script to be launched after initial cluster creation (will be passed the connection URL as parameter)\n# post_init: \/usr\/local\/bin\/setup_cluster.sh\n\n  # Some additional users users which needs to be created after initializing new cluster\n  users:\n    admin:\n      password: admin\n      options:\n        - createrole\n        - createdb\n    replicator:\n      password: postgres\n      options:\n        - superuser\n\npostgresql:\n  listen: {{ hostvars[inventory_hostname]['ansible_enp0s8']['ipv4']['address'] }}:5432\n  connect_address: {{ hostvars[inventory_hostname]['ansible_enp0s8']['ipv4']['address'] }}:5432\n  data_dir: \/u02\/pgdata\/{{ postgresql_major_version }}\/{{ cluster_name }}\/\n  bin_dir: \/u01\/app\/{{ postgres_user }}\/product\/{{ dmk_postgresql_version }}\/bin\n#  config_dir:\n  pgpass: \/u01\/app\/{{ postgres_user }}\/local\/dmk\/etc\/pgpass0\n  authentication:\n    replication:\n      username: replicator\n      password: postgres\n    superuser:\n      username: postgres\n      password: postgres\n  parameters:\n    unix_socket_directories: '\/tmp'\n\nwatchdog:\n  mode: automatic # Allowed values: off, automatic, required\n  device: \/dev\/watchdog\n  safety_margin: 5\n\ntags:\n    nofailover: false\n    noloadbalance: false\n    clonefrom: false\n    nosync: false\n<\/pre>\n<p>Then we have the configuration template for HAProxy:<\/p>\n<pre class=\"brush: bash; gutter: true; first-line: 1\">\ndwe@dwe:~\/Documents\/dbiProjects\/dbi_dmk_postgres\/dmk\/ansible$ cat roles\/patroni\/templates\/haproxy.cfg.j2 \n#jinja2: trim_blocks:False\nglobal\n    maxconn 100\n\ndefaults\n    log global\n    mode tcp\n    retries 2\n    timeout client 30m\n    timeout connect 4s\n    timeout server 30m\n    timeout check 5s\n\nlisten stats\n    mode http\n    bind *:7000\n    stats enable\n    stats uri \/\n    # stats auth haproxy:haproxy\n    # stats refresh 10s\n\nlisten {{ cluster_name }}\n    bind *:5000\n    option httpchk\n    http-check expect status 200\n    default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions\n    {%- for h in ansible_play_hosts %}\n    server postgresql_{{ hostvars[h]['ansible_enp0s8']['ipv4']['address'] }}_5432 {{ hostvars[h]['ansible_enp0s8']['ipv4']['address'] }}:5432 maxconn 100 check port 8008\n    {% endfor %}\n<\/pre>\n<p>Finally the template for keepalived:<\/p>\n<pre class=\"brush: bash; gutter: true; first-line: 1\">\ndwe@dwe:~\/Documents\/dbiProjects\/dbi_dmk_postgres\/dmk\/ansible$ cat roles\/patroni\/templates\/keepalived.conf.j2 \nvrrp_script chk_haproxy {\n    script \"killall -0 haproxy\"\n    interval 2\n    weight 2\n}\n\nvrrp_instance VI_1 {\n    interface enp0s8\n    state {{ item.role }} \n    virtual_router_id 51\n    priority {{ item.priority }}\n    virtual_ipaddress {\n      {{ virtual_ip }}\n  }\n  track_script {\n    chk_haproxy\n  }\n}\n<\/pre>\n<p>What is left are the systemd service files. The one for etcd:<\/p>\n<pre class=\"brush: bash; gutter: true; first-line: 1\">\ndwe@dwe:~\/Documents\/dbiProjects\/dbi_dmk_postgres\/dmk\/ansible$ cat roles\/patroni\/files\/etcd.service \n#\n# systemd integration for etcd \n# Put this file under \/etc\/systemd\/system\/etcd.service\n#     then: systemctl daemon-reload\n#     then: systemctl list-unit-files | grep etcd\n#     then: systemctl enable etcd.service\n#\n\n[Unit]\nDescription=dbi services etcd service\nAfter=network.target\n\n[Service]\nUser=postgres\nType=notify\nExecStart=\/u01\/app\/postgres\/local\/dmk\/bin\/etcd --config-file \/u01\/app\/postgres\/local\/dmk\/etc\/etcd.conf\nRestart=always\nRestartSec=10s\nLimitNOFILE=40000\n\n[Install]\nWantedBy=multi-user.target\n<\/pre>\n<p>For Patroni:<\/p>\n<pre class=\"brush: bash; gutter: true; first-line: 1\">\ndwe@dwe:~\/Documents\/dbiProjects\/dbi_dmk_postgres\/dmk\/ansible$ cat roles\/patroni\/files\/patroni.service \n#\n# systemd integration for patroni \n# Put this file under \/etc\/systemd\/system\/patroni.service\n#     then: systemctl daemon-reload\n#     then: systemctl list-unit-files | grep patroni\n#     then: systemctl enable patroni.service\n#\n\n[Unit]\nDescription=dbi services patroni service\nAfter=etcd.service syslog.target network.target\n\n[Service]\nUser=postgres\nGroup=postgres\nType=simple\nExecStartPre=-\/usr\/bin\/sudo \/sbin\/modprobe softdog\nExecStartPre=-\/usr\/bin\/sudo \/bin\/chown postgres \/dev\/watchdog\nExecStart=\/u01\/app\/postgres\/local\/dmk\/bin\/patroni \/u01\/app\/postgres\/local\/dmk\/etc\/patroni.yml\nExecReload=\/bin\/kill -s HUP $MAINPID\nKillMode=process\nRestart=no\nTimeoutSec=30\n\n[Install]\nWantedBy=multi-user.target\n<\/pre>\n<p>The last bit is the site definition which combines all of the above.<\/p>\n<pre class=\"brush: bash; gutter: true; first-line: 1\">\ndwe@dwe:~\/Documents\/dbiProjects\/dbi_dmk_postgres\/dmk\/ansible$ cat roles\/patroni\/site.yml \n---\n# This playbook deploys a three node patroni PostgreSQL cluster with HAProxy\n\n- hosts: patroni-servers\n  become: true\n  become_user: root\n\n  roles:\n    - common\n    - patroni\n<\/pre>\n<p>Once all of that is in place the palybook can be executed:<\/p>\n<pre class=\"brush: bash; gutter: true; first-line: 1\">\ndwe@dwe:~\/Documents\/dbiProjects\/dbi_dmk_postgres\/dmk\/ansible$ ansible-playbook -i ..\/patroni patroni\/site.yml -u postgres\n<\/pre>\n<p>This runs for a couple of minutes as especially upgrading all the operating system packages and comling PostgreSQL will take some time. Once it completed you only need to reboot the systems and your cluster is ready:<\/p>\n<pre class=\"brush: bash; gutter: true; first-line: 1\">\npostgres@patroni1:\/home\/postgres\/ [pg11.1] patronictl list \n+---------+----------+----------------+--------+---------+-----------+\n| Cluster |  Member  |      Host      |  Role  |  State  | Lag in MB |\n+---------+----------+----------------+--------+---------+-----------+\n|   PG1   | patroni1 | 192.168.22.240 | Leader | running |       0.0 |\n|   PG1   | patroni2 | 192.168.22.241 |        | running |       0.0 |\n|   PG1   | patroni3 | 192.168.22.242 |        | running |       0.0 |\n+---------+----------+----------------+--------+---------+-----------+\n<\/pre>\n<p>HAProy is running as well on all three nodes and you can check that by pointing your browser to any of the hosts on port 7000:<br \/>\n<a href=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2022\/04\/Selection_062-1.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2022\/04\/Selection_062-1.png\" alt=\"Selection_062\" width=\"1910\" height=\"492\" class=\"aligncenter size-full wp-image-30400\" \/><\/a><\/p>\n<p>Hope that helps.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Automation is key today, nobody wants to do the same tasks over and over and again. Cloud without automation is not even possible. There are several tools around that help with automation and one of the most popular is Ansible. We already have several posts about Ansible on our blog platform but this one will [&hellip;]<\/p>\n","protected":false},"author":29,"featured_media":12189,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[229],"tags":[150,1348,1543,77],"type_dbi":[],"class_list":["post-12188","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-database-administration-monitoring","tag-ansible","tag-haproxy","tag-patroni","tag-postgresql"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v27.2 (Yoast SEO v27.2) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Using Ansible to bring up a three node Patroni cluster in minutes - dbi Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Using Ansible to bring up a three node Patroni cluster in minutes\" \/>\n<meta property=\"og:description\" content=\"Automation is key today, nobody wants to do the same tasks over and over and again. Cloud without automation is not even possible. There are several tools around that help with automation and one of the most popular is Ansible. We already have several posts about Ansible on our blog platform but this one will [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/\" \/>\n<meta property=\"og:site_name\" content=\"dbi Blog\" \/>\n<meta property=\"article:published_time\" content=\"2019-01-07T06:48:41+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2022\/04\/Selection_062-1.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1910\" \/>\n\t<meta property=\"og:image:height\" content=\"492\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Daniel Westermann\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@westermanndanie\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Daniel Westermann\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"18 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/\"},\"author\":{\"name\":\"Daniel Westermann\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/8d08e9bd996a89bd75c0286cbabf3c66\"},\"headline\":\"Using Ansible to bring up a three node Patroni cluster in minutes\",\"datePublished\":\"2019-01-07T06:48:41+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/\"},\"wordCount\":700,\"commentCount\":0,\"image\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2022\/04\/Selection_062-1.png\",\"keywords\":[\"Ansible\",\"HAProxy\",\"Patroni\",\"PostgreSQL\"],\"articleSection\":[\"Database Administration &amp; Monitoring\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/\",\"url\":\"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/\",\"name\":\"Using Ansible to bring up a three node Patroni cluster in minutes - dbi Blog\",\"isPartOf\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2022\/04\/Selection_062-1.png\",\"datePublished\":\"2019-01-07T06:48:41+00:00\",\"author\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/8d08e9bd996a89bd75c0286cbabf3c66\"},\"breadcrumb\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/#primaryimage\",\"url\":\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2022\/04\/Selection_062-1.png\",\"contentUrl\":\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2022\/04\/Selection_062-1.png\",\"width\":1910,\"height\":492},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Accueil\",\"item\":\"https:\/\/www.dbi-services.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Using Ansible to bring up a three node Patroni cluster in minutes\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/#website\",\"url\":\"https:\/\/www.dbi-services.com\/blog\/\",\"name\":\"dbi Blog\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.dbi-services.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/8d08e9bd996a89bd75c0286cbabf3c66\",\"name\":\"Daniel Westermann\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/secure.gravatar.com\/avatar\/31350ceeecb1dd8986339a29bf040d4cd3cd087d410deccd8f55234466d6c317?s=96&d=mm&r=g\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/31350ceeecb1dd8986339a29bf040d4cd3cd087d410deccd8f55234466d6c317?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/31350ceeecb1dd8986339a29bf040d4cd3cd087d410deccd8f55234466d6c317?s=96&d=mm&r=g\",\"caption\":\"Daniel Westermann\"},\"description\":\"Daniel Westermann is Principal Consultant and Technology Leader Open Infrastructure at dbi services. He has more than 15 years of experience in management, engineering and optimization of databases and infrastructures, especially on Oracle and PostgreSQL. Since the beginning of his career, he has specialized in Oracle Technologies and is Oracle Certified Professional 12c and Oracle Certified Expert RAC\/GridInfra. Over time, Daniel has become increasingly interested in open source technologies, becoming \u201cTechnology Leader Open Infrastructure\u201d and PostgreSQL expert. \u00a0Based on community or EnterpriseDB tools, he develops and installs complex high available solutions with PostgreSQL. He is also a certified PostgreSQL Plus 9.0 Professional and a Postgres Advanced Server 9.4 Professional. He is a regular speaker at PostgreSQL conferences in Switzerland and Europe. Today Daniel is also supporting our customers on AWS services such as AWS RDS, database migrations into the cloud, EC2 and automated infrastructure management with AWS SSM (System Manager). He is a certified AWS Solutions Architect Professional. Prior to dbi services, Daniel was Management System Engineer at LC SYSTEMS-Engineering AG in Basel. Before that, he worked as Oracle Developper &amp;\u00a0Project Manager at Delta Energy Solutions AG in Basel (today Powel AG). Daniel holds a diploma in Business Informatics (DHBW, Germany). His branch-related experience mainly covers the pharma industry, the financial sector, energy, lottery and telecommunications.\",\"sameAs\":[\"https:\/\/x.com\/westermanndanie\"],\"url\":\"https:\/\/www.dbi-services.com\/blog\/author\/daniel-westermann\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Using Ansible to bring up a three node Patroni cluster in minutes - dbi Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/","og_locale":"en_US","og_type":"article","og_title":"Using Ansible to bring up a three node Patroni cluster in minutes","og_description":"Automation is key today, nobody wants to do the same tasks over and over and again. Cloud without automation is not even possible. There are several tools around that help with automation and one of the most popular is Ansible. We already have several posts about Ansible on our blog platform but this one will [&hellip;]","og_url":"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/","og_site_name":"dbi Blog","article_published_time":"2019-01-07T06:48:41+00:00","og_image":[{"width":1910,"height":492,"url":"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2022\/04\/Selection_062-1.png","type":"image\/png"}],"author":"Daniel Westermann","twitter_card":"summary_large_image","twitter_creator":"@westermanndanie","twitter_misc":{"Written by":"Daniel Westermann","Est. reading time":"18 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/#article","isPartOf":{"@id":"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/"},"author":{"name":"Daniel Westermann","@id":"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/8d08e9bd996a89bd75c0286cbabf3c66"},"headline":"Using Ansible to bring up a three node Patroni cluster in minutes","datePublished":"2019-01-07T06:48:41+00:00","mainEntityOfPage":{"@id":"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/"},"wordCount":700,"commentCount":0,"image":{"@id":"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/#primaryimage"},"thumbnailUrl":"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2022\/04\/Selection_062-1.png","keywords":["Ansible","HAProxy","Patroni","PostgreSQL"],"articleSection":["Database Administration &amp; Monitoring"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/","url":"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/","name":"Using Ansible to bring up a three node Patroni cluster in minutes - dbi Blog","isPartOf":{"@id":"https:\/\/www.dbi-services.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/#primaryimage"},"image":{"@id":"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/#primaryimage"},"thumbnailUrl":"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2022\/04\/Selection_062-1.png","datePublished":"2019-01-07T06:48:41+00:00","author":{"@id":"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/8d08e9bd996a89bd75c0286cbabf3c66"},"breadcrumb":{"@id":"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/#primaryimage","url":"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2022\/04\/Selection_062-1.png","contentUrl":"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2022\/04\/Selection_062-1.png","width":1910,"height":492},{"@type":"BreadcrumbList","@id":"https:\/\/www.dbi-services.com\/blog\/using-ansible-to-bring-up-a-three-node-patroni-cluster-in-minutes\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Accueil","item":"https:\/\/www.dbi-services.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Using Ansible to bring up a three node Patroni cluster in minutes"}]},{"@type":"WebSite","@id":"https:\/\/www.dbi-services.com\/blog\/#website","url":"https:\/\/www.dbi-services.com\/blog\/","name":"dbi Blog","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.dbi-services.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/8d08e9bd996a89bd75c0286cbabf3c66","name":"Daniel Westermann","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/31350ceeecb1dd8986339a29bf040d4cd3cd087d410deccd8f55234466d6c317?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/31350ceeecb1dd8986339a29bf040d4cd3cd087d410deccd8f55234466d6c317?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/31350ceeecb1dd8986339a29bf040d4cd3cd087d410deccd8f55234466d6c317?s=96&d=mm&r=g","caption":"Daniel Westermann"},"description":"Daniel Westermann is Principal Consultant and Technology Leader Open Infrastructure at dbi services. He has more than 15 years of experience in management, engineering and optimization of databases and infrastructures, especially on Oracle and PostgreSQL. Since the beginning of his career, he has specialized in Oracle Technologies and is Oracle Certified Professional 12c and Oracle Certified Expert RAC\/GridInfra. Over time, Daniel has become increasingly interested in open source technologies, becoming \u201cTechnology Leader Open Infrastructure\u201d and PostgreSQL expert. \u00a0Based on community or EnterpriseDB tools, he develops and installs complex high available solutions with PostgreSQL. He is also a certified PostgreSQL Plus 9.0 Professional and a Postgres Advanced Server 9.4 Professional. He is a regular speaker at PostgreSQL conferences in Switzerland and Europe. Today Daniel is also supporting our customers on AWS services such as AWS RDS, database migrations into the cloud, EC2 and automated infrastructure management with AWS SSM (System Manager). He is a certified AWS Solutions Architect Professional. Prior to dbi services, Daniel was Management System Engineer at LC SYSTEMS-Engineering AG in Basel. Before that, he worked as Oracle Developper &amp;\u00a0Project Manager at Delta Energy Solutions AG in Basel (today Powel AG). Daniel holds a diploma in Business Informatics (DHBW, Germany). His branch-related experience mainly covers the pharma industry, the financial sector, energy, lottery and telecommunications.","sameAs":["https:\/\/x.com\/westermanndanie"],"url":"https:\/\/www.dbi-services.com\/blog\/author\/daniel-westermann\/"}]}},"_links":{"self":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts\/12188","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/users\/29"}],"replies":[{"embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/comments?post=12188"}],"version-history":[{"count":0,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts\/12188\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/media\/12189"}],"wp:attachment":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/media?parent=12188"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/categories?post=12188"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/tags?post=12188"},{"taxonomy":"type","embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/type_dbi?post=12188"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}