From d196bb99aa1081092be0792ad6a6d06285b8c2ca Mon Sep 17 00:00:00 2001 From: Arnaldo Hernandez Date: Thu, 27 Jun 2024 10:42:43 -0500 Subject: [PATCH] Adding playbook and role to setup DNS. Issue-ID: INF-441 Change-Id: Ic14a0a52f752ea17f864093fb24460d13621274e Signed-off-by: Arnaldo Hernandez --- okd/playbooks/deploy_dns.yml | 14 +++ okd/roles/insert_dns_records/README.md | 55 +++++++++++ okd/roles/insert_dns_records/defaults/main.yml | 31 ++++++ okd/roles/insert_dns_records/files/nm-dnsmasq.conf | 2 + okd/roles/insert_dns_records/handlers/main.yml | 8 ++ .../tasks/configure_firewall.yml | 47 +++++++++ .../insert_dns_records/tasks/create_host_entry.yml | 20 ++++ okd/roles/insert_dns_records/tasks/dnsmasq.yml | 13 +++ okd/roles/insert_dns_records/tasks/main.yml | 107 +++++++++++++++++++++ .../insert_dns_records/tasks/network-manager.yml | 24 +++++ .../templates/nm-dnsmasq.conf.j2 | 2 + .../templates/openshift-cluster.conf.j2 | 82 ++++++++++++++++ okd/roles/validate_dns_records/README.md | 3 + okd/roles/validate_dns_records/defaults/main.yml | 13 +++ okd/roles/validate_dns_records/tasks/check.yml | 16 +++ okd/roles/validate_dns_records/tasks/main.yml | 36 +++++++ 16 files changed, 473 insertions(+) create mode 100644 okd/playbooks/deploy_dns.yml create mode 100644 okd/roles/insert_dns_records/README.md create mode 100644 okd/roles/insert_dns_records/defaults/main.yml create mode 100644 okd/roles/insert_dns_records/files/nm-dnsmasq.conf create mode 100644 okd/roles/insert_dns_records/handlers/main.yml create mode 100644 okd/roles/insert_dns_records/tasks/configure_firewall.yml create mode 100644 okd/roles/insert_dns_records/tasks/create_host_entry.yml create mode 100644 okd/roles/insert_dns_records/tasks/dnsmasq.yml create mode 100644 okd/roles/insert_dns_records/tasks/main.yml create mode 100644 okd/roles/insert_dns_records/tasks/network-manager.yml create mode 100644 okd/roles/insert_dns_records/templates/nm-dnsmasq.conf.j2 create mode 100644 okd/roles/insert_dns_records/templates/openshift-cluster.conf.j2 create mode 100644 okd/roles/validate_dns_records/README.md create mode 100644 okd/roles/validate_dns_records/defaults/main.yml create mode 100644 okd/roles/validate_dns_records/tasks/check.yml create mode 100644 okd/roles/validate_dns_records/tasks/main.yml diff --git a/okd/playbooks/deploy_dns.yml b/okd/playbooks/deploy_dns.yml new file mode 100644 index 00000000..839378c1 --- /dev/null +++ b/okd/playbooks/deploy_dns.yml @@ -0,0 +1,14 @@ +--- +- name: Setup DNS Records + hosts: dns_host + gather_facts: false + vars: + SETUP_DNS_SERVICE: "{{ setup_dns_service | default(false) }}" + pre_tasks: + - name: Setup facts + ansible.builtin.setup: + when: SETUP_DNS_SERVICE | bool + roles: + - role: insert_dns_records + when: SETUP_DNS_SERVICE | bool + - role: validate_dns_records diff --git a/okd/roles/insert_dns_records/README.md b/okd/roles/insert_dns_records/README.md new file mode 100644 index 00000000..dc1686cd --- /dev/null +++ b/okd/roles/insert_dns_records/README.md @@ -0,0 +1,55 @@ +# Insert DNS Records roles + +Setups `dnsmasq` (either directly or via `NetworkManager`) inserting the DNS A records required for OpenShift install. + +## Role Variables + +| Variable | Required | Default | Options | Comments | +| --------------------- | -------- | -------------- | ----------------------- | ----------------------------------------------------------- | +| domain | yes | | | base for the DNS entries | +| dns_entries_file_name | no | domains.dns | | | +| dns_service_name | no | NetworkManager | NetworkManager, dnsmasq | the name of the service you want to manage your DNS records | +| node_dns_records | no | | | dns records for the nodes of the OpenShift cluster | +| extra_dns_records | no | | | used to defined dns records which are excess of the | + +The structure of `node_dns_records` and `extra_dns_records` is the same and as follows: + +```yaml +node_dns_records: + master-0: + address: "" + ip: "" +extra_dns_records: + place-0: + name: "place-0" + address: "
" + ip: "" + use_dhcp: false +``` + +## Example Playbook + +```yaml +- name: Setup DNS Records + hosts: dns_host + roles: + - insert_dns_records + vars: + domain: "cluster.example.com" + node_dns_records: + master-0: + name: "master-0" + address: "master-0.cluster.example.com" + ip: "111.111.111.111" + use_dhcp: false + master-1: + name: "master-1" + address: "master-1.cluster.example.com" + ip: "111.111.111.112" + use_dhcp: false + master-2: + name: "master-2" + address: "master-2.cluster.example.com" + ip: "111.111.111.113" + use_dhcp: false +``` diff --git a/okd/roles/insert_dns_records/defaults/main.yml b/okd/roles/insert_dns_records/defaults/main.yml new file mode 100644 index 00000000..0dc5ec67 --- /dev/null +++ b/okd/roles/insert_dns_records/defaults/main.yml @@ -0,0 +1,31 @@ +write_dnsmasq_config: true +domain: "{{ cluster_name }}.{{ base_dns_domain }}" +host_ip_keyword: "ansible_host" +dns_entries_file_name: "{{ 'dnsmasq.' + cluster_name + '.conf' }}" +dns_bmc_domain: "infra.{{ base_dns_domain }}" +dns_bmc_address_suffix: "-bmc.{{ dns_bmc_domain }}" +dns_service_name: NetworkManager +dns_records: + apps: + address: ".apps.{{ domain }}" + ip: "{{ ingress_vip }}" + api: + address: "api.{{ domain }}" + ip: "{{ api_vip }}" + api_int: + address: "api-int.{{ domain }}" + ip: "{{ api_vip }}" + +node_dns_records: {} +extra_dns_records: {} + +use_pxe: false +use_dhcp: false +dhcp_lease_time: 24h + +listen_address: "{{ ansible_default_ipv4.address }}" +listen_addresses: + - "127.0.0.1" + - "{{ listen_address }}" + +TFTP_ROOT: "{% if (hostvars['tftp_host'] is defined) and (hostvars['tftp_host']['tftp_directory']) is defined %}{{ hostvars['tftp_host']['tftp_directory'] }}{% else %}/var/lib/tftpboot/{% endif %}" diff --git a/okd/roles/insert_dns_records/files/nm-dnsmasq.conf b/okd/roles/insert_dns_records/files/nm-dnsmasq.conf new file mode 100644 index 00000000..53a8b172 --- /dev/null +++ b/okd/roles/insert_dns_records/files/nm-dnsmasq.conf @@ -0,0 +1,2 @@ +[main] +dns=dnsmasq diff --git a/okd/roles/insert_dns_records/handlers/main.yml b/okd/roles/insert_dns_records/handlers/main.yml new file mode 100644 index 00000000..796f35b8 --- /dev/null +++ b/okd/roles/insert_dns_records/handlers/main.yml @@ -0,0 +1,8 @@ +- name: "Restart {{ dns_service_name }}" + ansible.builtin.service: + name: "{{ dns_service_name }}" + state: restarted + async: 45 + poll: 5 + listen: restart_service + become: true diff --git a/okd/roles/insert_dns_records/tasks/configure_firewall.yml b/okd/roles/insert_dns_records/tasks/configure_firewall.yml new file mode 100644 index 00000000..dd99c941 --- /dev/null +++ b/okd/roles/insert_dns_records/tasks/configure_firewall.yml @@ -0,0 +1,47 @@ +--- +- name: Open port in firewall for DNS + ansible.posix.firewalld: + port: "53/udp" + permanent: true + immediate: true + state: enabled + zone: "{{ item }}" + loop: + - internal + - public + +- name: Open port in firewall for DHCP + ansible.posix.firewalld: + port: "67/udp" + permanent: true + immediate: true + state: enabled + zone: "{{ item }}" + loop: + - internal + - public + when: use_dhcp == true + +- name: Open port in firewall for proxy DHCP + ansible.posix.firewalld: + port: "4011/udp" + permanent: true + immediate: true + state: enabled + zone: "{{ item }}" + loop: + - internal + - public + when: use_pxe == true + +- name: Open port in firewall for PXE + ansible.posix.firewalld: + port: "69/udp" + permanent: true + immediate: true + state: enabled + zone: "{{ item }}" + loop: + - internal + - public + when: use_pxe == true diff --git a/okd/roles/insert_dns_records/tasks/create_host_entry.yml b/okd/roles/insert_dns_records/tasks/create_host_entry.yml new file mode 100644 index 00000000..414af709 --- /dev/null +++ b/okd/roles/insert_dns_records/tasks/create_host_entry.yml @@ -0,0 +1,20 @@ +--- +- name: Make sure ansible_fqdn is populated if required. + ansible.builtin.setup: + delegate_to: "{{ entry_name }}" + delegate_facts: true + when: + - entry_extra_check | default(true) + - hostvars[entry_name]['ansible_fqdn'] is not defined + +- name: "Populate dns entry for {{ entry_name }}" + ansible.builtin.set_fact: + other_host_dns_records: "{{ (other_host_dns_records | default({})) | combine( + { + entry_address : { + 'name': (other_host_dns_records[entry_address]['name'] | default([])) + [entry_name], + 'address': entry_address, + 'ip': hostvars[entry_name][host_ip_keyword], + } + } + ) }}" diff --git a/okd/roles/insert_dns_records/tasks/dnsmasq.yml b/okd/roles/insert_dns_records/tasks/dnsmasq.yml new file mode 100644 index 00000000..f25ee690 --- /dev/null +++ b/okd/roles/insert_dns_records/tasks/dnsmasq.yml @@ -0,0 +1,13 @@ +--- +- name: Create dns file + ansible.builtin.template: + src: openshift-cluster.conf.j2 + dest: "/etc/dnsmasq.d/{{ dns_entries_file_name }}" + mode: "0644" + notify: restart_service + +- name: Start dnsmasq + ansible.builtin.service: + name: dnsmasq + state: started + enabled: true diff --git a/okd/roles/insert_dns_records/tasks/main.yml b/okd/roles/insert_dns_records/tasks/main.yml new file mode 100644 index 00000000..ca773504 --- /dev/null +++ b/okd/roles/insert_dns_records/tasks/main.yml @@ -0,0 +1,107 @@ +--- +- name: Get node_records for nodes + ansible.builtin.set_fact: + node_dns_records: "{{ (node_dns_records | default({})) | combine( + { + item: { + 'name': item, + 'address': item + '.' + cluster_name + '.' + base_dns_domain, + 'ip': hostvars[item][hostvars[item]['host_ip_keyword'] | default(host_ip_keyword)], + 'mac': hostvars[item]['mac'] | default(False), + 'use_dhcp': hostvars[item]['ip'] | default('dhcp') == 'dhcp', + } + } ) }}" + loop: "{{ groups['nodes'] }}" + when: hostvars[item][hostvars[item]['host_ip_keyword'] | default(host_ip_keyword)] is defined + +- name: Get node_records for node bmc_addresses when it is an IP address + ansible.builtin.set_fact: + bmc_dns_records: "{{ (bmc_dns_records | default({})) | combine( + { + item: { + 'name': item, + 'address': item + dns_bmc_address_suffix, + 'ip': hostvars[item]['bmc_ip'], + } + } ) }}" + loop: "{{ groups['nodes'] }}" + when: + - hostvars[item]['bmc_ip'] is defined + - hostvars[item]['bmc_ip'] | ansible.utils.ipaddr('bool') + +- name: Define bmc_address where required + ansible.builtin.set_fact: + bmc_address: "{{ item.data.address }}" + delegate_to: "{{ item.host }}" + delegate_facts: true + loop: "{{ bmc_dns_records | dict2items(key_name='host', value_name='data') }}" + when: + - bmc_dns_records is defined + +- name: Get bastions, services (not including registry) when it ansible_host is an IP address + ansible.builtin.include_tasks: create_host_entry.yml + vars: + entry_address: "{{ hostvars[item]['ansible_fqdn'] }}" + entry_name: "{{ item }}" + loop: "{{ groups['bastions'] + groups['services'] }}" + when: + - item != 'registry_host' + - hostvars[item][hostvars[item]['host_ip_keyword'] | default(host_ip_keyword)] | ansible.utils.ipaddr('bool') + - not (hostvars[item]['dns_skip_record'] | default(False)) | bool + +- name: Get registry_host when it ansible_host is an IP address + ansible.builtin.include_tasks: create_host_entry.yml + vars: + entry_address: "{{ hostvars['registry_host']['registry_fqdn'] | default(hostvars['registry_host']['ansible_fqdn']) }}" + entry_name: "registry_host" + entry_extra_check: "{{ hostvars['registry_host']['registry_fqdn'] is not defined }}" + when: + - "'registry_host' in hostvars" + - hostvars['registry_host'][hostvars['registry_host']['host_ip_keyword'] | default(host_ip_keyword)] | ansible.utils.ipaddr('bool') + - not (hostvars['registry_host']['dns_skip_record'] | default(False)) | bool + +- name: Get vm_hosts when ansible_host is an IP address + ansible.builtin.include_tasks: create_host_entry.yml + vars: + entry_address: "{{ hostvars[item]['sushy_fqdn'] | default(hostvars[item]['ansible_fqdn']) }}" + entry_name: "{{ item }}" + entry_extra_check: "{{ hostvars[item]['sushy_fqdn'] is not defined }}" + loop: "{{ groups['vm_hosts'] | default([]) }}" + when: >- + hostvars[item][hostvars[item]['host_ip_keyword'] | default(host_ip_keyword)] | + ansible.utils.ipaddr('bool') and (not (hostvars[item]['dns_skip_record'] | default(False))) | bool + +- name: Configure firewall + become: true + ansible.builtin.import_tasks: configure_firewall.yml + +- name: Install dnsmasq + become: true + ansible.builtin.package: + name: dnsmasq + state: present + +- name: "Make sure foder exists {{ TFTP_ROOT }}" + ansible.builtin.file: + path: "{{ TFTP_ROOT }}" + state: directory + recurse: true + when: use_pxe | bool + +- name: Configure dnsmasq via NetworkManager + become: true + ansible.builtin.import_tasks: network-manager.yml + when: dns_service_name == "NetworkManager" + +- name: Configure dnsmasq via dnsmasq + become: true + ansible.builtin.import_tasks: dnsmasq.yml + when: dns_service_name == "dnsmasq" + +- name: "Restart {{ dns_service_name }}" + become: true + ansible.builtin.service: + name: "{{ dns_service_name }}" + state: restarted + async: 45 + poll: 5 diff --git a/okd/roles/insert_dns_records/tasks/network-manager.yml b/okd/roles/insert_dns_records/tasks/network-manager.yml new file mode 100644 index 00000000..4a622095 --- /dev/null +++ b/okd/roles/insert_dns_records/tasks/network-manager.yml @@ -0,0 +1,24 @@ +--- +- name: Setup network manager to run dnsmasq + ansible.builtin.copy: + src: nm-dnsmasq.conf + dest: /etc/NetworkManager/conf.d/dnsmasq.conf + mode: "0644" + +- name: Create dnsmasq openshift-cluster config file + ansible.builtin.template: + src: openshift-cluster.conf.j2 + dest: "/etc/NetworkManager/dnsmasq.d/{{ dns_entries_file_name }}" + mode: "0644" + notify: restart_service + +- name: Start NetworkManager + ansible.builtin.service: + name: NetworkManager + state: started + enabled: true + +- name: Reload NetworkManager + ansible.builtin.service: + name: NetworkManager + state: reloaded diff --git a/okd/roles/insert_dns_records/templates/nm-dnsmasq.conf.j2 b/okd/roles/insert_dns_records/templates/nm-dnsmasq.conf.j2 new file mode 100644 index 00000000..53a8b172 --- /dev/null +++ b/okd/roles/insert_dns_records/templates/nm-dnsmasq.conf.j2 @@ -0,0 +1,2 @@ +[main] +dns=dnsmasq diff --git a/okd/roles/insert_dns_records/templates/openshift-cluster.conf.j2 b/okd/roles/insert_dns_records/templates/openshift-cluster.conf.j2 new file mode 100644 index 00000000..cf68a554 --- /dev/null +++ b/okd/roles/insert_dns_records/templates/openshift-cluster.conf.j2 @@ -0,0 +1,82 @@ +domain={{ domain }} +{% if write_dnsmasq_config %} +domain-needed +bogus-priv +listen-address={{ listen_addresses | join(',') }} +{% for listening_intf in (listening_interfaces | default([])) %} +interface={{ listening_intf }} +{% endfor%} +{% for no_dhcp_intf in (no_dhcp_interfaces | default([])) %} +no-dhcp-interface={{ no_dhcp_intf }} +{% endfor%} +expand-hosts +{% if upstream_dns | default(False) %} +server={{ upstream_dns }} +{% endif %} +{% endif %} + +{% if use_dhcp %} +dhcp-range= tag:{{ cluster_name }},{{ dhcp_range_first }},{{ dhcp_range_last }} +dhcp-option= tag:{{ cluster_name }},option:netmask,{{ (gateway + '/' + prefix | string) | ansible.utils.ipaddr('netmask') }} +dhcp-option= tag:{{ cluster_name }},option:router,{{ gateway }} +dhcp-option= tag:{{ cluster_name }},option:dns-server,{{ listen_address }} +dhcp-option= tag:{{ cluster_name }},option:domain-search,{{ domain }} +dhcp-option= tag:{{ cluster_name }},option:ntp-server,{{ ntp_server }} +{% endif %} + +# Wildcard for apps and other api domains +{% for item in dns_records.values() %} +address=/{{ item.address }}/{{ item.ip }} +{% endfor %} + +# Node addresses +{% for item in node_dns_records.values() %} +# {{ item.name }} +{% if item.use_dhcp %} +dhcp-host={{item.mac}},{{ item.ip }},{{ item.address }}, set:{{ cluster_name }} +{% endif %} +address=/{{ item.address }}/{{ item.ip }} +ptr-record={{ item.ip.split('.')[::-1] | join('.') }}.in-addr.arpa,{{ item.address }} + +{% endfor %} + +{% if bmc_dns_records is defined %} +# Node BMC addresses +{% for item in bmc_dns_records.values() %} +# {{ item.name }} +address=/{{ item.address }}/{{ item.ip }} +ptr-record={{ item.ip.split('.')[::-1] | join('.') }}.in-addr.arpa,{{ item.address }} + +{% endfor %} + +{% endif %} +{% if other_host_dns_records is defined %} +# Bastions, services and vm_hosts +{% for item in other_host_dns_records.values() %} +# {{ item.name | join(', ') }} +address=/{{ item.address }}/{{ item.ip }} +ptr-record={{ item.ip.split('.')[::-1] | join('.') }}.in-addr.arpa,{{ item.address }} + +{% endfor %} + +{% endif %} +# User provided entries +{% for item in extra_dns_records.values() %} +# {{ item.name }} +{% if item.use_dhcp %} +dhcp-host={{item.mac}},{{ item.ip }},{{ item.address }}, set:{{ cluster_name }} +{% endif %} +address=/{{ item.address }}/{{ item.ip }} +ptr-record={{ item.ip.split('.')[::-1] | join('.') }}.in-addr.arpa,{{ item.address }} + +{% endfor %} +{% if use_pxe %} + +# PXE boot config +enable-tftp +tftp-root={{ TFTP_ROOT }} +dhcp-vendorclass=BIOS,PXEClient:Arch:00000 +dhcp-boot=tag:BIOS,lpxelinux.0 +dhcp-boot=tag:!BIOS,BOOTX64.EFI + +{% endif %} diff --git a/okd/roles/validate_dns_records/README.md b/okd/roles/validate_dns_records/README.md new file mode 100644 index 00000000..99d439b7 --- /dev/null +++ b/okd/roles/validate_dns_records/README.md @@ -0,0 +1,3 @@ +# validate_dns_records + +Checks for the required dns entries for ingress and API VIPs. diff --git a/okd/roles/validate_dns_records/defaults/main.yml b/okd/roles/validate_dns_records/defaults/main.yml new file mode 100644 index 00000000..2073c06f --- /dev/null +++ b/okd/roles/validate_dns_records/defaults/main.yml @@ -0,0 +1,13 @@ +required_domains: + "api": "api.{{ domain }}" + "api-int": "api-int.{{ domain }}" + "apps": "*.apps.{{ domain }}" + +expected_answers: + "api": "{{ api_vip }}" + "api-int": "{{ api_vip }}" + "apps": "{{ ingress_vip }}" + +required_binary: dig +required_binary_provided_in_package: bind-utils +domain: "{{ cluster_name }}.{{ base_dns_domain }}" diff --git a/okd/roles/validate_dns_records/tasks/check.yml b/okd/roles/validate_dns_records/tasks/check.yml new file mode 100644 index 00000000..5603788e --- /dev/null +++ b/okd/roles/validate_dns_records/tasks/check.yml @@ -0,0 +1,16 @@ +- name: Check required domain {item} exists + ansible.builtin.shell: + cmd: "{{ required_binary }} {{ item.value }} A {{ item.value }} AAAA +short" + register: res + changed_when: false + +- name: Check stdout for expected IP address + ansible.builtin.set_fact: + failed_domains: "{{ (failed_domains | default({})) | combine( + {item.value: { + 'stdout': res.stdout, + 'stderr': res.stderr, + 'expected': expected_answers[item.key], + }} + ) }}" + when: expected_answers[item.key] not in res.stdout diff --git a/okd/roles/validate_dns_records/tasks/main.yml b/okd/roles/validate_dns_records/tasks/main.yml new file mode 100644 index 00000000..ab936722 --- /dev/null +++ b/okd/roles/validate_dns_records/tasks/main.yml @@ -0,0 +1,36 @@ +- name: Check if the required binary for testing exists + ansible.builtin.shell: + cmd: "which {{ required_binary }}" + register: required_binary_check + ignore_errors: true + changed_when: false + +- name: (if binary is missing) Install the package providing the required binary + ansible.builtin.package: + name: "{{ required_binary_provided_in_package }}" + state: present + become: true + when: required_binary_check.rc != 0 + +- name: Set inital failed_domains + ansible.builtin.set_fact: + failed_domains: {} + +- name: Check domains + ansible.builtin.include_tasks: "check.yml" + loop: "{{ required_domains | dict2items() }}" + +- name: List failed_domains + ansible.builtin.fail: + msg: | + Failed domains: + {% for failed in (failed_domains | dict2items) %} + {{ failed.key }}: + expected: + {{ failed.value.expected | indent(14) }} + stdout: + {{ failed.value.stdout | indent(14)}} + stderr: + {{ failed.value.stderr | indent(14) }} + {% endfor %} + when: failed_domains | length > 0 -- 2.16.6