by R.I. Pienaar | Nov 16, 2010 | Uncategorized
I am working toward releasing The Marionette Collective 1.0.0 and finishing off a last few features. I’ll maintain 1.0.0 as a long term supported stable version while 1.1.x will be where new development will happen.
One of the last features that will go into 1.0.0 is the ability to do a discovery but then only target a subset of hosts.
I have a Nagios Notification system called Angelia and I have three instances of this deployed on my network. Any machine that needs to send a message can do this via the angelianotify agent. I need the Angelia service to be highly available so that even if I do maintenance on one instance the others should transparently keep sending my alerts.
The problem is that till now I had to filter based on enough state to uniquely identify a single instance of Angelia else I will receive multiple alerts, this new feature solves this problem.
$ mc-rpc -1 angelianotify sendmsg msg="hello world" recipient="clickatell://0044xxx" |
$ mc-rpc -1 angelianotify sendmsg msg="hello world" recipient="clickatell://0044xxx"
With the new -1 flag a discovery will be done, it will find 3 instances and by default it will pick the the node that was quickest to respond. This is a poor mans nearest detection. You can also configure it to send the request to a random node in the results.
The -1 is shorthand for another option, the full option is –limit-nodes 1 so you can specify any arbitrary number of nodes but you can also specify something like –limit-nodes 10% to only hit a subset of your machines.
The percentage targeting is useful for only initiating deploys against a subset of your nodes while your monitoring system detects errors before you do your full deploy to all your servers.
This was a simple thing to implement but I think it’s a pretty big deal – it helps us step further away from DNS as an addressing system and rather use your metadata.
It also means if you’re exposing services over SimpleRPC you can now easily create a very reliable network of these services using the communication layer provided by the middleware. You can create a fully meshed network of ActiveMQ servers that can route around network outages, around maintenance windows and other outages without costly load balancers or without issues such as relying on locally redundant hosted instances of these services.
With my Angelia example above – should my monitoring server in the US want to send an alert while I am doing maintenance on the nearest Angelia instance it will simply use the next nearest one, probably the one in the UK. Effectively and cheaply realizing my need for a HA system over some arbitrarily simple bit of software like Angelia that doesn’t need to concern itself with being HA aware.
by R.I. Pienaar | Oct 7, 2010 | Uncategorized
Today at Puppet Camp Luke Kanies and I announced the sale of The Marionette Collective to Puppet Labs – The company behind the well known Puppet Configuration Management system. There’s an official press release and FAQ up.
This is a pretty major milestone for me and MCollective as a project. MCollective started as an exploration of new ideas and eventually evolved into a viable product that large customers might want to use.
Personally I am not that interested in becoming a software vendor with sales, support and all that kind of thing. I’d much rather devote my time to the more technical aspects of such a venture. When I was approached by Puppet Labs about this I was pretty excited – they already know how to run a commercial open source project and have the team and structure in place to make it work.
In time we will be publishing a detailed road map for the product and how it will integrate with the plans at Puppet Labs. One of the plans I’m already very excited about is the integration into the Puppet Dashboard. A graphical front-end is something I’ve often wanted but do not have the skill set to build, Puppet Labs has a great team in place and together we’ll lower the barrier to entry of Data Center Automation significantly.
As a heavy Puppet user I’ve designed MCollective to work well with Configuration Management tools in general – by aligning commercially with what can rightfully be described as the best of breed CM tool and working on creating a tighter integration between the two projects I hope we’ll see some great innovation and improvement.
While I am not joining Puppet Labs on a permanent basis I will be working with the team on improving MCollective as well as Puppet, I will still be developing and still be around the community and advising on the direction that MCollective will take.
The work till now was done on my own time and funded by me personally. This sale will give me the opportunity to be paid for my ongoing work on the project and to devote serious time to it which will translate in a faster pace of innovation and achieving the goals of the current road map in a much quicker time than I first hoped for.
Finally I’d like to thank the devoted early adopter community, there’s been some excellent work done by utilizing MCollective as a framework and I look forward to growing this community. This move should validate your choice and time spent in supporting my project and I hope I can provide you all with an even better tool in the near future.
by R.I. Pienaar | Sep 21, 2010 | Uncategorized
I’ve released version 0.4.9 of MCollective, it’s a bugfix/small feature release the only big thing is that I added a new agent called rpcutil that lets you get all sorts of information about the running collective.
You can for example find out a list of all the agents and all their meta data including versions etc, this will be great for documenting/auditing an infrastructure based on mcollective.
The mc-inventory script has been rewritten to use the new agent and now shows you some stats in addition to the nodes inventory when invoked:
Server Statistics:
Version: 0.4.8
Start Time: Mon Sep 20 17:29:24 +0100 2010
Config File: /etc/mcollective/server.cfg
Process ID: 20215
Total Messages: 122317
Messages Passed Filters: 120731
Messages Filtered: 1586
Replies Sent: 3701
Total Processor Time: 191.29 seconds
System Time: 109.82 seconds
Full information about the release can be found here.
by R.I. Pienaar | Sep 19, 2010 | Uncategorized
I’ve had a pipe dream of creating something like IRB for ruby but tailored for mcollective, something with DDL assisted completion and all sorts of crazy kewl things.
Having looked a few times into this I concluded IRB is a black box of undocumented voodoo and always gave up. I had another google this weekend and came across what set me off in the right direction.
Christopher Burnett has a great little Posterous post up showing how to build custom IRB shells. With a little further digging I came across Bond from Gabriel Horner. These two combined into something that will definitely be one of my favorite toys.
The result is a mcollective IRB shell that you can grab in the ext directory of the mcollective tarball – it brings in some native gem dependencies that I really don’t want in the base deploy.
It’s best to see it in action before looking at the code so you know what the behavior is, see the screen cast below.
Getting the basic mc-irb going was pretty much exactly as Christopher’s posterous shows so I won’t go into the detail of that, what I do want to show is the DDL based command completion with Bond.
require 'bond'
Bond.start
Bond.complete(:method => "rpc") do |e|
begin
if e.argument == 1
if e.arguments.last == "?"
puts "\n\nActions for #{@agent_name}:\n"
@agent.ddl.actions.each do |action|
puts "%20s - %s" % [ ":#{action}", @agent.ddl.action_interface(action)[:description] ]
end
print "\n" + e.line
end
@agent.ddl.actions
elsif e.argument > 1
action = eval(e.arguments[0]).to_s
ddl = @agent.ddl.action_interface(action)
if e.arguments.last == "?"
puts "\n\nArguments for #{action}:\n"
ddl[:input].keys.each do |input|
puts "%20s - %s" % [ ":#{input}", ddl[:input][input][:description] ]
end
print "\n" + e.line
end
ddl[:input].keys
end
rescue Exception
[]
end
end |
require 'bond'
Bond.start
Bond.complete(:method => "rpc") do |e|
begin
if e.argument == 1
if e.arguments.last == "?"
puts "\n\nActions for #{@agent_name}:\n"
@agent.ddl.actions.each do |action|
puts "%20s - %s" % [ ":#{action}", @agent.ddl.action_interface(action)[:description] ]
end
print "\n" + e.line
end
@agent.ddl.actions
elsif e.argument > 1
action = eval(e.arguments[0]).to_s
ddl = @agent.ddl.action_interface(action)
if e.arguments.last == "?"
puts "\n\nArguments for #{action}:\n"
ddl[:input].keys.each do |input|
puts "%20s - %s" % [ ":#{input}", ddl[:input][input][:description] ]
end
print "\n" + e.line
end
ddl[:input].keys
end
rescue Exception
[]
end
end
This code checks the argument count, handles the first and subsequent arguments differently and supports the ? special case by loading the DDL and displaying the right stuff.
I’ve not found much by way of complex examples on the Bond site or its own bundled completions, hopefully this helps someone.
by R.I. Pienaar | Sep 18, 2010 | Uncategorized
The current supported way of making a Puppet node aware of the state of other nodes is Exported Resources, there’s some movement in this space see for example a recent thread on the developers list.
I’ve personally never found exported resources to be a good fit for me, I have many masters spread over long network distances and they often operate in a split brain scenario where the masters couldn’t see each other. This makes using something like MySQL as a database hard since you’d effectively need a single write point and replication is point to point.
There are other problems too, consider a use case where you have modules for phpbb, wordpress, rails apps, phpmyadmin and several others that need to talk to the master database. Maybe you also want to show in the motd a resource list for customers.
Every one of these modules have configuration parameters that differ in some way or the other. The exported resources model kind of expect you to export from the MySQL Master the configs for all the apps using the database, this isn’t viable when you don’t know what all will talk to this MySQL server and the resource abstraction doesn’t really work well when the resulting config might need to look like this:
$write_database = "db1.your.net";
$read_databases = "db2.your.net", "db3.your.net", "db4.your.net"; |
$write_database = "db1.your.net";
$read_databases = "db2.your.net", "db3.your.net", "db4.your.net";
Filling in that array with exported resources is pretty hard.
So I favor searching nodes and walking the results. I tend to use extlookup for this and just maintain the hosts lists by hand, this works but it’s maintained by hand, there has to be a better way.
I wrote a mcollective registration system that puts all node data in MongoDB instances.
Simply using mcollective to manage the data store has several advantages:
- You can run many instances of the registration receiver, one per puppet master. As writes are not done by the master but by MCollective there’s no need for crazy database replication system, mcollective gives you that for free
- Using ActiveMQ meshed network architecture you can achieve better resiliency and ensure more up to date data than with simple point to point replication of database systems. Routing around internet outages is much easier this way.
- If you bootstrap machines via mcollective your registration data will be there before you even do your first Puppet run
And storing the data in a document database – and specifically Mongo over some of the other contenders:
- Writes are very fast – ms per record written as the update is written as a single document and not 100s or 1000s of rows
- Mongo has a query language to query the documents, other NoSQL systems insist on writing map reduce queries, this would be a bad fit for use inside Puppet manifests without writing a lot of boiler place query map reduce functions, something I don’t have time for.
The mcollective registration system will store per node the following data:
- A list of all the Agents
- A list of all the facts
- A list of all the Puppet classes assigned to this node
- FQDN, time of most recent update and a few other bits
You can now use this in Puppet manifests as follows to fill some of our stated needs, the code below use the search engine to find all the machines with class “mysql::server” for a specific customer and then display this data in the MOTD:
# Connect to the database, generally do this in site.pp
search_setup("localhost", "puppet", "nodes")
# A define to add the hostname and ip address of the MySQL server to motd
define motd::register_mysql {
$node = load_node($name)
motd::register{"MySQL Server: ${node['fqdn']} / ${node['facts']['ipaddress']}"
}
$nodes = search_nodes("{'facts.customer' => '$customer', 'classes' => 'mysql::server'}")
motd::register_mysql{$nodes: } |
# Connect to the database, generally do this in site.pp
search_setup("localhost", "puppet", "nodes")
# A define to add the hostname and ip address of the MySQL server to motd
define motd::register_mysql {
$node = load_node($name)
motd::register{"MySQL Server: ${node['fqdn']} / ${node['facts']['ipaddress']}"
}
$nodes = search_nodes("{'facts.customer' => '$customer', 'classes' => 'mysql::server'}")
motd::register_mysql{$nodes: }
Here is a template that does a search using this too, it makes a comma seperate list of mysql slave ip addresses for a customer:
$read_databases = <%=
nodes = Puppet::Util::MongoQuery.instance.find_nodes({"facts.customer" => "#{customer}", "classes" => "mysql::slave"})
nodes.map {|n| '"' + n["facts"]["ipaddress"] + '"' }.join(", ")
%> |
$read_databases = <%=
nodes = Puppet::Util::MongoQuery.instance.find_nodes({"facts.customer" => "#{customer}", "classes" => "mysql::slave"})
nodes.map {|n| '"' + n["facts"]["ipaddress"] + '"' }.join(", ")
%>
This code can probably be cleaned up a bit, would be helpful if Puppet gave us better ways to extend it to enable this kind of thing.
I find this approach much less restrictive and easier to get my head around than exported resources. I obviously see the need for tracking actual state across a infrastructure and exported resources will solve that in future when you have the ability to require a database on another node to be up before starting a webserver process, but that seems to be long way off for Puppet. And I think even then a combination system like this will have value.
A note about the code and how to install it. Puppet has a very limited extension system at present, you cannot add Puppet::Util classes via pluginsync and there’s no support for any kind of pooling of resources from plugins. So this code needs to be installed into your Ruby libdir directly and I use a Singleton to maintain a database pool per Puppetmaster process. This is not ideal, I’ve filed feature enhancement requests with Puppet Labs, hopefully things will become easier in time.
The code to enable above is in my GitHub account..