Skip to main content

NMState - Migrating my Hypervisor from imperative to declarative Network Configuration

I've been working with NMState for quite a bit of time, mostly in OpenShift environments, and I've really grown to like the declarative way of handling network configuration. Since I still mainly use EL9 and NMState is packaged directly in the official repositories, I figured it was time to finally switch my traditional, imperative NetworkManager configuration over.

First, let's iron out some basics. NMState doesn't completely replace NetworkManager; it relies on it and generates standard .nmconnection files under the hood. Think of it as a clean, declarative wrapper to configure NetworkManager and manage connections seamlessly.

There are two distinct ways you can run NMState:

  • Imperatively with a Failsafe: Best for initial testing and manual updates
  • As a Service on Boot: Automatically enforces your declared configuration during startup (here be dragons).
Let's look at the imperative mode first, which features a brilliant safeguard known as a "dead man's trigger". Running nmstatectl apply with the '--no-commit' and '--timeout 30' flags will automatically revert any changes if you don't explicitly confirm them within 30 seconds. This provides an excellent safety net if an adjustment accidentally breaks your connectivity.

Conversely, running NMState as a system service will automatically apply your configuration files at reboot, unless they've already been processed. Once a configuration is successfully applied, the service moves the file from 'filename.yaml' to 'filename.applied' to prevent it from executing over and over again.

Here Be Dragons: Running NMState strictly as a service lacks an easy, automated failsafe. If your configuration contains a typo or structural mistake, it will apply on boot and can easily lock you out of your system's network interfaces. Always thoroughly test your files before automated deployment!

Here's a step-by-step guide on how I migrated my Hypervisor to declarative Network Config Management.

1. Installing NMState
 [root@hyv02 ~]# dnf -y --refresh install nmstate  

2. Capture the current config
 [root@hyv02 ~]# nmstatectl show > /etc/nmstate/config.yaml  
The generated configuration is going to be incredibly verbose. Interfaces that you don't want NMState to actively manage can be safely deleted from the file, along with detailed ethtool settings if you haven't explicitly customized them. Since we're living in the age of AI, you can easily feed this verbose text to an LLM to strip out the fluff, as it's a fantastic tool for parsing large amounts of configuration text quickly.

3. Crafting the Core Configuration
Below is a cleaned-up example snippet of what a finalized configuration looks like for a hypervisor host handling a 10G LACP bond, tagged VLANs, and Linux bridges assigned to virtual machines:
 dns-resolver:  
  config:  
   server:  
    - 172.31.10.5  
   search:  
    - archyslife.lan  
 routes:  
  config:  
   - destination: 0.0.0.0/0  
    next-hop-interface: br0  
    next-hop-address: 172.31.10.254  
 interfaces:  
  # -- Physical Interfaces for Bonds --  
  # -- 10G Fiber Interfaces --  
  - name: ens5f0  
   type: ethernet  
   state: up  
   mtu: 9000  
  - name: ens5f1  
   type: ethernet  
   state: up  
   mtu: 9000  
  # -- Bond Interface (LACP) --  
  # -- 10G Fiber Interfaces --  
  - name: bond0  
   type: bond  
   state: up  
   mtu: 9000  
   link-aggregation:  
    mode: 802.3ad  
    port:  
     - ens5f0  
     - ens5f1  
  # -- VLANs on Bond --  
  # -- Public Server Network VLAN --  
  - name: bond0.100  
   type: vlan  
   state: up  
   mtu: 9000  
   vlan:  
    base-iface: bond0  
    id: 100  
  # -- Untrusted Server Network VLAN --  
  - name: bond0.200  
   type: vlan  
   state: up  
   mtu: 9000  
   vlan:  
    base-iface: bond0  
    id: 200  
  # -- VLAN Bridges for VMs --  
  # -- Internal Server Network --  
  - name: br0  
   type: linux-bridge  
   state: up  
   mtu: 9000  
   ipv4:  
    enabled: true  
    dhcp: false  
    address:  
     - ip: 172.31.10.249  
      prefix-length: 24  
   ipv6:  
    enabled: false  
   bridge:  
    port:  
     - name: bond0  
  # -- Public Server Network --  
  - name: br0.100  
   type: linux-bridge  
   state: up  
   mtu: 9000  
   ipv4:  
    enabled: false  
   ipv6:  
    enabled: false  
   bridge:  
    port:  
     - name: bond0.100  
  # -- Untrusted Server Network --  
  - name: br0.200  
   type: linux-bridge  
   state: up  
   mtu: 9000  
   ipv4:  
    enabled: false  
   ipv6:  
    enabled: false  
   bridge:  
    port:  
     - name: bond0.200  

4. Testing the layout
Because NMState unfortunately doesn't include a built-in 'dry-run' validation command, we will test the configuration safely using our rollback timer. This command gives you a 30-second window to verify you still have access before automatically reverting the changes:
 [root@hyv02 ~]# nmstatectl apply --no-commit --timeout 30 /etc/nmstate/config.yaml  
If the configuration works as expected and your server remains perfectly reachable, go ahead and permanently commit those changes before the timer expires:
 [root@hyv02 ~]# nmstatectl commit  
5. Enforcing state on Boot
Now that you know your configuration is completely stable and valid, you can safely enable the NMState system service to apply these configurations at boot, mitigating the risk of future configuration drift:
 [root@hyv02 ~]# systemctl enable --now nmstate.service  
6. Final Verification
You can easily keep tabs on the service's execution and check the status of your configuration files using journalctl and ls:
 [root@hyv02 ~]# journalctl -e -f -b 0 -u nmstate.service  
 [root@hyv02 ~]# ls -ahils /etc/nmstate  
 total 24K  
 8388896    0 drwxr-xr-x.  2  root root   42 May 19 22:16 .  
 8388738  16K drwxr-xr-x. 122 root root  12K May 20 08:36 ..  
 8390960 4.0K -rw-r-----.  1  root root 2.2K May 19 22:16 config.applied  
 8392075 4.0K -rw-r--r--.  1  root root   95 Dec 9  05:56 README  
When configurations apply cleanly, you will notice your configuration file has taken on a '.applied' extension, and the underlying interfaces will be natively running inside NetworkManager.

Feel free to comment and / or suggest a topic.

Comments

Popular posts from this blog

Dynamic DNS with BIND and ISC-DHCP

I personally prefer to work with hostnames instead of ip-addresses. If you have anything like freeipa or active directory, it will do that for you by registering the client you added to your realm to the managed dns and edit the records dynamically. We can achieve the same goal with just bind and isc-dhcp. I'll use a raspberry pi with raspbian 9 for this setup. So here is a quick tutorial on how to configure the isc-dhcp-server to dynamically update bind. First set a static ip to your server. [archy@ddns ~]$ sudo vim /etc/network/interfaces # interfaces(5) file used by ifup(8) and ifdown(8) # Please note that this file is written to be used with dhcpcd # For static IP, consult /etc/dhcpcd.conf and 'man dhcpcd.conf' # Include files from /etc/network/interfaces.d: source-directory /etc/network/interfaces.d auto eth0 iface eth0 inet static address 172.31.30.5 network 172.31.30.0 broadcast 172.31.30.255 netmask 255.255.255.0 ...

Push logs and data into elasticsearch - Part 2 Mikrotik Logs

This is only about the setup of different logging, one being done with Filebeat and the other being done with sending logging to a dedicated port opened in Logstash using the TCP / UDP Inputs. Prerequesites: You'll need a working Elasticsearch Cluster with Logstash and Kibana. Start by getting the Log Data you want to structure parsed correctly. Mikrotik Logs are a bit difficult since they show you Data in the interface which is already enriched with Time / Date. That means a message that the remote logging will send to Logstash will look like this: firewall,info forward: in:lan out:wan, src-mac aa:bb:cc:dd:ee:ff, proto UDP, 172.31.100.154:57061->109.164.113.231:443, len 76 You can check them in the grok debugger and create your own filters and mapping. The following is my example which might not fit your needs. Here are some custom patterns I wrote for my pattern matching: MIKROTIK_DATE \b(?:jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?...

LACP-Teaming on CentOS 7 / RHEL 7

What is teaming? Teaming or LACP (802.3ad) is a technique used to bond together multiple interfaces to achieve higher combined bandwith. NOTE: every clients speed can only be as high as the single link speed of one of the members. That means, if the interfaces I use in the bond have 1 Gigabit, every client will only have a maximum speed of 1 Gigabit. The advantage of teaming is, that it can handle multiple connections with 1 Gigabit. How many connections depends on the amount of your network cards. I'm using 2 network cards for this team on my server. That means I can handle 2 Gigabit connections at full rate on my server provided the rest of the hardware can deliver that speed. There also exists 'Bonding' in the Linux world. They both do the same in theory but  for a detailed comparison check out this  article about teaming in RHEL7 . To create a teaming-interface, we will first have to remove all the interface configurations we've done on the (soon to be) sla...

FreeIPA - Integrating your DHCPD dynamic Updates into IPA

I recently went over my network configuration and noticed that the dhcp-leases were not pushed into the IPA-DNS yet. So I thought, why not do it now. The setup is very similar to setting it up on a single bind instance not managed by IPA (I've already written a guide about this here ). My setup is done with the following hosts: ipa01.archyslife.lan - 172.31.0.1 inf01.archyslife.lan - 172.31.0.5 First of all, create a rndc-key: [archy@ipa01 ~]$ sudo rndc-confgen -a -b 512 This will create the following file '/etc/rndc-key' [archy@ipa01 ~]$ sudo cat /etc/rndc.key key "rndc-key" { algorithm hmac-md5; secret "secret_key_here=="; }; We also need to make named aware of the rndc-key and allow our remote dhcp server to write dns entries: [archy@ipa01 ~]$ sudo vim /etc/named.conf ... include "/etc/rndc-key"; controls { inet 172.31.0.1 port 953 allow { 172.31.0.5; } keys ...

Creating a pgpool-II based PostgreSQL Cluster

This time I'm going to do a small and quick walkthrough for a postgresql cluster install. I assume you have a clean install of CentOS 7.3 with all updates. The configuration itself is surprisingly simple. The enviroment I'm working with is: Node1: Hostname: pgsql01.archyslife.lan IP: 172.31.10.31 Member of IPA-Domain Selinux: enforcing Node2: Hostname: pgsql02.archyslife.lan IP: 172.31.10.32 Member of IPA-Domain Selinux: enforcing Cluster: Main Node: pgsql01.archyslife.lan Replica: pgsql02.archyslife.lan Virtual IP: 172.31.10.33 for the sake completeness I'll be adding a A-Record entry in the IPA-DNS. Let's start with the configuration of each node. First I will completely setup the Master without restarting the services, afterwards the replica will follow. Steps necessary for both nodes. Add the pgsql-repo to yum. [archy@pgsql01 ~]$ sudo yum -y install https://download.postgresql.org/pub/repos/yum/9.6/redhat/rhel-7-x86_64/pgdg-centos96-9.6...