Skip to content

Commit 94a08ae

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 94a08ae

File tree

6 files changed

+223
-0
lines changed

6 files changed

+223
-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: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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+
- name: Define target hosts for DNS records
26+
ansible.builtin.set_fact:
27+
target_hosts: "{{ groups | dict2items | rejectattr('key', 'equalto', 'maas') | map(attribute='value') | flatten | unique | default([]) }}"
28+
when: groups.keys() | length > 1
29+
30+
- name: Build DNS records for all interfaces
31+
ansible.builtin.set_fact:
32+
dns_records: "{{ dns_records + [{'name': item[0].split('.')[0], 'ip': interface_ip, 'type': 'A', 'domain': item[1].value}] }}"
33+
loop: "{{ (target_hosts | default([])) | product(dns_domains | dict2items) | list }}"
34+
vars:
35+
interface_ip: "{{ hostvars[item[0]][item[1].key] if item[1].key != 'ceph' else hostvars[item[0]]['ip'] }}"
36+
when:
37+
- target_hosts is defined and target_hosts | length > 0
38+
- "item[1].key in hostvars[item[0]] or (item[1].key == 'ceph' and 'ip' in hostvars[item[0]])"
39+
40+
- name: Parse desired FQDNs
41+
ansible.builtin.set_fact:
42+
desired_fqdns: "{{ dns_records | map(attribute='name') | zip(dns_records | map(attribute='domain')) | map('join', '.') | list }}"
43+
when: dns_records | length > 0
44+
45+
- name: Debug desired FQDNs
46+
ansible.builtin.debug:
47+
msg: "Desired FQDNs: {{ desired_fqdns | default([]) }}"
48+
when: dns_records | length > 0
49+
50+
- name: Remove unwanted DNS records
51+
ansible.builtin.command: "maas {{ maas_profile }} dnsresource delete {{ item.id }}"
52+
loop: "{{ existing_resources.stdout | from_json }}"
53+
when: >
54+
dns_records | length > 0 and
55+
item.fqdn not in desired_fqdns
56+
register: dns_deletion
57+
failed_when: dns_deletion.rc != 0 and "does not exist" not in dns_deletion.stderr
58+
59+
- name: Get updated DNS resources after deletions
60+
ansible.builtin.command: "maas {{ maas_profile }} dnsresources read"
61+
register: updated_resources
62+
changed_when: false
63+
64+
- name: Get existing DNS domains
65+
ansible.builtin.command: "maas {{ maas_profile }} domains read"
66+
register: existing_domains
67+
changed_when: false
68+
69+
- name: Parse existing domains
70+
ansible.builtin.set_fact:
71+
current_domains: "{{ existing_domains.stdout | from_json | map(attribute='name') | list }}"
72+
73+
- name: Remove unwanted domains
74+
ansible.builtin.command: "maas {{ maas_profile }} domain delete {{ item.id }}"
75+
loop: "{{ existing_domains.stdout | from_json }}"
76+
when: >
77+
item.name not in default_domains and
78+
item.name not in dns_domains.values()
79+
register: domain_deletion
80+
failed_when: domain_deletion.rc != 0 and "does not exist" not in domain_deletion.stderr and "protected foreign keys" not in domain_deletion.stderr
81+
82+
- name: Ensure new DNS domains exist
83+
ansible.builtin.command: "maas {{ maas_profile }} domains create name={{ item.value }}"
84+
loop: "{{ dns_domains | dict2items }}"
85+
when: item.value not in current_domains
86+
register: domain_creation
87+
failed_when: domain_creation.rc != 0 and "already exists" not in domain_creation.stderr
88+
89+
- name: Ensure DNS records exist
90+
ansible.builtin.command: >
91+
maas {{ maas_profile }} dnsresources create
92+
fqdn={{ item.name }}.{{ item.domain }}
93+
ip_addresses={{ item.ip }}
94+
loop: "{{ dns_records }}"
95+
when: >
96+
dns_records | length > 0 and
97+
(item.name + '.' + item.domain) not in
98+
(updated_resources.stdout | from_json | map(attribute='fqdn') | list)
99+
register: dns_creation
100+
failed_when: dns_creation.rc != 0 and "already exists" not in dns_creation.stderr
101+
102+
- name: Logout from MAAS
103+
ansible.builtin.command: "maas logout {{ maas_profile }}"
104+
when: maas_login is success
105+
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)