Skip to content

Commit 1d73c8b

Browse files
authored
Merge pull request #88 from Oddly/feature/cluster-settings
Add idempotent cluster settings management
2 parents b3f15b3 + 3e32563 commit 1d73c8b

5 files changed

Lines changed: 101 additions & 0 deletions

File tree

docs/roles/elasticsearch.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,24 @@ elasticsearch_recovery_max_bytes_per_sec: ""
220220

221221
`elasticsearch_recovery_max_bytes_per_sec` throttles shard recovery bandwidth (e.g., `"100mb"`). When empty (the default), Elasticsearch uses its internal default (40 MB/s). Increase this on fast networks to speed up recovery after node restarts, or decrease it on shared networks to prevent saturation.
222222

223+
### Persistent Cluster Settings
224+
225+
```yaml
226+
elasticsearch_cluster_settings: {}
227+
```
228+
229+
`elasticsearch_cluster_settings` applies persistent cluster settings via the `PUT _cluster/settings` API after the cluster is healthy. Unlike `elasticsearch_extra_config` (which writes to `elasticsearch.yml` and requires a restart), cluster settings take effect immediately at runtime and apply cluster-wide. The task reads current settings first and only sends the PUT when values differ.
230+
231+
```yaml
232+
elasticsearch_cluster_settings:
233+
cluster.logsdb.enabled: true
234+
indices.recovery.max_bytes_per_sec: "100mb"
235+
cluster.routing.allocation.disk.watermark.low: "90%"
236+
cluster.routing.allocation.disk.watermark.high: "95%"
237+
```
238+
239+
Any setting supported by the [cluster settings API](https://www.elastic.co/docs/reference/elasticsearch/rest-api/cluster/update-cluster-settings) can be used. Values can be strings, numbers, booleans, or nested objects — the YAML dict is serialized to JSON directly.
240+
223241
### Temperature Attribute
224242

225243
```yaml

molecule/elasticsearch_default/converge.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
elasticstack_release: "{{ lookup('env', 'ELASTIC_RELEASE') | default('9', true) | int }}"
1010
elasticsearch_heap: "1"
1111
elasticstack_no_log: false
12+
elasticsearch_cluster_settings:
13+
action.destructive_requires_name: "true"
1214
tasks:
1315
- name: Include Elastics repos role
1416
ansible.builtin.include_role:

molecule/elasticsearch_default/verify.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,23 @@
1616
- health.json.number_of_nodes == groups['elasticsearch'] | length
1717
fail_msg: "Expected {{ groups['elasticsearch'] | length }} nodes, got {{ health.json.number_of_nodes }}"
1818
run_once: true # noqa: run-once[task]
19+
20+
- name: Read cluster settings # noqa: run-once[task]
21+
ansible.builtin.uri:
22+
url: "https://localhost:9200/_cluster/settings?flat_settings=true"
23+
method: GET
24+
user: elastic
25+
password: "{{ elastic_pass.stdout }}"
26+
force_basic_auth: true
27+
validate_certs: false
28+
register: cluster_settings
29+
run_once: true
30+
31+
- name: Verify cluster settings were applied # noqa: run-once[task]
32+
ansible.builtin.assert:
33+
that:
34+
- cluster_settings.json.persistent['action.destructive_requires_name'] == 'true'
35+
fail_msg: >-
36+
elasticsearch_cluster_settings not applied.
37+
Got: {{ cluster_settings.json.persistent }}
38+
run_once: true

roles/elasticsearch/defaults/main.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,16 @@ elasticsearch_http_cors_allow_headers: "X-Requested-With, Content-Type, Content-
9393
# @var elasticsearch_http_cors_allow_credentials:description: Whether to send CORS credentials (cookies, auth headers)
9494
elasticsearch_http_cors_allow_credentials: false
9595

96+
# @var elasticsearch_cluster_settings:description: >
97+
# Persistent cluster settings applied via PUT _cluster/settings after the
98+
# cluster is healthy. Accepts any setting the cluster settings API supports.
99+
# Only applied when non-empty. Runs once per play on the CA host.
100+
# Example:
101+
# elasticsearch_cluster_settings:
102+
# cluster.logsdb.enabled: true
103+
# indices.recovery.max_bytes_per_sec: "100mb"
104+
elasticsearch_cluster_settings: {}
105+
96106
# @var elasticsearch_initialized_file:description: Marker file path that indicates the cluster has been initialized
97107
elasticsearch_initialized_file: "{{ elasticstack_initial_passwords | dirname }}/cluster_initialized"
98108
# @var elasticsearch_tls_key_passphrase:description: Passphrase for the Elasticsearch node TLS private key

roles/elasticsearch/tasks/main.yml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,3 +582,54 @@
582582
when:
583583
- elasticsearch_security | bool
584584
- inventory_hostname == elasticstack_ca_host
585+
586+
# -- Persistent cluster settings via _cluster/settings API --
587+
588+
- name: Apply persistent cluster settings # noqa: run-once[task]
589+
when:
590+
- elasticsearch_cluster_settings | default({}) | length > 0
591+
- not ansible_check_mode
592+
run_once: true
593+
delegate_to: "{{ elasticstack_ca_host | default(inventory_hostname) }}"
594+
block:
595+
- name: Read current persistent cluster settings
596+
ansible.builtin.uri:
597+
url: "{{ elasticsearch_http_protocol }}://{{ elasticsearch_api_host }}:{{ elasticstack_elasticsearch_http_port }}/_cluster/settings?flat_settings=true"
598+
method: GET
599+
user: "{{ 'elastic' if elasticsearch_security | bool else omit }}"
600+
password: "{{ elasticstack_password.stdout if elasticsearch_security | bool else omit }}"
601+
force_basic_auth: "{{ elasticsearch_security | bool }}"
602+
validate_certs: "{{ elasticsearch_validate_api_certs }}"
603+
return_content: true
604+
register: _es_current_cluster_settings
605+
no_log: "{{ elasticstack_no_log }}"
606+
607+
- name: Check if settings already match
608+
ansible.builtin.set_fact:
609+
_es_cluster_settings_changed: "{{ _needs_update }}"
610+
vars:
611+
_current: "{{ _es_current_cluster_settings.json.persistent }}"
612+
_needs_update: >-
613+
{% set ns = namespace(changed=false) %}
614+
{% for key, value in elasticsearch_cluster_settings.items() %}
615+
{% if _current.get(key) is none or _current[key] | string != value | string %}
616+
{% set ns.changed = true %}
617+
{% endif %}
618+
{% endfor %}
619+
{{ ns.changed }}
620+
621+
- name: Apply cluster settings
622+
ansible.builtin.uri:
623+
url: "{{ elasticsearch_http_protocol }}://{{ elasticsearch_api_host }}:{{ elasticstack_elasticsearch_http_port }}/_cluster/settings"
624+
method: PUT
625+
body_format: json
626+
body:
627+
persistent: "{{ elasticsearch_cluster_settings }}"
628+
user: "{{ 'elastic' if elasticsearch_security | bool else omit }}"
629+
password: "{{ elasticstack_password.stdout if elasticsearch_security | bool else omit }}"
630+
force_basic_auth: "{{ elasticsearch_security | bool }}"
631+
validate_certs: "{{ elasticsearch_validate_api_certs }}"
632+
status_code: 200
633+
no_log: "{{ elasticstack_no_log }}"
634+
when: _es_cluster_settings_changed | bool
635+
changed_when: true

0 commit comments

Comments
 (0)