The Marionette Collective (MCollective) is a server orchestration/parallel job execution framework available from Puppet Labs (http://docs.puppetlabs.com/mcollective/). It can be used to programmatically execute administrative tasks on clusters of servers. Rather than directly connecting to each host (think SSH in a for loop), it uses publish/subscribe middleware to communicate with many hosts at once. Instead of relying on a static list of hosts to command, it uses metadata-based discovery and filtering and can do real-time discovery across the network.
Getting MCollective up and running is not a trivial task. In this article I’ll walk through the steps required to setup a simple MCollective deployment. The middleware of choice as recommended by the Puppet Labs documentation is ActiveMQ. We’ll use a single ActiveMQ node for the purposes of this article. For a Production deployment, you should definitely consider the use of a clustered ActiveMQ configuration. Again for the sake of simplicity we will only configure a single MCollective client (i.e. our “admin” workstation). For real-world applications you’ll need to manage clients as per the standard deployment guide.
There are four hosts in the lab - centos01 which is our Puppet Master and MCollective client, centos02 which will be the ActiveMQ server and an MCollective server, centos03 and centos04 which are both MCollective servers. All hosts run Puppet clients already, which I’ll use to distribute the appropriate configuration across the deployment. All hosts are running Centos 6.5 x86_64.
Getting Started
As per the deployment guide, the first thing we need to do is get our numerous credentials and certificates in order. Thankfully, many of the required certificates are already part of the deployed Puppet infrastructure, so we can reuse those. Read the deployment guide for further understanding on which keys are required for which components of the MCollective deployment.
Traffic between MCollective and ActiveMQ uses CA-signed X.509 certificates for encryption and verification. We can use the Puppet CA for this.
First, we need to decide on a username and password for connecting to ActiveMQ. I’ll use the username mcollective with password Passw0rd. I suggest you choose a much stronger password - for the lab, this is fine.
Next we need to verify the location of our CA certificate (that which has already signed some of the required certificates, and that we’ll use to sign some of our new certificates below). Run the following command on the Puppet master, and verify the paths:
|
1 2 3 |
# puppet master --configprint certdir,privatekeydir certdir = /var/lib/puppet/ssl/certs privatekeydir = /var/lib/puppet/ssl/private_keys |
Our CA cert will be located at ${certdir}/ca.pem.
Generate a certificate for Active MQ on the Puppet master (we could choose to use an existing certificate here, but I opted for a new certificate).
|
1 |
# puppet cert generate activemq |
The certificate is now available at ${certdir}/activemq.pem and the key at ${privatekeydir}/activemq.pem. We’ll need to transfer those to the ActiveMQ server later and create a truststore and a keystore.
We now need to generate a shared server keypair - again on the Puppet master:
|
1 |
# puppet cert generate mcollective-servers |
The shared server certificate is now available at ${certdir}/mcollective-servers.pem and the key at ${privatekeydir}/mcollective-servers.pem.
We now need per-server certificates. Thankfully, every server node already has its own puppet agent certificate, so we can re-use it instead of generating new server certificates.The certificate and key are located at ${certdir}/<HOSTNAME>.pem and ${privatekeydir}/<HOSTNAME>.pem.
Finally, we need the client certificate. As in the real-world these would be generated for each admin user, I specified my name when generating the keypair:
|
1 |
# puppet cert generate toki |
The certificate is at ${certdir}/toki.pem and the key at ${privatekeydir}/toki.pem.
OK - all of the required certificates and credentials are taken care of.
ActiveMQ Configuration
I deployed ActiveMQ to centos02 via Puppet. My simple ActiveMQ Puppet module is located on centos01 at /etc/puppet/modules/activemq. I downloaded the sample activemq.xml single-broker configuration file to /etc/puppet/modules/activemq/files/activemq.xml as follows:
|
1 |
# wget https://raw.githubusercontent.com/puppetlabs/marionette-collective/master/ext/activemq/examples/single-broker/activemq.xml |
My /etc/puppet/modules/activemq/manifests/init.pp is as follows:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class activemq { package { 'activemq': ensure => latest, before => Service[ "activemq" ] } service { 'activemq': ensure => running, enable => true } file { '/etc/activemq/activemq.xml': ensure => present, source => 'puppet:///modules/activemq/activemq.xml', owner => 'activemq', group => 'activemq', mode => '0640', notify => Service['activemq'] } } |
ActiveMQ is installed from the Puppet repo which I already have enabled (from when I installed the Puppet agents on to the nodes). If you don’t already have the repo enabled, you can do so as follows:
|
1 |
# rpm -ivh http://yum.puppetlabs.com/puppetlabs-release-el-6.noarch.rpm |
Once the manifest was complete I just ran puppet agent --test, and ActiveMQ was installed, up and running.
We now need to perform further configuration. On the Puppet master, edit activemq.xml with the credentials we decided upon earlier. You can see I also set an admin password - as well as the password for the mcollective user. Remember to make your passwords more secure than this!:
|
1 2 3 4 5 6 |
<simpleAuthenticationPlugin> <users> <authenticationUser username="mcollective" password="Passw0rd" groups="mcollective,everyone"/> <authenticationUser username="admin" password="S3cr3t" groups="mcollective,admins,everyone"/> </users> </simpleAuthenticationPlugin> |
Edit the transportConnectors stanza so that stomp+nio+ssl is used as follows:
|
1 2 3 4 5 6 7 |
<transportConnectors> <transportConnector name="openwire" uri="tcp://0.0.0.0:61616"/> <!-- <transportConnector name="stomp+nio" uri="stomp+nio://0.0.0.0:61613"/> --> <transportConnector name="stomp+nio+ssl" uri="stomp+nio+ssl://0.0.0.0:61614?needClientAuth=true"/> </transportConnectors> |
Next, we need to transfer the appropriate certificates and key over to centos02 from centos01:
|
1 2 3 |
[root@centos02 activemq]# scp root@centos01:/var/lib/puppet/ssl/certs/ca.pem /root 100% 1931 1.9KB/s 00:00 [root@centos02 activemq]# scp root@centos01:/var/lib/puppet/ssl/certs/activemq.pem /root 100% 1935 1.9KB/s 00:00 [root@centos02 activemq]# scp root@centos01:/var/lib/puppet/ssl/private_keys/activemq.pem /root/activemq.key |
As you can see, we’ve copied the CA cert (ca.pem), the ActiveMQ certificate (activemq.pem) and the ActiveMQ key (which I carefully renamed activemq.key to avoid clobbering the certificate).
First, create a truststore:
|
1 2 3 4 5 6 7 8 |
# keytool -import -alias "Puppet CA" -file /root/ca.pem -keystore /root/truststore.jks Enter keystore password: Re-enter new password: Owner: CN=Puppet CA: centos01 Issuer: CN=Puppet CA: centos01 ... Trust this certificate? [no]: yes Certificate was added to keystore |
Verify that the md5 certificate fingerprints match:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# keytool -list -keystore /root/truststore.jks Enter keystore password: Keystore type: JKS Keystore provider: SUN Your keystore contains 1 entry puppet ca, 25/07/2014, trustedCertEntry, Certificate fingerprint (MD5): B1:C4:CB:28:6E:0E:B5:FA:28:37:85:99:BA:B6:AB:44 # openssl x509 -in /root/ca.pem -fingerprint -md5 -noout MD5 Fingerprint=B1:C4:CB:28:6E:0E:B5:FA:28:37:85:99:BA:B6:AB:44 |
Next, create the keystore, ensuring that the same password is used for all steps:
|
1 2 3 4 5 6 7 8 |
# cat /root/activemq.key /root/activemq.pem > /root/temp.pem # openssl pkcs12 -export -in /root/temp.pem -out /root/activemq.p12 -name activemq Enter Export Password: Verifying - Enter Export Password: # keytool -importkeystore -destkeystore /root/keystore.jks -srckeystore /root/activemq.p12 -srcstoretype PKCS12 -alias activemq Enter destination keystore password: Re-enter new password: Enter source keystore password: |
Again, verify the fingerprints:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# keytool -list -keystore /root/keystore.jks Enter keystore password: Keystore type: JKS Keystore provider: SUN Your keystore contains 1 entry activemq, 25/07/2014, PrivateKeyEntry, Certificate fingerprint (MD5): CD:71:D5:95:7D:49:B0:F8:9B:38:FB:EA:31:E9:6C:4E # openssl x509 -in /root/activemq.pem -fingerprint -md5 -noout MD5 Fingerprint=CD:71:D5:95:7D:49:B0:F8:9B:38:FB:EA:31:E9:6C:4E |
Copy the stores into place:
|
1 2 |
# cp /root/keystore.jks /etc/activemq # cp /root/truststore.jks /etc/activemq |
Next, update activemq.xml once again and add the following inside the broker stanza, substituting your passwords as appropriate:
|
1 2 3 4 5 6 |
<sslContext> <sslContext keyStore="keystore.jks" keyStorePassword="Passw0rd" trustStore="truststore.jks" trustStorePassword="Passw0rd" /> </sslContext> |
Add a firewall rule to allow inbound access on port 61614:
|
1 2 3 4 5 6 |
# vi /etc/sysconfig/iptables ... -A INPUT -m state --state NEW -m tcp -p tcp --dport 61614 -j ACCEPT ... # service iptables restart iptables: Applying firewall rules: [ OK ] |
Finally, do a Puppet run:
|
1 |
# puppet agent --test |
Your ActiveMQ configuration will be updated, and the middleware configuration will be complete.
MCollective Deployment
I used the following manifests to deploy MCollective. /etc/puppet/modules/mcollective/manifests/client.pp:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class mcollective::client { package { 'mcollective-client': ensure => latest } package { 'mcollective-puppet-client': ensure => latest } package { 'mcollective-service-client': ensure => latest } file { '/etc/mcollective/client.cfg': ensure => present, owner => 'root', group => 'root', mode => 0400, source => 'puppet:///modules/mcollective/client.cfg' } } |
You can see that this manifest installs a couple of additional client modules (service and puppet) so that I can query/restart system services remotely, and manage remote puppet agents. It also pulls down client.cfg, which is shown below. Remember - we’re running the client on the Puppet master, so paths can be specified directly to certificates within the Puppet /var/lib/puppet/ssl hierarchy:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# ~/.mcollective # or # /etc/mcollective/client.cfg # ActiveMQ connector settings: connector = activemq direct_addressing = 1 plugin.activemq.pool.size = 1 plugin.activemq.pool.1.host = centos02 plugin.activemq.pool.1.port = 61614 plugin.activemq.pool.1.user = mcollective plugin.activemq.pool.1.password = Passw0rd plugin.activemq.pool.1.ssl = 1 plugin.activemq.pool.1.ssl.ca = /var/lib/puppet/ssl/certs/ca.pem plugin.activemq.pool.1.ssl.cert = /var/lib/puppet/ssl/certs/toki.pem plugin.activemq.pool.1.ssl.key = /var/lib/puppet/ssl/private_keys/toki.pem plugin.activemq.pool.1.ssl.fallback = 0 # SSL security plugin settings: securityprovider = ssl plugin.ssl_server_public = /var/lib/puppet/ssl/certs/mcollective-servers.pem plugin.ssl_client_private = /var/lib/puppet/ssl/private_keys/toki.pem plugin.ssl_client_public = /var/lib/puppet/ssl/certs/toki.pem # Interface settings: default_discovery_method = mc direct_addressing_threshold = 10 ttl = 60 color = 1 rpclimitmethod = first # No additional subcollectives: collectives = mcollective main_collective = mcollective # Platform defaults: # These settings differ based on platform; the default config file created # by the package should include correct values or omit the setting if the # default value is fine. libdir = /usr/libexec/mcollective # Logging: logger_type = console loglevel = warn |
For the servers, I first did the following to get the appropriate certificates (and shared server key) as well as the authorised client public key (in my case, toki.pem), into a deployable structure:
|
1 2 3 4 5 |
# cd /etc/puppet/modules/mcollective/files # cp /var/lib/puppet/ssl/certs/mcollective-servers.pem mcollective-servers.pem # cp /var/lib/puppet/ssl/private_keys/mcollective-servers.pem mcollective-servers.key # mkdir clients # cp /var/lib/puppet/ssl/certs/toki.pem clients |
The following class then deployed the MCollective server components (and populated facts.yaml, as per the deployment guide):
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
class mcollective::server { $activemq_server = "centos02" $activemq_mcollective_password = "Passw0rd" package { 'mcollective': ensure => latest, before => Service['mcollective'] } package { 'mcollective-service-agent': ensure => latest, before => Service['mcollective'] } package { 'mcollective-puppet-agent': ensure => latest, before => Service['mcollective'] } service { 'mcollective': ensure => running, enable => true } file { '/etc/mcollective/server_public.pem': ensure => present, source => 'puppet:///modules/mcollective/mcollective-servers.pem', owner => 'root', group => 'root', mode => '0600', notify => Service['mcollective'] } file { '/etc/mcollective/server_private.pem': ensure => present, source => 'puppet:///modules/mcollective/mcollective-servers.key', owner => 'root', group => 'root', mode => '0600', notify => Service['mcollective'] } file { '/etc/mcollective/clients': ensure => directory, recurse => true, owner => 'root', group => 'root', mode => '0600', source => 'puppet:///modules/mcollective/clients', notify => Service['mcollective'] } file { '/etc/mcollective/facts.yaml': owner => 'root', group => 'root', mode => '0400', loglevel => debug, # reduce noise in Puppet reports content => inline_template("<%= scope.to_hash.reject { |k,v| k.to_s =~ /(uptime_seconds|timestamp|free)/ }.to_yaml %>"), # exclude rapidly changing facts } file { '/etc/mcollective/server.cfg': owner => root, group => root, mode => 0400, content => template('mcollective/server.cfg.erb'), notify => Service["mcollective"] } } |
server.cfg.erb is as follows:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
<% ssldir = '/var/lib/puppet/ssl' %> # /etc/mcollective/server.cfg # ActiveMQ connector settings: connector = activemq direct_addressing = 1 plugin.activemq.pool.size = 1 plugin.activemq.pool.1.host = <%= @activemq_server %> plugin.activemq.pool.1.port = 61614 plugin.activemq.pool.1.user = mcollective plugin.activemq.pool.1.password = <%= @activemq_mcollective_password %> plugin.activemq.pool.1.ssl = 1 plugin.activemq.pool.1.ssl.ca = <%= ssldir %>/certs/ca.pem plugin.activemq.pool.1.ssl.cert = <%= ssldir %>/certs/<%= scope.lookupvar('::clientcert') %>.pem plugin.activemq.pool.1.ssl.key = <%= ssldir %>/private_keys/<%= scope.lookupvar('::clientcert') %>.pem plugin.activemq.pool.1.ssl.fallback = 0 # SSL security plugin settings: securityprovider = ssl plugin.ssl_client_cert_dir = /etc/mcollective/clients plugin.ssl_server_private = /etc/mcollective/server_private.pem plugin.ssl_server_public = /etc/mcollective/server_public.pem # Facts, identity, and classes: identity = <%= scope.lookupvar('::fqdn') %> factsource = yaml plugin.yaml = /etc/mcollective/facts.yaml classesfile = /var/lib/puppet/state/classes.txt # No additional subcollectives: collectives = mcollective main_collective = mcollective # Registration: # We don't configure a listener, and only send these messages to keep the # Stomp connection alive. This will use the default "agentlist" registration # plugin. registerinterval = 600 # Auditing (optional): # If you turn this on, you must arrange to rotate the log file it creates. rpcaudit = 1 rpcauditprovider = logfile plugin.rpcaudit.logfile = /var/log/mcollective-audit.log # Authorization: # If you turn this on now, you won't be able to issue most MCollective # commands, although `mco ping` will work. You should deploy the # ActionPolicy plugin before uncommenting this; see "Deploy Plugins" below. # Logging: logger_type = file loglevel = info logfile = /var/log/mcollective.log keeplogs = 5 max_log_size = 2097152 logfacility = user # Platform defaults: # These settings differ based on platform; the default config file created by # the package should include correct values. If you are managing settings as # resources, you can ignore them, but with a template you'll have to account # for the differences. <% if scope.lookupvar('::osfamily') == 'RedHat' -%> libdir = /usr/libexec/mcollective daemonize = 1 <% elsif scope.lookupvar('::osfamily') == 'Debian' -%> libdir = /usr/share/mcollective/plugins daemonize = 1 <% else -%> # INSERT PLATFORM-APPROPRIATE VALUES FOR LIBDIR AND DAEMONIZE <% end %> |
It is important to note that this configuration allows ALL users to perform ALL commands on ALL nodes, which is fine for my purposes, but may not be what you want. If it isn’t, you’ll need to look at a plugin such as ActionPolicy.
Run a puppet agent --test on all MCollective servers.
Testing
Now that MCollective has been rolled out, it’s time to test. The administrative command-line tool mco is used for command-line interaction with MCollective from the client machine (centos01 in our case). Let’s try a simple ping test:
|
1 2 3 4 5 6 7 |
# mco ping centos04 time=38.28 ms centos02 time=43.75 ms centos03 time=44.57 ms ---- ping statistics ---- 3 replies max: 44.57 min: 38.28 avg: 42.20 |
All three servers are responding. Let’s try obtaining a fact about our machines:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# mco rpc rpcutil get_fact fact=operatingsystem Discovering hosts using the mc method for 2 second(s) .... 3 * [ ============================================================> ] 3 / 3 centos02 Fact: operatingsystem Value: CentOS centos03 Fact: operatingsystem Value: CentOS centos04 Fact: operatingsystem Value: CentOS Summary of Value: CentOS = 3 Finished processing 3 / 3 hosts in 48.06 ms |
Let’s kick off a Puppet run:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# mco puppet runonce -v Discovering hosts using the mc method for 2 second(s) .... 3 * [ ============================================================> ] 3 / 3 centos04 : OK {:summary=> "Started a Puppet run using the 'puppet agent --test --color=false --splay --splaylimit 30' command"} centos02 : OK {:summary=> "Started a Puppet run using the 'puppet agent --test --color=false --splay --splaylimit 30' command"} centos03 : OK {:summary=> "Started a Puppet run using the 'puppet agent --test --color=false --splay --splaylimit 30' command"} ---- rpc stats ---- Nodes: 3 / 3 Pass / Fail: 3 / 0 Start Time: Fri Jul 25 22:21:19 +1000 2014 Discovery Time: 2004.82ms Agent Time: 507.83ms Total Time: 2512.64ms |
All is working as expected.
This has only scratched the surface of the capabilities of MCollective, which is an amazingly rich tool for managing large-scale server deployments.