Node DSL

While Arborist has pluggable hooks for sourcing node data from various sources (databases, LDAP, etc), it only ships with file sources by default. This reference page outlines all possible options for this file backed source.

Please note: Any installed custom node types may extend or add DSL syntax options. See the RDoc for that specific node type -- this page only documents core Arborist.

Overview

Nodes files are loaded in by the Manager at startup. You can organize them however you please. Loading is 2-phase, so ordering by dependencies is not necessary. Files will be discovered irrespective of directory depth. Files must end in an .rb extension to be considered.

Of the three node types outlined on the Manager page, only the Host node type is referenced explicitly in the DSL. The service and resource nodes are normally intended to be a byproduct of describing the desired state of the host. The Host nodetype is assumed to be accessible over IP networks.

Hosts

Host nodes are created via the Arborist::Host class, with a unique identifier argument, and a ruby block with node keywords. All keywords are optional (though clearly not providing at least an address will make network monitoring more difficult than necessary.)

Host Keywords

keyword description
address One or more IP addresses (IPv4 or IPv6) to attach to this host. This can be called multiple times. If provided a hostname, it is automatically looked up via DNS.
config Arbitrary key/value pair data to carry with the node. Monitors and Observers can use this data however they please. This can be called multiple times.
description A human readable String describing the host's purpose.
depends_on Other nodes that should be functional for this node to be considered up. See the Secondary Dependencies section below for syntax detail.
hostname The canonical FQDN of the host node, if not determined via DNS from the address keyword.
parent Attach this Host node under another node's identifier, forming the Manager tree hierarchy. If omitted, the Host node is attached to the root Arborist node.
resource Create a new Arborist::Resource node as a child of this host. See Resource Options below for details.
service Create a new Arborist::Service node as a child of this host. See Service Options below for details.
tags One or more comma separated labels to attach to this node, for easy cross-selections with monitors and observers. This can be called multiple times.

Host Example

A full example that incorporates other topics in this category can be found later in this document. This is just an example of how to initialize a host node.

# Via the DSL
Arborist::Host 'unique-identifier' do
    address '10.1.23.4'
    # ...
end

# Ruby idiomatic
hostnode = Arborist::Node.create( :host, 'unique-identifier' )
hostnode.address( '10.1.23.4' )

Services

Services describe network accessible applications that run on their parent Arborist::Host node. There is one required one argument - the service name. This can be a well-known service (listed in /etc/services), or any label of your choosing.

A service node is created underneath its Arborist::Host in the tree with a fully-qualified identifier based on its parent identifier. For example, a service node labeled smtp under a host node with the identifier of mail-server would be automatically created with an identifier of 'mail-server-smtp.

Service Keywords

keyword description
address A single IP addresses (IPv4 or IPv6) to attach to this service. This is normally inherited automatically from the parent host node. This is to call out a specific IP address if the host has multiple. Note: The address provided must be present in the parent node.
app_protocol An optional hint for monitors for how to communicate with this service, such as running an http service on a non-standard port.
config Arbitrary key/value pair data to carry with the node. Monitors and Observers can use this data however they please. This can be called multiple times.
description A human readable String describing the service.
depends_on Other nodes that should be functional for this node to be considered up. See the Secondary Dependencies section below for syntax detail.
protocol The IP protocol used by this service. Defaults to tcp.
port The TCP/IP port the service communicates on. If a well-known service, this is determined automatically. Otherwise, it must be specified.
tags One or more comma separated labels to attach to this node, for easy cross-selections with monitors and observers. This can be called multiple times.

Service Examples

# A simple example, using an /etc/services lookup for a well-known service.
service 'ssh'

# Any service not well known needs to manually specify a port.
# We also specify the optional application protocol here.
service 'rest-api', port: 8080, app_protocol: 'http'

# Default protocol is 'tcp', but you can manually set it.
service 'dhcp', port: 67, protocol: 'udp'

# A more elaborate example.
service 'internal' do
    description 'Proprietary weirdo service'
    protocol 'udp'
    port 9223
    app_protocol 'custom'
    config apikey: '8034ac45-0c3b-11e8-83d9-7cd30acd3ed8'
    depends_on 'frontend-proxy'
end

Resources

A resource node describes a finite property of a parent Arborist::Host node that is capable of being measured. Resources aren't directly responsible for the health of a host, but rather contribute towards it. CPU, disk capacity, available addressable memory, file descriptors, etc.

Resource nodes are created underneath their Arborist::Host in the tree with a fully-qualified identifier based on the parent identifier. There is one required argument, the resource label. A resource node labeled memory under a host node with the identifier of mail-server would be automatically created with an identifier of mail-server-memory.

Resource Keywords

keyword description
address A single IP addresses (IPv4 or IPv6) to attach to this resource. This is normally inherited automatically from the parent host node. This is to call out a specific IP address if the host has multiple. Note: The address provided must be present in the parent node.
category A label for this resource, for easier selection from monitors and observers. If unset, this is the same as the identifier.
config Arbitrary key/value pair data to carry with the node. Monitors and Observers can use this data however they please. This can be called multiple times.
description A human readable String describing the resource.
depends_on Other nodes that should be functional for this node to be considered up. See the Secondary Dependencies section below for syntax detail.
tags One or more comma separated labels to attach to this node, for easy cross-selections with monitors and observers. This can be called multiple times.

Resource Examples

# A simple example.
resource 'load'

# An example that separates label from category.
resource '5-minute-load', category: 'load'

# A more elaborate example.
resource 'disk-capacity' do
    description 'Available hard drive space for mounts'
    category 'disk'
    config exclude: [ '^/var/tmp$' ]
    depends_on 'primary-storage-iscsi'
end

Secondary Dependencies

The depends_on keyword available to all node types creates one or more Arborist secondary dependencies. Primary dependencies are inferred directly from node ancestors - a service is automatically quieted and removed from search results if its parent node is down. A secondary dependency is a human declaration of a relationship between two nodes, that creates links across the tree.

When a node transitions to any state that removes it from search results (down, disabled, quieted), any nodes that indicate they are dependant on it are automatically transitioned to quieted with the reason.

Additionally, using the deps API call, you can ask a node what depends on me? This can make error reporting significantly more informative.

Dependency Examples

In the most simple case, you can just provide a list of fully qualified identifiers to the depends_on keyword. By default, all indicated nodes must be up for the dependency to evaluate successfully.

# All of these nodes must be operational for this node to be considered "okay".
depends_on 'primary-storage-iscsi', 'master-database'

There's a syntax helper if you want to indicate the same service/resource dependency on multiple host nodes:

# This expands to 'host1-disk', 'host2-disk', 'host3-disk'.
depends_on 'disk' on: [ 'host1', 'host2', 'host3' ]

Lets say a service relies on a load-balanced backend, and only needs one of them to be running successfully to be considered operational. This requires the any_of declarative.

# Only one of these dependencies needs to be up for this node to be considered "okay".
depends_on any_of( 'server-1-ldap', 'server-2-ldap' )

Technically, depends_on always wraps arguments in an all_of declaration. Knowing this, you can create complex dependency descriptions that are evaluated depth first.

# This node needs either 2 functional database backends,
# OR just one (of many) memcache nodes up to be considered "okay".
depends_on \
    any_of \
        any_of( 'memcache', on: ['server-1', 'server-2', 'server-3'] ),
        all_of( 'database' on: ['dbnode-1', 'dbnode-2'] )

Flap Detection

Arborist provides a path to squelch a node that is changing state too frequently. While disabled by default, this can be enabled globally in the configuration file by adjusting the status_history_size and flap_threshold values.

---
arborist:
  node:
    status_history_size: 15
    flap_threshold: 5

The above configuration says mark nodes as flapping if their status changes 5 times within 15 updates. Because frequency is an artifact of the Monitor, and notification of the Observer, what is considered too frequently can be individually adjusted per node:

keyword description
flap_threshold How many individual status changes in history should mark this node as flapping?
status_history_size How many total status updates to consider when checking for a flapping node.

It should probably be noted that a node that is considered flapping has no direct behavioral changes. It is simply marked as such, so your Observer(s) can be crafted to make better informed decisions.

Full Example

Arborist::Host 'primary-database' do
    description 'PostgreSQL master server'
    address 'db-master.example.com'
    parent 'network-switch-12'

    config os: '11-1-release'
    tags :database,
         :postgresql,
         :freebsd

    service 'ssh'
    service 'postgresql', port: 5432 do
        depends_on 'iscsi', on: 'primary-storage'
    end

    resource 'load', description: 'machine load'
    resource 'disk' do
        description 'partition capacity'
        config include: [ '^/var/db' ]
    end
end