Rubix

Rubix is a Ruby client for Zabbix. It has a few goals:

  • Provide a Connection and Response class that abstract away the complexity of authenticating with and sending API requests to Zabbix.

  • Provide an ORM for Zabbix resources like Hosts, HostGroups, Templates, &c. using the Zabbix API.

  • Provide a command-line script that wraps the href="http://www.zabbix.com/documentation/1.8/manpages/zabbix_sender"> utility, allowing it to consume JSON data and to ‘auto-vivify’ hosts, hostgroups, applications, and items.

  • Provide some Monitor classes that make it easy to write scripts that periodically measure something and push it to Zabbix.

  • Have as few dependencies as possible: the core classes only depend on Ruby 1.8 standard library & JSON and scripts additionally depend on Configliere[http://github.com/mrflip/configliere].

There are a lot of other projects out there that connect Ruby to Zabbix. Here’s a quick list:

zabbix

zabbix aws templates, scripts, chef automations

zabbixapi

Ruby module for work with zabbix api

zabbix-rb

send data to zabbix from ruby

zabbix_pusher

zabbix_pusher is a gem to parse zabbix templates and push the data to the corresponding zabbix server

zabbix-trappers

Collection of ruby scripts for zabbix trappers

rzabbix

Zabbix API client for Ruby

zabboard

zabbix analytics

zabbix-web

Zabbix frontend

zabcon

Zabcon is a command line interface for Zabbix written in Ruby

None of these projects was satisfactory for our purposes so I decided to write Rubix. The name is terrible but the code is better. Enjoy!

Connections, Requests, & Responses

Getting connected is easy

require 'rubix'

# Provide API URL & credentials.  These are the defaults.
Rubix.connect('http://localhost/api_jsonrpc.php', 'admin', 'zabbix')

As per the Zabbixi API documentation, each request to the Zabbix API needs four values:

id

an integer identifying the request ID.

auth

a string confirming that the API request is authenticated.

method

the name of the API method you’re calling, e.g. - host.get, template.delete, &c.

params

parameters for the invocation of the method.

When you send a request, Rubix only requires you to specify the method and the params, handling the id and authentication quietly for you:

response = Rubix.connection.request('host.get', 'filter' => { 'host' => 'My Zabbix Host' })

# We can check for errors, whether they are non-200 responses or 200
responses with a Zabbix API error.
puts response.error_message unless response.success?

# See the result
puts response.result
#=> [{"hostid"=>"10017"}]

On the command line

Rubix comes with a command line utility zabbix_api which lets you issue these sorts of requests directly on the command line.

$ zabbix_api host.get '{"filter": {"host": "My Zabbix Host"}}'
[{"hostid"=>"10017"}]

zabbix_api lets you specify the credentials and will pretty-print responses for you.

ORM

If you don’t want to deal with the particulars of the Zabbix API itself, Rubix provides a set of classes that you can use instead.

The following example goes through setting up an item on a host complete with host groups, templates, applications, and so on.

require 'rubix'
Rubix.connect('http://localhost/api_jsonrpc.php', 'admin', 'zabbix')

# Ensure the host group we want exists.
host_group = Rubix::HostGroup.new(:name => "My Zabbix Hosts")
host_group.create unless host_group.exists?

# Now the template -- created templates are empty by default!
template = Rubix::Template.new(:name => "Template_Some_Service")
template.create unless template.exists?

# Now the host.
host = Rubix::Host.new(:name => "My Host", :ip => '123.123.123.123', :templates => [template], :host_groups => [host_group])
# 'register' means the same as 'create unless exist' above...
host.register

# Now for the application
app = Rubix::Application.new(:name => 'Some App', :host => host)
app.register

# Now the item
item = Rubix::Item.new(:host => host, :key => 'foo.bar.baz', :description => "Some Item", :value_type => :unsigned_int, :applications => [app])
item.register

You can also update, destroy or unregister (‘destroy if exists’) resources.

Only host groups, templates, hosts, applications, and items are available at present. Other Zabbix resources (actions, alerts, events, maps, users, &c.) can be similarly wrapped, they just haven’t been because we don’t use them as much as we use these core resources.

Sender

Rubix comes with a zabbix_pipe script which, coupled with the href="http://www.zabbix.com/documentation/1.8/manpages/zabbix_sender"> utility, allows for writing data to Zabbix.

By the design of Zabbix, all data written via zabbix_sender (and therefore zabbix_pipe) must be written to items with type “Zabbix trapper”. This type instructs Zabbix to accept values written by zabbix_sender.

zabbix_pipe can consume data from STDIN, a file on disk, or a named pipe. It consumes data one line at a time from any of these sources and uses the zabbix_sender utility to send this data to a Zabbix server.

Here’s an example of starting zabbix_pipe and sending some data onto Zabbix. (Credentials and addresses for the Zabbix server and the Zabbix API can all be customized; try the --help option.)

# Send one data point
$ echo "foo.bar.baz	123" | zabbix_pipe --host='My Zabbix Host'
# Send a bunch of data points in a file
$ cat my_data.tsv | zabbix_pipe --host='My Zabbix Host'

You can also pass the file directly:

# Send a bunch of data points in a file
$ zabbix_pipe --host='My Zabbix Host' my_data.tsv

You can also listen from a named pipe. This is useful on a “production” system in which many processes may want to simply and easily write somewhere without worrying about what happens.

# In the first terminal
$ mkfifo /dev/zabbix
$ zabbix_pipe --pipe=/dev/zabbix --host='My Zabbix Host'

# In another terminal
$ echo "foo.bar.baz	123" > /dev/zabbix
$ cat my_data > /dev/zabbix

In any of these modes, you can also send JSON data directly.

$ echo '{"data": [{"key": "foo.bar.baz", "value": 123}, {"key": "foo.bar.baz", "value": 101}]}' > /dev/zabbix

This simple block of JSON doesn’t really add any power to zabbix_pipe. What becomes more interesting is that we can wedge in data for diffferent hosts, items, &c. when using JSON input:

$ echo '{"host": "My first host", "data": [{"key": "foo.bar.baz", "value": 123}, {"key": "foo.bar.baz", "value": 101}]}' > /dev/zabbix
$ echo '{"host": "My second host", "data": [{"key": "foo.bar.baz", "value": 123}, {"key": "foo.bar.baz", "value": 101}]}' > /dev/zabbix
$ echo '{"host": "My third host", "data": [{"key": "foo.bar.baz", "value": 123}, {"key": "foo.bar.baz", "value": 101}]}' > /dev/zabbix

Rubix will switch hosts on the fly.

Auto-vivification

By default, for every item written into zabbix_pipe, Rubix will first check, using the Zabbix API, whether an item with the given key exists for the given host. If not, Rubix will create the host and the item. You can pass a few details along with your data (when writing in JSON format) or at startup of the zabbix_pipe to tune some of the properites of newly created hosts and items:

$ echo '{"host": "My host", "hostgroups": "Host Group 1,Host Group 2", "templates": "Template 1,Template 2", "applications": "App 1, App2", "data": [{"key": "foo.bar.baz", "value": 123}, {"key": "foo.bar.baz", "value": 101}]}' > /dev/zabbix

If the host ‘My host’ does not exist, Rubix will create it, putting in each of the host groups ‘Host Group 1’ and ‘Host Group 2’ (which will also be auto-vivified), and attach it to templates ‘Template 1’ and ‘Template 2’ (auto-vivified). If the item does not exist for the host ‘My host’, then it will be created and put inside applications ‘App1’ and ‘App2’ (auto-vivified). The value type of the item (unsigned int, float, character, text, &c.) will be chosen dynamically based on the value being written. The created items will all be of the type “Zabbix trapper” so that they can be written to.

Auto-vivification is intended to make it easy to register a lot of dynamic hosts and items in Zabbix. This is perfect for cloud deployments where resources to be monitored are often dynamic.

Failed writes

By the design of Zabbix, a newly created item of type ‘Zabbix trapper’ will not accept data for some interval, typically a minute or so, after being created. This means that when writing a series of values for some non-existent item ‘foo.bar.baz’, the first write will cause the item to be created (auto-vivified), the next several writes will fail as the Zabbix server is not accepting writes for this item yet, and then all writes will begin to succeed as the Zabbix server catches up. If it is absolutely essential for all writes to succeed, including the first, then zabbix_pipe needs to go to sleep for a while after creating a new item in order to give the Zabbix server time to catch up. This can be configured with the --create_item_sleep option. By default this is set to 0.

Skipping auto-vivification

Attempting to auto-vivify keys on every single write is expensive and does not scale. It’s recommended, therefore, to run zabbix_pipe with the --fast flag in production settings. In this mode, zabbix_pipe will not attempt to auto-vivify anything – if items do not exist, writes will just fail, as they do with zabbix_sender itself.

A good pattern is to set up a production pipe (in --fast) mode at /dev/zabbix and to do all development/deployment with a separate copy of zabbix_pipe. Once development/deployment is complete and all hosts, groups, templates, applications, and items have been created, switch to using production mode.

Monitors