Using Salt with services managed by Monit

September 29, 2015

I use Salt for configuration management on all my newer servers. I’m particularly fond of its watch requisite, and use it thoroughly to handle restarting all my Salt managed services if need be.

This generally works without much fuss. However, when a service that’s managed by Salt is also being managed by Monit, there’s a chicken and egg problem: when Salt automatically restarts a service using its standard method, Monit thinks the service has died, and problems ensue.

I really wanted my Salt service management goodness and Monit, so after a bit of thinking I came up the the idea of creating a psuedo-service. Salt manages the psuedo-service instead of the regular service, and the psuedo-service simply wraps the Monit commands for starting, stopping, and restarting the service. This allows Salt to trigger service actions via Monit.

This approach solved the issue beautifully without much code or effort. Below I’m including the RHEL/CentOS version of the psuedo-service – other platforms would require some minor adjustments, but the idea is the same.

As seen from the line defining the service name, I manage this script in Salt as a template, so that I can install it for multiple services.

Hope this helps anybody who gets stuck on this same issue!

Vagrant crash course for the busy developer

May 12, 2015

Today a friend remarked on his interest to learn Vagrant, and lack of time to do so. It occurred to me that the script I’ve been building to quickly knock up and down Vagrant VMs could be a handy aid along the shortest path for developers to begin their journey into this amazing toolset.

So without further ado, the shortest path I know of for a busy dev to start playing with Vagrant:

  • Install VirtualBox and Vagrant

    …or for Homebrew Cask users, the even easier brew cask install virtualbox vagrant

  • Run the following from CLI:

    vagrant box add bento/centos-7.1 --provider virtualbox
    vagrant box add bento/debian-8.2 --provider virtualbox

    …or pick some other box you like here.

  • Download the script below, stick it in your PATH, make it executable
  • Execute without args for the well-written help

    …or run -c to spin up your first box…

  • Enjoy :)

I also made the script a bit like a cheat sheet for Vagrant commands – if you search it for ‘vagrant’, you’ll see the first time any command is used, there’s a comment describing what it does. Or, here’s a more neatly formatted cheat sheet.

If you find this helpful getting up to speed, please pass it along to your busy developer friends! If you’d like to suggest improvements to the script, feel free to contact me.

General node init script for RHEL/CentOS 5.x/6.x

March 16, 2015

I wanted to have a general RHEL/CentOS init script that I could drop onto servers to start node processes. The features I wanted were:

  • Ability to reuse it for multiple node processes on the same server.
  • Ability to run as non-root user.
  • The classic service start/stop/restart/status functionality.

I had previously written a node init script using forever, but I ended up not liking it. This script still isn’t perfect, but it seems more generally useful to me.

Drop the script in /etc/init.d/[name] and make it executable, the custom configuration in /etc/sysconfig/[name], and use service [name] start to fire it up.

Installing Jekyll on RHEL/CentOS 5.x and 6.x with modern Ruby and Python versions

January 20, 2015

Jekyll can be run fairly easily on older (5.x and 6.x) versions of RHEL/CentOS, even though the stock versions of the necessary software are too old. The key is to use some simple supporting software to install what you need in a clean way.

The first handy tool is Ruby Version Manager, or RVM. Here’s a handy cheat sheet – use that to install RVM and the latest version of Ruby, set the installed Ruby as the default, and install the Jekyll gem.

Once that’s done, you may run into this error when trying to use Jekyll:

Liquid Exception: Failed to get header

This Jekyll issue indicates that Python greater than 2.6 and less the 3.x needs to be installed in order for syntax highlighting to work. Never fear, pyenv to the rescue!

Use the pyenv installer to get pyenv set up and running on your server, and have a look at the pyenv command reference to install an appropriate version of Python and set it as the default. Note: If you’re running RHEL/CentOS 5.x, you’ll have one more hurdle to cross before using pyenv, see this post for detailed instructions.

If you’d also like to set up your Jekyll site to update from a git push, I’ve detailed one workable approach in this post – if you do use it, just make sure you put the necessary RVM/pyenv environment set up stuff in the .bashrc file of the user the git push command runs as on the server.

Happy Jekyll’ing!

Installing a working pyenv on RHEL/CentOS 5.x

January 19, 2015

RHEL/CentOS 5.x has long since lost its freshness, but some of us are still running servers with it, and can do so for several more years before its end of life.

Perhaps, like me, you have a need to run a more modern version of Python than 5.x installs by default. I recently found pyenv, and it looked to fit my needs perfectly, as I didn’t want to mess with the system version or build custom RPM’s.

Once I installed the build requirements, I used the handy pyenv-installer project to get it up and running, then ran the simple command to install Python 2.7.8. Unfortunately, things hit a bump with this error:

Error message:
subprocess.CalledProcessError: Command '['wget', '', '--quiet', '--output-document', '/tmp/python-build.20141210170309.3741/Python-2.7.8/']' returned non-zero exit status 1

After a bit of digging, I found that this was actually due to a bug in version 1.11 of wget. As far as I could tell, this issue was not fixed upstream until the 1.12 release, and CentOS 5.x is frozen at 1.11.

I decided it was worth building a custom wget RPM package for my 5.x servers to get past this issue. After setting up my RPM build environment, I headed over to to locate a suitable source RPM. wget-1.12-4.fc14.src.rpm ended up suiting my needs – the newer versions of wget RPMs had some build dependencies that were a bit awkward to fulfill, and 1.12 would solve my problem, so…

Per, the –nomd5 switch is needed when installing newer fedora source RPMs:

rpm --nomd5 -ivv wget-1.12-4.fc14.src.rpm

From there it was simply a matter of building the RPM:

cd /usr/src/redhat/SPECS
rpmbuild -bb wget.spec

Then installing it, by finding the wget-1.12-4 RPM file in one of the subdirectories of /usr/src/redhat/RPMS, changing to that directory, and running:

yum --nogpgcheck localinstall wget-1.14-3.x86_64.rpm

After this, pyenv install [version] should work as advertized…Python for the modern age!

Automating sysctl deployments using sysctl.d on RHEL/CentOS 5.x, 6.x, 7.x

January 8, 2015

I’ve fallen in love with automated server deployments in the last year, with my primary weapon being Salt.

One of the corner cases I’ve run into is adding sysctl settings specific to a feature set. For example, when a server needs Redis installed, I want to add the following kernel optimization via sysctl:

    vm.overcommit_memory = 1

It’s sloppy to add this to /etc/sysctl.conf – too hard to maintain in a modular fashion. Wouldn’t it be nice if there was a place we could drop a file with that sysctl setting in it, which would be automatically read on boot? This would enable adding and removing multiple sysctl settings a breeze to automate.

Well, it turns out that RHEL/CentOS does have this support via /etc/sysctl.d. While only RHEL/CentOS 7.x sports the directory out of the box, all three versions provide access to it via init scripts, and anything placed in /etc/sysctl.d will be read on boot, provided that the networking init script’s start action is called (it’s enabled by default).

Unfortunately, this is a bit of an odd placement for triggering a reload of the sysctl settings. I also wanted the ability to only reload the sysctl settings as part of a feature installation on a running server.

The path to get this feature turned out to be pretty short. /etc/init.d/functions contains an apply_sysctl function which handles all the dirty work of completely reloading all sysctl settings, including those placed in /etc/sysctl.d. This extremely short wrapper script does the job:

Armed with that script, I simply use Salt to automatically install it to /usr/local/bin on all servers, and call it any time a file in /etc/sysctl.d is added, removed, or modified.

Nginx logs in JSON format

July 14, 2012

I’ve recently decided that it’s a good idea to output server logs in JSON format. To this end, today I took some time to figure out how to do this for Nginx. The log_format parameter is the one you want to use, I simply added another named format to the http section of nginx.conf, which then allows the named format to be used in any other config file. Here’s what I whipped up – this is just the default main format ported to JSON:

log_format  json  '{'
                    '"remote_addr": "$remote_addr",'
                    '"remote_user": "$remote_user",'
                    '"time_local": "$time_local",'
                    '"request": "$request",'
                    '"status": $status,'
                    '"body_bytes_sent": $body_bytes_sent,'
                    '"http_referer": "$http_referer",'
                    '"http_user_agent": "$http_user_agent",'
                    '"http_x_forwarded_for": "$http_x_forwarded_for"'

I formatted it one row per parameter in the config file, as it’s easier for me to read, but Nginx will concatenate all those separate strings into one line in the log file. Once this is done, use the format in any of places where it’s accepted, for example:

access_log /path/to/file/access.log json;

For more details on all this stuff, check out the online documentation for Nginx’s logging module.

Resend Postfix messages stuck in mail queue to another address

July 6, 2012

Update 2015-02-19: Based on suggestions from David Keegel, I’ve tweaked the script to properly hold/unhold messages, and include the sender’s email address in the message envelope.

Ever had somebody give you a bad email address, and the messages pile up in your Postfix mail queue? Even if you have the right address to send the stuck emails to, Postfix (at least on CentOS 5.x based on all my research) provides no easy way to:

  • Resend all those messages to the new address
  • Remove the old stuck messages

We needed this functionality badly for our business, where we unfortunately get bad email addresses given to us all the time. So, using my marginal bash skills and several hours of my time, I whipped up the script below to automate this process.

Caveat: I’ve tested this script extensively with Postfix on CentOS 5.x, but nowhere else, really. So test first, use at your own risk!

db-query-assistant package released now available via npm

April 28, 2012

Today I’m happy to announce the official public release of db-query-assistant.

For those who haven’t read my initial post on the module, here is a quick summary:

  • High-level library for node database drivers.
  • Configurable connection pooling.
  • Issue multiple simultaneous queries, and get all results back in a callback when the last query completes.
  • Issue queries in series, getting the results for each previous query back before executing the next one.
  • Issue transactional queries, with automatic rollback on query failure.

This release includes a fairly comprehensive unit test suite for the core library, using the awesome mocha test framework. I decided against unit tests for the drivers, as I feel those would be better served by integration tests (which I may add in the future).

The module has been published to the npm registry, and a simple npm install db-query-assistant will now do the trick.

Sampler API 6.x 1.1 released

March 25, 2012

The 6.x-1.1 release of Sampler API is now out and ready for download. This has a number of important bugfixes, as well as the addition of locking support so the same metric cannot be run again while already running.

Most of the fixes came out of the work I’m doing to get metrics collection fully deployed on

Older posts