avatarAl Saleh

Summary

The provided content details the process of installing and configuring the ISC Kea DHCP server on Debian and Fedora using Ansible for dynamic IP assignment within a development lab environment, with a focus on automation and reserving IP addresses for development environments.

Abstract

In the latest installment of the Devlab Series, the author tackles the enhancement of the devlab router by integrating DHCP capabilities using the ISC Kea DHCP server. This comprehensive guide explains the selection of Kea over other DHCP server options, emphasizing its modularity and ease of automation through Ansible. The author has created a custom Ansible role that supports both Debian and Fedora systems, which includes templating for automated IP reservations and is designed to work with the devlab's pre-existing DHCP automation scheme. The role dynamically assigns package names and service names based on the operating system, streamlining the installation process. Additionally, the author has provided a detailed walkthrough of the configuration, including the use of JSON-based templates for reservations, ensuring that the router can be easily recreated and managed. Testing confirmed seamless operation and compatibility with existing infrastructure, such as a Docker swarm cluster, demonstrating the robustness of the new Linux router setup. The article concludes with troubleshooting tips for common issues and a teaser for the next steps in the Devlab Series, which include DNS configuration and a return to the LAMP development environment role. The automation code for the series is made available on GitHub, with an invitation for community contributions and feedback.

Opinions

  • The author expresses a clear preference for the ISC Kea DHCP server due to its high modularity and JSON-based configuration language that facilitates automation with Ansible.
  • The author believes in the importance of community contributions, as evidenced by the GitHub repository link and the welcome for issues, feature requests, and merge requests.
  • The author's approach to automation is highlighted as a significant advantage, with the new Kea role allowing for the dynamic assignment of package names and service names based on the operating system detected.
  • There is an underlying appreciation for the flexibility and scalability provided by the Linux router, as it is able to replace a RouterOS cloud router without disrupting the existing devlab infrastructure.
  • The article reflects the author's commitment to maintaining the lab's operation without interruption, demonstrated by the thorough testing and troubleshooting guidance provided after implementing the new DHCP setup.

The Devlab Series, season 1 episode 14

DHCP with Ansible

Install and configure ISC Kea DHCP Server on Debian and Fedora using Ansible.

Source SDXL; Prompt: Automatic Dynamic Host Configuration Protocol

We have built our first devlab router in Episode 3, and it was a basic router that does routing and routing only. Then in Episode 4 we experimented with RouterOS and replaced the Linux router with a RouterOS cloud router that can do many of the things you expect a devlab router to do. I have mentioned multiple times that we will go back to the Linux router and complete its setup after we do some development environments in the devlab. We have done that, and today we we will go back to the Linux router and add DHCP capability to it.

Choosing a DHCP Server

There are multiple free and open source DHCP server products to choose from. Products like Dnsmasq or Pihole are designed for a small network and can server DHCP and do other things like resolving DNS queries.

As usual, such a choice is subjective, and I would love to test with all of them, however, for this episode of the devlab series, I am choosing ISC Kea DHCP server.

ISC Kea

This is a very modular free and open source DHCP Server produced and supported by the Internet Service Consortium (ISC). In addition to its high modularity, Kea is very easy to automate, especially with Ansible, because of its configuration language that is based on JSON.

There are many published attempts to automate Kea with Ansible. I could not even browse half of the search results I got on GitHub, but in all the repositories I checked, I was not able to find a role that fits the needs of our series.

The Kea role

I wanted an Ansible role to install and configure Kea on both Fedora and Debian, and to be able to use support our DHCP automation scheme that we designed in Episode 2. None of the roles that I found on GitHub was able to do both of these tasks, however I got the concept of automating the configuration template from a repository by WAND and I found the same idea in a collection by Wes Marcum.

The galaxy role by WAND is designed for Debian, and does not support Redhat based systems because the package names are different and the service names are different. To solve that problem I defined the names in variables that I load depending on the operating system, so in my new kea role, I have the following:

in vars/Redhat.yml:

kea_install_packages:
  - kea
kea_dhcp4_service_name: kea-dhcp4

and in vars/Debian.yml:

kea_install_packages:
  - kea-dhcp4-server
kea_dhcp4_service_name: kea-dhcp4-server

With this I can install the appropriate package for the OS:

- name: Include Redhat variables
  ansible.builtin.include_vars: Redhat.yml
  when: ansible_facts['os_family'] == "RedHat"

- name: Include Debian variables
  ansible.builtin.include_vars: Debian.yml
  when: ansible_facts['os_family'] == "Debian"

- name: Install packages
  ansible.builtin.package:
    name: '{{ item }}'
  loop: '{{ kea_install_packages }}'

Configuring the DHCP automation scheme

Our devlab is designed to reserve the IP range 100–254 in the devlab subnet for dynamic development environments, and instead of manually reserving those hosts in the variable file, I decided to put them in a template that I can automate, and include the template in the configuration file. Luckily, Kea’s configuration language supports including other files. This allowed me to use the same template file I copied from WAND:

// {{ ansible_managed }}
{{ { "Dhcp4": kea_dhcp4_config } | to_nice_json(indent=4) }}

The I created a new template for the reservations, and called it reservations4.conf.j2

"reservations": [
// static reservations
{% for item in linux_router_static_reservations | default([]) %}
{
    "hw-address": "{{ item.mac }}",
    "ip-address": "{{ linux_router_ip_prefix }}.{{ item.ip  }}"
},
{% endfor %}
// Automatic reservations
{% for number in range(linux_router_reservation_start, linux_router_reservation_end) %}
{
    "hw-address": "{{ linux_router_mac_prefix }}:{{ "%02d" | format(number / 100) }}:{{ "%02d" | format(number % 100) }}",
    "ip-address": "{{ linux_router_ip_prefix }}.{{ number }}"
},
{% endfor %}
{
    "hw-address": "{{ linux_router_mac_prefix }}:{{ "%02d" | format(linux_router_reservation_end / 100) }}:{{ "%02d" | format(linux_router_reservation_end % 100) }}",
    "ip-address": "{{ linux_router_ip_prefix }}.{{ linux_router_reservation_end }}"
}

],

We have seen the algorithms used here in Episode 2, and we have used them all in Episode 3 when we configured RouterOS.

Copying the template was the easy part:

- name: Copy DHCP4 configuration
  ansible.builtin.template:
    src: kea-dhcp4.conf.j2
    dest: '/etc/kea/kea-dhcp4.conf'
    owner: root
    group: root
    mode: u=rw,g=r,o=r

- name: Copy global reservations
  ansible.builtin.template:
    src: reservations4.conf.j2
    dest: '/etc/kea/reservations4.conf'
    owner: root
    group: root
    mode: u=rw,g=r,o=r

- name: Inject global reservations Template
  ansible.builtin.lineinfile:
    path: '/etc/kea/kea-dhcp4.conf'
    line: '        <?include "/etc/kea/reservations4.conf" ?>'
    insertafter: '    "Dhcp4": {'

The the role restarts the Kea DHCP4 service

- name: Restart KEA
  ansible.builtin.service:
    name: '{{ kea_dhcp4_service_name }}'
    state: restarted
    enabled: true

The variable file

I renamed the previously created debian-router.yml file to linux-router.yml, and started by configuring the DHCP Scheme (See episode 2 for details):

linux_router_mac_prefix: '12:00:00:00'
linux_router_ip_prefix: '192.168.100'
linux_router_reservation_start: 100
linux_router_reservation_end: 254

linux_router_static_reservations:
  - mac: '12:00:00:00:00:10'
    ip: '10'
  - mac: '12:00:00:00:00:90'
    ip: '90'
  - mac: '12:00:00:00:00:99'
    ip: '99'
  - mac: '12:00:00:00:00:95'
    ip: '95'

Then I added the basic Kea configuration for the devlab network:

kea_dhcp4_config:
  multi-threading: 
    enable-multi-threading: false
  interfaces-config:
    interfaces:
      - eth100
  reservations-global: true
  subnet4:
    - id: 1
      subnet: 192.168.100.0/24
      interface: eth100
      option-data:
        - name: domain-name-servers
          data: 192.168.1.253, 9.9.9.9
          always-send: true
        - name: routers
          data: 192.168.100.1
          always-send: true
      match-client-id: false
      pools:
        - pool: 192.168.100.20-192.168.100.50

The linux-router playbook

I used the same playbook we created in Episode 2 after renaming it to linux-router.yml, and added a play to install sshd on fedora, and modified the the play that configured the router to run the base role first, then the router role, then the kea role:

---
- name: Recreate the router.
  hosts:
    - localhost
  vars_files:
    - vars/main-pve.yml
    - vaults/vault.yml
    - vars/linux-router.yml
  roles:
    - role: pve_purge
      when: recreate
    - role: pve_lxc_create
      when: recreate

- name: Enable ssh on Fedora
  hosts: main_pve
  vars_files:
    - vars/main-pve.yml
    - vars/linux-router.yml
  tasks:
    - name: Enable ssh
      ansible.builtin.include_role:
        name: pve_lxc_enable_sshd
      when: "recreate and 'fedora' in (pve_lxc_ostemplate | default(''))"

- name: Configure the router
  hosts: linux_router
  vars_files:
    - vaults/vault.yml
    - vars/linux-router.yml
  roles:
    - base
    - linux_router
    - kea

Testing the Linux router

I turned off the RouterOS router in my main lab, and ran the new linux-router.yml playbook. Then I restarted the entire devlab, and found that all my hosts got the same IPs, and that routing was working again.

I tested the swarm cluster to find it in the same state as before, with one small change, the second manager took the leader role:

[root@manager1 ~]# docker node ls
ID                            HOSTNAME                STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
9coso93775zewc469jq81h5ye *   manager1.swarm.devlab   Ready     Active         Reachable        25.0.3
hz8eugga9zrr8xd2idwvjch07     manager2                Ready     Active         Leader           25.0.3
xov39n7sb3535s91qdpxm93f9     worker1.swarm.devlab    Ready     Active                          25.0.3
giz7fb8zwbdma71usm7sbynyj     worker2                 Ready     Active                          25.0.3
5yf7xvm3y1i9e2iaiy2b0llou     worker3                 Ready     Active                          25.0.3

Basically, I saw that replacing the RouterOS VM with a Linux container did not affect the devlab’s operation in any way.

Then I added a line in the new router’s variable file to recreate the router based on fedora, so the configuration became:

recreate: false
vmid: 222
pve_lxc_ostemplate: 'local:vztmpl/fedora-39-default_20231118_amd64.tar.xz'
hostname: linux-router.devlab
pve_lxc_networks:
  net0: "name=eth0,bridge=vmbr0,ip=dhcp,firewall=1,hwaddr=02:00:00:00:02:22"
  net1: "name=eth100,bridge=vmbr100,ip=192.168.100.1/24,firewall=1"

And ran the playbook with the extra variable “recreate=true” and tested again, and the result was identical. My lab work with a Debian router and a Fedora router.

Troubleshooting

The configuration above works for my lab. If the configuration does not work for you, here are some troubleshooting ideas:

  • If you can not access the router from other hosts in the devlab, verify the interface names and the bridge names. I used eth0 for the interface connected to vmbr0, and eth100 for the interface connected to vmbr100.
  • If you can access the router, but you can get an IP address from hosts, verify that Kea was restarted successfully. You can use the following command to test the configuration file syntax:
kea-dhcp4 -t /etc/kea/kea-dhcp4.conf
  • If your hosts get IPs from the pool, not there reserved IPs, verify that your configuration enables global IP assignment:
  reservations-global: true
  • If you get weird IPs or IP conflicts, there could be another DHCP server running on the same network.

The next step

The next step for our Devlab router is to configure DNS, and I have not started preparing for that yet, so if you have any suggestions, please put them in the comments.

The next episode will be a development environment, and as promised earlier in the series, we will go back to the LAMP development environment role, and updated it so that we can run a Wordpress development project on Nginx.

The automation code for the series is released and updated with every episode in the GitHub repository https://github.com/alayham/devlab-series-automations

The code is under the MIT license which allows anybody to do anything with the code as long as they do not hold the author liable for any result.

GitHub issues, feature requests, and merge requests are welcome.

Ansible
Automation
Debian
Fedora
Networking
Recommended from ReadMedium