Skip to content

Commit 6c1e94d

Browse files
author
Adam Kraitman
committed
maas dns role for configuring maas dns domains and records
Signed-off-by: Adam Kraitman <akraitma@li-8b09b2cc-35b7-11b2-a85c-cd1dbade58f9.ibm.com>
1 parent fcd39aa commit 6c1e94d

File tree

6 files changed

+222
-0
lines changed

6 files changed

+222
-0
lines changed

maas_nameserver.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
- name: Configure MAAS DNS
3+
hosts: maas
4+
gather_facts: false
5+
roles:
6+
- maas_nameserver

roles/maas_nameserver/README.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# README for `maas_nameserver` Ansible Role
2+
3+
## Overview
4+
The `maas_nameserver` role configures DNS domains and records in MAAS (Metal as a Service) based on an Ansible inventory. It manages DNS entries for Ceph nodes and their IPMI interfaces, ensuring only desired records and domains exist while cleaning up unwanted ones. This role depends on a `secrets` role to load encrypted MAAS API credentials using Ansible Vault.
5+
6+
## Requirements
7+
- **Ansible**: Version 2.9 or higher
8+
- **MAAS CLI**: Installed on the target MAAS server
9+
- **Secrets Role**: A companion `secrets` role to load encrypted credentials
10+
11+
## Role Structure
12+
```
13+
roles/
14+
maas_nameserver/
15+
defaults/
16+
main.yml
17+
meta/
18+
main.yml
19+
tasks/
20+
main.yml
21+
vars/
22+
main.yml
23+
README.md
24+
```
25+
26+
## Dependencies
27+
- **`secrets` Role**: Defined in `meta/main.yml`, this role loads encrypted MAAS API credentials from a configurable path. Ensure the `secrets` role is available in your `roles/` directory.
28+
29+
## Usage
30+
1. **Place the Role**: Ensure the `maas_nameserver` and `secrets` roles are in your `roles/` directory.
31+
2. **Prepare Inventory**: Define your `maas` and your target hosts groups (e.g., in `/etc/ansible/hosts/tucson`):
32+
```ini
33+
[ceph]
34+
ceph001.internal.ceph.tucson.com ip=10.18.131.1 ipmi=10.18.139.1 vlan104=10.18.104.1 104mac=3c:fd:fe:d2:6a:84
35+
ceph002.internal.ceph.tucson.com ip=10.18.131.2 ipmi=10.18.139.2 vlan104=10.18.104.2 104mac=3c:fd:fe:b2:8e:24
36+
ceph003.internal.ceph.tucson.com ip=10.18.131.3 ipmi=10.18.139.3 vlan104=10.18.104.3 104mac=3c:fd:fe:e6:5c:13
37+
38+
[maas]
39+
ceph-vm-14.internal.ceph.tucson.com ip=9.11.120.237
40+
```
41+
3. **Set Up Secrets**: Encrypt your MAAS credentials in a secrets file (see "Secrets File" section below).
42+
4. **Run the Playbook**:
43+
```bash
44+
ansible-playbook maas-dns-playbook.yml
45+
```
46+
47+
## Variables
48+
49+
### `defaults/main.yml`
50+
These are overridable defaults:
51+
- `maas_api_url`: Default MAAS API endpoint (`http://localhost:5240/MAAS/api/2.0/`). Typically overridden by the secrets file.
52+
- `maas_profile`: Default MAAS profile name (`admin`). Overridden by the secrets file.
53+
- `default_domains`: List of domains to preserve/ignore (default: `["maas"]`). The default domain is a DNS domain that is used by maas when you deploy a machine it is used by maas for internal dns records so we choose to exclude it from our ansible role.
54+
55+
### `vars/main.yml`
56+
These are role-specific variables, they can be override as needed:
57+
- `dns_domains`:
58+
- `ceph`: Static primary domain (e.g., `internal.ceph.tucson.com`).
59+
- `ipmi`: Static IPMI domain (`ipmi.ceph.tucson.com`).
60+
- `vlan104`: Static sub-domain for vlan104 address(`vlan104.internal.ceph.tucson.com`).
61+
62+
## Secrets File
63+
The `maas_nameserver` role depends on the `secrets` role to load encrypted MAAS API credentials. The secrets file is expected at `{{ secrets_path }}/maas.yml`.
64+
65+
### Example Secrets File
66+
Create and encrypt the file (e.g., `{{ secrets_path }}/maas.yml`):
67+
```yaml
68+
---
69+
maas_api_url: "http://X.X.X.X:5240/MAAS/api/2.0/"
70+
maas_api_key: "XXXXXXXXXXXXXXXX"
71+
maas_profile: "admin"
72+
```
73+
74+
**Notes**:
75+
- **Location**: Store this file in a secure directory (e.g., `/etc/ansible/secrets` or a private repo like `~/secrets/ceph-secrets`). Adjust `secrets_path` if using a custom location.
76+
- **Security**: Ensure the file is readable only by the Ansible user (e.g., `chmod 600`).
77+
- **Variables**:
78+
- `maas_api_url`: The MAAS API endpoint.
79+
- `maas_api_key`: The API key for authentication.
80+
- `maas_profile`: The profile name used with the MAAS CLI.
81+
- **Vault Password**: Store the password securely (e.g., in `~/.vault_pass.txt` with `chmod 600`) or provide it at runtime.
82+
83+
## Behavior
84+
- **DNS Records**: Creates A records for Ceph nodes (`ip`) and IPMI interfaces (`ipmi`) in `internal.ceph.tucson.com` and `ipmi.ceph.tucson.com`, respectively.
85+
- **Cleanup**: Removes unwanted DNS records and domains not matching the desired state or `default_domains`.
86+
- **Idempotency**: Skips actions if the desired state is already met.
87+
88+
## Example Output
89+
Running the playbook should produce output similar to:
90+
```
91+
TASK [maas_nameserver : Debug desired FQDNs] ******************************************
92+
ok: [ceph-vm-14.internal.ceph.tucson.com] => {
93+
"msg": "Desired FQDNs: ['ceph001.internal.ceph.tucson.com', 'ceph002.internal.ceph.tucson.com', 'ceph003.internal.ceph.tucson.com', 'ceph001.ipmi.ceph.tucson.com', 'ceph002.ipmi.ceph.tucson.com', 'ceph003.ipmi.ceph.tucson.com']"
94+
}
95+
```
96+
97+
## Troubleshooting
98+
- **Secrets Not Loading**: Verify `secrets_path` points to the correct directory and the file is encrypted correctly. Check vault password access.
99+
- **MAAS CLI Errors**: Ensure the MAAS CLI is installed and the API credentials are valid.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
maas_api_url: "http://localhost:5240/MAAS/api/2.0/" # Default, overridden by secrets
3+
maas_profile: "admin" # Default, overridden by secrets
4+
default_domains:
5+
- "maas"

roles/maas_nameserver/meta/main.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
---
2+
dependencies:
3+
- role: secrets

roles/maas_nameserver/tasks/main.yml

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
---
2+
- name: Include secrets
3+
ansible.builtin.include_vars: "{{ item }}"
4+
no_log: true
5+
with_first_found:
6+
- "{{ secrets_path | mandatory }}/maas.yml"
7+
tags:
8+
- always
9+
10+
- name: Ensure MAAS CLI is logged in
11+
ansible.builtin.command: "maas login {{ maas_profile }} {{ maas_api_url }} {{ maas_api_key }}"
12+
register: maas_login
13+
changed_when: maas_login.rc == 0
14+
failed_when: maas_login.rc != 0 and maas_login.stderr != "Profile already exists"
15+
16+
- name: Get existing DNS resources
17+
ansible.builtin.command: "maas {{ maas_profile }} dnsresources read"
18+
register: existing_resources
19+
changed_when: false
20+
21+
- name: Initialize DNS records list
22+
ansible.builtin.set_fact:
23+
dns_records: []
24+
25+
# Define target hosts for DNS records, excluding the 'maas' group.
26+
# This can be overridden in secrets/maas.yml (e.g., target_hosts: ['host1.example.com', 'host2.example.com'])
27+
# or via extra vars (e.g., -e "target_hosts=['host1.example.com']").
28+
- name: Define target hosts for DNS records
29+
ansible.builtin.set_fact:
30+
target_hosts: "{{ target_hosts | default(groups | dict2items | rejectattr('key', 'equalto', 'maas') | map(attribute='value') | flatten | unique | default([])) }}"
31+
when: groups.keys() | length > 1
32+
33+
- name: Build DNS records for all interfaces
34+
ansible.builtin.set_fact:
35+
dns_records: "{{ dns_records + [{'name': item[0].split('.')[0], 'ip': interface_ip, 'type': 'A', 'domain': item[1].value}] }}"
36+
loop: "{{ (target_hosts | default([])) | product(dns_domains | dict2items) | list }}"
37+
vars:
38+
interface_ip: "{{ hostvars[item[0]][item[1].key] if item[1].key != 'ceph' else hostvars[item[0]]['ip'] }}"
39+
when:
40+
- target_hosts is defined and target_hosts | length > 0
41+
- "item[1].key in hostvars[item[0]] or (item[1].key == 'ceph' and 'ip' in hostvars[item[0]])"
42+
43+
- name: Parse desired FQDNs
44+
ansible.builtin.set_fact:
45+
desired_fqdns: "{{ dns_records | map(attribute='name') | zip(dns_records | map(attribute='domain')) | map('join', '.') | list }}"
46+
when: dns_records | length > 0
47+
48+
- name: Remove unwanted DNS records
49+
ansible.builtin.command: "maas {{ maas_profile }} dnsresource delete {{ item.id }}"
50+
loop: "{{ existing_resources.stdout | from_json }}"
51+
when: >
52+
dns_records | length > 0 and
53+
item.fqdn not in desired_fqdns
54+
register: dns_deletion
55+
failed_when: dns_deletion.rc != 0 and "does not exist" not in dns_deletion.stderr
56+
notify: log_deletion_failure
57+
58+
- name: Get updated DNS resources after deletions
59+
ansible.builtin.command: "maas {{ maas_profile }} dnsresources read"
60+
register: updated_resources
61+
changed_when: false
62+
63+
- name: Get existing DNS domains
64+
ansible.builtin.command: "maas {{ maas_profile }} domains read"
65+
register: existing_domains
66+
changed_when: false
67+
68+
- name: Parse existing domains
69+
ansible.builtin.set_fact:
70+
current_domains: "{{ existing_domains.stdout | from_json | map(attribute='name') | list }}"
71+
72+
- name: Remove unwanted domains
73+
ansible.builtin.command: "maas {{ maas_profile }} domain delete {{ item.id }}"
74+
loop: "{{ existing_domains.stdout | from_json }}"
75+
when: >
76+
item.name not in default_domains and
77+
item.name not in dns_domains.values()
78+
register: domain_deletion
79+
failed_when: domain_deletion.rc != 0 and "does not exist" not in domain_deletion.stderr and "protected foreign keys" not in domain_deletion.stderr
80+
81+
- name: Ensure new DNS domains exist
82+
ansible.builtin.command: "maas {{ maas_profile }} domains create name={{ item.value }}"
83+
loop: "{{ dns_domains | dict2items }}"
84+
when: item.value not in current_domains
85+
register: domain_creation
86+
failed_when: domain_creation.rc != 0 and "already exists" not in domain_creation.stderr
87+
88+
- name: Ensure DNS records exist
89+
ansible.builtin.command: >
90+
maas {{ maas_profile }} dnsresources create
91+
fqdn={{ item.name }}.{{ item.domain }}
92+
ip_addresses={{ item.ip }}
93+
loop: "{{ dns_records }}"
94+
when: >
95+
dns_records | length > 0 and
96+
(item.name + '.' + item.domain) not in
97+
(updated_resources.stdout | from_json | map(attribute='fqdn') | list)
98+
register: dns_creation
99+
failed_when: dns_creation.rc != 0 and "already exists" not in dns_creation.stderr
100+
101+
- name: Logout from MAAS
102+
ansible.builtin.command: "maas logout {{ maas_profile }}"
103+
when: maas_login is success
104+
changed_when: true

roles/maas_nameserver/vars/main.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
dns_domains:
3+
ceph: "internal.ceph.ibm.com"
4+
ipmi: "ipmi.ceph.ibm.com"
5+
vlan104: "vlan104.internal.ceph.ibm.com"

0 commit comments

Comments
 (0)