From f7e20c77e1a167fa017034e726a929a7b9cfc86f Mon Sep 17 00:00:00 2001 From: Marcos Dias Date: Mon, 2 Feb 2026 23:24:17 -0300 Subject: [PATCH 1/9] Added NUMA detection and configure mongodb to work correctly --- .../mongodb/tasks/configure-mongodb-numa.yml | 120 ++++++++++++++++++ roles/mongodb/tasks/install-mongodb.yml | 5 + 2 files changed, 125 insertions(+) create mode 100644 roles/mongodb/tasks/configure-mongodb-numa.yml diff --git a/roles/mongodb/tasks/configure-mongodb-numa.yml b/roles/mongodb/tasks/configure-mongodb-numa.yml new file mode 100644 index 00000000..a78ea015 --- /dev/null +++ b/roles/mongodb/tasks/configure-mongodb-numa.yml @@ -0,0 +1,120 @@ +# Copyright (c) 2025, Itential, Inc +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) +--- + +- name: Detect NUMA nodes + ansible.builtin.shell: + cmd: | + if [ -d /sys/devices/system/node ]; then + ls -d /sys/devices/system/node/node[0-9]* 2>/dev/null | wc -l + else + echo 0 + fi + args: + executable: /bin/bash + register: mongodb_numa_nodes + changed_when: false + become: true + +- name: Set NUMA enabled fact + ansible.builtin.set_fact: + mongodb_numa_enabled: "{{ (mongodb_numa_nodes.stdout | int) > 1 }}" + +- name: Ensure numactl is installed when NUMA is enabled + ansible.builtin.package: + name: numactl + state: present + when: mongodb_numa_enabled + become: true + +- name: Discover numactl path + ansible.builtin.shell: + cmd: "command -v numactl || true" + args: + executable: /bin/bash + register: mongodb_numactl_path_result + changed_when: false + when: mongodb_numa_enabled + become: true + +- name: Discover mongod path + ansible.builtin.shell: + cmd: "command -v mongod || true" + args: + executable: /bin/bash + register: mongodb_mongod_path_result + changed_when: false + when: mongodb_numa_enabled + become: true + +- name: Set NUMA helper facts + ansible.builtin.set_fact: + mongodb_numactl_path: "{{ mongodb_numactl_path_result.stdout | default('', true) }}" + mongodb_mongod_path: "{{ mongodb_mongod_path_result.stdout | default('/usr/bin/mongod', true) }}" + mongodb_numactl_present: "{{ (mongodb_numactl_path_result.stdout | default('', true) | length) > 0 }}" + when: mongodb_numa_enabled + +- name: Apply NUMA sysctl settings + ansible.posix.sysctl: + name: vm.zone_reclaim_mode + value: "{{ mongodb_vm_zone_reclaim_mode }}" + state: present + sysctl_file: "{{ mongodb_sysctl_file }}" + reload: true + when: mongodb_numa_enabled + become: true + +- name: Disable kernel NUMA balancing + ansible.posix.sysctl: + name: kernel.numa_balancing + value: "0" + state: present + sysctl_file: "{{ mongodb_sysctl_file }}" + reload: true + when: mongodb_numa_enabled + become: true + +- name: Ensure mongod systemd drop-in directory exists + ansible.builtin.file: + path: /etc/systemd/system/mongod.service.d + state: directory + mode: "0755" + when: + - mongodb_numa_enabled + - mongodb_numactl_present + become: true + +- name: Configure mongod to run with numactl interleave + ansible.builtin.copy: + dest: /etc/systemd/system/mongod.service.d/numa.conf + mode: "0644" + content: | + [Service] + ExecStart= + ExecStart={{ mongodb_numactl_path }} --interleave=all {{ mongodb_mongod_path }} --config {{ mongodb_conf_file }} + register: mongodb_numa_dropin + when: + - mongodb_numa_enabled + - mongodb_numactl_present + become: true + +- name: Reload systemd if NUMA drop-in changed + ansible.builtin.systemd: + daemon_reload: true + when: + - mongodb_numa_enabled + - mongodb_numactl_present + - mongodb_numa_dropin is defined + - mongodb_numa_dropin.changed + become: true + +- name: Restart mongod to apply NUMA settings + ansible.builtin.systemd: + name: mongod + state: restarted + when: + - mongodb_numa_enabled + - mongodb_numactl_present + - mongodb_numa_dropin is defined + - mongodb_numa_dropin.changed + become: true diff --git a/roles/mongodb/tasks/install-mongodb.yml b/roles/mongodb/tasks/install-mongodb.yml index d2d1784b..82ed07cb 100644 --- a/roles/mongodb/tasks/install-mongodb.yml +++ b/roles/mongodb/tasks/install-mongodb.yml @@ -68,6 +68,11 @@ file: configure-mongodb-logrotate.yml tags: configure_logrotate +- name: Configure NUMA for MongoDB + ansible.builtin.include_tasks: + file: configure-mongodb-numa.yml + tags: configure_numa + # Check if firewalld is running, if it is then open the appropriate ports - name: Gather service facts ansible.builtin.service_facts: From 3853805e8da8b3bb4f231d9cd0212bbb4eaaeab1 Mon Sep 17 00:00:00 2001 From: Marcos Dias Date: Tue, 3 Feb 2026 12:43:31 -0300 Subject: [PATCH 2/9] Remove redundant become directives in MongoDB config Removed unnecessary 'become: true' directives from various tasks in the configure-mongodb-numa.yml file. --- roles/mongodb/tasks/configure-mongodb-numa.yml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/roles/mongodb/tasks/configure-mongodb-numa.yml b/roles/mongodb/tasks/configure-mongodb-numa.yml index a78ea015..0b3ebe2e 100644 --- a/roles/mongodb/tasks/configure-mongodb-numa.yml +++ b/roles/mongodb/tasks/configure-mongodb-numa.yml @@ -14,7 +14,6 @@ executable: /bin/bash register: mongodb_numa_nodes changed_when: false - become: true - name: Set NUMA enabled fact ansible.builtin.set_fact: @@ -25,7 +24,6 @@ name: numactl state: present when: mongodb_numa_enabled - become: true - name: Discover numactl path ansible.builtin.shell: @@ -45,7 +43,6 @@ register: mongodb_mongod_path_result changed_when: false when: mongodb_numa_enabled - become: true - name: Set NUMA helper facts ansible.builtin.set_fact: @@ -62,7 +59,6 @@ sysctl_file: "{{ mongodb_sysctl_file }}" reload: true when: mongodb_numa_enabled - become: true - name: Disable kernel NUMA balancing ansible.posix.sysctl: @@ -72,7 +68,6 @@ sysctl_file: "{{ mongodb_sysctl_file }}" reload: true when: mongodb_numa_enabled - become: true - name: Ensure mongod systemd drop-in directory exists ansible.builtin.file: @@ -82,7 +77,6 @@ when: - mongodb_numa_enabled - mongodb_numactl_present - become: true - name: Configure mongod to run with numactl interleave ansible.builtin.copy: @@ -96,7 +90,6 @@ when: - mongodb_numa_enabled - mongodb_numactl_present - become: true - name: Reload systemd if NUMA drop-in changed ansible.builtin.systemd: @@ -106,7 +99,6 @@ - mongodb_numactl_present - mongodb_numa_dropin is defined - mongodb_numa_dropin.changed - become: true - name: Restart mongod to apply NUMA settings ansible.builtin.systemd: @@ -117,4 +109,4 @@ - mongodb_numactl_present - mongodb_numa_dropin is defined - mongodb_numa_dropin.changed - become: true + From 2d8537abaf46cac1c82f3c78705296e3c8b79fb1 Mon Sep 17 00:00:00 2001 From: Marcos Dias Date: Tue, 3 Feb 2026 14:09:20 -0300 Subject: [PATCH 3/9] Refactor systemd reload and restart tasks for mongod to streamline NUMA configuration --- roles/mongodb/tasks/configure-mongodb-numa.yml | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/roles/mongodb/tasks/configure-mongodb-numa.yml b/roles/mongodb/tasks/configure-mongodb-numa.yml index 0b3ebe2e..320619e6 100644 --- a/roles/mongodb/tasks/configure-mongodb-numa.yml +++ b/roles/mongodb/tasks/configure-mongodb-numa.yml @@ -91,22 +91,14 @@ - mongodb_numa_enabled - mongodb_numactl_present -- name: Reload systemd if NUMA drop-in changed - ansible.builtin.systemd: - daemon_reload: true - when: - - mongodb_numa_enabled - - mongodb_numactl_present - - mongodb_numa_dropin is defined - - mongodb_numa_dropin.changed - -- name: Restart mongod to apply NUMA settings +- name: Reload systemd and restart mongod to apply NUMA settings ansible.builtin.systemd: name: mongod state: restarted + daemon_reload: true when: - mongodb_numa_enabled - mongodb_numactl_present - mongodb_numa_dropin is defined - mongodb_numa_dropin.changed - + From b76fcb0e12c98d4fa2bc9487065db43c4a6aef2a Mon Sep 17 00:00:00 2001 From: Marcos Dias Date: Fri, 6 Feb 2026 10:32:22 -0300 Subject: [PATCH 4/9] Refactor NUMA configuration tasks for MongoDB to improve structure and clarity --- .../mongodb/tasks/configure-mongodb-numa.yml | 139 ++++++++---------- 1 file changed, 65 insertions(+), 74 deletions(-) diff --git a/roles/mongodb/tasks/configure-mongodb-numa.yml b/roles/mongodb/tasks/configure-mongodb-numa.yml index 320619e6..5a31ec66 100644 --- a/roles/mongodb/tasks/configure-mongodb-numa.yml +++ b/roles/mongodb/tasks/configure-mongodb-numa.yml @@ -19,86 +19,77 @@ ansible.builtin.set_fact: mongodb_numa_enabled: "{{ (mongodb_numa_nodes.stdout | int) > 1 }}" -- name: Ensure numactl is installed when NUMA is enabled - ansible.builtin.package: - name: numactl - state: present +- name: Enable NUMA settings if multiple NUMA nodes detected when: mongodb_numa_enabled + block: + - name: Ensure numactl is installed when NUMA is enabled + ansible.builtin.package: + name: numactl + state: present -- name: Discover numactl path - ansible.builtin.shell: - cmd: "command -v numactl || true" - args: - executable: /bin/bash - register: mongodb_numactl_path_result - changed_when: false - when: mongodb_numa_enabled - become: true + - name: Discover numactl path + ansible.builtin.shell: + cmd: "command -v numactl || true" + args: + executable: /bin/bash + register: mongodb_numactl_path_result + changed_when: false + become: true -- name: Discover mongod path - ansible.builtin.shell: - cmd: "command -v mongod || true" - args: - executable: /bin/bash - register: mongodb_mongod_path_result - changed_when: false - when: mongodb_numa_enabled + - name: Discover mongod path + ansible.builtin.shell: + cmd: "command -v mongod || true" + args: + executable: /bin/bash + register: mongodb_mongod_path_result + changed_when: false -- name: Set NUMA helper facts - ansible.builtin.set_fact: - mongodb_numactl_path: "{{ mongodb_numactl_path_result.stdout | default('', true) }}" - mongodb_mongod_path: "{{ mongodb_mongod_path_result.stdout | default('/usr/bin/mongod', true) }}" - mongodb_numactl_present: "{{ (mongodb_numactl_path_result.stdout | default('', true) | length) > 0 }}" - when: mongodb_numa_enabled + - name: Set NUMA helper facts + ansible.builtin.set_fact: + mongodb_numactl_path: "{{ mongodb_numactl_path_result.stdout | default('', true) }}" + mongodb_mongod_path: "{{ mongodb_mongod_path_result.stdout | default('/usr/bin/mongod', true) }}" + mongodb_numactl_present: "{{ (mongodb_numactl_path_result.stdout | default('', true) | length) > 0 }}" -- name: Apply NUMA sysctl settings - ansible.posix.sysctl: - name: vm.zone_reclaim_mode - value: "{{ mongodb_vm_zone_reclaim_mode }}" - state: present - sysctl_file: "{{ mongodb_sysctl_file }}" - reload: true - when: mongodb_numa_enabled - -- name: Disable kernel NUMA balancing - ansible.posix.sysctl: - name: kernel.numa_balancing - value: "0" - state: present - sysctl_file: "{{ mongodb_sysctl_file }}" - reload: true - when: mongodb_numa_enabled + - name: Apply NUMA sysctl settings + ansible.posix.sysctl: + name: vm.zone_reclaim_mode + value: "{{ mongodb_vm_zone_reclaim_mode }}" + state: present + sysctl_file: "{{ mongodb_sysctl_file }}" + reload: true -- name: Ensure mongod systemd drop-in directory exists - ansible.builtin.file: - path: /etc/systemd/system/mongod.service.d - state: directory - mode: "0755" - when: - - mongodb_numa_enabled - - mongodb_numactl_present + - name: Disable kernel NUMA balancing + ansible.posix.sysctl: + name: kernel.numa_balancing + value: "0" + state: present + sysctl_file: "{{ mongodb_sysctl_file }}" + reload: true -- name: Configure mongod to run with numactl interleave - ansible.builtin.copy: - dest: /etc/systemd/system/mongod.service.d/numa.conf - mode: "0644" - content: | - [Service] - ExecStart= - ExecStart={{ mongodb_numactl_path }} --interleave=all {{ mongodb_mongod_path }} --config {{ mongodb_conf_file }} - register: mongodb_numa_dropin - when: - - mongodb_numa_enabled - - mongodb_numactl_present + - name: Ensure mongod systemd drop-in directory exists + ansible.builtin.file: + path: /etc/systemd/system/mongod.service.d + state: directory + mode: "0755" + when: mongodb_numactl_present -- name: Reload systemd and restart mongod to apply NUMA settings - ansible.builtin.systemd: - name: mongod - state: restarted - daemon_reload: true - when: - - mongodb_numa_enabled - - mongodb_numactl_present - - mongodb_numa_dropin is defined - - mongodb_numa_dropin.changed + - name: Configure mongod to run with numactl interleave + ansible.builtin.copy: + dest: /etc/systemd/system/mongod.service.d/numa.conf + mode: "0644" + content: | + [Service] + ExecStart= + ExecStart={{ mongodb_numactl_path }} --interleave=all {{ mongodb_mongod_path }} --config {{ mongodb_conf_file }} + register: mongodb_numa_dropin + when: mongodb_numactl_present + - name: Reload systemd and restart mongod to apply NUMA settings + ansible.builtin.systemd: + name: mongod + state: restarted + daemon_reload: true + when: + - mongodb_numactl_present + - mongodb_numa_dropin is defined + - mongodb_numa_dropin.changed From 6d13f7b6f888260b8b0691f203029a8a2a60b3ba Mon Sep 17 00:00:00 2001 From: Marcos Dias Date: Fri, 6 Feb 2026 14:50:23 -0300 Subject: [PATCH 5/9] Add pipefail option to NUMA node detection command for improved error handling --- roles/mongodb/tasks/configure-mongodb-numa.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/roles/mongodb/tasks/configure-mongodb-numa.yml b/roles/mongodb/tasks/configure-mongodb-numa.yml index 5a31ec66..d4fd8ca7 100644 --- a/roles/mongodb/tasks/configure-mongodb-numa.yml +++ b/roles/mongodb/tasks/configure-mongodb-numa.yml @@ -5,6 +5,7 @@ - name: Detect NUMA nodes ansible.builtin.shell: cmd: | + set -o pipefail if [ -d /sys/devices/system/node ]; then ls -d /sys/devices/system/node/node[0-9]* 2>/dev/null | wc -l else From 16e5a2c11eb81e9a4db492a5d09e0a21489e4a69 Mon Sep 17 00:00:00 2001 From: Marcos Dias Date: Wed, 25 Mar 2026 12:01:05 -0300 Subject: [PATCH 6/9] Added configuration for MongoDB priority single replica set --- plugins/modules/mongodb_config_state.py | 2 +- roles/mongodb/defaults/main/mongodb.yml | 8 + .../tasks/configure-mongodb-replicaset.yml | 190 +++++++++++++++++- 3 files changed, 194 insertions(+), 6 deletions(-) diff --git a/plugins/modules/mongodb_config_state.py b/plugins/modules/mongodb_config_state.py index b9de3636..9c2425e6 100644 --- a/plugins/modules/mongodb_config_state.py +++ b/plugins/modules/mongodb_config_state.py @@ -117,7 +117,7 @@ def run_module(): uri = build_connection_string(module.params) - client = MongoClient(uri) + client = MongoClient(uri, directConnection=True) database = client.get_database("admin") hello = database.command("hello") diff --git a/roles/mongodb/defaults/main/mongodb.yml b/roles/mongodb/defaults/main/mongodb.yml index 44785c8c..c5f7c3a4 100644 --- a/roles/mongodb/defaults/main/mongodb.yml +++ b/roles/mongodb/defaults/main/mongodb.yml @@ -71,3 +71,11 @@ mongodb_user_itential_password: itential # The name of the mongo replica set mongodb_replset_name: "{{ mongodb_replset_name_default }}" + +# Preferred primary member for MongoDB replica sets. +# Leave empty to use the first host in the mongodb inventory group. +mongodb_preferred_primary: "" + +# Replica set member priorities. +mongodb_primary_priority: 2 +mongodb_secondary_priority: 1 diff --git a/roles/mongodb/tasks/configure-mongodb-replicaset.yml b/roles/mongodb/tasks/configure-mongodb-replicaset.yml index 14f7698f..629ec07c 100644 --- a/roles/mongodb/tasks/configure-mongodb-replicaset.yml +++ b/roles/mongodb/tasks/configure-mongodb-replicaset.yml @@ -56,29 +56,112 @@ - name: Set empty array of mongo servers ansible.builtin.set_fact: mongodb_servers: [] - when: not mongodb_state.replication_enabled + when: + - inventory_hostname in groups.mongodb + - groups.mongodb.index(inventory_hostname) == 0 + +- name: Set preferred primary host + ansible.builtin.set_fact: + mongodb_preferred_primary_host: "{{ groups.mongodb[0] }}" + mongodb_preferred_primary_resolved: "{{ mongodb_preferred_primary | default('', true) | length == 0 }}" + when: + - inventory_hostname in groups.mongodb + - groups.mongodb.index(inventory_hostname) == 0 + +- name: Normalize preferred primary override + ansible.builtin.set_fact: + mongodb_preferred_primary_input: >- + {{ mongodb_preferred_primary | regex_replace(':' + (mongodb_port | string) + '$', '') }} + when: + - inventory_hostname in groups.mongodb + - groups.mongodb.index(inventory_hostname) == 0 + - mongodb_preferred_primary | default('', true) | length > 0 + +- name: Resolve preferred primary override by inventory hostname + ansible.builtin.set_fact: + mongodb_preferred_primary_host: "{{ mongodb_preferred_primary_input }}" + mongodb_preferred_primary_resolved: true + when: + - inventory_hostname in groups.mongodb + - groups.mongodb.index(inventory_hostname) == 0 + - mongodb_preferred_primary | default('', true) | length > 0 + - mongodb_preferred_primary_input in groups.mongodb + +- name: Resolve preferred primary override by ansible_host or short hostname + ansible.builtin.set_fact: + mongodb_preferred_primary_host: "{{ item }}" + mongodb_preferred_primary_resolved: true + loop: "{{ groups.mongodb }}" + when: + - inventory_hostname in groups.mongodb + - groups.mongodb.index(inventory_hostname) == 0 + - mongodb_preferred_primary | default('', true) | length > 0 + - mongodb_preferred_primary_input not in groups.mongodb + - mongodb_preferred_primary_input in [ + item, + (item.split('.')[0]), + (hostvars[item].ansible_host | default('')) + ] + +- name: Validate preferred primary override + ansible.builtin.assert: + that: + - mongodb_preferred_primary_resolved | bool + fail_msg: >- + mongodb_preferred_primary must match a host in groups.mongodb. + Received {{ mongodb_preferred_primary }}. + Valid inventory hosts: {{ groups.mongodb | join(', ') }} + when: + - inventory_hostname in groups.mongodb + - groups.mongodb.index(inventory_hostname) == 0 + - mongodb_preferred_primary | default('', true) | length > 0 # This task should always run, arbiter or not - name: Create the replicaset members list (no arbiter) ansible.builtin.set_fact: - mongodb_servers: "{{ mongodb_servers + [item + ':' + mongodb_port | string] }}" + mongodb_servers: >- + {{ mongodb_servers + [{ + 'host': item + ':' + (mongodb_port | string), + 'priority': ((item == mongodb_preferred_primary_host) | + ternary(mongodb_primary_priority, mongodb_secondary_priority)) }] + }} with_items: "{{ groups.mongodb }}" when: - - not mongodb_state.replication_enabled - inventory_hostname in groups.mongodb - groups.mongodb.index(inventory_hostname) == 0 # This task will only run when there is an arbiter defined in the hosts file - name: Add the arbiter to the list of servers when there is one ansible.builtin.set_fact: - mongodb_servers: "{{ mongodb_servers + [item + ':' + mongodb_port | string] }}" + mongodb_servers: >- + {{ mongodb_servers + [{ + 'host': item + ':' + (mongodb_port | string), + 'priority': 0 }] + }} with_items: "{{ groups.mongodb_arbiter }}" when: - - not mongodb_state.replication_enabled - inventory_hostname in groups.mongodb - groups.mongodb.index(inventory_hostname) == 0 - groups.mongodb_arbiter is defined +- name: Debug replication state + ansible.builtin.debug: + msg: "replication_enabled={{ mongodb_state.replication_enabled }} primary={{ mongodb_state.primary }} members={{ mongodb_state.members }}" + when: + - inventory_hostname in groups.mongodb + - groups.mongodb.index(inventory_hostname) == 0 + +- name: Wait for all MongoDB members to be reachable before creating the replicaset + ansible.builtin.wait_for: + host: "{{ item }}" + port: "{{ mongodb_port }}" + timeout: 60 + loop: "{{ groups.mongodb + (groups.mongodb_arbiter | default([])) }}" + when: + - not mongodb_state.replication_enabled + - inventory_hostname in groups.mongodb + - groups.mongodb.index(inventory_hostname) == 0 + - name: Create the replicaset community.mongodb.mongodb_replicaset: arbiter_at_index: "{{ (groups.mongodb_arbiter | default([]) | length > 0) | ternary(mongodb_servers | length - 1, omit) }}" @@ -119,6 +202,103 @@ vars: ansible_python_interpreter: "{{ mongodb_python_venv }}/bin/python3" +- name: Refresh MongoDB configuration state after replicaset changes + itential.deployer.mongodb_config_state: + login_database: admin + login_host: "{{ inventory_hostname }}" + login_port: "{{ mongodb_port }}" + register: mongodb_state_after_replicaset + when: + - inventory_hostname in groups.mongodb + - groups.mongodb.index(inventory_hostname) == 0 + vars: + ansible_python_interpreter: "{{ mongodb_python_venv }}/bin/python3" + +- name: Reconfigure the replicaset priorities + community.mongodb.mongodb_shell: + mongo_cmd: auto + login_user: "{{ (mongodb_state.auth_enabled | bool) | ternary(mongodb_user_admin, omit) }}" + login_password: "{{ (mongodb_state.auth_enabled | bool) | ternary(mongodb_user_admin_password, omit) }}" + login_port: "{{ mongodb_port }}" + login_database: admin + login_host: "{{ mongodb_state_after_replicaset.primary }}" + eval: | + (() => { + const cfg = rs.conf(); + const preferredPrimary = "{{ mongodb_preferred_primary_host }}"; + const primaryPriority = {{ mongodb_primary_priority }}; + const secondaryPriority = {{ mongodb_secondary_priority }}; + let changed = false; + cfg.members.forEach(m => { + const host = m.host.split(':')[0]; + if (m.arbiterOnly) return; + const newPriority = (host === preferredPrimary) ? primaryPriority : secondaryPriority; + if (m.priority !== newPriority) { + m.priority = newPriority; + changed = true; + } + }); + if (changed) { + cfg.version += 1; + rs.reconfig(cfg); + return "Reconfigured replica set priorities"; + } + return "No priority changes needed"; + })() + register: reconfig_result + when: + - inventory_hostname in groups.mongodb + - groups.mongodb.index(inventory_hostname) == 0 + - mongodb_state_after_replicaset.primary is defined + - mongodb_state_after_replicaset.primary | length > 0 + vars: + ansible_python_interpreter: "{{ mongodb_python_venv }}/bin/python3" + +- name: Print reconfigure result + ansible.builtin.debug: + msg: "{{ reconfig_result }}" + when: + - reconfig_result is defined + - reconfig_result is not skipped + +- name: Step down non-preferred primary after replicaset changes + community.mongodb.mongodb_shell: + mongo_cmd: auto + login_user: "{{ mongodb_state.auth_enabled | ternary(mongodb_user_admin, omit) }}" + login_password: "{{ mongodb_state.auth_enabled | ternary(mongodb_user_admin_password, omit) }}" + login_port: "{{ mongodb_port }}" + login_database: admin + login_host: "{{ mongodb_state_after_replicaset.primary | regex_replace(':.*$', '') }}" + eval: "db.adminCommand({replSetStepDown: 60, force: true})" + failed_when: false + when: + - inventory_hostname in groups.mongodb + - groups.mongodb.index(inventory_hostname) == 0 + - mongodb_state_after_replicaset.primary is defined + - mongodb_state_after_replicaset.primary != mongodb_preferred_primary_host + vars: + ansible_python_interpreter: "{{ mongodb_python_venv }}/bin/python3" + +- name: Ensure replicaset is stable after primary stepdown + community.mongodb.mongodb_status: + login_user: "{{ mongodb_state.auth_enabled | ternary(mongodb_user_admin, omit) }}" + login_password: "{{ mongodb_state.auth_enabled | ternary(mongodb_user_admin_password, omit) }}" + login_port: "{{ mongodb_port }}" + login_database: admin + login_host: "{{ inventory_hostname }}" + replica_set: "{{ mongodb_replset_name }}" + poll: "{{ mongodb_status_poll }}" + interval: "{{ mongodb_status_interval }}" + validate: minimal + register: rs_after_stepdown + failed_when: + - "'Unable to determine if auth is enabled' not in rs_after_stepdown.msg" + - "'replicaset is in a converged state' not in rs_after_stepdown.msg" + when: + - inventory_hostname in groups.mongodb + vars: + ansible_python_interpreter: "{{ mongodb_python_venv }}/bin/python3" + # Starting in MongoDB 5.0, the implicit default write concern is w: majority. # However, special considerations are made for deployments containing arbiters: # The voting majority of a replica set is 1 plus half the number of voting From d43cb8254314306d122e3eecb3b094319bf5e17e Mon Sep 17 00:00:00 2001 From: Marcos Dias Date: Tue, 31 Mar 2026 09:49:54 -0300 Subject: [PATCH 7/9] Define mongo node priority. --- .../tasks/configure-mongodb-replicaset.yml | 40 ++++++------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/roles/mongodb/tasks/configure-mongodb-replicaset.yml b/roles/mongodb/tasks/configure-mongodb-replicaset.yml index 629ec07c..aa59f1cd 100644 --- a/roles/mongodb/tasks/configure-mongodb-replicaset.yml +++ b/roles/mongodb/tasks/configure-mongodb-replicaset.yml @@ -215,36 +215,18 @@ ansible_python_interpreter: "{{ mongodb_python_venv }}/bin/python3" - name: Reconfigure the replicaset priorities - community.mongodb.mongodb_shell: - mongo_cmd: auto + community.mongodb.mongodb_replicaset: + arbiter_at_index: "{{ (groups.mongodb_arbiter | default([]) | length > 0) | ternary(mongodb_servers | length - 1, omit) }}" + auth_mechanism: "SCRAM-SHA-256" login_user: "{{ (mongodb_state.auth_enabled | bool) | ternary(mongodb_user_admin, omit) }}" login_password: "{{ (mongodb_state.auth_enabled | bool) | ternary(mongodb_user_admin_password, omit) }}" login_port: "{{ mongodb_port }}" login_database: admin - login_host: "{{ mongodb_state_after_replicaset.primary }}" - eval: | - (() => { - const cfg = rs.conf(); - const preferredPrimary = "{{ mongodb_preferred_primary_host }}"; - const primaryPriority = {{ mongodb_primary_priority }}; - const secondaryPriority = {{ mongodb_secondary_priority }}; - let changed = false; - cfg.members.forEach(m => { - const host = m.host.split(':')[0]; - if (m.arbiterOnly) return; - const newPriority = (host === preferredPrimary) ? primaryPriority : secondaryPriority; - if (m.priority !== newPriority) { - m.priority = newPriority; - changed = true; - } - }); - if (changed) { - cfg.version += 1; - rs.reconfig(cfg); - return "Reconfigured replica set priorities"; - } - return "No priority changes needed"; - })() + login_host: "{{ mongodb_state_after_replicaset.primary | regex_replace(':.*$', '') }}" + members: "{{ mongodb_servers }}" + replica_set: "{{ mongodb_replset_name }}" + reconfigure: true + validate: true register: reconfig_result when: - inventory_hostname in groups.mongodb @@ -270,12 +252,14 @@ login_database: admin login_host: "{{ mongodb_state_after_replicaset.primary | regex_replace(':.*$', '') }}" eval: "db.adminCommand({replSetStepDown: 60, force: true})" + register: stepdown_result failed_when: false when: - inventory_hostname in groups.mongodb - groups.mongodb.index(inventory_hostname) == 0 + - reconfig_result is changed - mongodb_state_after_replicaset.primary is defined - - mongodb_state_after_replicaset.primary != mongodb_preferred_primary_host + - (mongodb_state_after_replicaset.primary | regex_replace(':.*$', '')) != mongodb_preferred_primary_host vars: ansible_python_interpreter: "{{ mongodb_python_venv }}/bin/python3" @@ -296,6 +280,8 @@ - "'replicaset is in a converged state' not in rs_after_stepdown.msg" when: - inventory_hostname in groups.mongodb + - stepdown_result is defined + - stepdown_result is not skipped vars: ansible_python_interpreter: "{{ mongodb_python_venv }}/bin/python3" From 75fad812e3ee97654d812108b8e764fb0e8529cb Mon Sep 17 00:00:00 2001 From: Marcos Dias Date: Tue, 31 Mar 2026 14:07:02 -0300 Subject: [PATCH 8/9] Added reconfigure tag and added variable in mongo guide. --- docs/mongodb_guide.md | 7 +++++++ roles/mongodb/defaults/main/mongodb.yml | 4 ++-- .../tasks/configure-mongodb-replicaset.yml | 21 ++++++++++++++++--- roles/mongodb/tasks/configure-mongodb.yml | 3 +++ 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/docs/mongodb_guide.md b/docs/mongodb_guide.md index a6a7d3cf..13f74f9e 100644 --- a/docs/mongodb_guide.md +++ b/docs/mongodb_guide.md @@ -95,6 +95,7 @@ The following table contains the most commonly overridden variables. | `mongodb_tls_enabled` | Boolean | Flag to enable MongoDB TLS. | `false` | | `mongodb_user_admin_password` | String | The MongoDB admin user password. | `admin` | | `mongodb_user_itential_password` | String | The MongoDB itential user password. | `itential` | +| `mongodb_preferred_primary` | String | Node hostname that will be considered primary, empty means the first hostname in the inventory | `` | > :warning: It is assumed that these default passwords will be changed to meet more rigorous security standards. These are intended to be defaults strictly used just for ease of the @@ -301,3 +302,9 @@ state that the installation tag produced you can run this command: ```bash ansible-playbook itential.deployer.mongodb -i --tags initialize_mongo_config ``` + +This tag is used to dynamically adjust the **MongoDB replica set member priorities** and influence which node becomes the **primary**: + +```bash +ansible-playbook itential.deployer.mongodb -i --tags reconfigure_priority +``` diff --git a/roles/mongodb/defaults/main/mongodb.yml b/roles/mongodb/defaults/main/mongodb.yml index c5f7c3a4..acfe0dbb 100644 --- a/roles/mongodb/defaults/main/mongodb.yml +++ b/roles/mongodb/defaults/main/mongodb.yml @@ -77,5 +77,5 @@ mongodb_replset_name: "{{ mongodb_replset_name_default }}" mongodb_preferred_primary: "" # Replica set member priorities. -mongodb_primary_priority: 2 -mongodb_secondary_priority: 1 +mongodb_primary_priority: 10 +mongodb_secondary_priority: 5 diff --git a/roles/mongodb/tasks/configure-mongodb-replicaset.yml b/roles/mongodb/tasks/configure-mongodb-replicaset.yml index aa59f1cd..1576f8b9 100644 --- a/roles/mongodb/tasks/configure-mongodb-replicaset.yml +++ b/roles/mongodb/tasks/configure-mongodb-replicaset.yml @@ -25,10 +25,12 @@ register: mongodb_state vars: ansible_python_interpreter: "{{ mongodb_python_venv }}/bin/python3" + tags: reconfigure_priority - name: Print MongoDB configuration state ansible.builtin.debug: msg: "{{ mongodb_state }}" + tags: reconfigure_priority # Execute the template to apply changes to the mongo.conf for replication - name: Create MongoDB config file (replicaset) @@ -59,6 +61,7 @@ when: - inventory_hostname in groups.mongodb - groups.mongodb.index(inventory_hostname) == 0 + tags: reconfigure_priority - name: Set preferred primary host ansible.builtin.set_fact: @@ -67,6 +70,7 @@ when: - inventory_hostname in groups.mongodb - groups.mongodb.index(inventory_hostname) == 0 + tags: reconfigure_priority - name: Normalize preferred primary override ansible.builtin.set_fact: @@ -76,6 +80,7 @@ - inventory_hostname in groups.mongodb - groups.mongodb.index(inventory_hostname) == 0 - mongodb_preferred_primary | default('', true) | length > 0 + tags: reconfigure_priority - name: Resolve preferred primary override by inventory hostname ansible.builtin.set_fact: @@ -86,6 +91,7 @@ - groups.mongodb.index(inventory_hostname) == 0 - mongodb_preferred_primary | default('', true) | length > 0 - mongodb_preferred_primary_input in groups.mongodb + tags: reconfigure_priority - name: Resolve preferred primary override by ansible_host or short hostname ansible.builtin.set_fact: @@ -102,6 +108,7 @@ (item.split('.')[0]), (hostvars[item].ansible_host | default('')) ] + tags: reconfigure_priority - name: Validate preferred primary override ansible.builtin.assert: @@ -115,20 +122,21 @@ - inventory_hostname in groups.mongodb - groups.mongodb.index(inventory_hostname) == 0 - mongodb_preferred_primary | default('', true) | length > 0 + tags: reconfigure_priority # This task should always run, arbiter or not - name: Create the replicaset members list (no arbiter) ansible.builtin.set_fact: mongodb_servers: >- - {{ mongodb_servers + [{ - 'host': item + ':' + (mongodb_port | string), + {{ mongodb_servers + [{'host': item + ':' + (mongodb_port | string), 'priority': ((item == mongodb_preferred_primary_host) | - ternary(mongodb_primary_priority, mongodb_secondary_priority)) }] + ternary(mongodb_primary_priority, mongodb_secondary_priority))}] }} with_items: "{{ groups.mongodb }}" when: - inventory_hostname in groups.mongodb - groups.mongodb.index(inventory_hostname) == 0 + tags: reconfigure_priority # This task will only run when there is an arbiter defined in the hosts file - name: Add the arbiter to the list of servers when there is one @@ -143,6 +151,7 @@ - inventory_hostname in groups.mongodb - groups.mongodb.index(inventory_hostname) == 0 - groups.mongodb_arbiter is defined + tags: reconfigure_priority - name: Debug replication state ansible.builtin.debug: @@ -150,6 +159,7 @@ when: - inventory_hostname in groups.mongodb - groups.mongodb.index(inventory_hostname) == 0 + tags: reconfigure_priority - name: Wait for all MongoDB members to be reachable before creating the replicaset ansible.builtin.wait_for: @@ -213,6 +223,7 @@ - groups.mongodb.index(inventory_hostname) == 0 vars: ansible_python_interpreter: "{{ mongodb_python_venv }}/bin/python3" + tags: reconfigure_priority - name: Reconfigure the replicaset priorities community.mongodb.mongodb_replicaset: @@ -235,6 +246,7 @@ - mongodb_state_after_replicaset.primary | length > 0 vars: ansible_python_interpreter: "{{ mongodb_python_venv }}/bin/python3" + tags: reconfigure_priority - name: Print reconfigure result ansible.builtin.debug: @@ -242,6 +254,7 @@ when: - reconfig_result is defined - reconfig_result is not skipped + tags: reconfigure_priority - name: Step down non-preferred primary after replicaset changes community.mongodb.mongodb_shell: @@ -262,6 +275,7 @@ - (mongodb_state_after_replicaset.primary | regex_replace(':.*$', '')) != mongodb_preferred_primary_host vars: ansible_python_interpreter: "{{ mongodb_python_venv }}/bin/python3" + tags: reconfigure_priority - name: Ensure replicaset is stable after primary stepdown community.mongodb.mongodb_status: @@ -284,6 +298,7 @@ - stepdown_result is not skipped vars: ansible_python_interpreter: "{{ mongodb_python_venv }}/bin/python3" + tags: reconfigure_priority # Starting in MongoDB 5.0, the implicit default write concern is w: majority. # However, special considerations are made for deployments containing arbiters: diff --git a/roles/mongodb/tasks/configure-mongodb.yml b/roles/mongodb/tasks/configure-mongodb.yml index 24b9abae..db4d7ebc 100644 --- a/roles/mongodb/tasks/configure-mongodb.yml +++ b/roles/mongodb/tasks/configure-mongodb.yml @@ -8,7 +8,10 @@ - name: Configure MongoDB replica set ansible.builtin.include_tasks: file: configure-mongodb-replicaset.yml + apply: + tags: reconfigure_priority when: mongodb_replication_enabled | bool + tags: reconfigure_priority # Configure auth - name: Configure MongoDB Auth From 05c7f87e5f84ef877d0d2ecfdf57d49ed281b946 Mon Sep 17 00:00:00 2001 From: Marcos Dias Date: Tue, 31 Mar 2026 14:20:27 -0300 Subject: [PATCH 9/9] Adjust lint errors. --- roles/mongodb/tasks/configure-mongodb-replicaset.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/roles/mongodb/tasks/configure-mongodb-replicaset.yml b/roles/mongodb/tasks/configure-mongodb-replicaset.yml index 1576f8b9..3f1b9987 100644 --- a/roles/mongodb/tasks/configure-mongodb-replicaset.yml +++ b/roles/mongodb/tasks/configure-mongodb-replicaset.yml @@ -132,7 +132,7 @@ 'priority': ((item == mongodb_preferred_primary_host) | ternary(mongodb_primary_priority, mongodb_secondary_priority))}] }} - with_items: "{{ groups.mongodb }}" + loop: "{{ groups.mongodb }}" when: - inventory_hostname in groups.mongodb - groups.mongodb.index(inventory_hostname) == 0 @@ -142,11 +142,8 @@ - name: Add the arbiter to the list of servers when there is one ansible.builtin.set_fact: mongodb_servers: >- - {{ mongodb_servers + [{ - 'host': item + ':' + (mongodb_port | string), - 'priority': 0 }] - }} - with_items: "{{ groups.mongodb_arbiter }}" + {{ mongodb_servers + [{'host': item + ':' + (mongodb_port | string), 'priority': 0}] }} + loop: "{{ groups.mongodb_arbiter }}" when: - inventory_hostname in groups.mongodb - groups.mongodb.index(inventory_hostname) == 0