diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..0e0df1f --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,57 @@ +name: Molecule + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + molecule: + runs-on: ubuntu-latest + strategy: + max-parallel: 4 + fail-fast: false + matrix: + scenario: + - tag: centos-systemd:stream9 + user: quay.io/gotmax23 + - tag: debian-systemd:12 + user: quay.io/gotmax23 + - tag: ubuntu-systemd:20.04 + user: quay.io/gotmax23 + - tag: ubuntu-systemd:22.04 + user: quay.io/gotmax23 + python-version: + - "3.10" + ansible-version: + - 2.12.10 + - 2.17.0 + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + path: "${{ github.repository }}" + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python3 -m pip install --upgrade pip + python3 -m pip install -r ${{ github.repository }}/test-requirements.txt + - name: Install ansible + run: | + python3 -m pip install ansible-core==${{ matrix.ansible-version }} + - name: Install ansible collections + run: | + ansible-galaxy install -r ${{ github.repository }}/test-requirements.yml + - name: Test with molecule + run: | + cd ${{ github.repository }} + export docker_image_tag="${{ matrix.scenario.tag }}" + export docker_user="${{ matrix.scenario.user }}" + python3 -m molecule test diff --git a/.gitignore b/.gitignore index 6fc3bc5..7778366 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ .vagrant vagrant - +.cache diff --git a/.yamllint.yml b/.yamllint.yml new file mode 100644 index 0000000..45a219b --- /dev/null +++ b/.yamllint.yml @@ -0,0 +1,14 @@ +--- +rules: + line-length: disable + truthy: + allowed-values: + - 'True' + - 'true' + - 'yes' + - 'False' + - 'false' + - 'no' + +ignore: | + .github/ diff --git a/README.md b/README.md index 643e3f4..c0c48e4 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,13 @@ -ansible-pacemaker-corosync role +ansible-role-pacemaker_corosync [![Build Status](https://github.com/noonedeadpunk/ansible-pacemaker-corosync/actions/workflows/main.yml/badge.svg?branch=master)] + =============================== -Deploys corosync/pacemaker on Ubuntu 14.04 +Deploys corosync/pacemaker # Variables - `pacemaker_corosync_group`: Ansible group name for corosync cluster (default: false, *mandatory*) +- `pacemaker_remote_group`: Ansible group name for pacemaker-remote cluster (default: false, *mandatory*) - `pacemaker_corosync_ring_interface`: Interface to use for ring0 communications (default: false, *mandatory*) - +- `pacemaker_remote_ring_interface`: Interface to use for ring0 communications on remote hosts (default: pacemaker_corosync_ring_interface) +- `pacemaker_corosync_fqdn`: Whether use inventory_hostname or ansible_fqdn as node name for corosync (default: false) diff --git a/defaults/main.yml b/defaults/main.yml index 86d8e38..fb4be33 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -1,2 +1,15 @@ +--- + +pacemaker_corosync_fqdn: false pacemaker_corosync_group: false +pacemaker_remote_group: false pacemaker_corosync_ring_interface: false +pacemaker_remote_ring_interface: "{{ pacemaker_corosync_ring_interface }}" +pacemaker_corosync_use_syslog: true +pacemaker_corosync_use_logfile: false + + +# Centos EPEL repository options (for haveged) +pacemaker_corosync_haveged_enabled: True +pacemaker_corosync_centos_epel_mirror: "{{ centos_epel_mirror | default('http://download.fedoraproject.org/pub/epel') }}" +pacemaker_corosync_centos_epel_key: "{{ centos_epel_key | default('http://download.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-' ~ ansible_facts['distribution_major_version']) }}" diff --git a/examples/play.yml b/examples/play.yml new file mode 100644 index 0000000..64636f6 --- /dev/null +++ b/examples/play.yml @@ -0,0 +1,7 @@ +--- + +- name: Install pacemaker-corosync + hosts: "{{ pacemaker_corosync_group }}" + become: true + roles: + - pacemaker_corosync diff --git a/handlers/main.yml b/handlers/main.yml index 10a59be..af28150 100644 --- a/handlers/main.yml +++ b/handlers/main.yml @@ -2,6 +2,6 @@ service: name: "{{ item }}" state: restarted + enabled: yes with_items: - corosync - - pacemaker diff --git a/meta/main.yml b/meta/main.yml index 31b8182..86463e1 100644 --- a/meta/main.yml +++ b/meta/main.yml @@ -3,14 +3,21 @@ dependencies: [] galaxy_info: - author: Michel Blanc + author: Dmitriy Rabotyagov company: ACME Corp - description: ansible-pacemaker-corosync role + namespace: noonedeadpunk + role_name: pacemaker_corosync + description: Deploys pacemaker and corosync stack. license: MIT - min_ansible_version: 1.6 + min_ansible_version: 2.6 platforms: - - name: Ubuntu - versions: - - all - categories: - - system + - name: Ubuntu + versions: + - bionic + - focal + - name: EL + versions: + - 7 + - 8 + galaxy_tags: + - system diff --git a/molecule/default/cleanup.yml b/molecule/default/cleanup.yml new file mode 100644 index 0000000..37ec6ba --- /dev/null +++ b/molecule/default/cleanup.yml @@ -0,0 +1,10 @@ +--- +- name: Prepare molecule instance + hosts: all[0] + gather_facts: no + tasks: + - name: Create a network with custom IPAM config + delegate_to: localhost + community.general.docker_network: + name: pacemaker-network + state: absent diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml new file mode 100644 index 0000000..2262dc1 --- /dev/null +++ b/molecule/default/converge.yml @@ -0,0 +1,41 @@ +--- +- name: Converge + hosts: all + vars_files: + - "tests/pacemaker_corosync_local.yml" + pre_tasks: + - name: Apt update and install rsync, ping, iproute + apt: + update_cache: yes + name: + - rsync + - inetutils-ping + - iproute2 + state: present + when: ansible_facts['os_family'] == "Debian" + + - name: Yum install iproute to fix undefined ansible_default_ipv4.address + yum: + name: iproute + state: present + when: + - ansible_facts['distribution'] == "CentOS" + + - name: Add a container to a network, leaving existing containers connected + delegate_to: localhost + community.general.docker_network: + name: pacemaker-network + connected: + - "{{ inventory_hostname }}" + appends: yes + + - name: Re-collect network facts required after installation iproute + setup: + gather_subset: network + + - name: Show ansible_interfaces + debug: + var: ansible_facts['interfaces'] + + roles: + - role: "{{ playbook_dir | dirname | dirname | basename }}" diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml new file mode 100644 index 0000000..c25014b --- /dev/null +++ b/molecule/default/molecule.yml @@ -0,0 +1,27 @@ +--- +lint: | + set -e + yamllint . + ansible-lint -vv --exclude=.tox --exclude=molecule/default/converge.yml -x 204,208 +dependency: + name: galaxy +driver: + name: docker +verifier: + name: ansible +provisioner: + name: ansible + options: + v: True + log: True + config_options: + defaults: + inject_facts_as_vars: false +platforms: + - name: pacemaker + image: "${docker_user:-quay.io/gotmax23}/${docker_image_tag:-ubuntu-systemd:focal}" + command: "" + pre_build_image: true + groups: + - corosync + privileged: true diff --git a/molecule/default/prepare.yml b/molecule/default/prepare.yml new file mode 100644 index 0000000..382580f --- /dev/null +++ b/molecule/default/prepare.yml @@ -0,0 +1,12 @@ +--- +- name: Prepare molecule instance + hosts: all[0] + tasks: + - name: Create a network with custom IPAM config + delegate_to: localhost + community.general.docker_network: + name: pacemaker-network + ipam_config: + - subnet: 192.168.33.0/24 + gateway: 192.168.33.254 + iprange: 192.168.33.0/26 diff --git a/molecule/default/tests/pacemaker_corosync_local.yml b/molecule/default/tests/pacemaker_corosync_local.yml new file mode 100644 index 0000000..1e142b1 --- /dev/null +++ b/molecule/default/tests/pacemaker_corosync_local.yml @@ -0,0 +1,4 @@ +--- + +pacemaker_corosync_group: corosync +pacemaker_corosync_ring_interface: eth1 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..c253c0e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +ansible==2.10.5 +docker==4.4.1 +molecule[docker]==3.2.2 +ansible-lint==4.3.7 \ No newline at end of file diff --git a/tasks/main.yml b/tasks/main.yml index 31e2e8a..ae8b141 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -1,2 +1,12 @@ -- include: check_vars.yml tags=pacemaker,check -- include: pacemaker.yml tags=pacemaker +--- + +- name: Importing variables check + import_tasks: check_vars.yml + tags: + - pacemaker + - check + +- name: Importing pacemaker tasks + import_tasks: pacemaker.yml + tags: + - pacemaker diff --git a/tasks/pacemaker.yml b/tasks/pacemaker.yml index 9f229f2..7890d8c 100644 --- a/tasks/pacemaker.yml +++ b/tasks/pacemaker.yml @@ -1,12 +1,82 @@ -- name: Installs pacemaker & corosync - apt: pkg={{item}} state=present +--- + +- name: Ensure config-manager is present for dnf + package: + name: dnf-plugins-core + state: present + when: + - ansible_facts['os_family'] | lower == 'redhat' + - ansible_facts['distribution_major_version'] is version('8', '>=') + +- name: Enable HighAvailability repository + command: "dnf config-manager --enable {{ _centos_ha_repo_name[ansible_facts['distribution_major_version']] }}" + changed_when: false + when: + - ansible_facts['os_family'] | lower == 'redhat' + +- name: Installs corosync + package: + name: "{{ item }}" + state: present with_items: - corosync + +- name: Installs pacemaker + package: + name: "{{ item }}" + state: present + with_items: - pacemaker + when: "inventory_hostname in groups[pacemaker_corosync_group]" + +- name: Installs pacemaker-remote + package: + name: "{{ item }}" + state: present + with_items: + - pacemaker-remote + when: + - _pacemaker_remote_group_exists + - "inventory_hostname in groups[pacemaker_remote_group]" + +- name: Install EPEL repo for CentOS + block: + - name: Download EPEL gpg keys + get_url: + url: "{{ pacemaker_corosync_centos_epel_key }}" + dest: /etc/pki/rpm-gpg + register: _get_yum_keys + until: _get_yum_keys is success + retries: 5 + delay: 2 + + - name: Install EPEL gpg keys + rpm_key: + key: "/etc/pki/rpm-gpg/{{ pacemaker_corosync_centos_epel_key.split('/')[-1] }}" + state: present + + - name: Install the EPEL repository + yum_repository: + name: epel-haveged + baseurl: "{{ pacemaker_corosync_centos_epel_mirror ~ '/' ~ ansible_facts['distribution_major_version'] ~ (ansible_facts['distribution_major_version'] is version('8', '>=')) | ternary('/Everything/', '/') ~ ansible_facts['architecture'] }}" + description: "Extra Packages for Enterprise Linux {{ ansible_facts['distribution_major_version'] }} - $basearch" + gpgcheck: yes + enabled: yes + state: present + includepkgs: "haveged" + register: install_epel_repo + until: install_epel_repo is success + retries: 5 + delay: 2 + when: + - pacemaker_corosync_haveged_enabled | bool + - ansible_facts['os_family'] | lower == 'redhat' - name: Install haveged - apt: pkg=haveged state=present - when: haveged_enabled | default(true) + package: + name: haveged + state: present + when: pacemaker_corosync_haveged_enabled | bool - name: Generates corosync key become: true @@ -15,41 +85,48 @@ args: creates: /etc/corosync/authkey when: inventory_hostname == groups[pacemaker_corosync_group][0] - register: __corosync_authkey_created notify: Restart corosync -- name: Generate tmpdir for authkey - local_action: tempfile +- name: Generate tmpfile for authkey + tempfile: + state: file register: authkey_tempfile changed_when: False check_mode: no + delegate_to: localhost when: inventory_hostname != groups[pacemaker_corosync_group][0] - name: Fetch authkey for other nodes - fetch: src=/etc/corosync/authkey dest="{{ authkey_tempfile.path }}" flat=yes + fetch: + src: /etc/corosync/authkey + dest: "{{ authkey_tempfile.path }}" + flat: yes delegate_to: "{{ groups[pacemaker_corosync_group][0] }}" changed_when: False check_mode: no when: inventory_hostname != groups[pacemaker_corosync_group][0] - name: Copy authkey to other nodes - copy: src="{{ authkey_tempfile.path }}" dest=/etc/corosync/authkey mode=0400 + copy: + src: "{{ authkey_tempfile.path }}" + dest: /etc/corosync/authkey + mode: "0400" when: inventory_hostname != groups[pacemaker_corosync_group][0] notify: Restart corosync - name: Clean up tmpdir - local_action: - module: file + file: path: "{{ authkey_tempfile.path }}" state: "absent" changed_when: False check_mode: no + delegate_to: localhost when: inventory_hostname != groups[pacemaker_corosync_group][0] - name: Chowns authkeys file: path: /etc/corosync/authkey - mode: 0400 + mode: "0400" owner: root notify: Restart corosync @@ -57,50 +134,44 @@ template: src: corosync.conf.j2 dest: /etc/corosync/corosync.conf - mode: 0400 + mode: "0400" owner: root notify: Restart corosync +- name: Creates log directory + file: + path: /var/log/corosync + state: directory + mode: "0775" + when: pacemaker_corosync_use_logfile | bool + - name: Adds logrotate config for corosync template: src: corosync_logrotate.conf.j2 dest: /etc/logrotate.d/corosync - mode: 0644 + mode: "0644" owner: root + when: pacemaker_corosync_use_logfile | bool - name: Creates services directory file: path: /etc/corosync/service.d/ state: directory + mode: "0755" - name: Adds pacemaker service copy: src: pcmk dest: /etc/corosync/service.d/pcmk owner: root - mode: 0400 + mode: "0400" notify: Restart corosync - name: Adds ferm filtering template: - src: "../templates/ferm.j2" + src: "ferm.j2" dest: /etc/ferm/filter-input.d/60_corosync.conf + mode: "0640" when: ferm_enabled | default(false) tags: ferm notify: Restart ferm - -- name: Enables corosync at boot - copy: - dest: /etc/default/corosync - content: "START=yes" - when: ansible_service_mgr != "systemd" - -- name: Enables corosync at boot - systemd: name=corosync.service state=started enabled=yes - when: ansible_service_mgr == "systemd" - -- name: Registers pacemaker service - service: - name: pacemaker - enabled: true - diff --git a/templates/corosync.conf.j2 b/templates/corosync.conf.j2 index 98ecfcf..7ab2a59 100644 --- a/templates/corosync.conf.j2 +++ b/templates/corosync.conf.j2 @@ -1,3 +1,9 @@ +{% if inventory_hostname in groups[pacemaker_corosync_group] %} +{% set _pacemaker_corosync_bind_addr = ansible_facts[pacemaker_corosync_ring_interface | replace('-', '_')]['ipv4']['address'] %} +{% elif pacemaker_remote_group in groups and inventory_hostname in groups[pacemaker_remote_group] %} +{% set _pacemaker_corosync_bind_addr = ansible_facts[pacemaker_remote_ring_interface | replace('-', '_')]['ipv4']['address'] %} +{% endif %} + totem { version: 2 cluster_name: {{ pacemaker_corosync_group }} @@ -6,7 +12,7 @@ totem { {% endif %} interface { ringnumber: 0 - bindnetaddr: {{ hostvars[inventory_hostname]['ansible_' + pacemaker_corosync_ring_interface].ipv4.address }} + bindnetaddr: {{ _pacemaker_corosync_bind_addr }} broadcast: yes mcastport: 5405 } @@ -27,20 +33,26 @@ quorum { {% if pacemaker_enable_nodelist|default(true) %} nodelist { -{% for node in groups[pacemaker_corosync_group]|sort %} + +{% for node in (_pacemaker_remote_group_exists | ternary(groups[pacemaker_remote_group], []) + groups[pacemaker_corosync_group]) | sort %} +{% if node in groups[pacemaker_corosync_group] %} +{% set _pacemaker_corosync_ring_interface = pacemaker_corosync_ring_interface %} +{% elif pacemaker_remote_group in groups and inventory_hostname in groups[pacemaker_remote_group] %} +{% set _pacemaker_corosync_ring_interface = pacemaker_remote_ring_interface %} +{% endif %} node { - ring0_addr: {{ hostvars[node]['ansible_' + pacemaker_corosync_ring_interface].ipv4.address }} - name: {{ node }} + ring0_addr: {{ hostvars[node]['ansible_facts'][_pacemaker_corosync_ring_interface | replace('-', '_')]['ipv4']['address'] }} + name: {{ pacemaker_corosync_fqdn | bool | ternary(hostvars[node]['ansible_facts']['fqdn'], node) }} nodeid: {{ loop.index }} } {% endfor %} -} {% endif %} - +} logging { - to_logfile: yes + to_logfile: {{ (pacemaker_corosync_use_logfile | bool) | ternary('yes', 'no') }} + {% if pacemaker_corosync_use_logfile | bool %} logfile: /var/log/corosync/corosync.log - to_syslog: yes + {% endif %} + to_syslog: {{ (pacemaker_corosync_use_syslog | bool) | ternary('yes', 'no') }} timestamp: on } - diff --git a/templates/ferm.j2 b/templates/ferm.j2 index d38112d..8c8ed4d 100644 --- a/templates/ferm.j2 +++ b/templates/ferm.j2 @@ -1,10 +1,10 @@ {% set cluster_members = [] -%} {%- for node in groups[pacemaker_corosync_group] %} - {%- set _ = cluster_members.append(hostvars[node]['ansible_' + pacemaker_corosync_ring_interface].ipv4.address) -%} + {%- set _ = cluster_members.append(ansible_facts[pacemaker_corosync_ring_interface]['ipv4']['address']) -%} {% endfor -%} # Allow access to cluster members protocol udp dport (5404 5405 5406) { - @def $ITEMS = ( @ipfilter( ( {{ cluster_members | unique | join(" ") }} ) ) + @def $ITEMS = ( @ipfilter( ( {{ cluster_members | unique | join(" ") }} ) ) ); saddr $ITEMS ACCEPT; } diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..41cc93c --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,8 @@ +bashate>=0.2 # Apache-2.0 +flake8<2.6.0,>=2.5.4 # MIT +docker<=7.0.0 +molecule==5.1.0 +molecule-plugins==23.5.0 +ansible-lint==6.19.0 +rich>=9.5.1,<13.5.0 +requests<=2.31.0 diff --git a/test-requirements.yml b/test-requirements.yml new file mode 100644 index 0000000..f3ad811 --- /dev/null +++ b/test-requirements.yml @@ -0,0 +1,5 @@ +--- +collections: + - community.general + - community.docker + - ansible.netcommon diff --git a/vars/main.yml b/vars/main.yml new file mode 100644 index 0000000..1fc549d --- /dev/null +++ b/vars/main.yml @@ -0,0 +1,7 @@ +--- + +_pacemaker_remote_group_exists: "{{ (pacemaker_remote_group is defined and pacemaker_remote_group in groups) }}" + +_centos_ha_repo_name: + '8': ha + '9': highavailability