ZFS Fun

Our primary backup box is a Nexenta Core Platform 3 box from Aberdeen, Inc. It pulls data via rsync from various clients and uses ZFS snapshots to do incrementals. Tivoli storage manager then pulls daily incrementals off of it, putting the data on tape. Well, we expanded the unit, adding 12 x 2TB Hitachi Deskstar drives. Yes, the Hitachi’s are consumer-level drives, but in our experience over the last two years, they are a fantastic bargain with good performance and failure rates very close to much more costly enterprise drives that we also use. Their return policy and support is also fantastic.

Anyway, after adding these drives to the system on our Areca 16xx controller, the SCSI IDs picked up by Nexenta (OpenSolaris) for the root rpool (syspool) changed, causing the system to panic at boot. My main expertise is in Linux, so the nuances of the boot sequence and its dependencies in Solaris are still a bit lost on me. Fortunately, after some digging, I found a mailing list post that saved my bacon… big time:

good point about the miniroot.

One more possible solution (component) would be this:

1) Set your root disk to AHCI or whatever you need
2) Boot from livecd
3) Import your rpool and mount the root filesystem into /a for example
4) Update device links:
# devfsadm -r /a -Cv
5) Update the miniroot
# bootadm update-archive -R /a
6) Export the rpool
7) Reboot and hope this helps ;)
//Jim

http://www.mail-archive.com/zfs-discuss@opensolaris.org/msg46515.html

Thanks a ton, Jim Klimov!

Keeping CMSes Up-to-date with Zabbix and Redmine

We currently run sites which utilize three popular CMS softwares: WordPress, Drupal, and Joomla. Keeping these sites up-to-date is critical to maintaining the security of our services and so we needed an automated way to keep us notified of version changes. Enter Zabbix & Redmine.

We use Zabbix for our infrastructure monitoring and notification, alerting us when servers go down, performance values get out of whack, sites return unexpected results, etc. Zabbix has the ability to use external scripts to get item values, so the idea is to use this external check script to compare the installed version of the web application with the latest version published on the CMSes project page (taking into account major and minor releases). A few things need to be done to pull this off

1. Create a script that will take as arguments a) the hostname b) the CMS type, and then do the following:

a. check the current installed version

b. check the most current minor release of that major release series from the CMS’ project web pagreturn a value of Up-to-date if everything looks good or something else if the values don’t match

2. Create a Zabbix template that includes

a. items for each CMS, utilizing the above script

b. triggers for each item that set an alert status (informational is fine) if the return value of the script is out-of-bounds

c. an action which calls a script that utilizes the Redmine REST API to insert a ticket into the “Web Services” project

The script for doing the version checking is really quite simple, as shown below:

#!/bin/bash
#
# Tool to check for
#
# a. latest version of specified software
# b. currently installed version
#

urlPath="http://$1"
appName=$2

# functions
function _latestVersion(){
    case "$appName" in
        wordpress)  wget -O - -q http://wordpress.org/download/ |
                         egrep -o 'Download.*WordPress.*([0-9]+\.)+[0-9]+' |
                         cut -d';' -f3 ;;
        joomla15)   wget -O - -q http://developer.joomla.org/development-status.html |
                         egrep -o 'Long Term Support Release.*Joomla!.*([0-9]+\.)+[0-9]+' |
                         awk '{ print $NF }'  ;;
        drupal)     wget -O - -q http://drupal.org/download |
                         egrep -o 'Download Drupal [0-9]+\.[0-9]+' |
                         awk '{ print $NF }';;
    esac
}

function _installedVersion(){
    case "$appName" in
        wordpress)  wget -O - -q ${urlPath}/readme.html |
                         egrep -o '<br /> Version ([0-9]+\.)+[0-9]' |
                         awk '{ print $NF }' ;;
        joomla15)   wget -O - -q ${urlPath}/9bcf1daadeadbeef.php |
                          base64 -d ;;
        drupal)     wget -O - -q ${urlPath}/CHANGELOG.txt |
                          egrep -o 'Drupal [0-9]+\.[0-9]+' |
                          awk '{ print $NF }' | head -n 1;;
    esac
}

installedVersion=$(_installedVersion)
latestVersion=$(_latestVersion)

if [ -z "$latestVersion" -o -z "$installedVersion" ]; then
    exit 1
fi

if [ "$installedVersion" != "$latestVersion" ]; then
    echo "installed: $installedVersion latest: $latestVersion"
    exit 0
else
    echo "Up-to-date"
    exit 0
fi

I dropped this in my /etc/zabbix/externalscripts folder and named it vcheck. For your action, you’ll configure it to go off when your triggers go off, using the appropriate actions. You will then call this script:

#!/bin/bash
#
# Tool to insert tickets into redmine.  Arguments are as follows:
#
# vcheckticketinsert <url> <drupal|joomla15|wordpress> <installed_version>
# <current_version> <redmine_url> <redmine_project> <redmine_rest_key>
#

urlPath="http://$1"
appName=$2
installedVersion=$3
currentVersion=$4
redmineUrl=$5
defProject=$6
restAPIkey=$7

function _insertTicket(){
    oldVer=$1
    newVer=$2
    url=$3
    md5=$(echo "$oldVer $newVer $url" | md5sum | awk '{ print $1 }')

    # Check if a ticket exists...
    wget --no-check-certificate -O - -q --header="X-Redmine-API-Key:$restAPIkey" \
         ${redmineUrl}/issues.xml?project_id=$defProject  | egrep -q "${md5}"
    [ $? -eq 0 ] && return 0

    case "$appName" in
        wordpress) ticketSubject="Update ${url} WordPress ${oldVer} to ${newVer}"
                   ticketText="Wordpress update required for ${url} during next \
                               maintenance window."
        ;;
        joomla15) ticketSubject="Update ${url} Joomla! ${oldVer} to ${newVer}"
                   ticketText="Joomla! update required for ${url} during next \
                               maintenance window."
        ;;
        drupal) ticketSubject="Update ${url} Drupal ${oldVer} to ${newVer}"
                   ticketText="Drupal update required for ${url} during next \
                               maintenance window."
        ;;
    esac

    # insert the ticket...
    cat > /tmp/.vcheck_ticket_insert.$$ <<EOF
<?xml version="1.0"?>
<issue>
<subject>$ticketSubject</subject>
<project_id>$defProject</project_id>
<description>$ticketText  ${md5} --</description>
</issue>
EOF
    curl -s --insecure -H "X-Redmine-API-Key:$restAPIkey" -H \
         "Content-Type: application/xml" -X POST --data \
         "@/tmp/.vcheck_ticket_insert.$$" ${redmineUrl}/issues.xml &> /dev/null
    rm -f /tmp/.vcheck_ticket_insert.$$
}

_insertTicket $installedVersion $latestVersion $urlPath

Now, whenever a web app gets out-of-date due to a minor version increase, you’ll get a ticket in Redmine to have that update completed, keeping you completely on top of your admin responsibilities.

Automation rocks.

pkgsync: Utility to keep packages in perfect sync between a number of hosts

I put up a git repository of the latest pkgsync code that I’ve been using to manage package changes on my hosts.  pkgsync uses yum-utils (specifically, yum-debug-dump and yum-debug-restore) to maintain synchronization of packages across multiple hosts.  I’m using this in two scenarios:

  1. Development/Production lifecycle.  For a new product, I will provision both a development and production environment.  During development, I will use pkgsync to track the changes of local packages on development so that when we go live with changes to production, a simple ‘pkgsync sync’ will put production in perfect alignment with development.
  2. HPC/multi-server environments.  Since HPC clusters tend to run better when the individual compute nodes are configured identically, maintaining package consistency is key.  You certainly don’t want jobs dying because one or two nodes is missing a compat-libstdc++ or some other library.  Also, server farms that utilize Apache behind a load balancer are a good fit.  You wouldn’t want to use a web application that requires something like php-mbstring and get inconsistent behavior because one of the servers in your cluster has a different version or has the package missing altogether.

We utilize mysql to contain a base64 blob of the yum-debug-dump command’s output in gzip format.  This output, when passed to yum-debug-restore, is used to generate a sequence of install/remove/update/upgrade commands to put the system in the same state as it was at the time of the dump.  The good thing is that you can grab the state of node A and restore it on node B, making the package list the same on both (with some small, edge-case, caveats).

By utilizing pdsh and genders, we’re able to

  • distribute our commands to a package group or pkg_grp (a grouping of servers)
  • verify package list consistency among the members of our pkg_grp
  • use ssh to pull it all off

pkgsync works with yum/rpm-based distributions.  It’s been tested against rhel-based distros but should work on fedora just fine.

The github page for the project is here if you’re interested: https://github.com/brichsmith/pkgsync

PathScale compiler Open-Sourced?

Whoa… never thought I’d see a commercial compiler vendor give away their product — in any meaningful way — let alone open-source it. That’s pretty awesome. I first caught wind of this from the guys at Phoronix, doing a horrible job at cloak-n-dagger referring to some project called “Drindl”. I had assumed there was some re-build of apps or of the Linux kernel by a commercial compiler and lo and behold, someone in the forums guessed it. I’ve yet to see anything else of this project “Drindl”, but I did find the github page of the PathScale EckoPath compiler: https://github.com/path64/compiler

This is going to be an incredible tool to use for building, packaging and distributing scientific applications through our planned central repository.  Of course, gcc 4.6 might by a formidable opponent, in some cases.

Blocking Ad Servers using the Actiontec MI424-WR

By enabling local telnet administration in the administration menu and using the built-in DNS server on the Actiontec MI424-WR, we can easily block Ads by masking their DNS entries at our local DNS server and pointing them all to the trusty 127.0.0.1 loopback interface. Sure, this is a lot like adding a bunch of entries to your hostfile, but this is much better.

Why?

1. Everyone on your network benefits automatically, as long as they use the router as their DNS server

2. At least on Linux, even on a fancy new quad-core workstation, having thousands of entries in your /etc/hosts file slows down host resolution noticeably. I don’t think the hosts file was designed with that many entries in mind.

So, what do we do?  First, we want to convert the ad server lists provided by http://pgl.yoyo.org/as/ into a set of commands to send the router in order to populate its DNS database.  But further than that, we also want to keep current on our list of ad servers and periodically check in, say once per day, to ensure that we have the best possible browsing experience.  So, write a script…

Or use the one provided below.  Enjoy, all you FIOS users! Pop it in a cron job and replace the PASSWORD variable with the administrative password for your MI424-WR and kiss those pesky ads goodbye.

#!/bin/bash

URL="http://pgl.yoyo.org/as/serverlist.php?showintro=0;hostformat=hosts"
USERNAME=admin
PASSWORD=XXXXXXXXX
ROUTER=192.168.1.1
SESSION="nc -t $ROUTER 23"

function session_login(){
	sleep 3
	echo $USERNAME
	sleep 1
	echo $PASSWORD
}

function session_exit(){
	sleep 1
	echo exit
}

function session_populate(){
	session_login
	wget -O - -q "$URL" | awk 'BEGIN { i=500; } /^127\..*/ { printf "dns_set %d %s %s\n\r", i, $NF, $1; i=i+1; }'
	session_exit
}

function session_current_ids(){
	session_login
	echo dns_get
	session_exit
}

function session_purge_ids(){
	session_login
	for id in $@; do
		echo dns_del $id
	done
	session_exit
}

function main(){
	printf "Latest Ad Server list contains %d entries...\n" $(wget -O - -q "$URL" | egrep '^127\..*' | wc -l)
	ids=( $(session_current_ids | $SESSION | awk '/^[0-9]+/ { if ($1 >= 500) { print $1; } }' ) )
	printf "Current Ad Server list contains %d entries...\n" $(echo ${ids[@]} | wc -w)

	echo -ne "Purging Current Ad Server list... "
	session_purge_ids ${ids[@]} | $SESSION &> /dev/null
	echo "done."

	echo -ne "Populating Ad Server list with fresh entries... "
	session_populate | $SESSION &> /dev/null
	echo "done."

	return 0
}

main $@