Pacemaker and Corosync don’t actually manage HAProxy itself; they manage the virtual IP address that HAProxy listens on, ensuring it’s always active on one node.

Let’s see this in action. Imagine we have two servers, node1 and node2, and we want to make sure a virtual IP address 192.168.1.100 is always available, and if it fails on node1, it immediately moves to node2.

First, we need to install Pacemaker and Corosync on both nodes.

# On node1 and node2
sudo apt update
sudo apt install pacemaker corosync pcs

Next, we need to configure Corosync to establish a cluster. This involves creating a configuration file, typically /etc/corosync/corosync.conf. A minimal configuration might look like this:

# /etc/corosync/corosync.conf
logging {
  debug: off
  info: on
  to_syslog: yes
}

nodelist {
  node {
    ring0_addr: 192.168.1.1
    nodeid: 1
  }
  node {
    ring0_addr: 192.168.1.2
    nodeid: 2
  }
}

quorum {
  provider: corosync_votequorum
  two_node: 1
}

# This is important for network communication between nodes
transport: udpu

In this config:

  • ring0_addr are the actual IP addresses of your nodes.
  • nodeid are unique identifiers for each node.
  • two_node: 1 tells Corosync that this is a two-node cluster and it can form a quorum with just these two nodes.
  • transport: udpu specifies UDP as the transport protocol.

After creating this file on both nodes, we start and enable the Corosync service:

# On node1 and node2
sudo systemctl start corosync
sudo systemctl enable corosync

Now, we use pcs to configure Pacemaker. We first authenticate the nodes:

# On node1
sudo pcs host auth node1 node2 -u hacluster -p your_password_here
sudo pcs cluster setup my_cluster_name node1 node2 --force
sudo pcs cluster start --all
sudo pcs cluster enable --all

This sets up a cluster named my_cluster_name and starts all cluster services. The hacluster user and the password you provide are used for secure communication between cluster nodes.

The core of this setup is defining a resource. For HAProxy, we usually want a virtual IP address to float between nodes. We can define this using the IPaddr2 resource agent.

# On any node in the cluster
sudo pcs resource create VirtualIP IPaddr2 ip=192.168.1.100 cidr_netmask=24 op monitor interval=30s

Here, VirtualIP is the name we give this resource. IPaddr2 is the Pacemaker resource agent for managing IP addresses. ip=192.168.1.100 is the virtual IP we want to manage, and cidr_netmask=24 defines its subnet mask. op monitor interval=30s tells Pacemaker to check the status of this IP every 30 seconds.

Now, if you check the status on either node:

# On node1 or node2
sudo pcs status

You’ll see VirtualIP listed as running on one of the nodes. If you manually move the IP (e.g., by stopping the resource on the current node), Pacemaker will detect the failure and start it on the other node.

The critical insight here is that Pacemaker and Corosync create a highly available infrastructure layer (the virtual IP) that your services, like HAProxy, can then bind to. HAProxy itself isn’t directly managed by Pacemaker in this basic setup. You would typically configure HAProxy to listen on 192.168.1.100.

To make HAProxy itself highly available, you’d create another Pacemaker resource for HAProxy, often using a systemd resource agent, and make it colocate with the VirtualIP resource.

# On any node in the cluster
sudo pcs resource create HAProxy systemd:haproxy op monitor interval=30s
sudo pcs constraint colocation add HAProxy with VirtualIP INFINITY
sudo pcs constraint order promote VirtualIP then HAProxy

systemd:haproxy tells Pacemaker to manage the HAProxy service using its systemd unit file. The colocation constraint ensures that the HAProxy service runs on the same node as the VirtualIP. The order constraint ensures that the VirtualIP is started before HAProxy. This way, when the VirtualIP fails over, HAProxy also fails over and starts listening on the new IP address.

The one thing most people don’t realize is that Pacemaker is fundamentally a distributed lock manager and state machine. It doesn’t "know" about HAProxy or any specific application; it only knows about the resources you define (like IPaddr2 or systemd:haproxy) and the constraints you place on them (like colocation and ordering). It uses Corosync to maintain a consistent view of the cluster state across all nodes, ensuring that only one node actively holds a resource at any given time, or that resources are started/stopped in the correct sequence.

The next common challenge is handling cluster fencing, which prevents split-brain scenarios when network partitions occur.

Want structured learning?

Take the full Haproxy course →