diff --git a/molecule/elasticsearch_default/verify.yml b/molecule/elasticsearch_default/verify.yml index b457b95..f31fc17 100644 --- a/molecule/elasticsearch_default/verify.yml +++ b/molecule/elasticsearch_default/verify.yml @@ -74,3 +74,34 @@ Got: {{ cluster_settings.json.persistent }} run_once: true when: elasticstack_release | int >= 9 + + # OS tuning checks — skipped in containers where sysctl is restricted + - name: Verify vm.max_map_count # noqa: run-once[task] + ansible.builtin.command: sysctl -n vm.max_map_count + register: _max_map_count + changed_when: false + run_once: true + when: ansible_facts.virtualization_type | default('') not in ['docker', 'container', 'containerd', 'lxc', 'podman'] + + - name: Assert vm.max_map_count is 262144 # noqa: run-once[task] + ansible.builtin.assert: + that: + - _max_map_count.stdout | int == 262144 + fail_msg: "vm.max_map_count is {{ _max_map_count.stdout }}, expected 262144" + run_once: true + when: _max_map_count is not skipped + + - name: Verify vm.swappiness # noqa: run-once[task] + ansible.builtin.command: sysctl -n vm.swappiness + register: _swappiness + changed_when: false + run_once: true + when: ansible_facts.virtualization_type | default('') not in ['docker', 'container', 'containerd', 'lxc', 'podman'] + + - name: Assert vm.swappiness is 1 # noqa: run-once[task] + ansible.builtin.assert: + that: + - _swappiness.stdout | int == 1 + fail_msg: "vm.swappiness is {{ _swappiness.stdout }}, expected 1" + run_once: true + when: _swappiness is not skipped diff --git a/roles/elasticsearch/defaults/main.yml b/roles/elasticsearch/defaults/main.yml index 67b9af6..f8d9512 100644 --- a/roles/elasticsearch/defaults/main.yml +++ b/roles/elasticsearch/defaults/main.yml @@ -62,6 +62,12 @@ elasticsearch_create_logpath: false elasticsearch_heap: "{{ [[(ansible_facts.memtotal_mb // 1024) // 2, 30] | min, 1] | max }}" # @var elasticsearch_pamlimits:description: Configure PAM limits for the elasticsearch user (open files, max processes) elasticsearch_pamlimits: true +# @var elasticsearch_os_tuning:description: > +# Apply OS-level sysctl settings required for production Elasticsearch. +# Sets vm.max_map_count (required for mmapfs), vm.swappiness, disables +# Transparent Huge Pages, and tunes TCP retries for faster fault detection. +# @end +elasticsearch_os_tuning: true # @var elasticsearch_check_calculation:description: Enable heap calculation verification and display debug output elasticsearch_check_calculation: false # @var elasticsearch_clustername:description: Name of the Elasticsearch cluster diff --git a/roles/elasticsearch/tasks/main.yml b/roles/elasticsearch/tasks/main.yml index 0960d76..30585e4 100644 --- a/roles/elasticsearch/tasks/main.yml +++ b/roles/elasticsearch/tasks/main.yml @@ -198,6 +198,49 @@ when: - elasticsearch_pamlimits | bool +# OS-level tuning required for production Elasticsearch. +# vm.max_map_count is required — ES refuses to start without it. +# The rest are strongly recommended by Elastic's production checklist. +- name: Apply OS-level sysctl settings for Elasticsearch + ansible.posix.sysctl: + name: "{{ item.name }}" + value: "{{ item.value }}" + sysctl_set: true + state: present + reload: true + loop: + - { name: vm.max_map_count, value: "262144" } + - { name: vm.swappiness, value: "1" } + - { name: net.ipv4.tcp_retries2, value: "5" } + loop_control: + label: "{{ item.name }}={{ item.value }}" + when: + - elasticsearch_os_tuning | bool + - ansible_facts.virtualization_type | default('') not in ['docker', 'container', 'containerd', 'lxc', 'podman'] + +- name: Disable Transparent Huge Pages (runtime) + ansible.builtin.shell: | + echo never > /sys/kernel/mm/transparent_hugepage/enabled + echo never > /sys/kernel/mm/transparent_hugepage/defrag + changed_when: false + when: + - elasticsearch_os_tuning | bool + - ansible_facts.virtualization_type | default('') not in ['docker', 'container', 'containerd', 'lxc', 'podman'] + failed_when: false # may fail where sysfs is read-only + +- name: Disable Transparent Huge Pages (persistent via tmpfiles) + ansible.builtin.copy: + dest: /etc/tmpfiles.d/elasticsearch-thp.conf + content: | + w /sys/kernel/mm/transparent_hugepage/enabled - - - - never + w /sys/kernel/mm/transparent_hugepage/defrag - - - - never + owner: root + group: root + mode: "0644" + when: + - elasticsearch_os_tuning | bool + - ansible_facts.virtualization_type | default('') not in ['docker', 'container', 'containerd', 'lxc', 'podman'] + - name: Construct exact name of Elasticsearch package ansible.builtin.set_fact: elasticsearch_package: >-