Keepalived

From NixOS Wiki
Jump to: navigation, search

Keepalived provides Virtual Router Redundancy Protocol (VRRP) and automatic virtual IP addresses for Linux. It is utilized to create high availability services. It can also be used for service discovery when working with something like Docker Swarm.

Basic Setup Example

This example configuration works well with NixOS 24.11. Use it as a starting point and tailor it for your needs.

The author migrated a known good keepalived.conf from another Linux distribution and adopted NixOS options where feasible. The configuration is imported in the primary /etc/nixos/configuration.nix.

imports =
  [
    ./hardware-configuration.nix
    ./keepalived.nix
  ]

Lessons Learned

  • Declare Keepalived configuration and its tracking scripts in the same NixOS configuration file. This lets your scripts be in scope and usable.
  • Use NixOS options for script tracking. Use Keepalived native configuration for process tracking. (The author did not work with file tracking.)


#
# keepalived.nix
#
# Notes
#
# - Use NixOS options for script tracking. Use extraConfig for 
#   process tracking.
# - Don't use pipes in Keepalived script configuration line:
#   https://github.com/acassen/keepalived/issues/1107
#   https://github.com/acassen/keepalived/issues/2381
# - The MASTER node will enable the Virtual IP (VIP) regardless of the 
#   tracked process state, unless a BACKUP has a higher priority.
#
# References
#
# - https://www.redhat.com/en/blog/ha-cluster-linux
# - https://www.redhat.com/en/blog/advanced-keepalived
# - https://keepalived.org/manpage.html
# - https://search.nixos.org/options?query=services.keepalived
# - use_symlink_paths -- https://github.com/acassen/keepalived/issues/2393
#
{
  config,
  pkgs,
  lib,
  ...
}: let
  docker-ps = pkgs.writeShellScriptBin "docker-ps" ''

    ctnr="$1"

    ${pkgs.docker}/bin/docker ps --filter "status=running" | \
    ${pkgs.gnugrep}/bin/grep -i "$ctnr" > /dev/null

    if [ $? -eq 0 ]; then
      exit 0  # Process is running
    else 
      exit 1  # Process is not running
    fi
  '';
in {
  environment.systemPackages = [docker-ps];

  system.activationScripts = {
    print-keepalived = {
      text = builtins.trace "building the keepalived configuration..." "";
    };
  };

  #
  # Keepalived
  #
  services.keepalived = {
    enable = true;

    openFirewall = true;

    extraGlobalDefs = ''
      use_symlink_paths true
    '';

    vrrpInstances.VIP_32 = {
      state = "BACKUP";
      interface = "eth0";
      virtualRouterId = 32;
      priority = 44;
      virtualIps = [{addr = "172.16.1.32/24";}];
      trackScripts = ["track_homepage"];
    };

    vrrpScripts = {
      track_homepage = {
        # Note: Shell pipes are not supported here. Call a self written 
        # script or something like that instead.
        script = "${docker-ps}/bin/docker-ps homepage";
        interval = 10;
        timeout = 2;
        rise = 2;
        fall = 2;
        weight = 10;
        user = "root";
      };
    };

    extraConfig = ''
      #
      # Track by Process
      #
      vrrp_track_process track_portainer {
        process portainer
        weight 10
      }

      vrrp_track_process track_smokeping {
        process smokeping
        weight 10
      }

      #
      # Order this section by IP
      #
      vrrp_instance VIP_41 {
        state BACKUP
        interface eth0
        virtual_router_id 41
        priority 44
        advert_int 5
        virtual_ipaddress {
          172.16.1.41/24
        }
        track_process {
          track_smokeping
        }
      }

      vrrp_instance VIP_42 {
        state BACKUP
        interface eth0
        virtual_router_id 42
        priority 44
        advert_int 5
        virtual_ipaddress {
          172.16.1.42/24
        }
        track_process {
          track_portainer
        }
      }
    '';
  };
}