# Two-Node High-Availability with Dqlite High availability (HA) is a mandatory requirement for most production-grade Kubernetes deployments, usually implying three or more nodes. Two-node HA clusters are sometimes preferred for cost savings and operational efficiency considerations. Follow this guide to learn how Canonical Kubernetes can achieve high availability with just two nodes while using the default datastore, [Dqlite]. Both nodes will be active members of the cluster, sharing the Kubernetes load. Dqlite cannot achieve a [Raft] quorum with fewer than three nodes. This means that Dqlite will not be able to replicate data and the secondaries will simply forward the queries to the primary node. In the event of a node failure, database recovery will require following the steps in the [Dqlite recovery guide]. ## Proposed solution Since Dqlite data replication is not available in this situation, we propose using synchronous block level replication through [Distributed Replicated Block Device] (DRBD). The cluster monitoring and failover process will be handled by [Pacemaker] and [Corosync]. After a node failure, the DRBD volume will be mounted on the standby node, allowing access to the latest Dqlite database version. Additional recovery steps are automated and invoked through Pacemaker. ### Prerequisites: * Please ensure that both nodes are part of the Kubernetes cluster. See the [getting started] and [add/remove nodes] guides. * The user associated with the HA service has SSH access to the peer node and passwordless sudo configured. For simplicity, the default "ubuntu" user can be used. * We recommend using static IP configuration. The [two-node-ha.sh script] automates most operations related to the two-node HA scenario and is included in the snap. The first step is to install the required packages: ``` /snap/k8s/current/k8s/hack/two-node-ha.sh install_packages ``` ### Distributed Replicated Block Device (DRBD) This example uses a loopback device as DRBD backing storage: ``` sudo dd if=/dev/zero of=/opt/drbd0-backstore bs=1M count=2000 ``` Ensure that the loopback device is attached at boot time, before Pacemaker starts. ``` cat < HATWO_ADDR= cat < HATWO_ADDR= sudo mv /etc/corosync/corosync.conf /etc/corosync/corosync.conf.orig cat < HATWO_ADDR= DRBD_MOUNT_DIR=${DRBD_MOUNT_DIR:-"/mnt/drbd0"} sudo crm configure < # remove the node constraint. sudo crm resource clear fs_res ``` ### Managing Kubernetes Snap Services For the two-node HA setup, k8s snap services should no longer start automatically. Instead, they will be managed by a wrapper service. ``` for f in `sudo snap services k8s | awk 'NR>1 {print $1}'`; do echo "disabling snap.$f" sudo systemctl disable "snap.$f"; done ``` ### Preparing the wrapper service The next step is to define the wrapper service. Add the following to ``/etc/systemd/system/two-node-ha-k8s.service``. ```{note} the sample uses the ``ubuntu`` user, feel free to use a different one as long as the prerequisites are met. ``` ``` [Unit] Description=K8s service wrapper handling Dqlite recovery for two-node HA setups. After=network.target pacemaker.service [Service] User=ubuntu Group=ubuntu Type=oneshot ExecStart=/bin/bash /snap/k8s/current/k8s/hack/two-node-ha.sh start_service ExecStop=/bin/bash sudo snap stop k8s RemainAfterExit=true [Install] WantedBy=multi-user.target ``` ```{note} The ``two-node-ha.sh start_service`` command used by the service wrapper automatically detects the expected Dqlite role based on the DRBD state. It then takes the necessary steps to bootstrap the Dqlite state directories, synchronize with the peer node (if available) and recover the database. ``` When a DRBD failover occurs, the ``two-node-ha-k8s`` service needs to be restarted. To accomplish this, we are going to define a separate service that will be invoked by Pacemaker. Create a file called ``/etc/systemd/system/two-node-ha-k8s-failover.service`` containing the following: ``` [Unit] Description=Managed by Pacemaker, restarts two-node-ha-k8s on failover. After=network.target home-ubuntu-workspace.mount [Service] Type=oneshot ExecStart=systemctl restart two-node-ha-k8s RemainAfterExit=true ``` Reload the systemd configuration and set ``two-node-ha-k8s`` to start automatically. Notice that ``two-node-ha-k8s-failover`` must not be configured to start automatically, but instead is going to be managed through Pacemaker. ``` sudo systemctl enable two-node-ha-k8s sudo systemctl daemon-reload ``` Make sure that both nodes have been configured using the above steps before moving forward. ### Automating the failover procedure Define a new Pacemaker resource that will invoke the ``two-node-ha-k8s-failover`` service when a DRBD failover occurs. ``` sudo crm configure < [Dqlite]: https://dqlite.io/ [Raft]: https://raft.github.io/ [Distributed Replicated Block Device]: https://ubuntu.com/server/docs/distributed-replicated-block-device-drbd [Dqlite recovery guide]: restore-quorum [external datastore guide]: external-datastore [two-node-ha.sh script]: https://github.com/canonical/k8s-snap/blob/main/k8s/hack/two-node-ha.sh [getting started]: ../tutorial/getting-started [add/remove nodes]: ../tutorial/add-remove-nodes [Pacemaker]: https://clusterlabs.org/pacemaker/ [Corosync]: https://clusterlabs.org/corosync.html [Pacemaker fencing]: https://clusterlabs.org/pacemaker/doc/2.1/Pacemaker_Explained/html/fencing.html [split brain]: https://en.wikipedia.org/wiki/Split-brain_(computing)