From 85a76a4ec24b65d0c62dffddbe459434fe79242c Mon Sep 17 00:00:00 2001 From: Afeef Ghannam Date: Thu, 15 Oct 2026 18:22:33 +0200 Subject: [PATCH 1/6] Add elasticstack_address_mode to choose how the roles address each other Closes #524 --- changelogs/fragments/address_mode.yml | 6 ++++ docs/getting-started.md | 2 +- roles/beats/tasks/main.yml | 2 ++ roles/beats/templates/auditbeat.yml.j2 | 2 +- roles/beats/templates/filebeat.yml.j2 | 2 +- roles/beats/templates/metricbeat.yml.j2 | 2 +- roles/elasticstack/README.md | 1 + roles/elasticstack/defaults/main.yml | 1 + roles/elasticstack/meta/argument_specs.yml | 18 ++++++++++++ roles/elasticstack/tasks/main.yml | 3 ++ roles/elasticstack/tasks/node_address.yml | 33 ++++++++++++++++++++++ roles/kibana/tasks/main.yml | 2 +- roles/logstash/tasks/main.yml | 2 +- 13 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 changelogs/fragments/address_mode.yml create mode 100644 roles/elasticstack/tasks/node_address.yml diff --git a/changelogs/fragments/address_mode.yml b/changelogs/fragments/address_mode.yml new file mode 100644 index 00000000..5d133680 --- /dev/null +++ b/changelogs/fragments/address_mode.yml @@ -0,0 +1,6 @@ +minor_changes: + - The new ``elasticstack_address_mode`` selects how the roles address the hosts they take from + the elasticsearch, logstash and kibana inventory groups. ``inventory`` keeps the name from the + inventory as before, ``fqdn``, ``hostname`` and ``ip`` take the address from the facts of + that host. It can be set per host in its host_vars. Facts of those hosts that are missing, + for example in a run limited with ``--limit``, are gathered first. diff --git a/docs/getting-started.md b/docs/getting-started.md index 324ce3f8..d0cd410f 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -10,7 +10,7 @@ Hosts in these groups get the respective services. Restricting your plays to onl Make sure every host that should be configured is part of your playbook, including hosts that only run Beats. The collection first gathers facts from all hosts and then uses facts such as host names and IP addresses to connect the tools to each other. -You will want reliable DNS resolution, or all hosts of the stack in your systems' hosts files. +By default the tools reach each other by the host names from your inventory, so these names have to resolve on every host, through DNS or the hosts files. Without that, set `elasticstack_address_mode` to `ip`, described with the [elasticstack role](../roles/elasticstack/README.md). ## Before the first run diff --git a/roles/beats/tasks/main.yml b/roles/beats/tasks/main.yml index 3f5c5717..282becc7 100644 --- a/roles/beats/tasks/main.yml +++ b/roles/beats/tasks/main.yml @@ -69,6 +69,7 @@ ansible.builtin.set_fact: beats_elasticsearch_hosts: >- {{ groups[elasticstack_elasticsearch_group_name] | default([]) + | map('extract', __elasticstack_node_address) | list if elasticstack_full_stack | bool else [] }} when: beats_elasticsearch_hosts is undefined tags: always @@ -77,6 +78,7 @@ ansible.builtin.set_fact: beats_logstash_hosts: >- {{ groups[elasticstack_logstash_group_name] | default([]) + | map('extract', __elasticstack_node_address) | list if elasticstack_full_stack | bool else [] }} when: beats_logstash_hosts is undefined tags: always diff --git a/roles/beats/templates/auditbeat.yml.j2 b/roles/beats/templates/auditbeat.yml.j2 index b2a688f1..237f3003 100644 --- a/roles/beats/templates/auditbeat.yml.j2 +++ b/roles/beats/templates/auditbeat.yml.j2 @@ -32,7 +32,7 @@ setup.template.settings: index.number_of_shards: 1 setup.kibana: {% if elasticstack_full_stack | bool and groups[elasticstack_kibana_group_name] | default([]) | length > 0 %} - host: "http://{{ groups[elasticstack_kibana_group_name] | first }}:{{ elasticstack_kibana_port }}" + host: "http://{{ __elasticstack_node_address[groups[elasticstack_kibana_group_name] | first] }}:{{ elasticstack_kibana_port }}" {% if beats_security | bool %} username: "elastic" password: "{{ beats_writer_password.stdout }}" diff --git a/roles/beats/templates/filebeat.yml.j2 b/roles/beats/templates/filebeat.yml.j2 index 453d8def..d4c1a62b 100644 --- a/roles/beats/templates/filebeat.yml.j2 +++ b/roles/beats/templates/filebeat.yml.j2 @@ -110,7 +110,7 @@ setup.template.settings: index.number_of_shards: 1 setup.kibana: {% if elasticstack_full_stack | bool and groups[elasticstack_kibana_group_name] | default([]) | length > 0 %} - host: "http://{{ groups[elasticstack_kibana_group_name] | first }}:{{ elasticstack_kibana_port }}" + host: "http://{{ __elasticstack_node_address[groups[elasticstack_kibana_group_name] | first] }}:{{ elasticstack_kibana_port }}" {% if beats_security | bool %} username: "elastic" password: "{{ beats_writer_password.stdout }}" diff --git a/roles/beats/templates/metricbeat.yml.j2 b/roles/beats/templates/metricbeat.yml.j2 index 373146ab..52ff5fcc 100644 --- a/roles/beats/templates/metricbeat.yml.j2 +++ b/roles/beats/templates/metricbeat.yml.j2 @@ -7,7 +7,7 @@ setup.template.settings: index.number_of_shards: 1 setup.kibana: {% if elasticstack_full_stack | bool and groups[elasticstack_kibana_group_name] | default([]) | length > 0 %} - host: "http://{{ groups[elasticstack_kibana_group_name] | first }}:{{ elasticstack_kibana_port }}" + host: "http://{{ __elasticstack_node_address[groups[elasticstack_kibana_group_name] | first] }}:{{ elasticstack_kibana_port }}" {% if beats_security | bool %} username: "elastic" password: "{{ beats_writer_password.stdout }}" diff --git a/roles/elasticstack/README.md b/roles/elasticstack/README.md index 1b27c92a..7d476324 100644 --- a/roles/elasticstack/README.md +++ b/roles/elasticstack/README.md @@ -35,6 +35,7 @@ The role runs automatically the first time any component role is applied to a ho | `elasticstack_elasticsearch_group_name`
**Type**: `str`
**Default**: `"elasticsearch"` | Inventory group that holds the Elasticsearch hosts. | | `elasticstack_logstash_group_name`
**Type**: `str`
**Default**: `"logstash"` | Inventory group that holds the Logstash hosts. | | `elasticstack_kibana_group_name`
**Type**: `str`
**Default**: `"kibana"` | Inventory group that holds the Kibana hosts. | +| `elasticstack_address_mode`
**Type**: `str`
**Default**: `"inventory"`
**Choices**: `inventory`, `fqdn`, `hostname`, `ip` | How the roles address the hosts they take from the elasticsearch, logstash and kibana inventory groups. "inventory" uses the name from the inventory, "fqdn" the fully qualified domain name, "hostname" the short host name and "ip" the default IPv4 address. The last three come from the facts of that host. Set it in group_vars/all. Set it in the host_vars of a single host to address only that host differently. Do not pass it with -e, because that also overrides the host_vars. Host lists you set yourself, such as beats_elasticsearch_hosts, are used as they are. The Elasticsearch nodes always reach each other by IP. | | `elasticstack_elasticsearch_http_port`
**Type**: `int`
**Default**: `9200` | Elasticsearch HTTP API port. | | `elasticstack_kibana_host`
**Type**: `str`
**Default**: `"{{ ansible_fqdn }}"` | Host name Kibana publishes itself under (server.publicBaseUrl). Defaults to the fully qualified domain name of the Kibana host. Set it when users reach Kibana under a different name, for example behind a reverse proxy or a load balancer. | | `elasticstack_kibana_port`
**Type**: `int`
**Default**: `5601` | Kibana HTTP port (also used to build the public base URL). | diff --git a/roles/elasticstack/defaults/main.yml b/roles/elasticstack/defaults/main.yml index 21219ca1..6397562c 100644 --- a/roles/elasticstack/defaults/main.yml +++ b/roles/elasticstack/defaults/main.yml @@ -3,6 +3,7 @@ elasticstack_elasticsearch_group_name: elasticsearch elasticstack_logstash_group_name: logstash elasticstack_kibana_group_name: kibana +elasticstack_address_mode: inventory elasticstack_beats_port: 5044 elasticstack_ca_host: "{{ groups[elasticstack_elasticsearch_group_name][0] | default('') }}" diff --git a/roles/elasticstack/meta/argument_specs.yml b/roles/elasticstack/meta/argument_specs.yml index e7d26978..737a88f8 100644 --- a/roles/elasticstack/meta/argument_specs.yml +++ b/roles/elasticstack/meta/argument_specs.yml @@ -81,6 +81,24 @@ argument_specs: default: kibana description: Inventory group that holds the Kibana hosts. + elasticstack_address_mode: + type: str + default: inventory + choices: + - inventory + - fqdn + - hostname + - ip + description: >- + How the roles address the hosts they take from the elasticsearch, logstash and + kibana inventory groups. "inventory" uses the name from the inventory, "fqdn" the + fully qualified domain name, "hostname" the short host name and "ip" the default + IPv4 address. The last three come from the facts of that host. Set it in + group_vars/all. Set it in the host_vars of a single host to address only that host + differently. Do not pass it with -e, because that also overrides the host_vars. + Host lists you set yourself, such as beats_elasticsearch_hosts, are used as they + are. The Elasticsearch nodes always reach each other by IP. + # ----- Ports ----- elasticstack_elasticsearch_http_port: type: int diff --git a/roles/elasticstack/tasks/main.yml b/roles/elasticstack/tasks/main.yml index bd92d8b9..6d41b251 100644 --- a/roles/elasticstack/tasks/main.yml +++ b/roles/elasticstack/tasks/main.yml @@ -22,6 +22,9 @@ - name: Install common packages and dependencies ansible.builtin.import_tasks: packages.yml + - name: Build the addresses of the Elastic Stack hosts + ansible.builtin.import_tasks: node_address.yml + - name: Register elasticstack role has run for that host ansible.builtin.set_fact: _elasticstack_role_imported: true diff --git a/roles/elasticstack/tasks/node_address.yml b/roles/elasticstack/tasks/node_address.yml new file mode 100644 index 00000000..bd16d6ae --- /dev/null +++ b/roles/elasticstack/tasks/node_address.yml @@ -0,0 +1,33 @@ +--- +- name: Build the address of every Elastic Stack host + vars: + __elasticstack_peer_hosts: >- + {{ (groups[elasticstack_elasticsearch_group_name] | default([]) + + groups[elasticstack_logstash_group_name] | default([]) + + groups[elasticstack_kibana_group_name] | default([])) | unique }} + tags: always + block: + - name: Gather the facts of Elastic Stack hosts that are still missing + ansible.builtin.setup: + gather_subset: + - min + - network + delegate_to: "{{ item }}" + delegate_facts: true + loop: "{{ __elasticstack_peer_hosts }}" + when: + - (hostvars[item].elasticstack_address_mode | default(elasticstack_address_mode)) != 'inventory' + - hostvars[item].ansible_fqdn is undefined + + - name: Pick the address of every Elastic Stack host + ansible.builtin.set_fact: + __elasticstack_node_address: >- + {{ __elasticstack_node_address | default({}) | combine({item: ( + item if __elasticstack_peer_mode == 'inventory' else + hostvars[item].ansible_fqdn if __elasticstack_peer_mode == 'fqdn' else + hostvars[item].ansible_hostname if __elasticstack_peer_mode == 'hostname' else + hostvars[item].ansible_default_ipv4.address + | default(hostvars[item].ansible_all_ipv4_addresses[0]))}) }} + vars: + __elasticstack_peer_mode: "{{ hostvars[item].elasticstack_address_mode | default(elasticstack_address_mode) }}" + loop: "{{ __elasticstack_peer_hosts }}" diff --git a/roles/kibana/tasks/main.yml b/roles/kibana/tasks/main.yml index f178f2ee..9db4eb2b 100644 --- a/roles/kibana/tasks/main.yml +++ b/roles/kibana/tasks/main.yml @@ -27,7 +27,7 @@ - name: Set Elasticsearch hosts if used with other roles ansible.builtin.set_fact: - kibana_elasticsearch_hosts: "{{ groups[elasticstack_elasticsearch_group_name] }}" + kibana_elasticsearch_hosts: "{{ groups[elasticstack_elasticsearch_group_name] | map('extract', __elasticstack_node_address) | list }}" when: - kibana_elasticsearch_hosts is undefined - groups[elasticstack_elasticsearch_group_name] is defined diff --git a/roles/logstash/tasks/main.yml b/roles/logstash/tasks/main.yml index dbf121a1..f2a6bd6f 100644 --- a/roles/logstash/tasks/main.yml +++ b/roles/logstash/tasks/main.yml @@ -19,7 +19,7 @@ - name: Set Elasticsearch hosts if used with other roles ansible.builtin.set_fact: - logstash_elasticsearch: "{{ groups[elasticstack_elasticsearch_group_name] }}" + logstash_elasticsearch: "{{ groups[elasticstack_elasticsearch_group_name] | map('extract', __elasticstack_node_address) | list }}" when: - logstash_elasticsearch is undefined - groups[elasticstack_elasticsearch_group_name] is defined From 5ee7d2fd2424915f00d68bbbae5000acb5c66e09 Mon Sep 17 00:00:00 2001 From: Afeef Ghannam Date: Thu, 15 Oct 2026 18:24:09 +0200 Subject: [PATCH 2/6] molecule: use the ip address mode and drop the /etc/hosts workaround --- changelogs/fragments/address_mode.yml | 4 ++++ molecule/elasticstack_default/converge.yml | 2 ++ molecule/elasticstack_default/prepare.yml | 17 ----------------- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/changelogs/fragments/address_mode.yml b/changelogs/fragments/address_mode.yml index 5d133680..76f8b7b7 100644 --- a/changelogs/fragments/address_mode.yml +++ b/changelogs/fragments/address_mode.yml @@ -4,3 +4,7 @@ minor_changes: inventory as before, ``fqdn``, ``hostname`` and ``ip`` take the address from the facts of that host. It can be set per host in its host_vars. Facts of those hosts that are missing, for example in a run limited with ``--limit``, are gathered first. + +trivial: + - The full stack Molecule scenario sets ``elasticstack_address_mode`` to ``ip`` and no longer + writes the container addresses into /etc/hosts. diff --git a/molecule/elasticstack_default/converge.yml b/molecule/elasticstack_default/converge.yml index 7f5320c0..dc96c6db 100644 --- a/molecule/elasticstack_default/converge.yml +++ b/molecule/elasticstack_default/converge.yml @@ -17,6 +17,8 @@ elasticstack_release: "{{ lookup('env', 'ELASTIC_RELEASE') | int}}" elasticsearch_heap: "1" elasticstack_full_stack: true + # The containers cannot resolve each other's names on the GitHub runner. + elasticstack_address_mode: ip elasticstack_no_log: false # Passphrases with arbitrary special characters (space, single quote, $, backtick) to test # that they reach certutil/openssl/keystore without shell or shlex mangling. ca_pass and diff --git a/molecule/elasticstack_default/prepare.yml b/molecule/elasticstack_default/prepare.yml index 37ea6841..611cf912 100644 --- a/molecule/elasticstack_default/prepare.yml +++ b/molecule/elasticstack_default/prepare.yml @@ -59,20 +59,3 @@ state: latest update_cache: yes when: ansible_os_family == "Debian" - - # Temporary CI workaround: containers cannot resolve each other by name - # (the runner's resolver is Azure's 168.63.129.16, which does not know the - # container hostnames). Seed /etc/hosts from gathered facts so name-based - # connections work. Remove once #524 (elasticstack_address_mode) lands and - # molecule sets elasticstack_address_mode: ip. - - name: Seed /etc/hosts from facts for inter-container name resolution - ansible.builtin.blockinfile: - path: /etc/hosts - marker: "# {mark} ANSIBLE CLUSTER HOSTS" - # /etc/hosts is a Docker bind-mount, so the default atomic temp+rename - # write fails with EBUSY; write in place instead. - unsafe_writes: true - block: |- - {% for h in groups['all'] %} - {{ hostvars[h].ansible_default_ipv4.address | default(hostvars[h].ansible_all_ipv4_addresses[0]) }} {{ hostvars[h].ansible_fqdn }} {{ hostvars[h].ansible_hostname }} {{ h }} - {% endfor %} From 5ae19f969151964b9f5f711d666a16fbe3572fb1 Mon Sep 17 00:00:00 2001 From: Afeef Ghannam Date: Thu, 15 Oct 2026 18:42:48 +0200 Subject: [PATCH 3/6] Let a host set the address the roles reach it at with elasticstack_node_address --- changelogs/fragments/address_mode.yml | 5 ++++ docs/getting-started.md | 2 +- roles/beats/README.md | 4 ++-- roles/beats/meta/argument_specs.yml | 18 ++++++++------- .../tasks/elasticsearch-security.yml | 11 +++++++-- roles/elasticstack/README.md | 3 ++- roles/elasticstack/meta/argument_specs.yml | 23 ++++++++++++++----- roles/elasticstack/tasks/node_address.yml | 3 +++ roles/kibana/README.md | 2 +- roles/kibana/meta/argument_specs.yml | 6 +++-- roles/logstash/README.md | 2 +- roles/logstash/meta/argument_specs.yml | 7 ++++-- roles/logstash/tasks/logstash-security.yml | 11 +++++++-- 13 files changed, 69 insertions(+), 28 deletions(-) diff --git a/changelogs/fragments/address_mode.yml b/changelogs/fragments/address_mode.yml index 76f8b7b7..79c67174 100644 --- a/changelogs/fragments/address_mode.yml +++ b/changelogs/fragments/address_mode.yml @@ -4,6 +4,11 @@ minor_changes: inventory as before, ``fqdn``, ``hostname`` and ``ip`` take the address from the facts of that host. It can be set per host in its host_vars. Facts of those hosts that are missing, for example in a run limited with ``--limit``, are gathered first. + - Setting ``elasticstack_node_address`` in the host_vars of a host makes the roles reach that + host at this address, whatever ``elasticstack_address_mode`` says, for example through the + IP of a particular network card. The address is also added to the certificate of an + Elasticsearch host and to the certificate a Logstash host presents to the Beats. Existing + certificates get it when they are renewed. trivial: - The full stack Molecule scenario sets ``elasticstack_address_mode`` to ``ip`` and no longer diff --git a/docs/getting-started.md b/docs/getting-started.md index d0cd410f..81ac64b0 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -10,7 +10,7 @@ Hosts in these groups get the respective services. Restricting your plays to onl Make sure every host that should be configured is part of your playbook, including hosts that only run Beats. The collection first gathers facts from all hosts and then uses facts such as host names and IP addresses to connect the tools to each other. -By default the tools reach each other by the host names from your inventory, so these names have to resolve on every host, through DNS or the hosts files. Without that, set `elasticstack_address_mode` to `ip`, described with the [elasticstack role](../roles/elasticstack/README.md). +By default Beats, Kibana and Logstash reach the other hosts by their names from your inventory, so these names have to resolve on those hosts, through DNS or the hosts files. Without that, set `elasticstack_address_mode` to `ip`, described with the [elasticstack role](../roles/elasticstack/README.md). ## Before the first run diff --git a/roles/beats/README.md b/roles/beats/README.md index 7287c99f..57fc1b72 100644 --- a/roles/beats/README.md +++ b/roles/beats/README.md @@ -39,8 +39,8 @@ Run only parts of the role with `--tags`: | `beats_filebeat`
**Type**: `bool`
**Default**: `true` | Install and manage Filebeat. | | `beats_auditbeat`
**Type**: `bool`
**Default**: `false` | Install and manage Auditbeat. | | `beats_metricbeat`
**Type**: `bool`
**Default**: `false` | Install and manage Metricbeat. | -| `beats_elasticsearch_hosts`
**Type**: `list` of `str` | Elasticsearch hosts the Beats ship to. When elasticstack_full_stack is true and this variable is not set, the role fills it with the hosts of the inventory group named in elasticstack_elasticsearch_group_name. When elasticstack_full_stack is false, set it yourself as soon as a Beat uses the elasticsearch output, otherwise the run stops. | -| `beats_logstash_hosts`
**Type**: `list` of `str` | Logstash hosts the Beats ship to. When elasticstack_full_stack is true and this variable is not set, the role fills it with the hosts of the inventory group named in elasticstack_logstash_group_name. When elasticstack_full_stack is false, set it yourself as soon as a Beat uses the logstash output, otherwise the run stops. | +| `beats_elasticsearch_hosts`
**Type**: `list` of `str` | Elasticsearch hosts the Beats ship to. When elasticstack_full_stack is true and this variable is not set, the role fills it with the addresses of the hosts in the inventory group named in elasticstack_elasticsearch_group_name, picked by elasticstack_address_mode and elasticstack_node_address. When elasticstack_full_stack is false, set it yourself as soon as a Beat uses the elasticsearch output, otherwise the run stops. | +| `beats_logstash_hosts`
**Type**: `list` of `str` | Logstash hosts the Beats ship to. When elasticstack_full_stack is true and this variable is not set, the role fills it with the addresses of the hosts in the inventory group named in elasticstack_logstash_group_name, picked by elasticstack_address_mode and elasticstack_node_address. When elasticstack_full_stack is false, set it yourself as soon as a Beat uses the logstash output, otherwise the run stops. | | `beats_fields`
**Type**: `list` of `str` | Global fields added to the log and syslog (tcp/udp) inputs (not to the mysql, journald or docker inputs), given as a list of "key: value" strings. This is the global counterpart to the per-input fields. Unset by default. See the Filebeat inputs documentation. | | `beats_logging`
**Type**: `str`
**Default**: `"file"` | Where the Beats log. Set to "file" to log into beats_logpath; any other value leaves the Beats built-in logging. | | `beats_loglevel`
**Type**: `str`
**Default**: `"info"` | Log level for all Beats. | diff --git a/roles/beats/meta/argument_specs.yml b/roles/beats/meta/argument_specs.yml index dc2b960e..a3cb2321 100644 --- a/roles/beats/meta/argument_specs.yml +++ b/roles/beats/meta/argument_specs.yml @@ -45,20 +45,22 @@ argument_specs: elements: str description: >- Elasticsearch hosts the Beats ship to. When elasticstack_full_stack is true and - this variable is not set, the role fills it with the hosts of the inventory group - named in elasticstack_elasticsearch_group_name. When elasticstack_full_stack is - false, set it yourself as soon as a Beat uses the elasticsearch output, otherwise - the run stops. + this variable is not set, the role fills it with the addresses of the hosts in the + inventory group named in elasticstack_elasticsearch_group_name, picked by + elasticstack_address_mode and elasticstack_node_address. When + elasticstack_full_stack is false, set it yourself as soon as a Beat uses the + elasticsearch output, otherwise the run stops. beats_logstash_hosts: type: list elements: str description: >- Logstash hosts the Beats ship to. When elasticstack_full_stack is true and this - variable is not set, the role fills it with the hosts of the inventory group - named in elasticstack_logstash_group_name. When elasticstack_full_stack is false, - set it yourself as soon as a Beat uses the logstash output, otherwise the run - stops. + variable is not set, the role fills it with the addresses of the hosts in the + inventory group named in elasticstack_logstash_group_name, picked by + elasticstack_address_mode and elasticstack_node_address. When + elasticstack_full_stack is false, set it yourself as soon as a Beat uses the + logstash output, otherwise the run stops. beats_fields: type: list diff --git a/roles/elasticsearch/tasks/elasticsearch-security.yml b/roles/elasticsearch/tasks/elasticsearch-security.yml index 9b71a36a..ea3daab8 100644 --- a/roles/elasticsearch/tasks/elasticsearch-security.yml +++ b/roles/elasticsearch/tasks/elasticsearch-security.yml @@ -263,9 +263,16 @@ - --name - "{{ hostvars[item].ansible_hostname }}" - --ip - - "{{ hostvars[item].ansible_default_ipv4.address | default(hostvars[item].ansible_all_ipv4_addresses[0]) }}" + - >- + {{ ([hostvars[item].ansible_default_ipv4.address | default(hostvars[item].ansible_all_ipv4_addresses[0])] + + ([hostvars[item].elasticstack_node_address | default('')] | select('match', '^[0-9.]+$|:') | list)) + | join(',') }} - --dns - - "{{ hostvars[item].ansible_hostname }},{{ hostvars[item].ansible_fqdn }},{{ hostvars[item].inventory_hostname }}" + - >- + {{ ([hostvars[item].ansible_hostname, hostvars[item].ansible_fqdn, hostvars[item].inventory_hostname] + + ([hostvars[item].elasticstack_node_address | default('')] + | reject('match', '^[0-9.]+$|:') | reject('equalto', '') | list)) + | join(',') }} - --pass - "{{ (hostvars[item].elasticsearch_tls_key_passphrase is defined) | ternary(hostvars[item].elasticsearch_tls_key_passphrase, elasticsearch_tls_key_passphrase) }}" - --out diff --git a/roles/elasticstack/README.md b/roles/elasticstack/README.md index 7d476324..4261675c 100644 --- a/roles/elasticstack/README.md +++ b/roles/elasticstack/README.md @@ -35,7 +35,8 @@ The role runs automatically the first time any component role is applied to a ho | `elasticstack_elasticsearch_group_name`
**Type**: `str`
**Default**: `"elasticsearch"` | Inventory group that holds the Elasticsearch hosts. | | `elasticstack_logstash_group_name`
**Type**: `str`
**Default**: `"logstash"` | Inventory group that holds the Logstash hosts. | | `elasticstack_kibana_group_name`
**Type**: `str`
**Default**: `"kibana"` | Inventory group that holds the Kibana hosts. | -| `elasticstack_address_mode`
**Type**: `str`
**Default**: `"inventory"`
**Choices**: `inventory`, `fqdn`, `hostname`, `ip` | How the roles address the hosts they take from the elasticsearch, logstash and kibana inventory groups. "inventory" uses the name from the inventory, "fqdn" the fully qualified domain name, "hostname" the short host name and "ip" the default IPv4 address. The last three come from the facts of that host. Set it in group_vars/all. Set it in the host_vars of a single host to address only that host differently. Do not pass it with -e, because that also overrides the host_vars. Host lists you set yourself, such as beats_elasticsearch_hosts, are used as they are. The Elasticsearch nodes always reach each other by IP. | +| `elasticstack_address_mode`
**Type**: `str`
**Default**: `"inventory"`
**Choices**: `inventory`, `fqdn`, `hostname`, `ip` | How the roles address the hosts they take from the elasticsearch, logstash and kibana inventory groups. "inventory" uses the name from the inventory, "fqdn" the fully qualified domain name, "hostname" the short host name and "ip" the IPv4 address of the network card with the default route. The last three come from the facts of that host. Set it in the host_vars of a single host to address only that host differently. Host lists you set yourself, such as beats_elasticsearch_hosts, are used as they are. The setting does not change discovery.seed_hosts, through which the Elasticsearch nodes find each other. | +| `elasticstack_node_address`
**Type**: `str` | Address the roles use to reach this host, an IP address or a name. Set it in the host_vars of that host. It takes precedence over elasticstack_address_mode, for example to reach a host with several network cards through the IP of a particular one. It is also added to the certificate of an Elasticsearch host and to the certificate a Logstash host presents to the Beats. A certificate that already exists gets it only when it is renewed, with the tag renew_es_cert or renew_logstash_cert. It does not change discovery.seed_hosts. | | `elasticstack_elasticsearch_http_port`
**Type**: `int`
**Default**: `9200` | Elasticsearch HTTP API port. | | `elasticstack_kibana_host`
**Type**: `str`
**Default**: `"{{ ansible_fqdn }}"` | Host name Kibana publishes itself under (server.publicBaseUrl). Defaults to the fully qualified domain name of the Kibana host. Set it when users reach Kibana under a different name, for example behind a reverse proxy or a load balancer. | | `elasticstack_kibana_port`
**Type**: `int`
**Default**: `5601` | Kibana HTTP port (also used to build the public base URL). | diff --git a/roles/elasticstack/meta/argument_specs.yml b/roles/elasticstack/meta/argument_specs.yml index 737a88f8..84d8815c 100644 --- a/roles/elasticstack/meta/argument_specs.yml +++ b/roles/elasticstack/meta/argument_specs.yml @@ -92,12 +92,23 @@ argument_specs: description: >- How the roles address the hosts they take from the elasticsearch, logstash and kibana inventory groups. "inventory" uses the name from the inventory, "fqdn" the - fully qualified domain name, "hostname" the short host name and "ip" the default - IPv4 address. The last three come from the facts of that host. Set it in - group_vars/all. Set it in the host_vars of a single host to address only that host - differently. Do not pass it with -e, because that also overrides the host_vars. - Host lists you set yourself, such as beats_elasticsearch_hosts, are used as they - are. The Elasticsearch nodes always reach each other by IP. + fully qualified domain name, "hostname" the short host name and "ip" the IPv4 + address of the network card with the default route. The last three come from the + facts of that host. Set it in the host_vars of a single host to address only that + host differently. Host lists you set yourself, such as beats_elasticsearch_hosts, + are used as they are. The setting does not change discovery.seed_hosts, through + which the Elasticsearch nodes find each other. + + elasticstack_node_address: + type: str + description: >- + Address the roles use to reach this host, an IP address or a name. Set it in the + host_vars of that host. It takes precedence over elasticstack_address_mode, for + example to reach a host with several network cards through the IP of a particular + one. It is also added to the certificate of an Elasticsearch host and to the + certificate a Logstash host presents to the Beats. A certificate that already exists + gets it only when it is renewed, with the tag renew_es_cert or renew_logstash_cert. + It does not change discovery.seed_hosts. # ----- Ports ----- elasticstack_elasticsearch_http_port: diff --git a/roles/elasticstack/tasks/node_address.yml b/roles/elasticstack/tasks/node_address.yml index bd16d6ae..1f1155fa 100644 --- a/roles/elasticstack/tasks/node_address.yml +++ b/roles/elasticstack/tasks/node_address.yml @@ -16,6 +16,7 @@ delegate_facts: true loop: "{{ __elasticstack_peer_hosts }}" when: + - hostvars[item].elasticstack_node_address is undefined - (hostvars[item].elasticstack_address_mode | default(elasticstack_address_mode)) != 'inventory' - hostvars[item].ansible_fqdn is undefined @@ -23,6 +24,8 @@ ansible.builtin.set_fact: __elasticstack_node_address: >- {{ __elasticstack_node_address | default({}) | combine({item: ( + hostvars[item].elasticstack_node_address + if hostvars[item].elasticstack_node_address is defined else item if __elasticstack_peer_mode == 'inventory' else hostvars[item].ansible_fqdn if __elasticstack_peer_mode == 'fqdn' else hostvars[item].ansible_hostname if __elasticstack_peer_mode == 'hostname' else diff --git a/roles/kibana/README.md b/roles/kibana/README.md index fe02e630..ab4a9b88 100644 --- a/roles/kibana/README.md +++ b/roles/kibana/README.md @@ -54,7 +54,7 @@ Run only parts of the role with `--tags`: | `kibana_enable`
**Type**: `bool`
**Default**: `true` | Start and enable the Kibana service. | | `kibana_config_backup`
**Type**: `bool`
**Default**: `true` | Keep a backup of kibana.yml whenever the role changes it. | | `kibana_manage_yaml`
**Type**: `bool`
**Default**: `true` | Manage and overwrite kibana.yml. | -| `kibana_elasticsearch_hosts`
**Type**: `list` of `str` | Elasticsearch hosts Kibana connects to. Defaults to the nodes from the elasticsearch group, or localhost when used standalone. | +| `kibana_elasticsearch_hosts`
**Type**: `list` of `str` | Elasticsearch hosts Kibana connects to. When it is not set, the role fills it with the addresses of the hosts in the inventory group named in elasticstack_elasticsearch_group_name, picked by elasticstack_address_mode and elasticstack_node_address, or with localhost if that group does not exist. | | `kibana_security`
**Type**: `bool`
**Default**: `true` | Enable X-Pack security: connect to Elasticsearch over HTTPS with authentication and set up the encryption keys. Only effective in a full stack setup with the elastic variant. | | `kibana_sniff_on_start`
**Type**: `bool`
**Default**: `false` | Ask Elasticsearch for the list of nodes at startup (elasticsearch.sniffOnStart). | | `kibana_sniff_on_connection_fault`
**Type**: `bool`
**Default**: `false` | Update the list of Elasticsearch nodes on connection fault (elasticsearch.sniffOnConnectionFault). | diff --git a/roles/kibana/meta/argument_specs.yml b/roles/kibana/meta/argument_specs.yml index 43f88202..6c721019 100644 --- a/roles/kibana/meta/argument_specs.yml +++ b/roles/kibana/meta/argument_specs.yml @@ -41,8 +41,10 @@ argument_specs: type: list elements: str description: >- - Elasticsearch hosts Kibana connects to. Defaults to the nodes from the - elasticsearch group, or localhost when used standalone. + Elasticsearch hosts Kibana connects to. When it is not set, the role fills it with + the addresses of the hosts in the inventory group named in + elasticstack_elasticsearch_group_name, picked by elasticstack_address_mode and + elasticstack_node_address, or with localhost if that group does not exist. kibana_security: type: bool diff --git a/roles/logstash/README.md b/roles/logstash/README.md index ace92725..822a838a 100644 --- a/roles/logstash/README.md +++ b/roles/logstash/README.md @@ -94,7 +94,7 @@ Run only parts of the role with `--tags`: | `logstash_redis_ssl_certificate_authorities`
**Type**: `list` of `str` | List of CA certificate paths used to verify the Redis server on the Redis outputs. Rendered only when logstash_redis_tls is true. | | `logstash_redis_ssl_supported_protocols`
**Type**: `str` | TLS protocol version accepted on the Redis outputs (for example "TLSv1.3"). Rendered only when logstash_redis_tls is true. | | `logstash_redis_ssl_verification_mode`
**Type**: `str` | Certificate verification mode for the Redis outputs ("full" or "none"). Rendered only when logstash_redis_tls is true. | -| `logstash_elasticsearch`
**Type**: `list` of `str` | Elasticsearch hosts for the default output. Defaults to the nodes from the elasticsearch group, or localhost when used standalone. | +| `logstash_elasticsearch`
**Type**: `list` of `str` | Elasticsearch hosts for the default output. When it is not set and elasticstack_full_stack is true, the role fills it with the addresses of the hosts in the inventory group named in elasticstack_elasticsearch_group_name, picked by elasticstack_address_mode and elasticstack_node_address. Without that group it defaults to localhost. | | `logstash_validate_after_inactivity`
**Type**: `str`
**Default**: `"300"` | Seconds Logstash waits before validating a previously idle connection to Elasticsearch. | | `logstash_sniffing`
**Type**: `bool`
**Default**: `false` | Enable sniffing for additional Elasticsearch nodes. | | `logstash_sniffing_delay`
**Type**: `str` | Seconds to wait between sniffing attempts. Unset by default. | diff --git a/roles/logstash/meta/argument_specs.yml b/roles/logstash/meta/argument_specs.yml index eb2bbbed..2cb09859 100644 --- a/roles/logstash/meta/argument_specs.yml +++ b/roles/logstash/meta/argument_specs.yml @@ -311,8 +311,11 @@ argument_specs: type: list elements: str description: >- - Elasticsearch hosts for the default output. Defaults to the nodes from - the elasticsearch group, or localhost when used standalone. + Elasticsearch hosts for the default output. When it is not set and + elasticstack_full_stack is true, the role fills it with the addresses of the hosts + in the inventory group named in elasticstack_elasticsearch_group_name, picked by + elasticstack_address_mode and elasticstack_node_address. Without that group it + defaults to localhost. logstash_validate_after_inactivity: type: str diff --git a/roles/logstash/tasks/logstash-security.yml b/roles/logstash/tasks/logstash-security.yml index 31a42f4c..66d2e2a4 100644 --- a/roles/logstash/tasks/logstash-security.yml +++ b/roles/logstash/tasks/logstash-security.yml @@ -248,9 +248,16 @@ - --name - "{{ ansible_hostname }}" - --ip - - "{{ ansible_default_ipv4.address }}" + - >- + {{ ([ansible_default_ipv4.address] + + ([elasticstack_node_address | default('')] | select('match', '^[0-9.]+$|:') | list)) + | join(',') }} - --dns - - "{{ ansible_hostname }},{{ ansible_fqdn }},{{ inventory_hostname }}" + - >- + {{ ([ansible_hostname, ansible_fqdn, inventory_hostname] + + ([elasticstack_node_address | default('')] + | reject('match', '^[0-9.]+$|:') | reject('equalto', '') | list)) + | join(',') }} - --pass - "{{ logstash_tls_key_passphrase }}" - --pem From b2da4e444585208e87216791d30fc5785d64f29a Mon Sep 17 00:00:00 2001 From: Afeef Ghannam Date: Fri, 16 Oct 2026 13:21:58 +0200 Subject: [PATCH 4/6] Treat elasticstack_node_address as an IPv4 address or a DNS name --- .../elasticsearch/tasks/elasticsearch-security.yml | 4 ++-- roles/elasticstack/README.md | 2 +- roles/elasticstack/meta/argument_specs.yml | 14 +++++++------- roles/logstash/tasks/logstash-security.yml | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/roles/elasticsearch/tasks/elasticsearch-security.yml b/roles/elasticsearch/tasks/elasticsearch-security.yml index ea3daab8..8083c998 100644 --- a/roles/elasticsearch/tasks/elasticsearch-security.yml +++ b/roles/elasticsearch/tasks/elasticsearch-security.yml @@ -265,13 +265,13 @@ - --ip - >- {{ ([hostvars[item].ansible_default_ipv4.address | default(hostvars[item].ansible_all_ipv4_addresses[0])] - + ([hostvars[item].elasticstack_node_address | default('')] | select('match', '^[0-9.]+$|:') | list)) + + ([hostvars[item].elasticstack_node_address | default('')] | select('match', '^[0-9.]+$') | list)) | join(',') }} - --dns - >- {{ ([hostvars[item].ansible_hostname, hostvars[item].ansible_fqdn, hostvars[item].inventory_hostname] + ([hostvars[item].elasticstack_node_address | default('')] - | reject('match', '^[0-9.]+$|:') | reject('equalto', '') | list)) + | reject('match', '^[0-9.]+$') | reject('equalto', '') | list)) | join(',') }} - --pass - "{{ (hostvars[item].elasticsearch_tls_key_passphrase is defined) | ternary(hostvars[item].elasticsearch_tls_key_passphrase, elasticsearch_tls_key_passphrase) }}" diff --git a/roles/elasticstack/README.md b/roles/elasticstack/README.md index 4261675c..2022ad8d 100644 --- a/roles/elasticstack/README.md +++ b/roles/elasticstack/README.md @@ -36,7 +36,7 @@ The role runs automatically the first time any component role is applied to a ho | `elasticstack_logstash_group_name`
**Type**: `str`
**Default**: `"logstash"` | Inventory group that holds the Logstash hosts. | | `elasticstack_kibana_group_name`
**Type**: `str`
**Default**: `"kibana"` | Inventory group that holds the Kibana hosts. | | `elasticstack_address_mode`
**Type**: `str`
**Default**: `"inventory"`
**Choices**: `inventory`, `fqdn`, `hostname`, `ip` | How the roles address the hosts they take from the elasticsearch, logstash and kibana inventory groups. "inventory" uses the name from the inventory, "fqdn" the fully qualified domain name, "hostname" the short host name and "ip" the IPv4 address of the network card with the default route. The last three come from the facts of that host. Set it in the host_vars of a single host to address only that host differently. Host lists you set yourself, such as beats_elasticsearch_hosts, are used as they are. The setting does not change discovery.seed_hosts, through which the Elasticsearch nodes find each other. | -| `elasticstack_node_address`
**Type**: `str` | Address the roles use to reach this host, an IP address or a name. Set it in the host_vars of that host. It takes precedence over elasticstack_address_mode, for example to reach a host with several network cards through the IP of a particular one. It is also added to the certificate of an Elasticsearch host and to the certificate a Logstash host presents to the Beats. A certificate that already exists gets it only when it is renewed, with the tag renew_es_cert or renew_logstash_cert. It does not change discovery.seed_hosts. | +| `elasticstack_node_address`
**Type**: `str` | Address the roles use to reach this host, an IPv4 address or a DNS name that resolves to this host. Set it in the host_vars of that host. It takes precedence over elasticstack_address_mode, for example to reach a host with several network cards through the IP of a particular one. It is also added to the certificate of an Elasticsearch host and to the certificate a Logstash host presents to the Beats. A certificate that already exists gets it only when it is renewed, with the tag renew_es_cert or renew_logstash_cert. It does not change discovery.seed_hosts. | | `elasticstack_elasticsearch_http_port`
**Type**: `int`
**Default**: `9200` | Elasticsearch HTTP API port. | | `elasticstack_kibana_host`
**Type**: `str`
**Default**: `"{{ ansible_fqdn }}"` | Host name Kibana publishes itself under (server.publicBaseUrl). Defaults to the fully qualified domain name of the Kibana host. Set it when users reach Kibana under a different name, for example behind a reverse proxy or a load balancer. | | `elasticstack_kibana_port`
**Type**: `int`
**Default**: `5601` | Kibana HTTP port (also used to build the public base URL). | diff --git a/roles/elasticstack/meta/argument_specs.yml b/roles/elasticstack/meta/argument_specs.yml index 84d8815c..a686f34e 100644 --- a/roles/elasticstack/meta/argument_specs.yml +++ b/roles/elasticstack/meta/argument_specs.yml @@ -102,13 +102,13 @@ argument_specs: elasticstack_node_address: type: str description: >- - Address the roles use to reach this host, an IP address or a name. Set it in the - host_vars of that host. It takes precedence over elasticstack_address_mode, for - example to reach a host with several network cards through the IP of a particular - one. It is also added to the certificate of an Elasticsearch host and to the - certificate a Logstash host presents to the Beats. A certificate that already exists - gets it only when it is renewed, with the tag renew_es_cert or renew_logstash_cert. - It does not change discovery.seed_hosts. + Address the roles use to reach this host, an IPv4 address or a DNS name that + resolves to this host. Set it in the host_vars of that host. It takes precedence + over elasticstack_address_mode, for example to reach a host with several network + cards through the IP of a particular one. It is also added to the certificate of + an Elasticsearch host and to the certificate a Logstash host presents to the Beats. + A certificate that already exists gets it only when it is renewed, with the tag + renew_es_cert or renew_logstash_cert. It does not change discovery.seed_hosts. # ----- Ports ----- elasticstack_elasticsearch_http_port: diff --git a/roles/logstash/tasks/logstash-security.yml b/roles/logstash/tasks/logstash-security.yml index 66d2e2a4..d3c17fe0 100644 --- a/roles/logstash/tasks/logstash-security.yml +++ b/roles/logstash/tasks/logstash-security.yml @@ -250,13 +250,13 @@ - --ip - >- {{ ([ansible_default_ipv4.address] - + ([elasticstack_node_address | default('')] | select('match', '^[0-9.]+$|:') | list)) + + ([elasticstack_node_address | default('')] | select('match', '^[0-9.]+$') | list)) | join(',') }} - --dns - >- {{ ([ansible_hostname, ansible_fqdn, inventory_hostname] + ([elasticstack_node_address | default('')] - | reject('match', '^[0-9.]+$|:') | reject('equalto', '') | list)) + | reject('match', '^[0-9.]+$') | reject('equalto', '') | list)) | join(',') }} - --pass - "{{ logstash_tls_key_passphrase }}" From 29f34d06271ec1a204cd63775ad5da2189e6f70e Mon Sep 17 00:00:00 2001 From: Afeef Ghannam Date: Fri, 16 Oct 2026 17:53:23 +0200 Subject: [PATCH 5/6] Let elasticstack_address_mode only change how the roles on a host address the others --- changelogs/fragments/address_mode.yml | 11 ++++++----- roles/elasticstack/README.md | 2 +- roles/elasticstack/meta/argument_specs.yml | 18 ++++++++++-------- roles/elasticstack/tasks/node_address.yml | 10 ++++------ 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/changelogs/fragments/address_mode.yml b/changelogs/fragments/address_mode.yml index 79c67174..1a01e419 100644 --- a/changelogs/fragments/address_mode.yml +++ b/changelogs/fragments/address_mode.yml @@ -1,9 +1,10 @@ minor_changes: - - The new ``elasticstack_address_mode`` selects how the roles address the hosts they take from - the elasticsearch, logstash and kibana inventory groups. ``inventory`` keeps the name from the - inventory as before, ``fqdn``, ``hostname`` and ``ip`` take the address from the facts of - that host. It can be set per host in its host_vars. Facts of those hosts that are missing, - for example in a run limited with ``--limit``, are gathered first. + - The new ``elasticstack_address_mode`` selects how the roles on a host address the hosts they + take from the elasticsearch, logstash and kibana inventory groups. ``inventory`` keeps the + name from the inventory as before, ``fqdn``, ``hostname`` and ``ip`` take the address from the + facts of the host that is addressed. Set for a single host, it changes only how the roles on + that host address the others. Facts of those hosts that are missing, for example in a run + limited with ``--limit``, are gathered first. - Setting ``elasticstack_node_address`` in the host_vars of a host makes the roles reach that host at this address, whatever ``elasticstack_address_mode`` says, for example through the IP of a particular network card. The address is also added to the certificate of an diff --git a/roles/elasticstack/README.md b/roles/elasticstack/README.md index 2022ad8d..86908352 100644 --- a/roles/elasticstack/README.md +++ b/roles/elasticstack/README.md @@ -35,7 +35,7 @@ The role runs automatically the first time any component role is applied to a ho | `elasticstack_elasticsearch_group_name`
**Type**: `str`
**Default**: `"elasticsearch"` | Inventory group that holds the Elasticsearch hosts. | | `elasticstack_logstash_group_name`
**Type**: `str`
**Default**: `"logstash"` | Inventory group that holds the Logstash hosts. | | `elasticstack_kibana_group_name`
**Type**: `str`
**Default**: `"kibana"` | Inventory group that holds the Kibana hosts. | -| `elasticstack_address_mode`
**Type**: `str`
**Default**: `"inventory"`
**Choices**: `inventory`, `fqdn`, `hostname`, `ip` | How the roles address the hosts they take from the elasticsearch, logstash and kibana inventory groups. "inventory" uses the name from the inventory, "fqdn" the fully qualified domain name, "hostname" the short host name and "ip" the IPv4 address of the network card with the default route. The last three come from the facts of that host. Set it in the host_vars of a single host to address only that host differently. Host lists you set yourself, such as beats_elasticsearch_hosts, are used as they are. The setting does not change discovery.seed_hosts, through which the Elasticsearch nodes find each other. | +| `elasticstack_address_mode`
**Type**: `str`
**Default**: `"inventory"`
**Choices**: `inventory`, `fqdn`, `hostname`, `ip` | How the roles on a host address the hosts they take from the elasticsearch, logstash and kibana inventory groups. "inventory" uses the name from the inventory, "fqdn" the fully qualified domain name, "hostname" the short host name and "ip" the IPv4 address of the network card with the default route. The last three come from the facts of the host that is addressed. Set for a single host, it changes only how the roles on that host address the others. To reach a single host at another address, set elasticstack_node_address for that host. Host lists you set yourself, such as beats_elasticsearch_hosts, are used as they are. The setting does not change discovery.seed_hosts, through which the Elasticsearch nodes find each other. | | `elasticstack_node_address`
**Type**: `str` | Address the roles use to reach this host, an IPv4 address or a DNS name that resolves to this host. Set it in the host_vars of that host. It takes precedence over elasticstack_address_mode, for example to reach a host with several network cards through the IP of a particular one. It is also added to the certificate of an Elasticsearch host and to the certificate a Logstash host presents to the Beats. A certificate that already exists gets it only when it is renewed, with the tag renew_es_cert or renew_logstash_cert. It does not change discovery.seed_hosts. | | `elasticstack_elasticsearch_http_port`
**Type**: `int`
**Default**: `9200` | Elasticsearch HTTP API port. | | `elasticstack_kibana_host`
**Type**: `str`
**Default**: `"{{ ansible_fqdn }}"` | Host name Kibana publishes itself under (server.publicBaseUrl). Defaults to the fully qualified domain name of the Kibana host. Set it when users reach Kibana under a different name, for example behind a reverse proxy or a load balancer. | diff --git a/roles/elasticstack/meta/argument_specs.yml b/roles/elasticstack/meta/argument_specs.yml index a686f34e..41190cb7 100644 --- a/roles/elasticstack/meta/argument_specs.yml +++ b/roles/elasticstack/meta/argument_specs.yml @@ -90,14 +90,16 @@ argument_specs: - hostname - ip description: >- - How the roles address the hosts they take from the elasticsearch, logstash and - kibana inventory groups. "inventory" uses the name from the inventory, "fqdn" the - fully qualified domain name, "hostname" the short host name and "ip" the IPv4 - address of the network card with the default route. The last three come from the - facts of that host. Set it in the host_vars of a single host to address only that - host differently. Host lists you set yourself, such as beats_elasticsearch_hosts, - are used as they are. The setting does not change discovery.seed_hosts, through - which the Elasticsearch nodes find each other. + How the roles on a host address the hosts they take from the elasticsearch, + logstash and kibana inventory groups. "inventory" uses the name from the + inventory, "fqdn" the fully qualified domain name, "hostname" the short host name + and "ip" the IPv4 address of the network card with the default route. The last + three come from the facts of the host that is addressed. Set for a single host, + it changes only how the roles on that host address the others. To reach a single + host at another address, set elasticstack_node_address for that host. Host lists + you set yourself, such as beats_elasticsearch_hosts, are used as they are. The + setting does not change discovery.seed_hosts, through which the Elasticsearch + nodes find each other. elasticstack_node_address: type: str diff --git a/roles/elasticstack/tasks/node_address.yml b/roles/elasticstack/tasks/node_address.yml index 1f1155fa..34b4fa10 100644 --- a/roles/elasticstack/tasks/node_address.yml +++ b/roles/elasticstack/tasks/node_address.yml @@ -17,7 +17,7 @@ loop: "{{ __elasticstack_peer_hosts }}" when: - hostvars[item].elasticstack_node_address is undefined - - (hostvars[item].elasticstack_address_mode | default(elasticstack_address_mode)) != 'inventory' + - elasticstack_address_mode != 'inventory' - hostvars[item].ansible_fqdn is undefined - name: Pick the address of every Elastic Stack host @@ -26,11 +26,9 @@ {{ __elasticstack_node_address | default({}) | combine({item: ( hostvars[item].elasticstack_node_address if hostvars[item].elasticstack_node_address is defined else - item if __elasticstack_peer_mode == 'inventory' else - hostvars[item].ansible_fqdn if __elasticstack_peer_mode == 'fqdn' else - hostvars[item].ansible_hostname if __elasticstack_peer_mode == 'hostname' else + item if elasticstack_address_mode == 'inventory' else + hostvars[item].ansible_fqdn if elasticstack_address_mode == 'fqdn' else + hostvars[item].ansible_hostname if elasticstack_address_mode == 'hostname' else hostvars[item].ansible_default_ipv4.address | default(hostvars[item].ansible_all_ipv4_addresses[0]))}) }} - vars: - __elasticstack_peer_mode: "{{ hostvars[item].elasticstack_address_mode | default(elasticstack_address_mode) }}" loop: "{{ __elasticstack_peer_hosts }}" From e15a289a449b911ac626c109a2b5e30e60c82415 Mon Sep 17 00:00:00 2001 From: Afeef Ghannam Date: Wed, 21 Oct 2026 10:23:32 +0200 Subject: [PATCH 6/6] Make elasticstack_address_mode apply to the host that is addressed --- changelogs/fragments/address_mode.yml | 19 ++++++------ docs/getting-started.md | 2 +- molecule/elasticstack_default/converge.yml | 2 -- molecule/elasticstack_default/molecule.yml | 5 +++ roles/beats/tasks/main.yml | 1 - roles/elasticsearch/tasks/main.yml | 1 - roles/elasticstack/README.md | 4 +-- roles/elasticstack/meta/argument_specs.yml | 36 ++++++++++++---------- roles/elasticstack/tasks/main.yml | 16 ++++++++++ roles/elasticstack/tasks/node_address.yml | 22 ++++++++++--- roles/kibana/tasks/main.yml | 1 - roles/logstash/tasks/main.yml | 1 - roles/repos/tasks/main.yml | 1 - 13 files changed, 72 insertions(+), 39 deletions(-) diff --git a/changelogs/fragments/address_mode.yml b/changelogs/fragments/address_mode.yml index 1a01e419..21cb6a7f 100644 --- a/changelogs/fragments/address_mode.yml +++ b/changelogs/fragments/address_mode.yml @@ -1,16 +1,17 @@ minor_changes: - - The new ``elasticstack_address_mode`` selects how the roles on a host address the hosts they - take from the elasticsearch, logstash and kibana inventory groups. ``inventory`` keeps the - name from the inventory as before, ``fqdn``, ``hostname`` and ``ip`` take the address from the - facts of the host that is addressed. Set for a single host, it changes only how the roles on - that host address the others. Facts of those hosts that are missing, for example in a run - limited with ``--limit``, are gathered first. - - Setting ``elasticstack_node_address`` in the host_vars of a host makes the roles reach that + - The new ``elasticstack_address_mode`` selects how the Ansible roles address a host of the + elasticsearch, logstash or kibana inventory group. ``inventory`` keeps the name from the + inventory as before, ``fqdn``, ``hostname`` and ``ip`` take the address from the facts of that + host. Each host is addressed in the mode set for it in the inventory, in group_vars or + host_vars, or with ``-e``; a value in the vars of a play or a role parameter stops the run + with an error. Facts of those hosts that are missing, for example in a run limited with + ``--limit``, are gathered first. + - Setting ``elasticstack_node_address`` in the host_vars of a host makes the Ansible roles reach that host at this address, whatever ``elasticstack_address_mode`` says, for example through the IP of a particular network card. The address is also added to the certificate of an Elasticsearch host and to the certificate a Logstash host presents to the Beats. Existing certificates get it when they are renewed. trivial: - - The full stack Molecule scenario sets ``elasticstack_address_mode`` to ``ip`` and no longer - writes the container addresses into /etc/hosts. + - The full stack Molecule scenario sets ``elasticstack_address_mode`` to ``ip`` in its inventory + and no longer writes the container addresses into /etc/hosts. diff --git a/docs/getting-started.md b/docs/getting-started.md index 81ac64b0..91ed6795 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -10,7 +10,7 @@ Hosts in these groups get the respective services. Restricting your plays to onl Make sure every host that should be configured is part of your playbook, including hosts that only run Beats. The collection first gathers facts from all hosts and then uses facts such as host names and IP addresses to connect the tools to each other. -By default Beats, Kibana and Logstash reach the other hosts by their names from your inventory, so these names have to resolve on those hosts, through DNS or the hosts files. Without that, set `elasticstack_address_mode` to `ip`, described with the [elasticstack role](../roles/elasticstack/README.md). +By default Beats, Kibana and Logstash reach the other hosts by their names from your inventory, so these names have to resolve on those hosts, through DNS or the hosts files. Without that, set `elasticstack_address_mode` to `ip`, described with the [elasticstack role](../roles/elasticstack/README.md). Unlike the variables in the example playbook below, it belongs into your inventory, for example `group_vars/all`; in the `vars` of a play it stops the run. ## Before the first run diff --git a/molecule/elasticstack_default/converge.yml b/molecule/elasticstack_default/converge.yml index dc96c6db..7f5320c0 100644 --- a/molecule/elasticstack_default/converge.yml +++ b/molecule/elasticstack_default/converge.yml @@ -17,8 +17,6 @@ elasticstack_release: "{{ lookup('env', 'ELASTIC_RELEASE') | int}}" elasticsearch_heap: "1" elasticstack_full_stack: true - # The containers cannot resolve each other's names on the GitHub runner. - elasticstack_address_mode: ip elasticstack_no_log: false # Passphrases with arbitrary special characters (space, single quote, $, backtick) to test # that they reach certutil/openssl/keystore without shell or shlex mangling. ca_pass and diff --git a/molecule/elasticstack_default/molecule.yml b/molecule/elasticstack_default/molecule.yml index 1efd1575..bd120dc7 100644 --- a/molecule/elasticstack_default/molecule.yml +++ b/molecule/elasticstack_default/molecule.yml @@ -40,6 +40,11 @@ provisioner: env: ANSIBLE_ROLES_PATH: $MOLECULE_EPHEMERAL_DIRECTORY/roles:${HOME}/.ansible/roles:$MOLECULE_PROJECT_DIRECTORY/roles inventory: + group_vars: + all: + # The containers cannot resolve each other's names on the GitHub runner. The roles read + # elasticstack_address_mode from the inventory, the vars of a play would stop the run. + elasticstack_address_mode: ip host_vars: elasticstack${ELASTIC_RELEASE}-cluster2-${MOLECULE_DISTRO}: elasticsearch_tls_key_passphrase: UniqueHostPassword diff --git a/roles/beats/tasks/main.yml b/roles/beats/tasks/main.yml index 282becc7..7c1f636f 100644 --- a/roles/beats/tasks/main.yml +++ b/roles/beats/tasks/main.yml @@ -3,7 +3,6 @@ - name: Include global role ansible.builtin.import_role: name: netways.elasticstack.elasticstack - when: not hostvars[inventory_hostname]._elasticstack_role_imported | default(false) - name: Update apt cache. ansible.builtin.apt: diff --git a/roles/elasticsearch/tasks/main.yml b/roles/elasticsearch/tasks/main.yml index 00eb880b..abe7c405 100644 --- a/roles/elasticsearch/tasks/main.yml +++ b/roles/elasticsearch/tasks/main.yml @@ -10,7 +10,6 @@ - name: Include global role ansible.builtin.import_role: name: netways.elasticstack.elasticstack - when: not hostvars[inventory_hostname]._elasticstack_role_imported | default(false) - name: Update apt cache. ansible.builtin.apt: diff --git a/roles/elasticstack/README.md b/roles/elasticstack/README.md index 86908352..f28afc69 100644 --- a/roles/elasticstack/README.md +++ b/roles/elasticstack/README.md @@ -35,8 +35,8 @@ The role runs automatically the first time any component role is applied to a ho | `elasticstack_elasticsearch_group_name`
**Type**: `str`
**Default**: `"elasticsearch"` | Inventory group that holds the Elasticsearch hosts. | | `elasticstack_logstash_group_name`
**Type**: `str`
**Default**: `"logstash"` | Inventory group that holds the Logstash hosts. | | `elasticstack_kibana_group_name`
**Type**: `str`
**Default**: `"kibana"` | Inventory group that holds the Kibana hosts. | -| `elasticstack_address_mode`
**Type**: `str`
**Default**: `"inventory"`
**Choices**: `inventory`, `fqdn`, `hostname`, `ip` | How the roles on a host address the hosts they take from the elasticsearch, logstash and kibana inventory groups. "inventory" uses the name from the inventory, "fqdn" the fully qualified domain name, "hostname" the short host name and "ip" the IPv4 address of the network card with the default route. The last three come from the facts of the host that is addressed. Set for a single host, it changes only how the roles on that host address the others. To reach a single host at another address, set elasticstack_node_address for that host. Host lists you set yourself, such as beats_elasticsearch_hosts, are used as they are. The setting does not change discovery.seed_hosts, through which the Elasticsearch nodes find each other. | -| `elasticstack_node_address`
**Type**: `str` | Address the roles use to reach this host, an IPv4 address or a DNS name that resolves to this host. Set it in the host_vars of that host. It takes precedence over elasticstack_address_mode, for example to reach a host with several network cards through the IP of a particular one. It is also added to the certificate of an Elasticsearch host and to the certificate a Logstash host presents to the Beats. A certificate that already exists gets it only when it is renewed, with the tag renew_es_cert or renew_logstash_cert. It does not change discovery.seed_hosts. | +| `elasticstack_address_mode`
**Type**: `str`
**Default**: `"inventory"`
**Choices**: `inventory`, `fqdn`, `hostname`, `ip` | How the Ansible roles of this collection address a host of the elasticsearch, logstash or kibana inventory group when they configure the connections to it. "inventory" uses the host's name in the inventory, "fqdn" its fully qualified domain name, "hostname" its short host name and "ip" the IPv4 address of its network card with the default route; the last three come from the facts of that host. Each host is addressed in the mode set for it, so a value in the group_vars or host_vars of some hosts changes only how those hosts are addressed. The Ansible roles read the value from the inventory or from extra vars; a value in the vars of a play or a role parameter stops the run with an error. The certificates the collection creates contain the IP, the short name, the FQDN and the inventory name, so every mode passes the TLS hostname check. Host lists you set yourself, such as beats_elasticsearch_hosts, are used as they are. The setting does not change discovery.seed_hosts, through which the Elasticsearch nodes find each other. | +| `elasticstack_node_address`
**Type**: `str` | Address the Ansible roles of this collection use for this host when they configure the connections to it, an IPv4 address or a DNS name that resolves to this host. Set it in the host_vars of that host. It takes precedence over elasticstack_address_mode, for example to reach a host with several network cards through the IP of a particular one. It is also added to the certificate of an Elasticsearch host and to the certificate a Logstash host presents to the Beats. A certificate that already exists gets it only when it is renewed, with the tag renew_es_cert or renew_logstash_cert. It does not change discovery.seed_hosts. | | `elasticstack_elasticsearch_http_port`
**Type**: `int`
**Default**: `9200` | Elasticsearch HTTP API port. | | `elasticstack_kibana_host`
**Type**: `str`
**Default**: `"{{ ansible_fqdn }}"` | Host name Kibana publishes itself under (server.publicBaseUrl). Defaults to the fully qualified domain name of the Kibana host. Set it when users reach Kibana under a different name, for example behind a reverse proxy or a load balancer. | | `elasticstack_kibana_port`
**Type**: `int`
**Default**: `5601` | Kibana HTTP port (also used to build the public base URL). | diff --git a/roles/elasticstack/meta/argument_specs.yml b/roles/elasticstack/meta/argument_specs.yml index 41190cb7..e29e8390 100644 --- a/roles/elasticstack/meta/argument_specs.yml +++ b/roles/elasticstack/meta/argument_specs.yml @@ -90,26 +90,30 @@ argument_specs: - hostname - ip description: >- - How the roles on a host address the hosts they take from the elasticsearch, - logstash and kibana inventory groups. "inventory" uses the name from the - inventory, "fqdn" the fully qualified domain name, "hostname" the short host name - and "ip" the IPv4 address of the network card with the default route. The last - three come from the facts of the host that is addressed. Set for a single host, - it changes only how the roles on that host address the others. To reach a single - host at another address, set elasticstack_node_address for that host. Host lists - you set yourself, such as beats_elasticsearch_hosts, are used as they are. The - setting does not change discovery.seed_hosts, through which the Elasticsearch - nodes find each other. + How the Ansible roles of this collection address a host of the elasticsearch, + logstash or kibana inventory group when they configure the connections to it. + "inventory" uses the host's name in the inventory, "fqdn" its fully qualified + domain name, "hostname" its short host name and "ip" the IPv4 address of its + network card with the default route; the last three come from the facts of that + host. Each host is addressed in the mode set for it, so a value in the group_vars + or host_vars of some hosts changes only how those hosts are addressed. The Ansible + roles read the value from the inventory or from extra vars; a value in the vars of + a play or a role parameter stops the run with an error. The certificates the + collection creates contain the IP, the short name, the FQDN and the inventory + name, so every mode passes the TLS hostname check. Host lists you set yourself, + such as beats_elasticsearch_hosts, are used as they are. The setting does not + change discovery.seed_hosts, through which the Elasticsearch nodes find each other. elasticstack_node_address: type: str description: >- - Address the roles use to reach this host, an IPv4 address or a DNS name that - resolves to this host. Set it in the host_vars of that host. It takes precedence - over elasticstack_address_mode, for example to reach a host with several network - cards through the IP of a particular one. It is also added to the certificate of - an Elasticsearch host and to the certificate a Logstash host presents to the Beats. - A certificate that already exists gets it only when it is renewed, with the tag + Address the Ansible roles of this collection use for this host when they configure + the connections to it, an IPv4 address or a DNS name that resolves to this host. + Set it in the host_vars of that host. It takes precedence over + elasticstack_address_mode, for example to reach a host with several network cards + through the IP of a particular one. It is also added to the certificate of an + Elasticsearch host and to the certificate a Logstash host presents to the Beats. A + certificate that already exists gets it only when it is renewed, with the tag renew_es_cert or renew_logstash_cert. It does not change discovery.seed_hosts. # ----- Ports ----- diff --git a/roles/elasticstack/tasks/main.yml b/roles/elasticstack/tasks/main.yml index 6d41b251..02935de6 100644 --- a/roles/elasticstack/tasks/main.yml +++ b/roles/elasticstack/tasks/main.yml @@ -1,4 +1,20 @@ --- +# The other hosts read this host's elasticstack_address_mode through hostvars, which holds neither +# play vars nor role parameters. Stop instead of silently ignoring a value set there. Outside the +# block below, so it runs on every start of a role and not only once per host; the roles therefore +# import this role without a when. +- name: Check that elasticstack_address_mode comes from the inventory + ansible.builtin.assert: + that: + # 'inventory' is the default from defaults/main.yml, hostvars does not see role defaults + - elasticstack_address_mode == (hostvars[inventory_hostname].elasticstack_address_mode | default('inventory')) + fail_msg: >- + elasticstack_address_mode has to be set in the inventory (group_vars, host_vars) or with -e. + The other hosts read it through hostvars, which does not contain the vars of a play or role + parameters, so a value set there would be ignored. + quiet: true + tags: always + - name: Include elasticstack role when: not hostvars[inventory_hostname]._elasticstack_role_imported | default(false) # Ensure the global meta role is only called once for each host block: diff --git a/roles/elasticstack/tasks/node_address.yml b/roles/elasticstack/tasks/node_address.yml index 34b4fa10..e0731529 100644 --- a/roles/elasticstack/tasks/node_address.yml +++ b/roles/elasticstack/tasks/node_address.yml @@ -5,8 +5,22 @@ {{ (groups[elasticstack_elasticsearch_group_name] | default([]) + groups[elasticstack_logstash_group_name] | default([]) + groups[elasticstack_kibana_group_name] | default([])) | unique }} + # The mode set for the host that is addressed (item), not for the host running the task. + # hostvars has no role defaults, so 'inventory' repeats the default from defaults/main.yml. + __elasticstack_peer_mode: "{{ hostvars[item].elasticstack_address_mode | default('inventory') }}" tags: always block: + # The argument specs check the mode only on the hosts of this run. Hosts left out with --limit + # are checked here; a misspelled mode such as "fdqn" would otherwise reach the last branch of + # "Pick the address" below and be used like "ip". + - name: Check the elasticstack_address_mode of every Elastic Stack host + ansible.builtin.assert: + that: + - __elasticstack_peer_mode in ['inventory', 'fqdn', 'hostname', 'ip'] + fail_msg: "elasticstack_address_mode of {{ item }} must be inventory, fqdn, hostname or ip." + quiet: true + loop: "{{ __elasticstack_peer_hosts }}" + - name: Gather the facts of Elastic Stack hosts that are still missing ansible.builtin.setup: gather_subset: @@ -17,7 +31,7 @@ loop: "{{ __elasticstack_peer_hosts }}" when: - hostvars[item].elasticstack_node_address is undefined - - elasticstack_address_mode != 'inventory' + - __elasticstack_peer_mode != 'inventory' - hostvars[item].ansible_fqdn is undefined - name: Pick the address of every Elastic Stack host @@ -26,9 +40,9 @@ {{ __elasticstack_node_address | default({}) | combine({item: ( hostvars[item].elasticstack_node_address if hostvars[item].elasticstack_node_address is defined else - item if elasticstack_address_mode == 'inventory' else - hostvars[item].ansible_fqdn if elasticstack_address_mode == 'fqdn' else - hostvars[item].ansible_hostname if elasticstack_address_mode == 'hostname' else + item if __elasticstack_peer_mode == 'inventory' else + hostvars[item].ansible_fqdn if __elasticstack_peer_mode == 'fqdn' else + hostvars[item].ansible_hostname if __elasticstack_peer_mode == 'hostname' else hostvars[item].ansible_default_ipv4.address | default(hostvars[item].ansible_all_ipv4_addresses[0]))}) }} loop: "{{ __elasticstack_peer_hosts }}" diff --git a/roles/kibana/tasks/main.yml b/roles/kibana/tasks/main.yml index 9db4eb2b..1cb7228d 100644 --- a/roles/kibana/tasks/main.yml +++ b/roles/kibana/tasks/main.yml @@ -10,7 +10,6 @@ - name: Include global role ansible.builtin.import_role: name: netways.elasticstack.elasticstack - when: not hostvars[inventory_hostname]._elasticstack_role_imported | default(false) - name: Update apt cache. ansible.builtin.apt: diff --git a/roles/logstash/tasks/main.yml b/roles/logstash/tasks/main.yml index f2a6bd6f..1ff49870 100644 --- a/roles/logstash/tasks/main.yml +++ b/roles/logstash/tasks/main.yml @@ -3,7 +3,6 @@ - name: Include global role ansible.builtin.import_role: name: netways.elasticstack.elasticstack - when: not hostvars[inventory_hostname]._elasticstack_role_imported | default(false) - name: Update apt cache. ansible.builtin.apt: diff --git a/roles/repos/tasks/main.yml b/roles/repos/tasks/main.yml index d3390966..bb37db9d 100644 --- a/roles/repos/tasks/main.yml +++ b/roles/repos/tasks/main.yml @@ -3,7 +3,6 @@ - name: Include global role ansible.builtin.import_role: name: netways.elasticstack.elasticstack - when: not hostvars[inventory_hostname]._elasticstack_role_imported | default(false) - name: Import RedHat tasks ansible.builtin.import_tasks: redhat.yml