Rubix
Rubix is a Ruby client for Zabbix. It has a few goals:
-
Provide a
Connection
andResponse
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 onConfigliere
[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. 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.