Skip to content

Instantly share code, notes, and snippets.

@free-pmx
Created November 10, 2024 00:53
Show Gist options
  • Save free-pmx/f305f003d7fa94bc6de018ad11db827d to your computer and use it in GitHub Desktop.
Save free-pmx/f305f003d7fa94bc6de018ad11db827d to your computer and use it in GitHub Desktop.

Proxmox VE - DHCP Deployment


DISCLAIMER You WILL suffer from the same issues as with static network configuration should your node IP or hostname change in terms of managing the transition. While it actually is possible to change both without a reboot (more on that below), the intended use case is to cover a rather stable environment, but allow for centralised management.


PVE static network configuration1 is not actually a real prerequisite, not even for clusters.

*** Tested with PVE 8.2. ***

Prerequisites

NOTE This guide may ALSO be used to setup a SINGLE NODE. Simply do NOT follow the instructions on the Clustering part.

IMPORTANT The steps below assume that the nodes:

  • have reserved their IP address at DHCP server; and
  • obtain reasonable lease time for the IPs; and
  • get hostname handed out via DHCP Option 12; and
  • get nameserver handed out via DHCP Option 6;
  • can reliably resolve their hostname via DNS lookup;

latest before you start adding them to the cluster and at all times after.

Example dnsmasq

Taking dnsmasq2 for an example, you will need at least the equivalent of the following (excerpt):

dhcp-range=set:DEMO_NET,10.10.10.100,10.10.10.199,255.255.255.0,1d
domain=demo.internal,10.10.10.0/24,local

dhcp-option=tag:DEMO_NET,option:domain-name,demo.internal
dhcp-option=tag:DEMO_NET,option:router,10.10.10.1
dhcp-option=tag:DEMO_NET,option:dns-server,10.10.10.11

dhcp-host=aa:bb:cc:dd:ee:ff,set:DEMO_NET,10.10.10.101
host-record=pve1.demo.internal,10.10.10.101

There are appliance-like solutions, e.g. VyOS3 that allow for this in an error-proof way.

Verification

Some tools that will help with troubleshooting during the deployment:

  • ip -c a should reflect dynamically assigned IP address (excerpt):
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether aa:bb:cc:dd:ee:ff brd ff:ff:ff:ff:ff:ff
    inet 10.10.10.101/24 brd 10.10.10.255 scope global dynamic enp1s0
  • hostnamectl checks the hostname, if static is unset or set to localhost, the transient one is decisive (excerpt):
Static hostname: (unset)
Transient hostname: pve1
  • dig nodename confirms correct DNS name lookup (excerpt):
;; ANSWER SECTION:
pve1.            50    IN    A    10.10.10.101
  • hostname -I can essentially verify all is well the same way the official docs4 actually suggest.

PART 1: Install

You may use any of the two manual installation methods.5 6 Unattended install is out of scope here.

ISO Installer

The ISO installer5 leaves you with static configuration.

Change this by editing /etc/network/interfaces - your vmbr0 will look like this (excerpt):

iface vmbr0 inet dhcp
        bridge-ports enp1s0
        bridge-stp off
        bridge-fd 0

Remove the FQDN hostname entry from /etc/hosts and remove the /etc/hostname file. Reboot.

See below for more details.

Install on top of Debian

There is official Debian installation walkthrough,6 simply skip the initial (static) part, i.e. install plain (i.e. with DHCP) Debian. You can fill in any hostname, (even localhost) and any domain (or no domain at all) to the installer.

After the installation, upon the first boot, remove the static hostname file:

rm /etc/hostname

The static hostname will be unset and the transient one will start showing in hostnamectl output.

NOTE If your initially chosen hostname was localhost, you could get away with keeping this file populated, actually.

It is also necessary to remove the 127.0.1.1 hostname entry from /etc/hosts.

Your /etc/hosts will be plain like this:

127.0.0.1       localhost
# NOTE: Non-loopback lookup managed via DNS

# The following lines are desirable for IPv6 capable hosts
::1     localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

This is also where you should actually start the official guide - "Install Proxmox VE".6


PART 2: Clustering

Setup

This part logically follows manual installs.5 6

Unfortunately, PVE tooling populates the cluster configuration (corosync.conf7) with resolved IP addresses upon the inception.

Creating a cluster from scratch (for brevity, all CLI only):

root@pve1:~# pvecm create demo-cluster
Corosync Cluster Engine Authentication key generator.
Gathering 2048 bits for key from /dev/urandom.
Writing corosync key to /etc/corosync/authkey.
Writing corosync config to /etc/pve/corosync.conf
Restart corosync and cluster filesystem

While all is well, the hostname got resolved and put into cluster configuration as an IP address:

root@pve1:~# cat /etc/pve/corosync.conf

logging {
  debug: off
  to_syslog: yes
}

nodelist {
  node {
    name: pve1
    nodeid: 1
    quorum_votes: 1
    ring0_addr: 10.10.10.101
  }
}

quorum {
  provider: corosync_votequorum
}

totem {
  cluster_name: demo-cluster
  config_version: 1
  interface {
    linknumber: 0
  }
  ip_version: ipv4-6
  link_mode: passive
  secauth: on
  version: 2
}

This will of course work just fine, but It defeats the purpose. You may choose to do the following now (one by one as nodes are added), or may defer the repetitive work till you gather all nodes for your cluster. The below demonstrates the former.

All there is to do is to replace the ringX_addr with the hostname. The official docs8 are rather opinionated how such edits shoulds be performed.

NOTE Be sure to include the domain as well in case your nodes do not share one. Do NOT change the name entry for the node.

At any point, you may check journalctl -u pve-cluster to see that all went well:

[dcdb] notice: wrote new corosync config '/etc/corosync/corosync.conf' (version = 2)
[status] notice: update cluster info (cluster name  demo-cluster, version = 2)

Now, when you are going to add a second node to the cluster (in CLI, this is done counter-intuively from to-be-added node referencing a node already in the cluster):

root@pve2:~# pvecm add pve1.demo.internal

Please enter superuser (root) password for 'pve1.demo.internal': **********

Establishing API connection with host 'pve1.demo.internal'
The authenticity of host 'pve1.demo.internal' can't be established.
X509 SHA256 key fingerprint is 52:13:D6:A1:F5:7B:46:F5:2E:A9:F5:62:A4:19:D8:07:71:96:D1:30:F2:2E:B7:6B:0A:24:1D:12:0A:75:AB:7E.
Are you sure you want to continue connecting (yes/no)? yes
Login succeeded.
check cluster join API version
No cluster network links passed explicitly, fallback to local node IP '10.10.10.102'
Request addition of this node
cluster: warning: ring0_addr 'pve1.demo.internal' for node 'pve1' resolves to '10.10.10.101' - consider replacing it with the currently resolved IP address for stability
Join request OK, finishing setup locally
stopping pve-cluster service
backup old database to '/var/lib/pve-cluster/backup/config-1726922870.sql.gz'
waiting for quorum...OK
(re)generate node files
generate new node certificate
merge authorized SSH keys
generated new node certificate, restart pveproxy and pvedaemon services
successfully added node 'pve2' to cluster.

It hints you about using the resolved IP as static entry (fallback to local node IP '10.10.10.102') for this action (despite hostname was provided) and indeed you would have to change this second incarnation of corosync.conf again.

So your nodelist (after the second change) should look like this:

nodelist {

  node {
    name: pve1
    nodeid: 1
    quorum_votes: 1
    ring0_addr: pve1.demo.internal
  }

  node {
    name: pve2
    nodeid: 2
    quorum_votes: 1
    ring0_addr: pve2.demo.internal
  }

}

NOTE If you wonder about the warnings on "stability" and how corosync actually supports resolving names, you may wish to consult7 (excerpt):

ADDRESS RESOLUTION

corosync resolves ringX_addr names/IP addresses using the getaddrinfo(3) call with respect of totem.ip_version setting.

getaddrinfo() function uses a sophisticated algorithm to sort node addresses into a preferred order and corosync always chooses the first address in that list of the required family. As such it is essential that your DNS or /etc/hosts files are correctly configured so that all addresses for ringX appear on the same network (or are reachable with minimal hops) and over the same IP protocol.

NOTE At this point, it is suitable to point out the importance of ip_version parameter (defaults to ipv6-4 when unspecified, but PVE actually populates it to ipv4-6),7 but also the configuration of hosts in nsswitch.conf.9

You may want to check if everything is well with your cluster at this point, either with pvecm status10 or generic corosync-cfgtool.11 Note you will still see IP addresses and IDs in this output, as they got resolved.


Corosync

Particularly useful to check at any time is netstat12 (you mat need to install net-tools):

netstat -pan | egrep '5405.*corosync'

This is especially true if you are wondering why your node is missing from a cluster. Why could this happen? If you e.g. have improperly configured DHCP and your node suddenly gets a new IP leased, corosync will NOT automatically take this into account:

DHCPREQUEST for 10.10.10.103 on vmbr0 to 10.10.10.11 port 67
DHCPNAK from 10.10.10.11
DHCPDISCOVER on vmbr0 to 255.255.255.255 port 67 interval 4
DHCPOFFER of 10.10.10.113 from 10.10.10.11
DHCPREQUEST for 10.10.10.113 on vmbr0 to 255.255.255.255 port 67
DHCPACK of 10.10.10.113 from 10.10.10.11
bound to 10.10.10.113 -- renewal in 57 seconds.
  [KNET  ] link: host: 2 link: 0 is down
  [KNET  ] link: host: 1 link: 0 is down
  [KNET  ] host: host: 2 (passive) best link: 0 (pri: 1)
  [KNET  ] host: host: 2 has no active links
  [KNET  ] host: host: 1 (passive) best link: 0 (pri: 1)
  [KNET  ] host: host: 1 has no active links
  [TOTEM ] Token has not been received in 2737 ms
  [TOTEM ] A processor failed, forming new configuration: token timed out (3650ms), waiting 4380ms for consensus.
  [QUORUM] Sync members[1]: 3
  [QUORUM] Sync left[2]: 1 2
  [TOTEM ] A new membership (3.9b) was formed. Members left: 1 2
  [TOTEM ] Failed to receive the leave message. failed: 1 2
  [QUORUM] This node is within the non-primary component and will NOT provide any services.
  [QUORUM] Members[1]: 3
  [MAIN  ] Completed service synchronization, ready to provide service.
[status] notice: node lost quorum
[dcdb] notice: members: 3/1080
[status] notice: members: 3/1080
[dcdb] crit: received write while not quorate - trigger resync
[dcdb] crit: leaving CPG group

This is because corosync has still link bound to the old IP, what is worse however, even if you restart the corosync service on the affected node, it will NOT be sufficient, the remaining cluster nodes will be rejecting its traffic with:

[KNET  ] rx: Packet rejected from 10.10.10.113:5405

It is necessary to restart corosync on ALL nodes to get them back into (eventually) the primary component of the cluster. Finally, you ALSO need to restart pve-cluster service on the affected node (only).

NOTE If you see wrong IP address even after restart, and you have all correct configuration in the corosync.conf, you need to troubleshoot starting with journalctl -t dhclient (and checking the DHCP server configuration if necessary), but eventually may even need to check nsswitch.conf9 and gai.conf.13


Notes

/etc/hosts

Should it be depended on for resolving own hostname? Even Debian ships with its own hostname pointing to 127.0.1.1 unless re-configured.

The strange superfluous loopback entry found its way to /etc/hosts as a workaround for a bug once:14

The IP address 127.0.1.1 in the second line of this example may not be found on some other Unix-like systems. The Debian Installer creates this entry for a system without a permanent IP address as a workaround for some software (e.g., GNOME) as documented in the bug #719621.

To be more precise, this was requested in 200515 as a stop-gap while "pursuing the goal of fixing programs so that they no longer rely on the UNIX hostname being resolvable as if it were a DNS domain name.",16 with a particularly valuable:

In the long run the UNIX hostname should not be put in /etc/hosts at all.

Footnotes

  1. https://pve.proxmox.com/wiki/Network_Configuration#_default_configuration_using_a_bridge

  2. https://wiki.debian.org/dnsmasq

  3. https://docs.vyos.io/en/latest/configuration/service/dhcp-server.html#cfgcmd-set-service-dhcp-server-hostfile-update

  4. https://manpages.debian.org/bookworm/hostname/hostname.1.en.html

  5. https://pve.proxmox.com/wiki/Installation#installation_installer 2 3

  6. https://pve.proxmox.com/wiki/Install_Proxmox_VE_on_Debian_12_Bookworm 2 3 4

  7. https://manpages.debian.org/bookworm/corosync/corosync.conf.5.en.html 2 3

  8. https://pve.proxmox.com/pve-docs/chapter-pvecm.html#pvecm_edit_corosync_conf

  9. https://manpages.debian.org/bookworm/manpages/nsswitch.conf.5.en.html 2

  10. https://pve.proxmox.com/wiki/Cluster_Manager

  11. https://manpages.debian.org/bookworm/corosync/corosync-cfgtool.8.en.html

  12. https://manpages.debian.org/bookworm/net-tools/netstat.8.en.html

  13. https://manpages.debian.org/bookworm/manpages/gai.conf.5.en.html

  14. https://www.debian.org/doc/manuals/debian-reference/ch05.en.html#_the_hostname_resolution

  15. https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=316099

  16. https://lists.debian.org/debian-boot/2005/06/msg00938.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment