Deploy and configure Knot DNS on multiple hosts using Ansible with role-based automation, configuration management, and service orchestration for Linux DevOps environments. This guide provides playbooks for installation, configuration, and ongoing management.
Create an inventory file to define your Knot DNS servers:
# inventory.ini
[knot_dns_servers]
dns-primary ansible_host=192.168.1.10
dns-secondary ansible_host=192.168.1.11
[knot_dns_servers:vars]
knot_version="3.5.3"
Create a Ansible role for Knot DNS management:
roles/knot-dns/
├── defaults/main.yml
├── handlers/main.yml
├── tasks/
│ ├── main.yml
│ ├── install.yml
│ ├── config.yml
│ └── service.yml
├── templates/
│ ├── knot.conf.j2
│ └── zones/
│ └── example.com.zone.j2
└── vars/main.yml
---
# Knot DNS package and service settings
knot_package_name: "knot"
knot_service_name: "knot"
knot_user: "knot"
knot_group: "knot"
# Version and repository settings
knot_version: "3.5.3"
knot_install_method: "package" # Options: package, source
# Configuration paths
knot_config_dir: "/etc/knot"
knot_zone_dir: "/var/lib/knot"
knot_log_dir: "/var/log/knot"
# Default domain for zone templates
knot_default_domain: "example.com"
# Server configuration
knot_server_listen: ["0.0.0.0@53", "::@53"]
knot_server_workers: "{{ ansible_processor_vcpus | default(2) }}"
knot_server_tcp_workers: "{{ (ansible_processor_vcpus | default(2)) // 2 }}"
# Database configuration
knot_database_storage: "{{ knot_zone_dir }}"
# Logging configuration
knot_logging_level: "info"
knot_logging_targets:
- target: "syslog"
any: "{{ knot_logging_level }}"
---
- name: Include installation tasks
import_tasks: install.yml
tags: [knot, install]
- name: Include configuration tasks
import_tasks: config.yml
tags: [knot, config]
- name: Include service tasks
import_tasks: service.yml
tags: [knot, service]
---
- name: Install Knot DNS on Debian/Ubuntu
apt:
name: "{{ knot_package_name }}"
state: present
update_cache: true
when: ansible_os_family == "Debian"
notify: restart knot
- name: Install Knot DNS on RHEL/CentOS/Rocky/Alma
dnf:
name: "{{ knot_package_name }}"
state: present
when: ansible_os_family == "RedHat"
notify: restart knot
- name: Create Knot DNS directories
file:
path: "{{ item.path }}"
state: directory
owner: "{{ knot_user }}"
group: "{{ knot_group }}"
mode: "{{ item.mode }}"
loop:
- { path: "{{ knot_config_dir }}", mode: "0750" }
- { path: "{{ knot_zone_dir }}", mode: "0750" }
- { path: "{{ knot_log_dir }}", mode: "0750" }
- name: Verify Knot DNS installation
command: knotd -V
register: knot_version_output
changed_when: false
failed_when: knot_version_output.rc != 0
---
- name: Generate main configuration file
template:
src: knot.conf.j2
dest: "{{ knot_config_dir }}/knot.conf"
owner: "{{ knot_user }}"
group: "{{ knot_group }}"
mode: "0640"
notify: reload knot
- name: Create zones directory
file:
path: "{{ knot_zone_dir }}/zones"
state: directory
owner: "{{ knot_user }}"
group: "{{ knot_group }}"
mode: "0750"
- name: Deploy sample zone file
template:
src: zones/example.com.zone.j2
dest: "{{ knot_zone_dir }}/zones/example.com.zone"
owner: "{{ knot_user }}"
group: "{{ knot_group }}"
mode: "0640"
notify: reload knot
---
- name: Enable and start Knot DNS service
systemd:
name: "{{ knot_service_name }}"
state: started
enabled: true
daemon_reload: true
- name: Check Knot DNS configuration
command: knotc conf-check
become: yes
become_user: "{{ knot_user }}"
register: config_check
failed_when: config_check.rc != 0
---
- name: reload knot
command: knotc reload
become: yes
become_user: "{{ knot_user }}"
listen: "reload knot"
- name: restart knot
systemd:
name: "{{ knot_service_name }}"
state: restarted
listen: "restart knot"
Create a main playbook to deploy the role:
---
# deploy-knotdns.yml
- name: Deploy Knot DNS
hosts: knot_dns_servers
become: true
gather_facts: true # Required for ansible_date_time variables
roles:
- knot-dns
vars:
knot_server_workers: 4
knot_logging_level: "info"
knot_default_domain: "example.com"
knot_zones:
- name: "example.com"
file: "{{ knot_zone_dir }}/zones/example.com.zone"
# Add DNSSEC signing if needed
# dnssec_signing: true
# Knot DNS Configuration
# Generated by Ansible on {{ ansible_date_time.iso8601 }}
server:
listen: [
{% for addr in knot_server_listen %}
{{ addr }}{% if not loop.last %},{% endif %}
{% endfor %}
]
workers: {{ knot_server_workers }}
tcp-workers: {{ knot_server_tcp_workers }}
max-udp-payload: 1232
background-refresh: on
database:
storage: {{ knot_database_storage }}
{% if knot_logging_targets %}
log:
{% for log_target in knot_logging_targets %}
- target: {{ log_target.target }}
any: {{ log_target.any | default(knot_logging_level) }}
{% endfor %}
{% endif %}
{% for zone in knot_zones %}
zone:
- domain: {{ zone.name }}
file: {{ zone.file }}
{% if zone.dnssec_signing is defined %}
dnssec-signing: {{ zone.dnssec_signing }}
{% endif %}
{% if zone.acl is defined %}
acl: [
{% for acl_id in zone.acl %}
{{ acl_id }}{% if not loop.last %},{% endif %}
{% endfor %}
]
{% endif %}
{% endfor %}
{% if knot_acls is defined %}
acl:
{% for acl in knot_acls %}
- id: {{ acl.id }}
{% if acl.address is defined %}
address: [
{% for addr in acl.address %}
{{ addr }}{% if not loop.last %},{% endif %}
{% endfor %}
]
{% endif %}
{% if acl.key is defined %}
key: [
{% for key in acl.key %}
{{ key }}{% if not loop.last %},{% endif %}
{% endfor %}
]
{% endif %}
action: [
{% for action in acl.action %}
{{ action }}{% if not loop.last %},{% endif %}
{% endfor %}
]
{% endfor %}
{% endif %}
$ORIGIN example.com.
$TTL 3600
@ IN SOA ns1.{{ knot_default_domain }}. admin.{{ knot_default_domain }}. (
{{ ansible_date_time.epoch | int }}01 ; serial
7200 ; refresh (2 hours)
3600 ; retry (1 hour)
1209600 ; expire (2 weeks)
3600 ) ; minimum (1 hour)
IN NS ns1.{{ knot_default_domain }}.
IN NS ns2.{{ knot_default_domain }}.
ns1 IN A {{ ansible_default_ipv4.address }}
ns2 IN A 192.168.1.11
www IN A {{ ansible_default_ipv4.address }}
mail IN A 192.168.1.12
IN MX 10 mail.{{ knot_default_domain }}.
# Advanced variables for deploy-knotdns.yml
knot_zones:
- name: "example.com"
file: "{{ knot_zone_dir }}/zones/example.com.zone"
dnssec_signing: true
acl: ["transfer_acl", "update_acl"]
- name: "internal.example.com"
file: "{{ knot_zone_dir }}/zones/internal.example.com.zone"
acl: ["internal_acl"]
knot_acls:
- id: transfer_acl
address: ["192.168.1.0/24", "10.0.0.0/8"]
action: ["transfer"]
- id: update_acl
address: ["192.168.1.100"]
action: ["update"]
- id: internal_acl
address: ["192.168.1.0/24"]
action: ["transfer", "query"]
# Run the complete deployment
ansible-playbook -i inventory.ini deploy-knotdns.yml
# Run with specific tags
ansible-playbook -i inventory.ini deploy-knotdns.yml --tags "install,config"
# Dry-run to check changes
ansible-playbook -i inventory.ini deploy-knotdns.yml --check
For zero-downtime updates across multiple servers:
# Update one server at a time
ansible-playbook -i inventory.ini deploy-knotdns.yml --serial 1
# Validate configuration after changes
ansible knot_dns_servers -i inventory.ini -m command -a "knotc conf-check" --become
Add monitoring tasks to your playbook:
---
# monitoring-tasks.yml
- name: Check if Knot DNS is responding
uri:
url: "http://{{ ansible_default_ipv4.address }}/"
method: GET
status_code: 200
delegate_to: localhost
run_once: true
- name: Check service status
systemd:
name: knot
state: started
enabled: yes
---
# backup-tasks.yml
- name: Create backup directory
file:
path: "/tmp/knot-backup-{{ ansible_date_time.date }}"
state: directory
- name: Backup configuration files
copy:
src: "{{ knot_config_dir }}/"
dest: "/tmp/knot-backup-{{ ansible_date_time.date }}/config/"
remote_src: yes
- name: Backup zone files
copy:
src: "{{ knot_zone_dir }}/"
dest: "/tmp/knot-backup-{{ ansible_date_time.date }}/zones/"
remote_src: yes
Permission denied errors:
# Check file permissions
ansible knot_dns_servers -i inventory.ini -a "ls -la {{ knot_config_dir }}" --become
Configuration validation failures:
# Validate configuration
ansible knot_dns_servers -i inventory.ini -m command -a "knotc conf-check" --become
Service startup failures:
# Check service status and logs
ansible knot_dns_servers -i inventory.ini -m systemd -a "name=knot state=started" --become
ansible knot_dns_servers -i inventory.ini -m command -a "journalctl -u knot --no-pager" --become
For automated deployments, consider:
Secure sensitive data with Ansible Vault:
# Encrypt sensitive variables
ansible-vault encrypt_string --name 'knot_dnssec_key_secret' 'your-secret-key-here'
Then reference in your variables:
knot_dnssec_keys:
- name: "example.com"
secret: !vault |
$ANSIBLE_VAULT;1.1;AES256
66386439653...
Beyond this playbook, we offer:
Contact our automation team: office@linux-server-admin.com