<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;DkECR38_fip7ImA9WhRUEkg.&quot;"><id>tag:blogger.com,1999:blog-3615332969083650973</id><updated>2012-01-22T09:51:06.146-08:00</updated><category term="logging" /><category term="background tasks" /><category term="amqp" /><category term="mediawiki" /><category term="power management" /><category term="tools" /><category term="logs" /><category term="reliable infrastructure" /><category term="development" /><category term="dotfiles" /><category term="events" /><category term="analytics" /><category term="lvm" /><category term="api" /><category term="service" /><category term="time management" /><category term="antipattern" /><category term="relax" /><category term="asset management" /><category term="cfengine" /><category term="truth" /><category term="daemontools" /><category term="backhoe vs cake and pie" /><category term="syscall" /><category term="configuration" /><category term="powershell" /><category term="git" /><category term="resources" /><category term="what's in a name" /><category term="upstart" /><category term="strace" /><category term="openvpn" /><category term="nosql" /><category term="freebsd" /><category term="cacti" /><category term="bofh" /><category term="ipmi" /><category term="extending" /><category term="scripting" /><category term="sanity" /><category term="packages" /><category term="visualization" /><category term="host" /><category term="babysitting" /><category term="home directory" /><category term="intro" /><category term="snmpd" /><category term="synchronization" /><category term="best practices" /><category term="cucumber" /><category term="ticketing" /><category term="wireshark" /><category term="trending" /><category term="jails" /><category term="puppet" /><category term="extlookup" /><category term="remote control" /><category term="build" /><category term="dns" /><category term="practices" /><category term="statistical probabilities" /><category term="network" /><category term="ubuntu" /><category term="power user" /><category term="subversion" /><category term="ruby" /><category term="user support" /><category term="moving" /><category term="stomp" /><category term="tcpdump" /><category term="introduction" /><category term="activeldap" /><category term="tmux" /><category term="availability" /><category term="customers" /><category term="monit" /><category term="clocks" /><category term="github" /><category term="imaging" /><category term="tracing" /><category term="mon" /><category term="tasks" /><category term="disk cloning" /><category term="CAKE AND PIE" /><category term="deployments" /><category term="bell labs" /><category term="systems" /><category term="best practice" /><category term="planning" /><category term="raptors" /><category term="gist" /><category term="maintenance" /><category term="team work" /><category term="code" /><category term="productivity" /><category term="devops" /><category term="metamon" /><category term="naming" /><category term="chef" /><category term="hardware" /><category term="helpers" /><category term="managing expectations" /><category term="metamonitoring" /><category term="screen" /><category term="knowledge" /><category term="smoke testing" /><category term="init" /><category term="deployment" /><category term="ssh" /><category term="erb" /><category term="hudson" /><category term="littlechef" /><category term="rcfiles" /><category term="net-snmp" /><category term="systemtap" /><category term="networks" /><category term="interaction" /><category term="human keyboard" /><category term="message broker" /><category term="sysv" /><category term="daemon monitoring" /><category term="perl subprocess output bash shell errors" /><category term="kernel" /><category term="compliance" /><category term="checklist" /><category term="server" /><category term="vpn" /><category term="try-not-to-get-fired" /><category term="standards" /><category term="career" /><category term="java deployment maven rpm fpm service management" /><category term="project management" /><category term="writing" /><category term="health" /><category term="management" /><category term="replication" /><category term="disk management" /><category term="package management" /><category term="active directory" /><category term="continuous integration" /><category term="for loop" /><category term="documentation" /><category term="web" /><category term="linux lxc containers resources virtualization virtualisation" /><category term="restart" /><category term="rsync" /><category term="C" /><category term="visibility" /><category term="storage" /><category term="pal" /><category term="supervisord" /><category term="troubleshooting" /><category term="tls" /><category term="tips" /><category term="ata over ethernet" /><category term="performance" /><category term="unix 4.0" /><category term="cron" /><category term="1980" /><category term="raid" /><category term="sniffing" /><category term="dtrace" /><category term="overview" /><category term="xml" /><category term="business" /><category term="certificates" /><category term="murphy's law" /><category term="scalability" /><category term="terminals" /><category term="security" /><category term="rc" /><category term="openssl" /><category term="labels" /><category term="style" /><category term="yaml" /><category term="persistence" /><category term="remote management" /><category term="ssl" /><category term="snmp" /><category term="testing" /><category term="redundancy" /><category term="requirements" /><category term="automation" /><category term="jms" /><category term="generation" /><category term="babysit" /><category term="locking" /><category term="xymon" /><category term="users" /><category term="SOX" /><category term="design patterns" /><category term="wiki" /><category term="revision control" /><category term="workflow" /><category term="debugging" /><category term="pastebin" /><category term="organization" /><category term="cloning" /><category term="change" /><category term="ISO20K" /><category term="config management" /><category term="http" /><category term="ldap" /><category term="rrdtool" /><category term="agile" /><category term="perfmon" /><category term="ganglia" /><category term="metrics" /><category term="operability" /><category term="truss" /><category term="graphing" /><category term="kanban" /><category term="parallel" /><category term="udpcast" /><category term="windows" /><category term="code review" /><category term="san" /><category term="system call" /><category term="linux" /><category term="system v" /><category term="sharing" /><category term="change management" /><category term="emacs" /><category term="inventory management" /><category term="stress" /><category term="tool" /><category term="logger" /><category term="process" /><category term="syslog" /><category term="nagios" /><category term="logical volume manager" /><category term="origin" /><category term="monitoring" /><category term="communication" /><category term="drbd" /><category term="config" /><category term="aoe" /><category term="time" /><category term="ad" /><category term="containers" /><category term="supervisor" /><category term="web interface" /><category term="tripwire" /><category term="collectd" /><category term="history" /><category term="aggregation" /><category term="formats" /><category term="capistrano" /><category term="data" /><title>sysadvent</title><subtitle type="html">Systems Administration Advent Calendar</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://sysadvent.blogspot.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://sysadvent.blogspot.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>Jordan Sissel</name><uri>http://www.blogger.com/profile/13694925032675599790</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="30" height="32" src="http://3.bp.blogspot.com/_u-5lMShiO40/STe9yI8pJoI/AAAAAAAAAAM/EazaL2lqoMI/s1600-R/jordan.png" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>101</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/sysadvent" /><feedburner:info uri="sysadvent" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry gd:etag="W/&quot;A08MSHg5cCp7ImA9WhRXGU4.&quot;"><id>tag:blogger.com,1999:blog-3615332969083650973.post-3287644784313320591</id><published>2011-12-26T14:35:00.000-08:00</published><updated>2011-12-26T14:51:29.628-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-26T14:51:29.628-08:00</app:edited><title>Day 25 - Learning from Other Industries</title><content type="html">&lt;p&gt;This was written by &lt;a href="http://twitter.com/jordansissel"&gt;Jordan Sissel&lt;/a&gt; (&lt;a href="http://semicomplete.com"&gt;semicomplete.com&lt;/a&gt;).&lt;/p&gt;&lt;p&gt;This post is late because, two days ago (December 24th), I went to the emergency room with appendicitis. Pretty sweet timing, eh?&lt;/p&gt;&lt;p&gt;After surgery, I was bored and took to observing nursing shift rotations - hand-offs of patients, documentation maintenance, etc. The entire process semeed to last about an hour for each shift change. I thought to myself, &amp;quot;Self, you have never been involved in any operations project, task, or fire hand-off that went as smoothly.&amp;quot; Pretty sad realization.&lt;/p&gt;&lt;p&gt;Further, throughout my hospital stay (emergency room, surgery, and recovery), not once did I observe any two individuals debating passionately about how best to treat my illness. Data was available at every decision point. There were specializations in surgeons for the surgical tasks; nurses to help stabilize, comfort, and feed me (no small task); and administrative staff to handle the beaurocracy stuff.&lt;/p&gt;&lt;p&gt;I'm not expecting systems administration to follow exactly what the medical industry does, just pointing out that it's nice to see a well-orchestrated system in action.&lt;/p&gt;&lt;p&gt;And the best part? I never once heard anyone say, &amp;quot;hey! We should try this new cool thing I heard about.&amp;quot;&lt;/p&gt;&lt;p&gt;Further, I never once heard anyone suggesting that we remove my appendix using node.js and mongodb.&lt;/p&gt;&lt;p&gt;Happy New Year, everyone :)&lt;/p&gt;&lt;h2 id="further-reading"&gt;Further Reading&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.kitchensoap.com/2011/07/18/systems-engineering-great-definition/"&gt;Learning from NASA Engineering&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://cuddletech.com/blog/?p=642"&gt;Nothing New Under the Sun&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://sysadvent.blogspot.com/2009/12/day-13-redundancy.html"&gt;Redundancy&lt;/a&gt; - Learning from the Airline Industry&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3615332969083650973-3287644784313320591?l=sysadvent.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/sysadvent/~4/y5HhbZYNxrk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sysadvent.blogspot.com/feeds/3287644784313320591/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=3615332969083650973&amp;postID=3287644784313320591" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/3287644784313320591?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/3287644784313320591?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/sysadvent/~3/y5HhbZYNxrk/day-25-learning-from-other-industries.html" title="Day 25 - Learning from Other Industries" /><author><name>Jordan Sissel</name><uri>http://www.blogger.com/profile/04677274979347685821</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>2</thr:total><feedburner:origLink>http://sysadvent.blogspot.com/2011/12/day-25-learning-from-other-industries.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEYEQX87cSp7ImA9WhRXF0w.&quot;"><id>tag:blogger.com,1999:blog-3615332969083650973.post-2688853936830780357</id><published>2011-12-24T00:10:00.001-08:00</published><updated>2011-12-24T00:41:40.109-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-24T00:41:40.109-08:00</app:edited><title>Day 24 - Implementing Configuration Management in Legacy Environments</title><content type="html">&lt;p&gt;This was written by &lt;a href="https://twitter.com/unixdaemon"&gt;Dean Wilson&lt;/a&gt; (&lt;a href="http://www.unixdaemon.net"&gt;www.unixdaemon.net&lt;/a&gt;).&lt;/p&gt;&lt;p&gt;Implementing configuration management is perfect for green field projects where you can have freedom to choose technical solutions free from most existing technical debt. It's a time where there are no old inconsistencies and you have a window to build, deploy and test all the manifests and recipes without risking user visible outages or interruptions.&lt;/p&gt;&lt;p&gt;Unfortunately, most of us don't have the luxury of starting from scratch in a pristine environment. We have to deal with the oddities, the battle scars, and the need to maintain a good quality of service while evolving and maintaining the platform.&lt;/p&gt;&lt;p&gt;In this article, I'll discuss some of the key points to consider as you begin the journey implementing a configuration management tool in an existing environment. I happen to use puppet and mcollective, but any config management tools will do - find one that works for you.&lt;/p&gt;&lt;h2 id="discovery"&gt;Discovery&lt;/h2&gt;&lt;p&gt;You'll need to start with some discovery (manual, automated, whatever) to learn how your current systems are configured. How many differences are there across the sshd_config files? Where do we use that backup users key? What's the log retention setting of the apache servers? Is it identical between those we upgraded from apache 1 and the fresh apache 2?&lt;/p&gt;&lt;p&gt;When I first started to bring puppet in to my systems, I wrote custom scripts, ssh loops, and inventory-reporting cronjobs to find the information I needed. Now, there is an excellent solution to gather information you need in real time: &lt;a href="http://puppetlabs.com/mcollective/introduction/"&gt;MCollective&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;With the appropriate mcollective agents you can compare, peer, and probe in to nearly every aspect of your infrastructure. The &lt;a href="http://www.unixdaemon.net/tools/puppet/mcollective-filemd5er.html"&gt;FileMD5er&lt;/a&gt; agent, for example, will show you groups of hosts that identical config files. Using this, you can partition the environment and the work, section by section. This will help you find smaller amounts of work to build into puppet.&lt;/p&gt;&lt;p&gt;One of my favourite current tricks is to query config file settings using &lt;a href="http://augeas.net/"&gt;Augeas&lt;/a&gt; in the &lt;a href="https://github.com/deanwilson/unixdaemon-mcollective-plugins/tree/master/augeasquery"&gt;AugeasQuery&lt;/a&gt; agent.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# Does your ssh permit root logins?
$ mco rpc augeasquery query query='/files/etc/ssh/sshd_config/PermitRootLogin' --with-agent augeasquery -v
...

test.example.com : OK
    {:matched=&amp;gt;[&amp;quot;no&amp;quot;]}

test2.example.com: OK
    {:matched=&amp;gt;[&amp;quot;yes&amp;quot;]}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;No more crazy regexes to find config settings!&lt;/p&gt;&lt;p&gt;You can learn more about how the &lt;a href="http://devstructure.com/"&gt;Blueprint&lt;/a&gt; tool can help you with this configuration discovery process in &lt;a href="http://sysadvent.blogspot.com/2011/12/day-12-reverse-engineer-servers-with.html"&gt;&amp;quot;Reverse-engineer Servers with Blueprint&amp;quot;&lt;/a&gt; (SysAdvent 2011, Day 12).&lt;/p&gt;&lt;h2 id="short-cycles"&gt;Short Cycles&lt;/h2&gt;&lt;p&gt;One of the key factors to consider when starting large scale infrastructure refactoring projects is that you'll need information and you'll need it quickly.&lt;/p&gt;&lt;p&gt;You should always be the first to know about things you've broken. Your monitoring system, centralised logging, trend graphing, and config management systems reporting server (like &lt;a href="http://puppetlabs.com/puppet/related-projects/dashboard/"&gt;Puppet Dashboard&lt;/a&gt; and &lt;a href="http://theforeman.org/"&gt;Foreman&lt;/a&gt;) will become your watchful allies.&lt;/p&gt;&lt;p&gt;The speed at which you can gather information, write the manifest, deploy, and run your tests is more important than you'd think. Keeping this cycle short will encourage you to work in smaller sections, reducing the time between you actually testing your work and keeping the possible problem scope small. Tools like MCollective, mc-nrpe and nrpe runner will enable you to rapidly verify your changes.&lt;/p&gt;&lt;h2 id="version-control"&gt;Version Control&lt;/h2&gt;&lt;p&gt;The short version is: version control everything.&lt;/p&gt;&lt;p&gt;There's no reason, whether you are in a team or working alone (especially when working alone!), to not keep your work in a version control system (such as &lt;a href="http://subversion.apache.org/"&gt;subversion&lt;/a&gt; or &lt;a href="http://git-scm.com/"&gt;git&lt;/a&gt;). &amp;quot;That change didn't work, so I've rolled it back&amp;quot;. Really? By hand? Are you sure you didn't forget to remove one of the additions? Why would you shoulder the mental burden of knowing all the things you changed recently when there are so many excellent tools that will do it for you and allow easier, auditable rollbacks to old revisions?&lt;/p&gt;&lt;p&gt;Beyond the benefits of providing an ever-present safety net, using a VCS provides a nice way to tie your exact change into incident, audit, or change control systems - a perfect place to do smaller code reviews and a basic, but permanent, collection of institutional knowledge. Being able to search through the puppet logs / reports when debugging an issue to find exactly which resources changed and when, pull up the related changeset and the actual reason for it (and who you should ask for help) changes your process and reports from best guesses based on peoples memories to quick fixes backed by supporting evidence.&lt;/p&gt;&lt;p&gt;On a positive note, it's amazing how much you can learn from reading through commits from things like DBAs doing performance tuning. Seeing which settings were changed and why can save you and your team from forgetting something or making the same mistake or redoing parts of the work again in the future.&lt;/p&gt;&lt;h2 id="use-packages"&gt;Use Packages&lt;/h2&gt;&lt;p&gt;Many older environments have fallen in to the habit of pushing tarred applications around (and sometimes compiling software on the hosts as needed). This isn't the best way to deploy in general, and it will make your manifests ugly, complicated, sprawling messes. In most cases, it's not that hard to produce native packages, and tools like &lt;a href="https://github.com/jordansissel/fpm"&gt;fpm&lt;/a&gt; make the process even easier.&lt;/p&gt;&lt;p&gt;By actually packaging software and running local repositories you can use native tools, such as yum and apt, in a more powerful way. Processes become shorter and have simpler moving parts, more meta information becomes available, and it's possible to trace the origin of files. You can then use other tools, such as puppet and MCollective to ease upgrades, additions, and reporting.&lt;/p&gt;&lt;p&gt;You can learn more about the world of software packages from &lt;a href="http://sysadvent.blogspot.com/2011/12/day-4-guide-to-packaging-systems.html"&gt;&amp;quot;A Guide to Package Systems&amp;quot;&lt;/a&gt; (SysAdvent 2011, Day 4).&lt;/p&gt;&lt;p&gt;As an aside, there are differing opinions on whether packages should handle related tasks, such as deploying users. However, once you've reached the point where you can have this argument, you've surpassed most of your peers and will have an environment that gives you the freedom to do whichever you choose.&lt;/p&gt;&lt;p&gt;You can learn more about overlaps in functionality of packaging and config management systems in &lt;a href="http://sysadvent.blogspot.com/2010/12/day-23-package-vs-config-management.html?utm_source=twitterfeed&amp;amp;utm_medium=twitter"&gt;&amp;quot;Packaging vs Config Management&amp;quot;&lt;/a&gt; (SysAdvent 2010, Day 23).&lt;/p&gt;&lt;h2 id="baked-in-goodness"&gt;Baked-in Goodness&lt;/h2&gt;&lt;p&gt;As you bring more of your system under puppet control, you'll begin to experience the continuously improving system baseline. Every improvement you make is felt throughout the infrastructure, and regressions become infrequent, easy to spot, and quick to remedy. For example, you should never wonder if you've deployed nginx checks to a new server, it should be impossible to have a mysql server without your graphing applied, and every wiki you deploy should be added to the backup system without you intervening.&lt;/p&gt;&lt;p&gt;By having infrastructure manifests such as a monitoring module and writing some generic &amp;quot;add monitoring check&amp;quot; definitions, you can sprinkle reuse throughout other, more functionality-focused modules. In my test manifests, for example, nearly every class has nagios checks associated with it, and every service declares the ports it listens to. This might not sound like much, but it's impossible for me to deploy a server without monitoring, every time I add a new nagios check, all the hosts with that role receive it. Further, generating security policies, firewall rule sets, and audit documents is done automatically for me and requires no manual data gathering. Being able to supply people with links to live documentation is a wonderful way to remove awkward manual steps from reports, audits and inventories.&lt;/p&gt;&lt;h2 id="involve-everyone"&gt;Involve Everyone&lt;/h2&gt;&lt;p&gt;You don't have to agree with the DevOps movement and ideas to use configuration management, but some of its principles (such as communication, openness, shared ownership and responsibility) are worthwhile and have a low overhead once you start to config manage everything. Look for simple requests for information from other teams. Do they want to know which cronjobs run and when? What modules the apache servers have configured? Which rubygems you've deployed on the database servers? (from packages!)&lt;/p&gt;&lt;p&gt;By sharing configuration modules, you help others develop a balanced understanding of the platform. You enable them to regain time wasted raising support tickets through sharing commit logs, audit and error reports, etc, without feeling like your coworkers or customers are wrestling your time away.&lt;/p&gt;&lt;p&gt;At $WORK, a large percentage of our developers are comfortable reading puppet manifests, running MCollective commands, and using custom &lt;a href="http://theforeman.org/"&gt;Foreman&lt;/a&gt; pages and cgi-scripts to investigate issues answering their own queries without the sysadmins being involved. This has dropped our workload, given them quicker answers and allowed them to ask targeted questions after doing their own research.&lt;/p&gt;&lt;p&gt;Such things increase efficiency, communication, and happiness.&lt;/p&gt;&lt;p&gt;It's not all about developers either. With a custom puppet define and a little reporting wrapper, we can expose the list of ports required by every machine to our network teams and auditors along with accurate timestamps showing when information was gathered. Using MCollective and custom database facts, our DBAs have dashboards showing deployed packages and services, current running configuration, and the ability to gather ad-hoc real time information from any of our systems: production, development or QA and even compare the differences between them.&lt;/p&gt;&lt;h2 id="pick-your-fights"&gt;Pick your fights&lt;/h2&gt;&lt;p&gt;While you're growing accustomed and skilled with your new tools, you need to be aware of the strengths. As the person pushing this change, you have to prove your way is at least as good as (and hopefully better than) the current practise. Because of this, some tasks are bad places to prove your point and you need to be weary of them. Try to find some &lt;a href="http://en.wiktionary.org/wiki/low-hanging_fruit#English"&gt;low hanging fruit&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Large monolithic deployments or complex configuration scripts can be handled if you break them down to manageable components. Unfortunately, tight coupling and intertwined code makes these situations bad places to start. It's never a good opening move to start explaining that it took three hours to pick apart a shell script that seems to work fine. Vaguely-defined processes are the other common tar pit. Bringing attention to processes that people seem to do slightly differently is another great way to unite people against what you are trying to accomplish.&lt;/p&gt;&lt;p&gt;You want to avoid resistance caused by confusion. Find and tackle tasks that make sense to put into configuration management, first.&lt;/p&gt;&lt;p&gt;User accounts, for example, are a complex area to automate. While it starts with a simple &amp;quot;deploy the admin accounts, add them to wheel, and push the SSH keys,&amp;quot; it can easily become a sprawl of teams needing different sets of access on a semi-rotating basis.&lt;/p&gt;&lt;p&gt;It's worth noting that your config management efforts will often be considered slower, especially when starting out. Writing a ten line throwaway shell script or hand-hacking something will be quicker in the short term than writing clean manifests that include monitoring, meta data and tests but the comparison is unfair. Remember, you're building flexible, reproducible, systems that support future change - not focusing on a single server that just needs to work now and only now.&lt;/p&gt;&lt;h2 id="roles-not-hosts---no-more-snowflakes"&gt;Roles, not hosts - No more snowflakes&lt;/h2&gt;&lt;p&gt;As you build your collection of configuration management techniques, you'll find special snowflake hosts: Machines performing several small but important tasks; ones hand-constructed under time constraints (and needing that one extra special config option) - boxes you'll &amp;quot;only ever have one of,&amp;quot; right? Don't fall in the trap of assuming any machine is special or that you'll only ever have one of them. Building a second machine for disaster recovery, a staging version, or even an instance for upgrade tests means there will be times when you'll want more than one of them. If you find yourself adding special cases to your config management &amp;quot;If the hostname is 'foo.example.com'&amp;quot; then it's time to stop, take a step back, and consider alternatives. Any list, especially of resource names, that you hand maintain is a burden and will eventually be wrong.&lt;/p&gt;&lt;p&gt;A sign that things are starting to click is when you stop thinking in terms of hosts and start thinking in roles. Once you begin assigning roles to hosts you will find the natural level of granularity you should be writing your modules at. It's common to revisit previously written modules and extract aspects of them as you discover more potential reuse points.&lt;/p&gt;&lt;p&gt;You can learn more about configuration in terms of roles instead of machines in &lt;a href="http://sysadvent.blogspot.com/2008/12/day-7-host-vs-service.html"&gt;&amp;quot;Host vs Service&amp;quot;&lt;/a&gt; (SysAdvent 2008, Day 7).&lt;/p&gt;&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;&lt;p&gt;With a little care and consideration it's possible to integrate configuration management and even the largest legacy infrastructures while enjoying the same benefits as the newer, less encumbered projects.&lt;/p&gt;&lt;p&gt;Take small steps, master your tools and good luck.&lt;/p&gt;&lt;h2 id="further-reading"&gt;Further Reading&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.planetpuppet.org/"&gt;Planet Puppet&lt;/a&gt; (especially posts by Masterzen and R.I.Pienaar)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.planetdevops.net/"&gt;PlanetDevops&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3615332969083650973-2688853936830780357?l=sysadvent.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/sysadvent/~4/iXFpO-O67eM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sysadvent.blogspot.com/feeds/2688853936830780357/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=3615332969083650973&amp;postID=2688853936830780357" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/2688853936830780357?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/2688853936830780357?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/sysadvent/~3/iXFpO-O67eM/day-24-implementing-configuration.html" title="Day 24 - Implementing Configuration Management in Legacy Environments" /><author><name>Jordan Sissel</name><uri>http://www.blogger.com/profile/04677274979347685821</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://sysadvent.blogspot.com/2011/12/day-24-implementing-configuration.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkcBRHs-eSp7ImA9WhRXFk8.&quot;"><id>tag:blogger.com,1999:blog-3615332969083650973.post-4615918281471466956</id><published>2011-12-23T00:14:00.000-08:00</published><updated>2011-12-23T00:14:15.551-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-23T00:14:15.551-08:00</app:edited><title>Day 23 - All The Metrics! Or How You Too Can Graph Everything.</title><content type="html">&lt;p&gt;This was written by &lt;a href="https://github.com/tabletcorry"&gt;Corry Haines&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;As your company grows, you may find that your existing metric collection system(s) cannot keep up. Alternately, you may find that the interface used to read those metrics does not work with everything that you want to collect retrics for.&lt;/p&gt;&lt;p&gt;The problems that I have seen with existing solutions that I have used are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Munin: Fails to scale in all respects. Collection is more complicated than it should be, and graphs are &lt;em&gt;pre rendered&lt;/em&gt; for every defined time window. Needless to say, this does not scale well and cannot be used dynamically.&lt;/li&gt;
&lt;li&gt;Collectd: While this system is excellent at collecting data, the project does not officially support any frontend. This has led to a proliferation of frontend projects that, if taken together, have all of the features you need, but no one frontend does everything.&lt;/li&gt;
&lt;li&gt;XYMon: It's been some time since I used this, and I have not used it on a large set of systems. My guess is that it would suffer from some of Munin's issues.&lt;/li&gt;
&lt;/ul&gt;&lt;h2 id="enter-graphite"&gt;Enter Graphite&lt;/h2&gt;&lt;p&gt;Graphite is a collection of services that can replace or enhance your existing metric collection setup. Yes, it's written in python... but I like python.&lt;/p&gt;&lt;p&gt;The major components are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Whisper: Replaces RRD with a vastly simpler &lt;em&gt;storage only&lt;/em&gt; system. Unlike RRD, whisper cannot graph data directly. Also unlike RRD, you can actually read and understand the entire codebase in less than an hour (only 725 lines of well commented python).&lt;/li&gt;
&lt;li&gt;Carbon: Takes data on a variety of interfaces (TCP, UDP, Pickle, AMQP) and stores the data into whisper.&lt;/li&gt;
&lt;li&gt;Graphite-webapp: Graphs data from whisper or RRD files.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;The best thing about the components being independent is that you can run graphite on your existing RRD data with no hassle. While there are advantages to using whisper, it is not required to get the power of graphite.&lt;/p&gt;&lt;p&gt;The only negatives that I currently hold against graphite are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;The documentation is still a bit lacking, though they are working to improve this. You can invoke the community (mailing lists, etc) as a workaround.&lt;/li&gt;
&lt;li&gt;The learning curve can be a bit steep. While there is an interface to see all of the functions, you still need to learn how they are applied. This is offset by the ability to save named graphs for all users to see.&lt;/li&gt;
&lt;li&gt;Feedback is a bit lacking. After a graph is requested it is difficult to tell if it is being rendered, or simply failed in the backend.&lt;/li&gt;
&lt;li&gt;They use launchpad and thus bazaar, for their project management and source control. In a post-github world, this is starting to get a bit painful.&lt;/li&gt;
&lt;/ul&gt;&lt;h2 id="the-power-of-filters-and-functions"&gt;The Power of Filters and Functions&lt;/h2&gt;&lt;p&gt;As wonderful as whisper and carbon are (and they really are worth using), the true power of graphite lies in its web interface. Unlike some other interfaces, graphite treats each metric as an independent data series. So long as you have an understanding of the system, you can apply functions (think &lt;code&gt;sum&lt;/code&gt;, &lt;code&gt;avg&lt;/code&gt;, &lt;code&gt;stddev&lt;/code&gt;, etc.) to the metrics either by themselves, or more often, in aggregate.&lt;/p&gt;&lt;p&gt;In addition, you can use wildcards to select multiple machines quickly. While you could do a sum operation like this: &lt;code&gt;sumSeries(host1.load,host2.load,etc)&lt;/code&gt; you could more easily type &lt;code&gt;sumSeries(*.load)&lt;/code&gt;.&lt;/p&gt;&lt;h2 id="filter-example"&gt;Filter Example&lt;/h2&gt;&lt;p&gt;As an example, if I wanted to find overloaded webservers I could construct a query like &lt;code&gt;highestAverage(webservers.*.load.longterm, 3)&lt;/code&gt; producing:&lt;/p&gt;&lt;p&gt;&lt;img src="https://lh5.googleusercontent.com/-EGuYH5jOcb8/TvQ03KuohlI/AAAAAAAAACU/CL3kWP7YL6U/s510/load-highest3.jpg" alt="highestAverage graph" /&gt; &lt;a href="https://github.com/tabletcorry/sysadvent-articles/raw/master/load-highest3.png"&gt;higher resolution graph here&lt;/a&gt;&lt;/p&gt;&lt;h2 id="stacking-example"&gt;Stacking example&lt;/h2&gt;&lt;p&gt;Another example, graphing the amount of unused memory on the webservers (time for more memcached if so!) &lt;code&gt;movingAverage(webservers.*.memory.free, 10)&lt;/code&gt; producing:&lt;/p&gt;&lt;p&gt;&lt;img src="https://lh4.googleusercontent.com/-aiLnKh6FmNk/TvQ02z8IMEI/AAAAAAAAACQ/YZYiDWZIu28/s510/memory-3.jpg" alt="memory movingaverage graph" /&gt; &lt;a href="https://github.com/tabletcorry/sysadvent-articles/raw/master/memory-3.png"&gt;higher resolution graph here&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Note that I am also creating a moving average over 10 datapoints here. Also, the series are stacked to produce a sum while still showing the responsible server&lt;/p&gt;&lt;h2 id="functions-are-the-best"&gt;Functions Are the Best!&lt;/h2&gt;&lt;p&gt;And this is only a small selection of the functions available to you. Moreover, &lt;em&gt;you can write your own&lt;/em&gt;! And easily too! Here is an example function in graphite:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# A function to scale up all datapoints by a given factor
def scale(requestContext, seriesList, factor):
  for series in seriesList:
    series.name = &amp;quot;scale(%s,%.1f)&amp;quot; % (series.name,float(factor))
    for i,value in enumerate(series):
      series[i] = safeMul(value,factor)
  return seriesList
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="graphite-in-production"&gt;Graphite in Production&lt;/h2&gt;&lt;p&gt;We are currently collecting &amp;gt;93,000 metrics every 10 seconds. Most of the data is gathered on machines using collectd and then passed to &lt;a href="https://github.com/loggly/collectd-to-graphite"&gt;a proxy&lt;/a&gt; written by sysadvent's editor. The proxy then ships all of the data, via TCP, to our central Carbon node.&lt;/p&gt;&lt;p&gt;All of the data is consumed by carbon and stored on a single machine with six 10k SAS drives in a RAID 10 array. Although this disk setup is not enough to write the data in real time, it batches up the data and writes sets at once. It only needs to use about 300 MB of RAM for caching.&lt;/p&gt;&lt;p&gt;In reality, this hardware is probably overkill for our current workload. While testing, I was running about 50,000 metrics on four 7.2k SATA drives in a RAID 10 and the machine was doing just fine. It was using several GB of RAM to cache the data, but it was still able to keep up.&lt;/p&gt;&lt;h2 id="in-closing"&gt;In Closing&lt;/h2&gt;&lt;p&gt;If you are considering the installation of a metric gathering system, I would absolutely recommend Graphite. If you are using Collectd or Munin already, you can try the graphite web interface without changing how you collect metrics. It only takes a few minutes to setup and might give you better insight into your systems.&lt;/p&gt;&lt;h2 id="further-reading"&gt;Further Reading&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://graphite.wikidot.com/"&gt;Graphite Homepage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://readthedocs.org/docs/graphite/en/latest/"&gt;New Graphite Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://codeascraft.etsy.com/2010/12/08/track-every-release/"&gt;Etsy Blog Post on Graphite&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3615332969083650973-4615918281471466956?l=sysadvent.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/sysadvent/~4/7Vdwmz3T3X8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sysadvent.blogspot.com/feeds/4615918281471466956/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=3615332969083650973&amp;postID=4615918281471466956" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/4615918281471466956?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/4615918281471466956?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/sysadvent/~3/7Vdwmz3T3X8/day-23-all-metrics-or-how-you-too-can.html" title="Day 23 - All The Metrics! Or How You Too Can Graph Everything." /><author><name>Jordan Sissel</name><uri>http://www.blogger.com/profile/04677274979347685821</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://lh5.googleusercontent.com/-EGuYH5jOcb8/TvQ03KuohlI/AAAAAAAAACU/CL3kWP7YL6U/s72-c/load-highest3.jpg" height="72" width="72" /><thr:total>1</thr:total><feedburner:origLink>http://sysadvent.blogspot.com/2011/12/day-23-all-metrics-or-how-you-too-can.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEIFQXY7eSp7ImA9WhRXFUk.&quot;"><id>tag:blogger.com,1999:blog-3615332969083650973.post-6108373173856410879</id><published>2011-12-22T00:28:00.000-08:00</published><updated>2011-12-22T00:28:30.801-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-22T00:28:30.801-08:00</app:edited><title>Day 22 - Load Balancing Solutions on EC2</title><content type="html">&lt;p&gt;This was written by &lt;a href="http://agiletesting.blogspot.com"&gt;Grig Gheorghiu&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Before Amazon introduced the &lt;a href="http://aws.amazon.com/elasticloadbalancing/"&gt;Elastic Load Balancing&lt;/a&gt; (ELB) service, the only way to do load balancing in EC2 was to use one of the software-based solutions such as &lt;a href="http://haproxy.1wt.eu/"&gt;HAProxy&lt;/a&gt; or &lt;a href="http://www.apsis.ch/pound"&gt;Pound&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Having just one EC2 instance running a software-based load balancer would obviously be a single point of failure, so a popular technique was to do DNS Round-Robin and have the domain name corresponding to your Web site point to several IP addresses via separate A records. Each IP address would be an Elastic IP associated to an EC2 instance running the load balancer software. This was still not perfect, because if one of these instances would go down, users pointed to that instance via DNS Round-Robin would still get an error until another instance would be launched.&lt;/p&gt;&lt;p&gt;Another issue that comes up all the time in the context of load balancing is SSL termination. Ideally you would like the load balancer to act as an SSL end-point, in order to offload the SSL computations from your Web servers, and also for easier management of the SSL certificates. HAProxy does not support SSL termination, but Pound does (note: that you can still pass SSL traffic through HAProxy by using its TCP mode, you just cannot terminate SSL traffic there.)&lt;/p&gt;&lt;p&gt;In short, if Elastic Load Balancing weren’t available, you could still cobble together a load balancing solution in EC2. There is no reason to ‘roll your own’ anymore however now that you can use the ELB service. Note that HAProxy is still the king of load balancers when it comes to the different algorithms you can use (and to a myriad of other features), so if you want the best of both worlds, you can have an ELB upfront, pointing to one or more EC2 instances running HAProxy, which in turn delegate traffic to your Web server farm.&lt;/p&gt;&lt;h2 id="elastic-load-balancing-and-the-dns-root-domain"&gt;Elastic Load Balancing and the DNS Root Domain&lt;/h2&gt;&lt;p&gt;One other issue that comes up all the time is that an ELB is only available as a CNAME (this is due to the fact that Amazon needs to scale the ELB service in the background depending on the traffic that hits it, so they cannot simply provide an IP address). A CNAME is fine if you want to load balance traffic to www.yourdomain.com, since that name can be mapped to a CNAME. However, the root or apex of your DNS zone, yourdomain.com, can only be mapped to an A record, so for yourdomain.com you could not use an ELB in theory. In practice, however, there are DNS providers that allow you to specify an alias for your root domain (I know &lt;a href="http://dyn.com/dns/dynect-managed-dns/"&gt;Dynect&lt;/a&gt; does this, and Amazon’s own &lt;a href="http://aws.amazon.com/route53/"&gt;Route 53&lt;/a&gt; DNS service).&lt;/p&gt;&lt;h2 id="elastic-load-balancing-and-ssl"&gt;Elastic Load Balancing and SSL&lt;/h2&gt;&lt;p&gt;The &lt;a href="https://console.aws.amazon.com"&gt;AWS console&lt;/a&gt; makes it easy to associate an SSL certificate with an ELB instance, at ELB creation time. You do need to add an SSL line to the HTTP protocol table when you create the ELB. Note that even though you terminate the SSL traffic at the ELB, you have a choice of using either unencrypted HTTP traffic or encrypted SSL traffic between the ELB and the Web servers behind it. If you want to offload the SSL processing from your Web servers, you can choose HTTP between the ELB and the Web server instances.&lt;/p&gt;&lt;p&gt;If however you want to associate an existing ELB instance with a different SSL certificate (say for instance you initially associated it with a self-signed SSL cert, and now you want to use a real SSL cert), you can’t do that with the AWS console anymore. You need to use command-line tools. Here’s how.&lt;/p&gt;&lt;p&gt;Before you install the command-line tools, a caveat: you need Java 1.6. If you use Java 1.5 you will most likely get errors such as java.lang.NoClassDefFoundError when trying to run the tools.&lt;/p&gt;&lt;ol style="list-style-type: decimal"&gt;&lt;li&gt;&lt;p&gt;Install and configure the AWS Elastic Load Balancing command-line tools&lt;/p&gt;&lt;ul&gt;&lt;li&gt;download &lt;a href="http://aws.amazon.com/developertools/2536"&gt;ElasticLoadBalancing.zip&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;unzip &lt;code&gt;ElasticLoadBalancing.zip&lt;/code&gt;; this will create a directory named ElasticLoadBalancing-version (latest version at the time of this writing is 1.0.15.1)&lt;/li&gt;
&lt;li&gt;set environment variable &lt;code&gt;AWS_ELB_HOME=/path/to/ElasticLoadBalancing-1.0.15.1&lt;/code&gt; (in .bashrc)&lt;/li&gt;
&lt;li&gt;add &lt;code&gt;$AWS_ELB_HOME/bin&lt;/code&gt; to your &lt;code&gt;$PATH&lt;/code&gt; (in &lt;code&gt;.bashrc&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Install and configure the AWS Identity and Access Management (IAMCli) tools&lt;/p&gt;&lt;ul&gt;&lt;li&gt;download &lt;a href="http://aws.amazon.com/developertools/AWS-Identity-and-Access-Management/4143"&gt;&lt;code&gt;IAMCli.zip&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;unzip &lt;code&gt;IAMCli.zip&lt;/code&gt;; this will create a directory named IAMCli-version (latest version at the time of this writing is 1.3.0)&lt;/li&gt;
&lt;li&gt;set environment variable &lt;code&gt;AWS_IAM_HOME=/path/to/IAMCli-1.3.0&lt;/code&gt; (in &lt;code&gt;.bashrc&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;add &lt;code&gt;$AWS_IAM_HOME/bin&lt;/code&gt; to your &lt;code&gt;$PATH&lt;/code&gt; (in &lt;code&gt;.bashrc&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create AWS credentials file&lt;/p&gt;&lt;ul&gt;&lt;li&gt;create file with following content AWSAccessKeyId=your_aws_access_key AWSSecretKey=your_aws_secret_key&lt;/li&gt;
&lt;li&gt;if you named this file &lt;code&gt;aws_credentials&lt;/code&gt;, set environment variable &lt;code&gt;AWS_CREDENTIAL_FILE=/path/to/aws_credentials&lt;/code&gt; (in .bashrc)&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Get DNS name for ELB instance you want to modify&lt;/p&gt;&lt;p&gt;We will use the ElasticLoadBalancing tool called elb-describe-lbs:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# elb-describe-lbs
LOAD_BALANCER  mysite-prod  mysite-prod-2639879155.us-east-1.elb.amazonaws.com  2011-05-24T22:38:31.690Z
LOAD_BALANCER  mysite-stage   mysite-stage-714225413.us-east-1.elb.amazonaws.com    2011-09-16T18:01:16.180Z
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In our case, we will modify the ELB instance named mysite-stage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Upload SSL certificate to AWS&lt;/p&gt;&lt;p&gt;I assume you have 3 files:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;the SSL private key in a file called &lt;code&gt;stage.mysite.com.key&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;the SSL certificate in a file called &lt;code&gt;stage.mysite.com.crt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;an intermediate certificate from the SSL vendor, in a file called &lt;code&gt;stage.mysite.com.intermediate.crt&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;We will use the IAMCli tool called &lt;code&gt;iam-servercertupload&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# iam-servercertupload -b stage.mysite.com.crt -c stage.mysite.com.intermediate.crt -k stage.mysite.com.key -s stage.mysite.com
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;List the SSL certificates you have uploaded to AWS&lt;/p&gt;&lt;p&gt;We will use the IAMCli tool called &lt;code&gt;iam-servercertlistbypath&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# iam-servercertlistbypath
arn:aws:iam::YOUR_IAM_ID:server-certificate/stage.mysite.com
arn:aws:iam::YOUR_IAM_ID:server-certificate/www.mysite.com
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Associate the ELB instance with the desired SSL certificate&lt;/p&gt;&lt;p&gt;We will use the ElasticLoadBalancing tool called &lt;code&gt;elb-set-lb-listener-ssl-cert&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# elb-set-lb-listener-ssl-cert mysite-stage --lb-port 443 --cert-id arn:aws:iam::YOUR_IAM_ID:server-certificate/stage.mysite.com
OK-Setting SSL Certificate
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;That's it! At this point, the SSL certificate for stage.mysite.com will be associated with the ELB instance handling HTTP and SSL traffic for stage.mysite.com. Not rocket science, but not trivial to put together all these bits of information either.&lt;/p&gt;&lt;h2 id="further-reading"&gt;Further Reading&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://docs.amazonwebservices.com/ElasticLoadBalancing/latest/DeveloperGuide/"&gt;Elastic Load Balancing Developer Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://docs.amazonwebservices.com/IAM/latest/UserGuide/"&gt;AWS Identity and Access Management User Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Blog post by Werner Vogels on &lt;a href="http://www.allthingsdistributed.com/2011/05/aws_ipv6.html"&gt;New Route 53 and ELB features&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3615332969083650973-6108373173856410879?l=sysadvent.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/sysadvent/~4/fXKlq35c2-Y" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sysadvent.blogspot.com/feeds/6108373173856410879/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=3615332969083650973&amp;postID=6108373173856410879" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/6108373173856410879?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/6108373173856410879?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/sysadvent/~3/fXKlq35c2-Y/day-22-load-balancing-solutions-on-ec2.html" title="Day 22 - Load Balancing Solutions on EC2" /><author><name>Jordan Sissel</name><uri>http://www.blogger.com/profile/04677274979347685821</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>4</thr:total><feedburner:origLink>http://sysadvent.blogspot.com/2011/12/day-22-load-balancing-solutions-on-ec2.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkMCQX49fip7ImA9WhRXFEg.&quot;"><id>tag:blogger.com,1999:blog-3615332969083650973.post-4093463143104135765</id><published>2011-12-21T00:01:00.000-08:00</published><updated>2011-12-21T00:01:00.066-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-21T00:01:00.066-08:00</app:edited><title>Day 21 - Automating Web Monitoring</title><content type="html">&lt;p&gt;This article is written by &lt;a href="http://www.inatree.org"&gt;Brandon Burton&lt;/a&gt;, who can mostly be found posting lolcats and retweeting &lt;a href="http://www.twitter.com/solarce"&gt;@solarce&lt;/a&gt;, though he occasionally posts interesting links to things sysadmin, devops, and unix.&lt;/p&gt;&lt;p&gt;As systems administrators, we all know that &lt;a href="https://twitter.com/#!/griggheo/status/62239661568958464"&gt;it's not in production until it's monitored&lt;/a&gt;, but this isn't always as simple a rule to live by as it may sound. Not all web applications, for example, are easily monitored through traditional monitoring solutions such as Nagios, Zenoss, or various commercial tools. These tools tend to take a &amp;quot;curl | grep&amp;quot; style monitoring, or they may support somewhat more complex POSTing of XML or JSON data and validation of the returned data. But often the most key parts of applications being deployed into production involve complex browser interactions and behaviors - AJAX, or some other session or transaction that traditional monitoring frameworks don't have an easy way to accommodate.&lt;/p&gt;&lt;p&gt;Enter &lt;a href="http://seleniumhq.org/"&gt;Selenium&lt;/a&gt;. &lt;a href="http://en.wikipedia.org/wiki/Selenium_%28software%29"&gt;Selenium&lt;/a&gt; is a mature and robust framework for doing complex interactions with web applications. It originated as a tool at the consulting company ThoughtWorks as a way to do testing against web applications by driving a web browser. Since its release, it has seen the development of numerous tools, including browser plugins to make it easy to develop Selenium tests quickly and easily, language bindings to write tests in pretty much every major language, and tools to run many browsers across many operating systems, in parallel.&lt;/p&gt;&lt;p&gt;Additionally, services, such as &lt;a href="http://www.browsermob.com"&gt;BrowserMob&lt;/a&gt; and &lt;a href="http://www.saucelabs.com"&gt;Sauce Labs&lt;/a&gt;, have grown around the Selenium ecosystem to help you do testing and monitoring in a scalable and offsite fashion. It is these services that we'll focus on utilizing in this blog post.&lt;/p&gt;&lt;p&gt;So what does all this mean? It means that we have a mature and robust toolset that we can utilize and perform testing and monitoring of the complex web applications that we are deploying into production.&lt;/p&gt;&lt;h2 id="getting-started"&gt;Getting started&lt;/h2&gt;&lt;p&gt;So how do we get started? My preferred method is to begin by developing tests locally. You can use the &lt;a href="http://seleniumhq.org/projects/ide/"&gt;Selenium IDE&lt;/a&gt;, but for this example I'll show a Firefox extension called &lt;a href="http://saucelabs.com/docs/builder"&gt;Sauce Builder&lt;/a&gt; which makes it a snap to build and run your first test locally.&lt;/p&gt;&lt;p&gt;To get started you'll need Firefox installed, then go to the &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/sauce-builder/?src=search"&gt;Sauce Builder download page&lt;/a&gt; and walk through getting the extension installed.&lt;/p&gt;&lt;p&gt;Once you've got the Sauce Builder extension installed, it is time to build our first test.&lt;/p&gt;&lt;p&gt;I'm going to walk you through building a test to search for jelly beans on &lt;a href="http://www.amazon.com"&gt;Amazon&lt;/a&gt;.&lt;/p&gt;&lt;ol style="list-style-type: decimal"&gt;&lt;li&gt;Open Firefox&lt;/li&gt;
&lt;li&gt;Click on Tools -&amp;gt; Sauce Builder&lt;/li&gt;
&lt;li&gt;Enter ''amazon.com'' in the Start Record prompt and click Go&lt;/li&gt;
&lt;li&gt;Enter ''jelly beans'' for the search term&lt;/li&gt;
&lt;li&gt;Click Go&lt;/li&gt;
&lt;li&gt;Click on the first search result, for me this was '''Kirkland Signature Jelly Belly Jelly Beans 49 Flavors (4 Lbs)'''&lt;/li&gt;
&lt;li&gt;Go back to the Sauce Builder window and click Stop recording.&lt;/li&gt;
&lt;li&gt;Now that we've recorded a test, we should save it for safe keeping. Click File -&amp;gt; Save or Export -&amp;gt; Choose HTML as the format and name it, then click Save.&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;As you can see from the test we've recorded. The test is composed of a series of actions and each action will have one or more options associated with it.&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.screencast.com/t/ufRm1SfMbEo"&gt;Here is a short video of recording your first test&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Digging into how to modify and adapt tests is beyond the scope of what I want to cover in this post, but the following links are some good places to go deeper:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://saucelabs.com/docs/builder"&gt;Sauce Builder documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://seleniumhq.org/docs/06_test_design_considerations.html"&gt;Selenium Docs - Test Design Considerations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://seleniumhq.org/docs/02_selenium_ide.html#building-test-cases"&gt;Selenium Docs - Selenium IDE - Building Test Cases&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Now that we've recorded our first test, it is time to run it.&lt;/p&gt;&lt;ol style="list-style-type: decimal"&gt;&lt;li&gt;Click on Run and choose Run test locally.&lt;/li&gt;
&lt;li&gt;The test will begin running in the currently selected tab in Firefox.&lt;/li&gt;
&lt;li&gt;Obviously this is a pretty simple test and you could do a lot more with it, including go through adding it to a cart, checking out, and buying the order. But for the purposes of getting started, it's a good place to stop.&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;&lt;a href="http://www.screencast.com/t/2bQlCLPKUGOr"&gt;Here is a video of running your first test&lt;/a&gt;&lt;/p&gt;&lt;p&gt;The next thing we want to do, since our focus is on monitoring, is add some verification steps to each page load. This step is crucial in making our test doing the same kind of checking that your traditional &lt;code&gt;curl URL | grep STRING&lt;/code&gt; style monitoring did, but now it's integrated into our browser-driven mode of execution.&lt;/p&gt;&lt;ol style="list-style-type: decimal"&gt;&lt;li&gt;Go to the Sauce Builder window&lt;/li&gt;
&lt;li&gt;Mouse over the second step and choose New step below&lt;/li&gt;
&lt;li&gt;Select the new step&lt;/li&gt;
&lt;li&gt;Choose edit action&lt;/li&gt;
&lt;li&gt;Select the assertion option&lt;/li&gt;
&lt;li&gt;Choose page content&lt;/li&gt;
&lt;li&gt;Choose assertText&lt;/li&gt;
&lt;li&gt;Click Ok&lt;/li&gt;
&lt;li&gt;Choose locator and enter ''link=Your Amazon.com'&lt;/li&gt;
&lt;li&gt;Click Ok&lt;/li&gt;
&lt;li&gt;Choose equal to and enter the string ''Your Amazon.com''&lt;/li&gt;
&lt;li&gt;Click Ok&lt;/li&gt;
&lt;li&gt;Click on Run and Run test locally&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;The test should run successfully, if it does not, then you may want to click on &lt;code&gt;locator&lt;/code&gt; and choose &lt;code&gt;Find a different Target&lt;/code&gt; and use the tool to select the element you're asserting text with.&lt;/p&gt;&lt;p&gt;This is a critical step as the assertions are somewhat brittle and must be maintained as your application changes over time. For more details, see &lt;a href="http://release.seleniumhq.org/selenium-core/1.0/reference.html"&gt;help on choosing good locators&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.screencast.com/t/JIQnnbD6MX"&gt;Here is a video of adding the assertion to your test and running it locally&lt;/a&gt;&lt;/p&gt;&lt;h2 id="using-sauce-labs-for-testing"&gt;Using Sauce Labs for Testing&lt;/h2&gt;&lt;p&gt;Now that you've gotten your test running locally and you've added some assertions to make the test useful for monitoring, it is a good idea to run the test externally. As previously mentioned, the &lt;a href="http://www.saucelabs.com/"&gt;Sauce Labs&lt;/a&gt; folks run a service to run your tests in the &lt;em&gt;Cloud&lt;/em&gt;, and they are nice enough to offer a free plan that gives you 200 &amp;quot;execution&amp;quot; minutes per month and the ability to run your tests under multiple browsers and operating systems with ease. Plus you'll get your jobs stored, logs, screenshots, and a video recorded of the whole test for later review and analysis. So now that you're thinking &amp;quot;where do I sign up?!&amp;quot;&lt;/p&gt;&lt;p&gt;To sign up for the free plan, do the following.&lt;/p&gt;&lt;ol style="list-style-type: decimal"&gt;&lt;li&gt;Go to &lt;a href="https://saucelabs.com/signup"&gt;https://saucelabs.com/signup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Enter a username&lt;/li&gt;
&lt;li&gt;Enter your email address&lt;/li&gt;
&lt;li&gt;Enter a password&lt;/li&gt;
&lt;li&gt;Click Sign Me Up&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;Now configure your Sauce Builder installation to use your free account&lt;/p&gt;&lt;ol style="list-style-type: decimal"&gt;&lt;li&gt;Login to https://saucelabs.com/ and click on View My API key&lt;/li&gt;
&lt;li&gt;Copy your API key&lt;/li&gt;
&lt;li&gt;In your test, choose Run -&amp;gt; Run on Sauce OnDemand&lt;/li&gt;
&lt;li&gt;Leave the default Linux - Firefox 3.0&lt;/li&gt;
&lt;li&gt;Click Run&lt;/li&gt;
&lt;li&gt;When prompted if you have a Sauce Labs account, choose Yes&lt;/li&gt;
&lt;li&gt;Enter your username and API key&lt;/li&gt;
&lt;li&gt;Choose Save&lt;/li&gt;
&lt;li&gt;Your test will start running. Grab a snickers.&lt;/li&gt;
&lt;li&gt;You'll end up with a Job URL that looks something like https://saucelabs.com/jobs/6f4629f04dad85cd7803d8049ec00888 (which I've made public, since there is nothing private in it.)&lt;/li&gt;
&lt;li&gt;Review the details of the test, as you can see, you get the following for each test&lt;/li&gt;
&lt;li&gt;Platform&lt;/li&gt;
&lt;li&gt;Start and End Times&lt;/li&gt;
&lt;li&gt;Duration&lt;/li&gt;
&lt;li&gt;Status&lt;/li&gt;
&lt;li&gt;Break down of each Selenium command that's executed&lt;/li&gt;
&lt;li&gt;Screenshot of the final page of the test&lt;/li&gt;
&lt;li&gt;Video recording of the whole test run.&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;At this point you've successfully executed a test on Sauce Labs. I recommend you review the following to get a full idea of Sauce Labs features, which includes being able to use it programmatically from various languages, which is beyond the scope of what I'm covering this post.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="https://saucelabs.com/tech-resources"&gt;Sauce Labs - FAQ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://saucelabs.com/docs/ondemand/browsers/env/ruby/se2/mac"&gt;Sauce Labs - Supported Browsers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://saucelabs.com/docs/ondemand/additional-config"&gt;Sauce Labs - Additional Config Options&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://saucelabs.com/docs/saucerest"&gt;Sauce Labs - REST API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;h1 id="using-browsermob-for-monitoring"&gt;Using BrowserMob for monitoring&lt;/h1&gt;&lt;p&gt;So you've succeeded in getting your test run locally, you've run it externally in the &amp;quot;Cloud&amp;quot;, and now you're thinking &amp;quot;wasn't I promised I could use this for monitoring?&amp;quot;. Yes, you were, and that's where &lt;a href="http://www.browsermob.com/"&gt;BrowserMob&lt;/a&gt; comes in.&lt;/p&gt;&lt;p&gt;While BrowserMob's primary product is focused on load testing, they've also built a great &lt;a href="https://browsermob.com/website-monitoring-features"&gt;monitoring product&lt;/a&gt; and that's what we'll using to get our monitoring up and running.&lt;/p&gt;&lt;p&gt;BrowserMob is kind enough to offer a free plan, so let's start with getting signed up.&lt;/p&gt;&lt;p&gt;To sign up for the free plan, do the following.&lt;/p&gt;&lt;ol style="list-style-type: decimal"&gt;&lt;li&gt;Go to &lt;a href="https://browsermob.com/website-monitoring-load-testing-signup"&gt;https://browsermob.com/website-monitoring-load-testing-signup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Enter all the required info.&lt;/li&gt;
&lt;li&gt;Click Sign Up&lt;/li&gt;
&lt;li&gt;Complete the email verification.&lt;/li&gt;
&lt;li&gt;You're done.&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;Now upload and verify your first test.&lt;/p&gt;&lt;ol style="list-style-type: decimal"&gt;&lt;li&gt;Go to &lt;a href="https://browsermob.com/account/overview"&gt;https://browsermob.com/account/overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Click on Scripts&lt;/li&gt;
&lt;li&gt;Click on Upload Selenium Browser Script&lt;/li&gt;
&lt;li&gt;Give it a Name&lt;/li&gt;
&lt;li&gt;Click Browser, locate your test file you saved from Sauce Builder&lt;/li&gt;
&lt;li&gt;Click on Upload&lt;/li&gt;
&lt;li&gt;It should automatically validate.&lt;/li&gt;
&lt;li&gt;If it passes validation, you should then see Revalidate, View Log, and Screenshot links&lt;/li&gt;
&lt;li&gt;Check out the log and screenshot to get an idea of what will be recorded for each monitoring test run.&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;&lt;a href="http://www.screencast.com/t/fmt9oswX"&gt;Here is a short video showing uploading and verifying your first test&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Let's configure an email address for notifications&lt;/p&gt;&lt;ol style="list-style-type: decimal"&gt;&lt;li&gt;Click on Monitoring&lt;/li&gt;
&lt;li&gt;Clic on Notifications&lt;/li&gt;
&lt;li&gt;Click on &lt;a href="https://browsermob.com/monitoring/create-notification-preferences"&gt;create one&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Enter a name&lt;/li&gt;
&lt;li&gt;Confirm the contact name and email, it will default to what you registered with&lt;/li&gt;
&lt;li&gt;Click Create&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;Now let's set up a monitoring job.&lt;/p&gt;&lt;ol style="list-style-type: decimal"&gt;&lt;li&gt;Click on Monitoring&lt;/li&gt;
&lt;li&gt;Click on Schedule&lt;/li&gt;
&lt;li&gt;Give the job a name&lt;/li&gt;
&lt;li&gt;Select a Frequency * With the free account, you can run a simple test every 12 hours, for higher frequency or more complicated tests, you'll need to &lt;a href="https://browsermob.com/website-monitoring-pricing"&gt;purchase&lt;/a&gt; a paid account.&lt;/li&gt;
&lt;li&gt;If you want to do an alert, click create&lt;/li&gt;
&lt;li&gt;Select a location&lt;/li&gt;
&lt;li&gt;Select your notification preference&lt;/li&gt;
&lt;li&gt;Click Activate Now&lt;/li&gt;
&lt;li&gt;The job will be scheduled and will run at the next internal after the minute the job was created.&lt;/li&gt;
&lt;li&gt;Since you just signed up for a trial, you can get the test to run a bit sooner, but only a couple times, so we'll do that now, so we can see what it looks like.&lt;/li&gt;
&lt;li&gt;Click Edit next to the test&lt;/li&gt;
&lt;li&gt;Change Frequency to 10 minutes&lt;/li&gt;
&lt;li&gt;Click Save and Activate&lt;/li&gt;
&lt;li&gt;Set a timer for 12 minutes and wait, once it is done, we'll review what things look like. * Once you're done with this, you may want to revert to every 12 hours so that when you're trial expires you won't be over your credits, or just pause/delete the monitoring job.&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;&lt;a href="http://www.screencast.com/t/vpkz9XepvrYn"&gt;Here is a video of creating the monitoring job&lt;/a&gt;&lt;/p&gt;&lt;p&gt;So now that that test has run, let's take a look at what it looks like.&lt;/p&gt;&lt;ol style="list-style-type: decimal"&gt;&lt;li&gt;Click on Dashboard&lt;/li&gt;
&lt;li&gt;Mouse over the name of the job and click on the URL, it should look like something like this: https://browsermob.com/monitoring/view/{some_id_here}&lt;/li&gt;
&lt;li&gt;You should see a chart that defaults to 1 day and shows you each test, with a bar showing each data point, based on the overall time it took to run the test. * This gives you some quick insight to how performance (as measured by execution time) is doing over time.&lt;/li&gt;
&lt;li&gt;You can drill into each data point, and you'll get a waterfall style break down of each test run: how long each element of the page took to load, etc.&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;Below is a screenshot of a test that has run for a few days.&lt;/p&gt;&lt;div class="thumbnail"&gt;&lt;a href="https://skitch.com/solarce/g1kjc/view-monitoring-job-browsermob"&gt;&lt;img src="https://img.skitch.com/20111219-dhh47y4pg9gr8bdmi74dispkbb.preview.png" alt="View Monitoring Job | BrowserMob" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
&lt;/div&gt;&lt;br /&gt;
&lt;p&gt;So a couple tips on how you can use custom stuff from BrowserMob's API to make your tests that much more effective.&lt;/p&gt;&lt;h3 id="setting-variables."&gt;Setting variables.&lt;/h3&gt;&lt;p&gt;Since the BrowserMob scripts are written in JavaScript, doing variables is as simple as doing &lt;code&gt;var zipcode = '90210'&lt;/code&gt;&lt;/p&gt;&lt;h3 id="getting-back-data-from-a-webpage."&gt;Getting back data from a webpage.&lt;/h3&gt;&lt;p&gt;I've only ever used this to get back the whole response from a page and use it as is, so you'd need to break out a bit of your own JS-fu if you want to use part of a response, but here's how I did it. The code below also shows using a previously declared variable in your request.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;var response = c.get(&amp;quot;http://api.example.com:8080/id?&amp;quot;+zipcode)
var testid = response.getBody()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;At this point the &lt;code&gt;testid&lt;/code&gt; variable contains the string returned in the response from the request to &lt;a href="http://api.example.com:8080/id?90210"&gt;http://api.example.com:8080/id?90210&lt;/a&gt;&lt;/p&gt;&lt;h3 id="extra-logging"&gt;Extra Logging&lt;/h3&gt;&lt;p&gt;BrowserMob's JS API has a nice function called &lt;code&gt;browserMob.log()&lt;/code&gt; which lets you log arbitrary data and it will show up in the raw logs that BrowserMob keeps for each test run. An example of this is&lt;/p&gt;&lt;pre&gt;&lt;code&gt;browserMob.beginStep(&amp;quot;Step 2&amp;quot;);
selenium.waitForPageToLoad(60000);
selenium.type(&amp;quot;id=twotabsearchtextbox&amp;quot;, &amp;quot;jelly beans&amp;quot;);
browserMob.log('searched for jelly beans')
browserMob.endStep();
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For more info on these and more functions, check out the &lt;a href="http://static.browsermob.com/api/"&gt;BrowserMob API Documentation&lt;/a&gt;&lt;/p&gt;&lt;h2 id="what-next"&gt;What Next?&lt;/h2&gt;&lt;p&gt;At this point you've successfully built a test, run it locally, run it in the &amp;quot;cloud&amp;quot;, and deployed it to monitor every 12 hours and are getting alerted by email, you're wondering what's next.&lt;/p&gt;&lt;p&gt;Well, amongst the things you could would would be&lt;/p&gt;&lt;ol style="list-style-type: decimal"&gt;&lt;li&gt;&lt;a href="https://browsermob.com/website-load-testing"&gt;Load Testing&lt;/a&gt; through BrowserMob&lt;/li&gt;
&lt;li&gt;Get called or pager by sending your email alerts into &lt;a href="http://www.pagerduty.com"&gt;PagerDuty&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Interact with your own web services by using the ''getting data back'' example from above&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;I've made a &lt;a href="https://github.com/solarce/sysadvent_2011_examples"&gt;github repository&lt;/a&gt; with my Amazon.com example.&lt;/p&gt;&lt;p&gt;As a challenge and a way to motivate people to contribute and give feedback, the 5 most interesting tests that people submit as pull requests on Github, I will send them a package of stickers, including SysAdvent, Github, Riak, and more!&lt;/p&gt;&lt;p&gt;I hope you've found this post to be informative and would love feedback via email or Twitter on how you do end up using any or all of the services in this post.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3615332969083650973-4093463143104135765?l=sysadvent.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/sysadvent/~4/grjjs2LCCaU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sysadvent.blogspot.com/feeds/4093463143104135765/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=3615332969083650973&amp;postID=4093463143104135765" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/4093463143104135765?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/4093463143104135765?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/sysadvent/~3/grjjs2LCCaU/day-21-automating-web-monitoring.html" title="Day 21 - Automating Web Monitoring" /><author><name>Jordan Sissel</name><uri>http://www.blogger.com/profile/04677274979347685821</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>2</thr:total><feedburner:origLink>http://sysadvent.blogspot.com/2011/12/day-21-automating-web-monitoring.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUcGR3c8cCp7ImA9WhRXFEk.&quot;"><id>tag:blogger.com,1999:blog-3615332969083650973.post-1151821742278245377</id><published>2011-12-20T00:00:00.000-08:00</published><updated>2011-12-20T21:57:06.978-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-20T21:57:06.978-08:00</app:edited><title>Day 20 - Thoughts On Load Testing</title><content type="html">&lt;p&gt;This was written by &lt;a href="https://twitter.com/adamfblahblah"&gt;Adam Fletcher&lt;/a&gt; (&lt;a href="https://twitter.com/adamfblahblah"&gt;@adamfblahblah&lt;/a&gt;&lt;/p&gt;&lt;p&gt;One task that often falls on the lonely sysadmin is load testing. In this article I'm going to talk about some philosophies and processes I used when doing load testing in my past roles.&lt;/p&gt;&lt;p&gt;I'm going to focus on testing the server side. There's a lot of articles on how to optimize the client side experience, and it is very important that you are aware of both the client side and server side tuning changes so that you can give the customer the best experience.&lt;/p&gt;&lt;h2 id="use-science"&gt;Use Science&lt;/h2&gt;&lt;div class="figure"&gt;&lt;img src="https://lh5.googleusercontent.com/-hw-HGdbj1YM/TvBKGsVetrI/AAAAAAAAABU/rKsIV_eZE5s/s300/stand_back_square_0.png" alt="Science" /&gt;&lt;p class="caption"&gt;Science&lt;/p&gt;&lt;/div&gt;&lt;p&gt;(credit to XKCD. &lt;a href="http://store.xkcd.com/xkcd/#StandBackScience"&gt;Buy the shirt!&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;What I mean by that is that load testing is really experimentation. You're testing a hypothesis: Is setup A better than setup B? Develop your hypothesis, experiment, and measurements, and make conclusions based on data, not feelings. Don't forget that you need to control as many variables as possible. Don't test on your VMs or your staging server that the customer is also using.&lt;/p&gt;&lt;h2 id="have-defined-targets"&gt;Have Defined Targets&lt;/h2&gt;&lt;p&gt;You can't determine if your architecture scales/is fast enough/can handle traffic during a disaster/won't crash when you launch/etc. without have something measurable that determines success. For example, instead of saying &amp;quot;make each page load in 400 milliseconds&amp;quot;, it is better to say something like &amp;quot;Every page load must have also resources loaded within an average 400 milliseconds with a standard deviation of 25 milliseconds with 1000 clients performing actions every X seconds on Y series of pages.&amp;quot; You will then know when you are done load testing because all the measurements you are taking show you have achieved success.&lt;/p&gt;&lt;h2 id="scale-first"&gt;Scale First&lt;/h2&gt;&lt;p&gt;If the Y axis is latency and the X axis is number of workers, then scaling is keeping Y constant while you increase X to infinity. This is much harder than keeping the number of workers constant and lowering latency. The first thing to look at during load testing is the shape of your latency curve as load increases. Keep that curve flat and you're most of the way there.&lt;/p&gt;&lt;h2 id="understand-your-traffic"&gt;Understand Your Traffic&lt;/h2&gt;&lt;p&gt;If you're already suffering a load problem in production, great! Track the pages being requested and the path of the requests through your system. Use a tool like Google Analytics to get a picture of the diversity of the pages hit and the flow your users take through the product. You'll want to be able to model those flows in your load generation software.&lt;/p&gt;&lt;p&gt;For example, an online store may have a few different paths users commonly take through the system: arrive at the home page organically, search for a product, add to a cart, and check out; arrive at a special landing page for a sale, add to cart, checkout; arrive at the home page organically and use the customer services features; etc. If you viewed this store's traffic at single point in time you could divide your simultaneous traffic up by percentage of pages hit: at a point in time, 30% of the requests are to /search, 20% are to /checkout, 40% are to /, and finally 10% are to /customer-service. This is the model you should use for load generation.&lt;/p&gt;&lt;p&gt;Your traffic also follows patterns that depend on the time of day. With the advent of (somewhat) elastic capacity allocation, you can model these usage patterns and adjust your capacity to fit the pattens of usage.&lt;/p&gt;&lt;p&gt;Furthermore, you need to be aware of client side changes such as allowing your users to use HTTP pipelining or inlining Javascript versus loading the Javascript in another HTTP request. Making the server scale requires you understanding the client.&lt;/p&gt;&lt;h2 id="know-your-resource-limits"&gt;Know Your Resource Limits&lt;/h2&gt;&lt;p&gt;You don't have infinite computing power. You don't have infinite money. Most importantly, you don't have infinite time. Be smart about how you use that time.&lt;/p&gt;&lt;p&gt;It's expensive to have people do all the load testing science we're talking about in this article. With a little thought, you can probably guess where your bottleneck is - I'm going to guess it is something related to your data storage. Use your systems knowledge to make your first hypothesis &amp;quot;If I remove the obvious bottleneck, the system will be faster&amp;quot;.&lt;/p&gt;&lt;p&gt;Also, to paraphrase Artur Bergman, don't be a backwards idiot - &lt;a href="http://www.livestream.com/oreillyconfs/video?clipId=pla_3beec3a2-54f5-4a19-8aaf-35a839b6ecaa"&gt;buy some SSDs&lt;/a&gt;. They are expensive per GB but they are dead cheap per IOP/S. They're also cheaper than the time you are spending doing the load testing. You'll want to use these SSDs in the machines that have the highest IO load (and you know which machines those are, because you're measuring IO load, right?).&lt;/p&gt;&lt;h2 id="graphs-lie"&gt;Graphs Lie&lt;/h2&gt;&lt;p&gt;There was an excellent talk at Velocity this year about the dangers of trusting your graphs given by John Rauser entitled &lt;a href="http://www.youtube.com/watch?v=coNDCIMH8bk"&gt;Look at Your Data&lt;/a&gt;. He pointed out that you have to be careful of the trap of representing many points of data at the same X as the mean of those points of data as this representation hides the distribution of that data. This most commonly occurs when measuring latency and during load testing, when you have many requests at time X that, when averaged, come to Z milliseconds. Plot Z for many Xs and you miss the distribution of the latencies at X.&lt;/p&gt;&lt;p&gt;John's video explains it better, but if you look at this graph:&lt;/p&gt;&lt;div class="figure"&gt;&lt;img src="https://lh6.googleusercontent.com/-VCg2m9qnuW4/TvBKG4JKkMI/AAAAAAAAABk/JLN3CMwNjs4/s550/mean_latency.png" alt="Request Latency Over Time" /&gt;&lt;p class="caption"&gt;Request Latency Over Time&lt;/p&gt;&lt;/div&gt;&lt;p&gt;You'd think from this graph that everything is great - your latency went down!&lt;/p&gt;&lt;p&gt;But if we look at the distribution of our data at each sampling point:&lt;/p&gt;&lt;div class="figure"&gt;&lt;img src="https://lh5.googleusercontent.com/-IfvuUvcU2Ew/TvBKHYmTqzI/AAAAAAAAABs/k8tIo-lq0CM/s466/mean_latency_and_distribution.png" alt="Request Latency Over Time With Sample Distribution" /&gt;&lt;p class="caption"&gt;Request Latency Over Time With Sample Distribution&lt;/p&gt;&lt;/div&gt;&lt;p&gt;We see that some of users are having a really bad experience on our site.&lt;/p&gt;&lt;p&gt;A good example of a tool that doesn't have this problem is &lt;a href="http://oss.oetiker.ch/smokeping/"&gt;Smokeping&lt;/a&gt;. Here's an example of Smokeping telling me that my home internet connection has some jitter in latency:&lt;/p&gt;&lt;div class="figure"&gt;&lt;img src="https://lh5.googleusercontent.com/-slTLwMGanV8/TvBKGq2yx-I/AAAAAAAAABY/pFm7U6uLgPQ/s727/smokeping_example.png" alt="Comcast ICMP Ping Latency" /&gt;&lt;p class="caption"&gt;Comcast ICMP Ping Latency&lt;/p&gt;&lt;/div&gt;&lt;p&gt;I've also put a gist up with the R code used to generate the graphs above &lt;a href="https://gist.github.com/1043012"&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;h2 id="measure-time-and-resources-spent-in-each-component"&gt;Measure Time and Resources Spent in Each Component&lt;/h2&gt;&lt;p&gt;If you aren't instrumenting each piece of software in your stack you should start doing so. Instrument the entry point to your software and the exit point and graph this data over time. Combine this with even simple data from &lt;code&gt;sar&lt;/code&gt;, &lt;code&gt;iostat&lt;/code&gt;, other *stat tools, etc, and you can learn a lot about your code without ever firing up a profiler.&lt;/p&gt;&lt;h2 id="learn-and-use-the-right-tools"&gt;Learn And Use The Right Tools&lt;/h2&gt;&lt;p&gt;Good tools will allow you to export the raw data in such a way that you can then do analysis on it. Tools that expose your system resource consumption metrics are critical, and it probably doesn't matter what you use as long as you are storing and graphing roughly what &lt;code&gt;iostat&lt;/code&gt;, &lt;code&gt;sar&lt;/code&gt;, &lt;code&gt;vmstat&lt;/code&gt;, &lt;code&gt;netstat&lt;/code&gt; and &lt;code&gt;top&lt;/code&gt; give you. Learn what each metric really means - do you know why your software is context switching 4000 a second? Do you know if that is bad (hint: probably)? How would that manifest itself in top?&lt;/p&gt;&lt;p&gt;Learn to use the profiler that comes with your product's implementation language. Profilers are amazing things. If you can't use a language-specific profiler try a system-wide profiler such as oprofile or similar.&lt;/p&gt;&lt;p&gt;When you have all this data, use a real data analysis tool to look at it. Learn some R or NumPy/SciPy. Instead of using Excel or a clone for data analysis, consider learning a numerical computing language such as R. For example, in R or NumPy you can write a script that takes all of your raw resource consumption data (CPU, RAM, IOPS, etc) and runs correlation tests against the latency data. Try to do that in Excel! Oh, you can then use that script in your monitoring.&lt;/p&gt;&lt;p&gt;People often call load testing an art, but all that really means is that they're not doing science. Load testing can be challenging, but hopefully this article has given you some things to think about to make your load testing easier and more effective.&lt;/p&gt;&lt;h2 id="further-reading"&gt;Further Reading&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://learnr.wordpress.com"&gt;Learning R&lt;/a&gt; - a blog covering lots of cool visualizations and techniques in R.&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3615332969083650973-1151821742278245377?l=sysadvent.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/sysadvent/~4/1mnAIlmHbDM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sysadvent.blogspot.com/feeds/1151821742278245377/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=3615332969083650973&amp;postID=1151821742278245377" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/1151821742278245377?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/1151821742278245377?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/sysadvent/~3/1mnAIlmHbDM/day-20-thoughts-on-load-testing.html" title="Day 20 - Thoughts On Load Testing" /><author><name>Jordan Sissel</name><uri>http://www.blogger.com/profile/04677274979347685821</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://lh5.googleusercontent.com/-hw-HGdbj1YM/TvBKGsVetrI/AAAAAAAAABU/rKsIV_eZE5s/s72-c/stand_back_square_0.png" height="72" width="72" /><thr:total>1</thr:total><feedburner:origLink>http://sysadvent.blogspot.com/2011/12/day-20-thoughts-on-load-testing.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUMBQHc6eCp7ImA9WhRXFEQ.&quot;"><id>tag:blogger.com,1999:blog-3615332969083650973.post-5548157148652618664</id><published>2011-12-19T19:29:00.000-08:00</published><updated>2011-12-21T10:50:51.910-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-21T10:50:51.910-08:00</app:edited><title>Day 19 - Why Use Configuration Management?</title><content type="html">&lt;p&gt;This was written by &lt;a href="https://twitter.com/atsaloli"&gt;Aleksey Tsalolikhin&lt;/a&gt; (&lt;a href="http://verticalsysadmin.com/blog/"&gt;http://verticalsysadmin.com/blog/&lt;/a&gt;). Illustrations by &lt;a href="https://twitter.com/josephkern"&gt;Joseph Kern&lt;/a&gt;&lt;/p&gt;&lt;p&gt;If you ask &lt;a href="http://en.wikipedia.org/wiki/Configuration_management"&gt;Wikipedia&lt;/a&gt;, &amp;quot;Configuration management (CM) is a field of management that focuses on establishing and maintaining consistency of a system.&amp;quot;&lt;/p&gt;&lt;p&gt;Configuration management tools increase sysadmin efficiency and make sysadmin life better. As our systems grow larger and more complex, we need better tools to help us increase control and reliability of ever growing quanities and complexities in computing. Examples of such tools include Bcfg2, Cfengine, Chef, and Puppet - all of which are open source!&lt;/p&gt;&lt;p&gt;Configuring systems manually in interactive sessions is error-prone and extremely labor-intensive. Even with mostly-automated scripts, such as the typical &amp;quot;ssh and a for-loop&amp;quot; solution, pushing ad-hoc changes are still error prone. For example, if a system is down for maintenance while a change is being pushed out over ssh, it will miss that change, and &amp;quot;state drift&amp;quot; will occur between it and other systems in the same class.&lt;/p&gt;&lt;p&gt;You want a tool that helps keep actual and desired state the same.&lt;/p&gt;&lt;p&gt;System imaging is a common strategy for dealing with complexities of config management - make a copy of a system image, label it &amp;quot;gold master&amp;quot;, and clone it to make new systems. While this approach helps to crank out identically configured systems, it has the weakness that updating the master image can be a pain and it does nothing to maintain the systems configured after the initial deploy. It is also not very auditable (what changed between golden image v1 and v2?).&lt;/p&gt;&lt;p&gt;Many sysadmins still configure systems with more traditional manual, ad-hoc, and hard-to-audit methods. In some cases, sysadmin teams build home-grown tools to solve these problems. An example of this is &lt;a href="http://code.ticketmaster.com/"&gt;Ticketmaster&lt;/a&gt;, who released their own config management, &amp;quot;ssh and for loop&amp;quot; tool, and provisioning systems.&lt;/p&gt;&lt;p&gt;Why do we care to do this? Well, why do we administer systems? Correct configuration helps keep computer systems &lt;em&gt;in use by human civilization&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;CM tools free sysadmin's time for more challenging and creative system engineering and architecture work and for taking naps which power such work.&lt;/p&gt;&lt;h2 id="minimize-manual-effort"&gt;Minimize Manual Effort&lt;/h2&gt;&lt;p&gt;Minimize manual effort by automatically configuring new systems. This works well because repeatable work is best left to computers; they don't get bored, and they don't forget steps.&lt;/p&gt;&lt;p&gt;&lt;a href="http://www.thinkgeek.com/tshirts-apparel/unisex/frustrations/374d/"&gt;&amp;quot;Go away or I will replace you with a very small shell script&amp;quot;&lt;/a&gt; - you've probably seen this shirt before, right? How about hearing someone recommend &lt;a href="https://www.google.com/search?q=automate+yourself+out+of+a+job"&gt;&amp;quot;automating yourself out of a job&amp;quot;&lt;/a&gt;? Building systems and fighting fires without any tools is a slow task that is difficult to repeat accurately, and with many sysadmin skills being software-related, it is in your interest to automate system turn up, maintenance, and repair. Automation helps reduce time spent in corrective actions, reduces mental energy consumed, reduces stress, and increases business value and agility. Winning!&lt;/p&gt;&lt;p&gt;In using a config management system, you are implicitly documenting the system's &amp;quot;desired state&amp;quot; - Why is the system configured this way? What are its dependencies? Who cares about the system? This documenting capability helps protect against knowledge loss by moving configuration knowledge out your brains and into a version control system. This helps defend against data lost through forgetfulness or staff changes, and it also facilitates alignment of efforts on a multi-sysadmin team.&lt;/p&gt;&lt;p&gt;In general, configuration management is in the realm of &lt;a href="http://www.youtube.com/watch?v=LKENuz-DKTg"&gt;&amp;quot;Infrastructure as Code&amp;quot;&lt;/a&gt;. Once your infrastructure is represented in code, you can think about apply release engineering and other tools - tag a new policy as &amp;quot;unstable&amp;quot;, test it, then move the new policy into the &amp;quot;stable&amp;quot; branch where servers will apply it.&lt;/p&gt;&lt;h2 id="a-visualization"&gt;A Visualization&lt;/h2&gt;&lt;div class="figure"&gt;&lt;img src="https://lh3.googleusercontent.com/-mRx1kbMl2ok/Tu_5mv71VAI/AAAAAAAAAAY/aJ_k8E6sae8/s500/Sysadvent-2011-CM-1.png" alt="Sys Admin configures a server manually, ad hoc, and hands-on." /&gt;&lt;p class="caption"&gt;Sys Admin configures a server manually, ad hoc, and hands-on.&lt;/p&gt;&lt;/div&gt;&lt;div class="figure"&gt;&lt;img src="https://lh3.googleusercontent.com/-KQj63clPNDw/Tu_5n_8Qv-I/AAAAAAAAAAg/qp9NtDCCr3o/s500/Sysadvent-2011-CM-2.png" alt="Sys Admin writes a configuration management tool program to configure a server. Then the CM tool (like a little sysadmin robot) configures the server." /&gt;&lt;p class="caption"&gt;Sys Admin writes a configuration management tool program to configure a server. Then the CM tool (like a little sysadmin robot) configures the server.&lt;/p&gt;&lt;/div&gt;&lt;div class="figure"&gt;&lt;img src="https://lh3.googleusercontent.com/-sXhaP1q6hGo/Tu_5ysQpACI/AAAAAAAAABA/ooJ02zxgjHY/s500/Sysadvent-2011-CM-3.png" alt="Sys Admin takes a nap, while the CM tool configures more servers, and keeps checking and re-configuring the servers (as needed) to keep them in compliance with the program." /&gt;&lt;p class="caption"&gt;Sys Admin takes a nap, while the CM tool configures more servers, and keeps checking and re-configuring the servers (as needed) to keep them in compliance with the program.&lt;/p&gt;&lt;/div&gt;&lt;h2 id="getting-started"&gt;Getting Started&lt;/h2&gt;&lt;p&gt;To encourage sysadmins to start using Configuration Management, the following is a rough manual of how to do some small tasks in a few different, open source configuration management tools demonstratiing what policies look like in common open-source server. Bourne shell examples are provided to help aid in understanding.&lt;/p&gt;&lt;h2 id="using-these-examples"&gt;Using these examples&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;Bourne shell: Can be run on the command line or via cron&lt;/li&gt;
&lt;li&gt;CFengine: Follow the &lt;a href="http://cfengine.com/manuals/cf3-quickstart.html"&gt;quick start guide&lt;/a&gt; In a nutshell, put into a promise bundle inside a policy file (example.cf) and run from the command line with &amp;quot;cf-agent -f example.cf -b $bundlename&amp;quot;; or integrate into the default policy set in promises.cf in the CFEngine work directory, often found in &lt;code&gt;/var/cfengine/inputs&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Chef: Follow the &lt;a href="http://wiki.opscode.com/display/chef/Fast+Start+Guide"&gt;Chef Fast Start guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Puppet: Follow the &lt;a href="http://docs.puppetlabs.com/learning/"&gt;Getting Started guide&lt;/a&gt;. For quick testing of these examples, you can write them to a file 'foo.pp' and execute them with &lt;code&gt;puppet apply foo.pp&lt;/code&gt;. Puppet also supports a client-server model that is more common for production deployments.&lt;/li&gt;
&lt;/ul&gt;&lt;h2 id="set-permissions-on-a-file"&gt;Set Permissions on a File&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;Bourne shell&lt;/p&gt;&lt;pre&gt;&lt;code&gt;chmod 600 /tmp/testfile
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CFengine&lt;/p&gt;&lt;pre&gt;&lt;code&gt;files:
    &amp;quot;/tmp/testfile&amp;quot;
         perms   =&amp;gt; m(&amp;quot;600&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Chef&lt;/p&gt;&lt;pre&gt;&lt;code&gt;file &amp;quot;/tmp/testfile&amp;quot; do
  mode &amp;quot;0600&amp;quot;          
end                  
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Puppet&lt;/p&gt;&lt;pre&gt;&lt;code&gt;file { &amp;quot;/tmp/testfile&amp;quot;:
   mode =&amp;gt; 0600;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;h2 id="create-with-some-content"&gt;Create with some content&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;Bourne shell&lt;/p&gt;&lt;pre&gt;&lt;code&gt;echo 'Server will be down for maintenance 2 AM - 4 AM' &amp;gt; /etc/nologin
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CFengine&lt;/p&gt;&lt;pre&gt;&lt;code&gt;files:
   &amp;quot;/etc/nologin&amp;quot;
        create     =&amp;gt; &amp;quot;true&amp;quot;,
        edit_line  =&amp;gt;  insert_lines(&amp;quot;Server will be down for maintenance 2 AM - 4 AM&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Chef&lt;/p&gt;&lt;pre&gt;&lt;code&gt;file &amp;quot;/etc/nologin&amp;quot; do
  content 'Server will be down for maintenance 2 AM - 4 AM' 
end 
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Puppet&lt;/p&gt;&lt;pre&gt;&lt;code&gt;file { &amp;quot;/etc/nologin&amp;quot;:
  ensure =&amp;gt; present,
  content =&amp;gt; &amp;quot;Server will be down for maintenance 2 AM - 4 AM&amp;quot;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;h2 id="install-a-package"&gt;Install a package&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;Bourne shell&lt;/p&gt;&lt;pre&gt;&lt;code&gt;yum -y install httpd
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CFengine&lt;/p&gt;&lt;pre&gt;&lt;code&gt;packages:  
    &amp;quot;httpd&amp;quot;
        package_policy =&amp;gt; &amp;quot;add&amp;quot;,
        package_method =&amp;gt; yum;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Chef&lt;/p&gt;&lt;pre&gt;&lt;code&gt;package &amp;quot;httpd&amp;quot; 
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Puppet&lt;/p&gt;&lt;pre&gt;&lt;code&gt;package { &amp;quot;httpd&amp;quot;:
  ensure =&amp;gt; present;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;h2 id="make-sure-a-service-daemon-is-running"&gt;Make sure a service daemon is running&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;Bourne shell&lt;/p&gt;&lt;pre&gt;&lt;code&gt;ps -ef | grep httpd &amp;gt;/dev/null 

if [ $? -ne 0 ]  
  then /etc/init.d/httpd start 
fi                            
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CFengine&lt;/p&gt;&lt;pre&gt;&lt;code&gt;processes:
   &amp;quot;httpd&amp;quot;
        restart_class =&amp;gt; &amp;quot;restart_httpd&amp;quot;;

commands:
 restart_httpd::
   &amp;quot;/etc/init.d/httpd start&amp;quot;;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Chef&lt;/p&gt;&lt;pre&gt;&lt;code&gt;service &amp;quot;http&amp;quot; do 
  action :start   
end             
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Puppet&lt;/p&gt;&lt;pre&gt;&lt;code&gt;service { &amp;quot;httpd&amp;quot;:
  ensure =&amp;gt; running;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;h2 id="final-thoughts"&gt;Final Thoughts&lt;/h2&gt;&lt;p&gt;There's going to be a learning curve to any config management system, but I have found that the benefits in being able to audit, repeat, test, and share &amp;quot;desired state&amp;quot; in code far outweigh any time spent learning the config management tools.&lt;/p&gt;&lt;h2 id="further-reading"&gt;Further Reading&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.kitchensoap.com/2010/11/07/mttr-mtbf-for-most-types-of-f/"&gt;MTTR is more important than MTBF&lt;/a&gt; - John Allspaw presents that for many types of failure, speed of recovery is often more important than frequency of failure.&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3615332969083650973-5548157148652618664?l=sysadvent.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/sysadvent/~4/4HC8k8CBNdA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sysadvent.blogspot.com/feeds/5548157148652618664/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=3615332969083650973&amp;postID=5548157148652618664" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/5548157148652618664?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/5548157148652618664?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/sysadvent/~3/4HC8k8CBNdA/day-19-why-use-configuration-management.html" title="Day 19 - Why Use Configuration Management?" /><author><name>Jordan Sissel</name><uri>http://www.blogger.com/profile/04677274979347685821</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://lh3.googleusercontent.com/-mRx1kbMl2ok/Tu_5mv71VAI/AAAAAAAAAAY/aJ_k8E6sae8/s72-c/Sysadvent-2011-CM-1.png" height="72" width="72" /><thr:total>2</thr:total><feedburner:origLink>http://sysadvent.blogspot.com/2011/12/day-19-why-use-configuration-management.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUQBRn4-fyp7ImA9WhRXEk8.&quot;"><id>tag:blogger.com,1999:blog-3615332969083650973.post-9097111364968543788</id><published>2011-12-18T01:02:00.000-08:00</published><updated>2011-12-18T08:55:57.057-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-18T08:55:57.057-08:00</app:edited><title>Day 18 - Why Businesses Do Things</title><content type="html">&lt;p&gt;This was written by &lt;a href="https://twitter.com/josephkern"&gt;Joseph Kern&lt;/a&gt; (&lt;a href="http://www.semafour.net/"&gt;www.semafour.net&lt;/a&gt;).&lt;/p&gt;&lt;p&gt;Imagine your whole professional career as a sysadmin and you never understood the OSI model. Those seven simple layers that allow you to build an effective internal framework of network communications. Without this model how would you even begin to understand larger and more complex systems or the complex interactions between multiple systems?&lt;/p&gt;&lt;p&gt;You might get by for a time; hard work and dedication can take you a long way. But you would never be able to progress beyond a certain point. The problem space becomes to complex to brute force.&lt;/p&gt;&lt;p&gt;Now imagine that successfully managing and running a business is at least as complex as managing a network. Managing a 1000 computers is much easier than managing a business of 1000 people. I'd like to take you into the shallow end of business management and show you how the services that we sysadmins maintain are viewed from a business perspective.&lt;/p&gt;&lt;p&gt;Fortunately this framework is simple and avoids any hand waving. We just need this three word phrase, &amp;quot;differentiation and neutralization.&amp;quot;&lt;/p&gt;&lt;p&gt;Differentiation builds services that create competitive advantage. Neutralization builds services that seek to maintain competitive equilibrium. This interplay is the heart of what drives business and directs the supporting activities of enterprise level IT. Working DNS is needed for almost all aspects of modern enterprise IT infrastructure, and it will serve as a technical example for this discussion.&lt;/p&gt;&lt;p&gt;Building services that neutralize competitive advantage usually involves buying solutions; these are often disguised as &amp;quot;industry best practices&amp;quot;, and have many accompanying white papers offered as proof. More often than not this niche is filled by Microsoft or other large software vendors. You can buy your way out of a problem.&lt;/p&gt;&lt;p&gt;When building a DNS service it is most often thought of as a way to neutralize the advantage of other organizations. Seldom is thought given to how an organization might run DNS 30% &amp;quot;better&amp;quot; (not necessarily faster or any particular quality) than its competitors. In most cases, &amp;quot;better&amp;quot; would not matter at all.&lt;/p&gt;&lt;p&gt;&amp;quot;Better&amp;quot; does not create an advantage with a neutralizing service. Instead, it in fact creates a disadvantage. Time, attention, and resources are being funneled into a project that creates no value from a business standpoint. The business does not (and should not) care about innovating in services that they consider neutralizing. (See footnote #1)&lt;/p&gt;&lt;p&gt;Building services that create competitive differentiation is much different than neutralization as most of these services are built rather than bought. These tend to be very custom to the environment. The prime consideration for these services is adaptability. You must be able to extend the software providing the service as this allows you to out maneuver your competition. You are able to think your way out of a problem.&lt;/p&gt;&lt;h2 id="turning-neutralizing-into-differentiating"&gt;Turning Neutralizing into Differentiating&lt;/h2&gt;&lt;p&gt;OpenDNS took a service that was neutralizing and rebuilt it from the ground up and adding many other services such as anti-phishing, content filtering (based on domain), and reporting. These services created a differentiation in their business model and offered something new to the market. OpenDNS created a reason to build &amp;quot;better&amp;quot; DNS services, as this is their core business model and their competitive advantage.&lt;/p&gt;&lt;p&gt;As it turns out, setting security and content filtering at the DNS level works equally well across all devices all the time and requires no client installation. Now other businesses must appear to neutralize the differential advantage by creating their own services to match. Norton, for example, has followed suit with their Norton Everywhere product offering DNS services that largely mirror OpenDNS. (See footnote #2)&lt;/p&gt;&lt;p&gt;OpenDNS must now continue to differentiate their services from their competitors. OpenDNS recently started offering DNSCrypt, which creates an encrypted channel for DNS queries between the client and the DNS server. Consider it to be SSL for DNS. No doubt, there will be other service providers that follow suit, creating their own DNSCrypt implementations. (See footnote #3)&lt;/p&gt;&lt;p&gt;Why do businesses seemingly chase the tail of their competiors? This is because if organization declines the opportunity to neutralize the advantage of their competition, they will be excluded from further innovation in this field and may be locked out of the market entirely. A technical term for this is a &amp;quot;feature&amp;quot;. As the differentiation of services increases, the cost to enter the market (the table stakes) increases accordingly.&lt;/p&gt;&lt;h2 id="why-should-you-care"&gt;Why Should You Care?&lt;/h2&gt;&lt;p&gt;Senior sysadmins and engineers need to not only understand how to build a service, but we must also understand why we are building it and what the business requires from this deployment. Understanding the complete picture, we will understand what technology is required, how it needs to be implemented, and how much effort we should put into a project.&lt;/p&gt;&lt;p&gt;Both the engineer and the business get something valuable from this understanding - keeping time and attention focused on important projects. The next time you are asked to deploy a new service ask yourself (and your management) one simple question:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&amp;quot;Is this a service that neutralizes or differentiates?&amp;quot;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Knowing this helps you set your own expectations. If you find yourself wanting to spend energy improving a service, knowing whether it is neutralizing or differentiating will help you make the case to your team and managers that you should be working on it. Knowing it is a neutralizing service might help you set expectations such that you don't spend time and energy thinking hard about how to improve a service that doesn't benefit the business if improved, and having that knowledge and expectations can help keep you from burning out optimizing things that effectively are unimportant.&lt;/p&gt;&lt;h2 id="footnotes"&gt;Footnotes&lt;/h2&gt;&lt;ol style="list-style-type: decimal"&gt;&lt;li&gt;&lt;p&gt;Why do you think sharepoint is so popular? It's not because it does everything well ...&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the light of Windows 8 coming preloaded with Anti-Virus software, Norton is facing an almost complete lockout of their traditional market.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The great thing about standards, there are so many to choose from.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;h2 id="further-reading"&gt;Further Reading&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.kepplerspeakers.com/2011/07/business-strategy-expert-geoffrey-moore-on-neutralizing-your-competitors/"&gt;Geoffrey Moore on Neutralization&lt;/a&gt; - ok, so this isn't reading, it's 2-minute video!&lt;/li&gt;
&lt;li&gt;&lt;a href="http://thebln.com/2011/09/context-core-geoffrey-moore-at-business-of-software-video-transcript/"&gt;The full version of the video above&lt;/a&gt;&lt;/li&gt;

&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3615332969083650973-9097111364968543788?l=sysadvent.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/sysadvent/~4/3JE67owLWKg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sysadvent.blogspot.com/feeds/9097111364968543788/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=3615332969083650973&amp;postID=9097111364968543788" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/9097111364968543788?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/9097111364968543788?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/sysadvent/~3/3JE67owLWKg/day-18-why-businesses-do-things.html" title="Day 18 - Why Businesses Do Things" /><author><name>Jordan Sissel</name><uri>http://www.blogger.com/profile/04677274979347685821</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>2</thr:total><feedburner:origLink>http://sysadvent.blogspot.com/2011/12/day-18-why-businesses-do-things.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEUFQnY5fyp7ImA9WhRXEUw.&quot;"><id>tag:blogger.com,1999:blog-3615332969083650973.post-247480092914038168</id><published>2011-12-17T00:51:00.000-08:00</published><updated>2011-12-17T00:56:53.827-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-17T00:56:53.827-08:00</app:edited><title>Day 17 - Speaking the Same Language</title><content type="html">&lt;p&gt;This was written by &lt;a href="http://twitter.com/jordansissel"&gt;Jordan Sissel&lt;/a&gt; (&lt;a href="http://semicomplete.com"&gt;semicomplete.com&lt;/a&gt;).&lt;/p&gt;&lt;p&gt;Language is important.&lt;/p&gt;&lt;p&gt;You've probably had disagreements or confusions that, during or after, you realized were caused by miscommunication or misinterpretation. Something got lost in translation, right?&lt;/p&gt;&lt;p&gt;Language is important, so do you resolve these issues? Lawyers do it by dedicating great lengths of text to defining terms to eliminate confusion. What was the last legal document you read? Perhaps the constantly-changing iTunes EULA? Did you read it? Did you skip reading it because it was 70 pages long? Was it readable? Was it plain english?&lt;/p&gt;&lt;p&gt;What was the last legal document you saw that seemed approachable? Can you even read &lt;a href="http://images.apple.com/legal/sla/docs/iphone4.pdf"&gt;this document&lt;/a&gt; at the default font size?&lt;/p&gt;&lt;p&gt;Legal documents are long for many reasons, but the main reason I believe is to reduce loopholes or confusion by defining and using a common language and vocabulary. Vocabulary is important. Most of these documents have embedded definitions of just about every major term used. Look at your current employment contract - how much of it is simply defining words?&lt;/p&gt;&lt;p&gt;Centrally defining all words allows two parties who speak different languages to speak on a common ground. Look at the &lt;a href="http://www.gnu.org/licenses/gpl-3.0.html"&gt;GPL3&lt;/a&gt; and &lt;a href="http://www.apache.org/licenses/LICENSE-2.0.html"&gt;Apache 2&lt;/a&gt; licenses compared to the &lt;a href="http://www.opensource.org/licenses/mit-license.php"&gt;MIT&lt;/a&gt; license. Both the GPL3 and Apache 2 licenses specify more requirements or allowances than the MIT one, but my point is that much of the raw text in both GPL3 and Apache 2 are definitions. Compare this to the MIT license which has practically no definitions embedded.&lt;/p&gt;&lt;h2 id="verbal-communication"&gt;Verbal Communication&lt;/h2&gt;&lt;p&gt;In your average day, it is unlikely you have the time or energy to spend defining terms in the middle of every conversation. Most of the time you might assume the other person (or people) know what you mean when you say it.&lt;/p&gt;&lt;p&gt;I propose that the likelihood of one person understanding your words is in inversely related to their distance from your context, job role, and other factors you could have (or have not) in common.&lt;/p&gt;&lt;p&gt;Explaining something job-related to a fellow sysadmin will require a different set of terminology, a different language, than doing the same to your manager, someone outside your engineering group, someone in marketing, etc. The knee-jerk reaction is often to assume the other person is stupid. They aren't understanding you! It sucks. It increases tension and distrust.&lt;/p&gt;&lt;p&gt;Like I said, you probably don't have time to work with each person (or group) to design a common terminology. You have stuff to do. What other options do you have?&lt;/p&gt;&lt;p&gt;My best recommendation here is to study everyone. Watch what they say, how they say it, and what you think they mean when they say something. Study their reactions when you say things. If they appear confused, ask and clarify. Don't treat them like they're an idiot. Speaking loudly and slowly doesn't help anyone; ask for what is confusing, and ask what needs clarification and definition.&lt;/p&gt;&lt;p&gt;Know your audience.&lt;/p&gt;&lt;p&gt;Avoid analogies. Analogies are you translating your words into other words you hope the audience will understand. Bad analogies are very easy to make, and you can accidentally increase confusion and distrust like the famous &lt;a href="http://en.wikipedia.org/wiki/Series_of_tubes"&gt;&amp;quot;the internet is not a truck&amp;quot;&lt;/a&gt; failure. If you have bidirectional communication with a person, instead of making an analogy, why not ask for clarification and definition?&lt;/p&gt;&lt;h2 id="visual-communication"&gt;Visual Communication&lt;/h2&gt;&lt;p&gt;Pictures can help, too. Bought anything from Ikea recently? If not, &lt;a href="http://www.ikea.com/ms/en_US/customer_service/assembly_instructions.html"&gt;check them out&lt;/a&gt;, or just look at this &lt;a href="http://www.ikea.com/ms/en_US/customer_service/assembly/E/E70162862.pdf"&gt;table assembly manual&lt;/a&gt;. All the Ikea furniture assembly manuals I've seen are pretty effective with only pictures. No words.&lt;/p&gt;&lt;p&gt;Study your audience.&lt;/p&gt;&lt;p&gt;If you study and interact with a given set of folks frequently enough, you should learn to more easily speak a language they understand. Marketing and PR folks probably don't care about disks being full, but they care about the external impact of that problem. Try to understand what a person knows and what they care about and frame your words accordingly.&lt;/p&gt;&lt;h2 id="software-similarity-and-language-problems"&gt;Software Similarity and Language Problems&lt;/h2&gt;&lt;p&gt;Language problems affect even groups with small distances between them. Look at logging tools: You'll see terms like facility, severity, log level, debug level, source, and more all referring to pretty much the same things. What about monitoring? Nagios calls it a 'service'; Zabbix calls it an 'item' and sometimes a 'check'; Xymon calls it a 'service'; Sensu calls it a 'plugin'.&lt;/p&gt;&lt;p&gt;All of monitoring term examples above mean pretty much the same thing, and before you disagree, consider that I say they mean the same thing because they look like the same thing from a distance. Knowing Nagios and learning a new monitoring tool requires learning new term definitions as well as new software, and overloaded terms (like 'service') can have different meanings in different projects. It trips up the brain!&lt;/p&gt;&lt;p&gt;Look at features provided by similar tools, and each feature is likely to have a different name for the same thing. Intentional or otherwise, this is a language-equivalent of &lt;a href="http://en.wikipedia.org/wiki/Vendor_lock-in"&gt;vendor lock-in&lt;/a&gt;, and it sucks.&lt;/p&gt;&lt;p&gt;Puppet calls it a module and manifest; Chef calls it a cookbook and recipe. Ruby and Python use the word &amp;quot;yield&amp;quot; to mean different things, and this causes much confusion for people trying to learn both languages. You can reduce learning curves if you use a common language in your systems and projects.&lt;/p&gt;&lt;h2 id="when-to-define-terms"&gt;When to Define Terms&lt;/h2&gt;&lt;p&gt;It is worth defining terms if you need to have a long-lived common ground. You want defined common terms and features of a project that stay defined through out the project's life cycle.&lt;/p&gt;&lt;p&gt;As an example, there's a small group of folks who say, &amp;quot;&lt;a href="http://lusislog.blogspot.com/2011/06/why-monitoring-sucks.html"&gt;Monitoring sucks&lt;/a&gt;,&amp;quot; myself included. We got together to discuss ways to solve crappy monitoring problems, and one of the tasks was to define a common set of terms - we went the route lawyers go because having a common terminology ground would strengthen the &lt;a href="https://twitter.com/#!/search/%23monitoringsucks"&gt;#monitoringsucks&lt;/a&gt; movement. Agree or disagree with the &lt;a href="http://lusislog.blogspot.com/2011/07/monitoring-sucks-watch-your-language.html"&gt;definitions we came up with&lt;/a&gt;, the point was to lay a path for discussion that could avoid religious wars over confused terminology. The common terms were also chosen to help steer any new projects to use the same terms and reduce the learning curve as a result.&lt;/p&gt;&lt;h2 id="parting-thoughts"&gt;Parting Thoughts&lt;/h2&gt;&lt;p&gt;Much of technical writing education focuses on knowing the audience. While 'technical writing' leans towards one-way communication (writer communicating with readers), the ideas are important in general communication.&lt;/p&gt;&lt;p&gt;Who are you talking to? What are their interests? What are the boundaries of their knowledge?&lt;/p&gt;&lt;h2 id="further-reading"&gt;Further Reading&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.prismnet.com/~hcexres/textbook/aud.html"&gt;This page&lt;/a&gt; gives a reasonable overview of audience analysis.&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3615332969083650973-247480092914038168?l=sysadvent.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/sysadvent/~4/2ZkGv1MfmNA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sysadvent.blogspot.com/feeds/247480092914038168/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=3615332969083650973&amp;postID=247480092914038168" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/247480092914038168?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/247480092914038168?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/sysadvent/~3/2ZkGv1MfmNA/day-17-speaking-same-language.html" title="Day 17 - Speaking the Same Language" /><author><name>Jordan Sissel</name><uri>http://www.blogger.com/profile/04677274979347685821</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>2</thr:total><feedburner:origLink>http://sysadvent.blogspot.com/2011/12/day-17-speaking-same-language.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEQCRXs5fCp7ImA9WhRXEUk.&quot;"><id>tag:blogger.com,1999:blog-3615332969083650973.post-8619634335742740271</id><published>2011-12-16T16:55:00.000-08:00</published><updated>2011-12-17T10:26:04.524-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-17T10:26:04.524-08:00</app:edited><title>Day 16 - Shipping Some Logs</title><content type="html">&lt;p&gt;This was written by &lt;a href="http://twitter.com/jordansissel"&gt;Jordan Sissel&lt;/a&gt; (&lt;a href="http://semicomplete.com"&gt;semicomplete.com&lt;/a&gt;).&lt;/p&gt;&lt;p&gt;Logging is messy. Ever have logs fill up your disk and crash services as a result? Me too, and that sucks.&lt;/p&gt;&lt;p&gt;You can solve the disk-filling problem by rotation logs and expiring old ones, but there's a better solution that solves more problems: ship your logs somewhere else. Shipping logs somewhere centralized helps you more quickly access those logs later when you need to debug or do analytics.&lt;/p&gt;&lt;p&gt;There are plenty of tools in this area to help you solve log transport problems. Common syslog servers like &lt;a href="http://rsyslog.com/"&gt;rsyslog&lt;/a&gt; and &lt;a href="http://www.balabit.com/network-security/syslog-ng/opensource-logging-system/overview"&gt;syslog-ng&lt;/a&gt; are useful if syslog is your preferred transport. Other tools like &lt;a href="https://cwiki.apache.org/FLUME/"&gt;Apache Flume&lt;/a&gt;, &lt;a href="https://github.com/facebook/scribe/wiki"&gt;Facebook's Scribe&lt;/a&gt;, and &lt;a href="http://logstash.net"&gt;logstash&lt;/a&gt; provide an infrastructure for reliable and robust log transport. Many tools that help solve log transportation problems also solve other problems, for example, rsyslog can do more than simply moving a log event to another server.&lt;/p&gt;&lt;h2 id="starting-with-log-files"&gt;Starting with Log Files&lt;/h2&gt;&lt;p&gt;For all of these systems, one pleasant feature is that in most cases, you don't need to make any application-level changes to start shipping your logs elsewhere: If you already log to files, these tools can read those files and ship them out to your central log repository.&lt;/p&gt;&lt;p&gt;Files are a great common ground. Can you 'tail -F' to read your logs? Perfect.&lt;/p&gt;&lt;p&gt;Even rsyslog and syslog-ng, while generally a syslog server, can both follow files and stream out logs as they are written to disk. In rsyslog, you use the &lt;a href="http://rsyslog.com/doc/imfile.html"&gt;&lt;code&gt;imfile&lt;/code&gt; module&lt;/a&gt;. In syslog-ng, you use the &lt;a href="http://www.balabit.com/sites/default/files/documents/syslog-ng-v3.0-guide-admin-en.html/index.html-single.html#configuring_sources_file"&gt;file() driver&lt;/a&gt;. In Flume, you use the &lt;a href="http://archive.cloudera.com/cdh/3/flume/UserGuide/index.html#_tailing_a_file_name_literal_tail_literal_and_literal_multitail_literal"&gt;&lt;code&gt;tail() source&lt;/code&gt;&lt;/a&gt;. In logstash, you use the &lt;a href="http://logstash.net/docs/1.0.17/inputs/file"&gt;&lt;code&gt;file&lt;/code&gt; input plugin&lt;/a&gt;.&lt;/p&gt;&lt;h2 id="filtering-logs"&gt;Filtering Logs&lt;/h2&gt;&lt;p&gt;Most of the tools mentioned here support some kind of filtering, whether it's dropping certain logs or modifying them in-flight.&lt;/p&gt;&lt;p&gt;&lt;a href="http://logstash.net/"&gt;Logstash&lt;/a&gt;, for example supports dropping events matched by a certain pattern, parsing events into a structured piece of data like JSON, normalizing timestamps, and figuring out what events are single-line and what events are multi-line (like java stack traces). &lt;a href=""&gt;Flume&lt;/a&gt; lets you do similar filter behaviors in &lt;a href="http://archive.cloudera.com/cdh/3/flume/UserGuide/#_custom_metadata_extraction"&gt;&lt;code&gt;decorators&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;In rsyslog, you can use &lt;a href="http://rsyslog.com/doc/rsyslog_conf_filter.html"&gt;filter conditions&lt;/a&gt; and &lt;a href="http://rsyslog.com/doc/rsyslog_conf_templates.html"&gt;templates&lt;/a&gt; to selectively drop and modify events before they are output. Similarly, in syslog-ng, &lt;a href="http://www.balabit.com/sites/default/files/documents/syslog-ng-v3.0-guide-admin-en.html/index.html-single.html#filters"&gt;filters&lt;/a&gt; let you drop events and &lt;a href="http://www.balabit.com/sites/default/files/documents/syslog-ng-v3.0-guide-admin-en.html/index.html-single.html#configuring_macros"&gt;templates&lt;/a&gt; let you reshape the output event.&lt;/p&gt;&lt;h2 id="final-destination"&gt;Final Destination&lt;/h2&gt;&lt;p&gt;Where are you putting logs?&lt;/p&gt;&lt;p&gt;You could put them on a large disk server for backups and archival, but logs have valuable data in them and are worth mining.&lt;/p&gt;&lt;p&gt;Recall &lt;a href="http://sysadvent.blogspot.com/2011/12/day-10-analyzing-logs-with-pig-and.html"&gt;Sysadvent Day 10&lt;/a&gt; which covered how to analyze logs stored in S3 using Pig on Amazon EC2. &amp;quot;Logs stored in S3&amp;quot; - how do you get your logs into S3? Flume supports S3 out of the box allowing you to ship your logs up to Amazon for later processing. Check out &lt;a href="http://eric.lubow.org/2011/system-administration/distributed-flume-setup-with-an-s3-sink/"&gt;this blog post&lt;/a&gt; for an example of doing exactly this.&lt;/p&gt;&lt;p&gt;If you're looking for awesome log analytics and debugging, there are a few tools out there to help you do that without strong learning curves. Some open source tools include &lt;a href="http://graylog2.org/"&gt;Graylog2&lt;/a&gt; and &lt;a href="http://logstash.net/"&gt;logstash&lt;/a&gt; are both popular and have active communities. Hadoop's Hive and Pig can help, but may have slightly steeper learning curves. If you're looking for a hosted log searching service, there's &lt;a href="https://papertrailapp.com/"&gt;papertrail&lt;/a&gt;. Hosted options also vary in features and scope; for example, &lt;a href="http://airbrake.io/pages/home"&gt;Airbrake&lt;/a&gt; (previously called 'hoptoad') focuses on helping you analyze logged errors.&lt;/p&gt;&lt;h2 id="and-then"&gt;And then?&lt;/h2&gt;&lt;p&gt;Companies like Splunk have figured out that there is money to be made from your logs, and web advertising companies log everything because logs are money, so don't just treat your logs like they're a painful artifact that can only be managed with aggressive log rotation policies.&lt;/p&gt;&lt;p&gt;Centralize your logs somewhere and build some tools around them. You'll get faster at debugging problems and be able to better answer business and operations questions.&lt;/p&gt;&lt;h2 id="further-reading"&gt;Further Reading&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;Log4j has a cool feature called &lt;a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/MDC.html"&gt;MDC&lt;/a&gt; and &lt;a href="http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/NDC.html"&gt;NDC&lt;/a&gt; that lets you log more than just a text message.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fedorahosted.org/logrotate/"&gt;logrotate&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3615332969083650973-8619634335742740271?l=sysadvent.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/sysadvent/~4/i6GksANWTRs" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sysadvent.blogspot.com/feeds/8619634335742740271/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=3615332969083650973&amp;postID=8619634335742740271" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/8619634335742740271?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/8619634335742740271?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/sysadvent/~3/i6GksANWTRs/day-16-shipping-some-logs.html" title="Day 16 - Shipping Some Logs" /><author><name>Jordan Sissel</name><uri>http://www.blogger.com/profile/04677274979347685821</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>1</thr:total><feedburner:origLink>http://sysadvent.blogspot.com/2011/12/day-16-shipping-some-logs.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0AFSXgzeyp7ImA9WhRQGUk.&quot;"><id>tag:blogger.com,1999:blog-3615332969083650973.post-7048325371250491345</id><published>2011-12-15T01:35:00.000-08:00</published><updated>2011-12-15T01:35:18.683-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-15T01:35:18.683-08:00</app:edited><title>Day 15 - Automating WordPress with CFEngine</title><content type="html">&lt;p&gt;System administration is a relatively new profession. Without a standard curriculum, practitioners have different philosophies and practices. It is challenging for new sysadmins because every organization implements differently: the how and why of system setup, how and why of maintenance, and the how and why of disaster recovery and growth.&lt;/p&gt;&lt;p&gt;A software tool can respond faster than a human sysadmin to a deviation from configuration policy (something being broken). The corrective action can be automated, so chaos is kept to a minimum while not requiring human action.&lt;/p&gt;&lt;p&gt;Why WordPress? Installing WordPress involves coordinating multiple system components into a harmonious whole. It is a great demonstration of the power of automated configuration management. It involves copying and editing files, installing packages, and starting and restarting services.&lt;/p&gt;&lt;p&gt;Manually installing WordPress often takes tens of minutes. An automated install under CFEngine greatly shortens the time required and most importantly provides a repeatable and auditable experience.&lt;/p&gt;&lt;p&gt;Lastly, an introduction to CFEngine is out of scope for this post, but you can learn more &lt;a href="http://www.verticalsysadmin.com/config2010/"&gt;here&lt;/a&gt;, &lt;a href="http://www.verticalsysadmin.com/cfengine/Getting_Started_with_CFEngine_3.pdf"&gt;here&lt;/a&gt;, and &lt;a href="http://sysadvent.blogspot.com/2009/12/day-24-config-management-with-cfengine.html"&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;h2 id="automating-wordpress-installation"&gt;Automating WordPress Installation&lt;/h2&gt;&lt;p&gt;The two main parts of infrastructure involved in making WordPress work are a web server and a database. In this example, we'll use Apache httpd and MySQL as well as assume a Red Hat (or derivative) system.&lt;/p&gt;&lt;p&gt;The most up to date version of the cfengine implementation of this post can be found here: &lt;a href="https://github.com/cfengine/contrib/raw/master/wordpress_installer.cf"&gt;&lt;code class="url"&gt;https://github.com/cfengine/contrib/raw/master/wordpress_installer.cf&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;You can run this policy with:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;cf-agent -f /var/cfengine/inputs/wordpress_installer.cf
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The rest of this post covers the manual steps you might do to install WordPress and also the equivalent implementation in CFengine.&lt;/p&gt;&lt;h2 id="ordering-things"&gt;Ordering Things&lt;/h2&gt;&lt;p&gt;Below shows the control promise which controls the behavior of cf-agent including which files it should import (the standard Cfengine library) and in what sequences to examine and keep bundles (collections) of promises.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;body common control 
{

        bundlesequence =&amp;gt; {
                                &amp;quot;packages_installed&amp;quot;,
                                &amp;quot;services_up&amp;quot;,
                                &amp;quot;wordpress_tarball_is_present&amp;quot;,
                                &amp;quot;wordpress_tarball_is_unrolled&amp;quot;,
                                &amp;quot;configuration_of_mysql_db_for_wordpress&amp;quot;,
                                &amp;quot;wpconfig_exists&amp;quot;,
                                &amp;quot;wpconfig_is_properly_configured&amp;quot;,
                                &amp;quot;allow_http_inbound&amp;quot;,
                          };

        inputs =&amp;gt;        { &amp;quot;/var/cfengine/inputs/cfengine_stdlib.cf&amp;quot; };
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="get-the-right-packages"&gt;Get the Right Packages&lt;/h2&gt;&lt;p&gt;With that order given above, let's start by ensuring we have all the necessary packages. We will use the &amp;quot;yum&amp;quot; &lt;code&gt;package_method&lt;/code&gt; since we are using a Red Hat derivative.&lt;/p&gt;&lt;p&gt;The &lt;code&gt;packages_installed&lt;/code&gt; bundle depicted in below promises to restart the httpd if any packages are added to cover the case where httpd is up and running, but &amp;quot;php&amp;quot; and &amp;quot;php-mysql&amp;quot; are missing, and Cfengine installs them.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;bundle agent packages_installed
{

vars: &amp;quot;desired_package&amp;quot; slist =&amp;gt; {
          &amp;quot;httpd&amp;quot;,
          &amp;quot;php&amp;quot;,
          &amp;quot;php-mysql&amp;quot;,
          &amp;quot;mysql-server&amp;quot;,
         };

packages: &amp;quot;$(desired_package)&amp;quot;
    package_policy =&amp;gt; &amp;quot;add&amp;quot;,
    package_method =&amp;gt; yum,
    classes =&amp;gt; if_repaired(&amp;quot;packages_added&amp;quot;);

commands:
  packages_added::
  &amp;quot;/sbin/service httpd graceful&amp;quot;
    comment =&amp;gt; &amp;quot;Restarting httpd so it can pick up new modules.&amp;quot;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="apache-and-mysql"&gt;Apache and MySQL&lt;/h2&gt;&lt;p&gt;Now let's make sure httpd and mysqld are running with the &lt;code&gt;services_up&lt;/code&gt; bundle shown below:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;bundle agent services_up {
processes:
  &amp;quot;^mysqld&amp;quot; restart_class =&amp;gt; &amp;quot;start_mysqld&amp;quot;;
  &amp;quot;^httpd&amp;quot;  restart_class =&amp;gt; &amp;quot;start_httpd&amp;quot;;

commands:
  start_mysqld::
    &amp;quot;/sbin/service mysqld start&amp;quot;;
  start_httpd::
    &amp;quot;/sbin/service httpd start&amp;quot;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &amp;quot;restart_class&amp;quot; is used to scan the &amp;quot;ps&amp;quot; output for the named string, and if not found, the right hand side class will be set. We can then use that to launch a command to start the server.&lt;/p&gt;&lt;h2 id="downloading-wordpress"&gt;Downloading WordPress&lt;/h2&gt;&lt;p&gt;The next section shows the &lt;code&gt;wordpress_tarball_is_present&lt;/code&gt; bundle where we make sure we have a copy of WordPress in an arbitrary location - let's say in &lt;code&gt;/root&lt;/code&gt;. We'll need it later to install WordPress under the httpd document root.&lt;/p&gt;&lt;p&gt;We test using Cfengine built-in test function &amp;quot;fileexists()&amp;quot;. If the file exists the &amp;quot;wordpress_tarball_is_present&amp;quot; class gets defined. (A class is Cfengine implicit if/then test. If it is defined, the test passes. If it is not defined it does not. In other words, defined = true, not defined = false.)&lt;/p&gt;&lt;p&gt;If the file does not exist, the &amp;quot;wordpress_tarball_is_present&amp;quot; class will not be defined and the commands promise will download it. If the file does exist, no action will be taken.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;bundle agent wordpress_tarball_is_present
{
classes:
  &amp;quot;wordpress_tarball_is_present&amp;quot; expression =&amp;gt;
    fileexists(&amp;quot;/root/wordpress-latest.tar.gz&amp;quot;);

reports:
  wordpress_tarball_is_present::
    &amp;quot;WordPress tarball is on disk.&amp;quot;;

commands:
  !wordpress_tarball_is_present::
    &amp;quot;/usr/bin/wget -q -O /root/wordpress-latest.tar.gz
http://wordpress.org/latest.tar.gz&amp;quot;
    comment =&amp;gt; &amp;quot;Downloading WordPress.&amp;quot;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="unpacking-wordpress"&gt;Unpacking WordPress&lt;/h2&gt;&lt;p&gt;Next, we test if the WordPress directory exists under the document root (assumed to be &amp;quot;/var/www/html&amp;quot;).&lt;/p&gt;&lt;p&gt;If it doesn't, we'll extract our WordPress tarball to the docroot using &amp;quot;tar&amp;quot;.&lt;/p&gt;&lt;p&gt;Note that the &amp;quot;tar&amp;quot; extract promise depends on the earlier promise that the tar ball is on disk. Because Cfengine does three passes through the promises when it runs: on the first pass, the tar ball will be downloaded if necessary; on the second pass, Cfengine will extract it. This is an example of convergence to desired state, part of the basic philosophy of Cfengine.&lt;/p&gt;&lt;p&gt;Because Cfengine is convergent in its operation, the above cf-agent command can be run multiple times, and the system will always stay at or approach the desired state, never get further away from it. It can fight entropy and system state drift.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;bundle agent wordpress_tarball_is_unrolled
{
classes:
  &amp;quot;wordpress_directory_is_present&amp;quot; expression =&amp;gt;
    fileexists(&amp;quot;/var/www/html/wordpress/&amp;quot;);
reports:
  wordpress_directory_is_present::
    &amp;quot;WordPress directory is present.&amp;quot;;
commands:
  !wordpress_directory_is_present::
    &amp;quot;/bin/tar -C /var/www/html -xvzf /root/wordpress-latest.tar.gz&amp;quot;
      comment =&amp;gt; &amp;quot;Unrolling wordpress tarball to /var/www/html/.&amp;quot;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="configuring-mysql"&gt;Configuring MySQL&lt;/h2&gt;&lt;p&gt;Next, we use the &amp;quot;mysql&amp;quot; command to create the database for the application data store as well as credentials to access it:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;bundle agent configuration_of_mysql_db_for_wordpress
{
commands:
  &amp;quot;/usr/bin/mysql -u root -e \&amp;quot;
    CREATE DATABASE IF NOT EXISTS wordpress;
    GRANT ALL PRIVILEGES ON wordpress.*
    TO 'wordpress'@localhost
    IDENTIFIED BY 'lopsa10linux';
    FLUSH PRIVILEGES;\&amp;quot;
  &amp;quot;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Please note the above command (like all these promise bundles) is convergent to desired state - it will either get us to the desired state if we are not there, or keep us there if we are there already.&lt;/p&gt;&lt;p&gt;The desired state is a &amp;quot;wordpress&amp;quot; database that can be accessed via a &amp;quot;wordpress&amp;quot; user with the password &amp;quot;lopsa10linux&amp;quot;.&lt;/p&gt;&lt;h2 id="adding-the-wordpress-config"&gt;Adding the WordPress Config&lt;/h2&gt;&lt;p&gt;Let's copy the sample config file WordPress ships with to &lt;code&gt;wp-config.php&lt;/code&gt; if it doesn't exist.&lt;/p&gt;&lt;p&gt;First, we check if &lt;code&gt;wp-config.php&lt;/code&gt; exists using the built-in &amp;quot;fileexists()&amp;quot; function. If &lt;code&gt;wp-config.php&lt;/code&gt; exists, this will set a &amp;quot;wordpress_config_file_exists&amp;quot; class.&lt;/p&gt;&lt;p&gt;This class will be used to control what happens next: if the class is set, no changes will be made to the system; we'll just report wp-config.php is there. If the class is not defined, we'll report wp-config.php is not there, and then put it there by copying it from wp-config-sample.php&lt;/p&gt;&lt;pre&gt;&lt;code&gt;bundle agent wpconfig_exists
{
classes:
  &amp;quot;wordpress_config_file_exists&amp;quot;
  expression =&amp;gt; fileexists(&amp;quot;/var/www/html/wordpress/wp-config.php&amp;quot;);
reports:
  wordpress_config_file_exists::
    &amp;quot;WordPress config file /var/www/html/wordpress/wp-config.php is present&amp;quot;;
commands:
  !wordpress_config_file_exists::
  &amp;quot;/bin/cp -p /var/www/html/wordpress/wp-config-sample.php \
    /var/www/html/wordpress/wp-config.php&amp;quot;
    comment =&amp;gt; &amp;quot;Creating wp-config.php from wp-config-sample.php&amp;quot;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here is the &lt;code&gt;wp-config-sample.php&lt;/code&gt; sample config:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define('DB_NAME', 'database_name_here');

/** MySQL database username */
define('DB_USER', 'username_here');

/** MySQL database password */
define('DB_PASSWORD', 'password_here');
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Taking the sample config above, we can use the &amp;quot;replace_patterns&amp;quot; in &lt;code&gt;cfengine_stdlib.cf&lt;/code&gt; to replace &lt;code&gt;database_name_here&lt;/code&gt; with our database name, and so on. Just like using a template, we replace placeholders with actual values.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;bundle agent wpconfig_is_properly_configured
{
files:
  &amp;quot;/var/www/html/wordpress/wp-config.php&amp;quot;
    edit_line =&amp;gt; replace_default_wordpress_config_with_ours;
}

bundle edit_line replace_default_wordpress_config_with_ours
{
replace_patterns:
  &amp;quot;database_name_here&amp;quot; replace_with =&amp;gt; value(&amp;quot;wordpress&amp;quot;);

replace_patterns:
  &amp;quot;username_here&amp;quot; replace_with =&amp;gt; value(&amp;quot;wordpress&amp;quot;);

replace_patterns:
  &amp;quot;password_here&amp;quot; replace_with =&amp;gt; value(&amp;quot;lopsa10linux&amp;quot;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="configure-iptables"&gt;Configure IPTables&lt;/h2&gt;&lt;p&gt;As a finishing touch, let's make sure our host firewall allows inbound connections on port 80 TCP (Figure 9).&lt;/p&gt;&lt;p&gt;The is our most complicated promise bundle. There are three levels of abstraction: a &amp;quot;files&amp;quot; type promise that edits a file using &amp;quot;edit_line&amp;quot; type promise bundle uses &amp;quot;insert_lines&amp;quot; (from cfengine_stdlib.cf) which has an attribute &amp;quot;location&amp;quot; which is defined (in a separate promise attribute body) as before the iptables rule for accepting established TCP connections.&lt;/p&gt;&lt;p&gt;Incidentally, this promise bundle will also restart iptables if it edits the iptables config file.&lt;/p&gt;&lt;p&gt;Abstracting the details allows the sysadmin to see at a high level what's going on without being blinded by too many details at once, yet the details are accessible to examination if needed.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;bundle agent allow_http_inbound
{
files:
  redhat::  # tested on RHEL only, file location may vary on other OSs
  &amp;quot;/etc/sysconfig/iptables&amp;quot;
    edit_line =&amp;gt; insert_HTTP_allow_rule_before_the_accept_established_tcp_conns_rule,
    comment =&amp;gt; &amp;quot;insert HTTP allow rule into /etc/sysconfig/iptables&amp;quot;,
    classes =&amp;gt; if_repaired(&amp;quot;iptables_edited&amp;quot;);
commands:
  iptables_edited::
  &amp;quot;/sbin/service iptables restart&amp;quot;
    comment =&amp;gt; &amp;quot;Restarting iptables to load new config&amp;quot;;
}

bundle edit_line insert_HTTP_allow_rule_before_the_accept_established_tcp_conns_rule
{
vars:
  &amp;quot;http_rule&amp;quot; string =&amp;gt; &amp;quot;-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT&amp;quot;;
insert_lines: &amp;quot;$(http_rule)&amp;quot;,
  location =&amp;gt; before_the_accept_established_tcp_conns_rule;
}

body location before_the_accept_established_tcp_conns_rule
{
before_after =&amp;gt; &amp;quot;before&amp;quot;;
first_last =&amp;gt; &amp;quot;first&amp;quot;;
select_line_matching =&amp;gt; &amp;quot;^-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT.*&amp;quot;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To summarize, here is what the policy can do:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Install Web server and required httpd modules (php, php-mysql);&lt;/li&gt;
&lt;li&gt;Install Web app in httpd docroot&lt;/li&gt;
&lt;li&gt;Install and configure the database for the Web app&lt;/li&gt;
&lt;li&gt;Configure the Web app to use the database&lt;/li&gt;
&lt;li&gt;Configure the host firewall&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;There is a more sophisticated version of this automated WordPress installer in Aleksey's &amp;quot;CFEngine 3 Examples Collection&amp;quot; ( http://www.verticalsysadmin.com/cfengine/cfengine_examples.tar ), see 2030_More_Examples._EC2._Example102_wordpress_installation.cf&lt;/p&gt;&lt;h2 id="further-reading"&gt;Further Reading&lt;/h2&gt;&lt;p&gt;Aleksey has been a UNIX/Linux system administrator for 13 years, and will share his knowledge during the &amp;quot;Time Management for System Administrators&amp;quot; session at the So Cal Linux Expo on 20 Jan 2011 (http://www.socallinuxexpo.org/scale10x/events/scale-university) and &amp;quot;Automating System Administration using CFEngine 3&amp;quot;, a 3 day hands-on course, in the Bay Area on 25-27 January 2012 and in Los Angeles on 20-22 February 2012.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.cfengine.com/"&gt;&lt;code class="url"&gt;http://www.cfengine.com/&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.verticalsysadmin.com/cfengine/"&gt;&lt;code class="url"&gt;http://www.verticalsysadmin.com/cfengine/&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.stevencharlesrobinson.com/sites/scrobinson.nsf/docs/Automated%20WordPress"&gt;Automating Wordpress with Puppet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3615332969083650973-7048325371250491345?l=sysadvent.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/sysadvent/~4/awN16NPAkD4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sysadvent.blogspot.com/feeds/7048325371250491345/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=3615332969083650973&amp;postID=7048325371250491345" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/7048325371250491345?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/7048325371250491345?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/sysadvent/~3/awN16NPAkD4/day-15-automating-wordpress-with.html" title="Day 15 - Automating WordPress with CFEngine" /><author><name>Jordan Sissel</name><uri>http://www.blogger.com/profile/04677274979347685821</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://sysadvent.blogspot.com/2011/12/day-15-automating-wordpress-with.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkcFRXg6eyp7ImA9WhRQGEg.&quot;"><id>tag:blogger.com,1999:blog-3615332969083650973.post-3583949727448230794</id><published>2011-12-14T00:06:00.000-08:00</published><updated>2011-12-14T00:06:54.613-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-14T00:06:54.613-08:00</app:edited><title>Day 14 - Write Your Resume in Markdown Already!</title><content type="html">&lt;p&gt;This was written by &lt;a href="http://www.twitter.com/philiph"&gt;Phil Hollenback&lt;/a&gt; (&lt;a href="http://www.hollenback.net"&gt;www.hollenback.net&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;Like most of you, I hate dealing with my resume. That means I don't keep it as current as I should. Over the years, I've kept my resume in all sorts of formats: straight text, nroff, TeX, etc. I think I might have even handcrafted a postscript resume once.&lt;/p&gt;&lt;p&gt;However, I got so tired of recruiters always asking for a Word version of my resume that somewhere along the way I gave up and wrote one in Word (probably OpenOffice now that I think about it). I saved that as a pdf and an html version and forgot about it. That seemed to work ok since I've been gainfully employed for the last decade or so.&lt;/p&gt;&lt;p&gt;However, I recently got a promotion at work so I decided it was time to revisit my resume. Frankly it was a little embarrassing as much of my resume was no longer particularly current or relevant. Does anyone really care that I once worked as a Tcl programmer? Also there's probably no real need for me to list jobs from 10 years ago that I barely remember. Thus, I decided to rewrite my resume.&lt;/p&gt;&lt;p&gt;Speaking of promotions, I often see folks only maintaining their resumes when considering job changes, but you should keep it updated! A record of accomplishments at your current job is useful in times of performance reviews or promotion consideration. For company internal things, you can maintain an internal company resume.&lt;/p&gt;&lt;h2 id="selecting-a-markup-language"&gt;Selecting a Markup Language&lt;/h2&gt;&lt;p&gt;You sysadmins can see where this is going: I used my resume rewrite as an excuse to try new tools. Specifically, I wanted to answer the question of how a system administrator should be managing their resume in 2011. I decided on a few criteria:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;human-readable source format&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;I should be able to look at the original document and understand it. That presumably means straight ASCII text.&lt;/li&gt;
&lt;li&gt;(yes I know that's US English only).&lt;/li&gt;
&lt;li&gt;This also eliminates the 'oops, my deleted text is still in the file' problem with Word.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;PDF output&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;PDFs seem the most portable way to exchange documents and ensure that formatting remains intact.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;HTML output&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;I want a version of my resume I can post on a website.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Open-source tools&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;No lockdown in proprietary formats.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;the &lt;em&gt;cool factor&lt;/em&gt;&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;I wanted something fresh and interesting that I could (for example) blog about.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;With these constraints, it was pretty clear I'd have to choose something like &lt;a href="https://en.wikipedia.org/wiki/Markdown"&gt;Markdown&lt;/a&gt; or &lt;a href="https://en.wikipedia.org/wiki/TeX"&gt;TeX&lt;/a&gt; for the my source document and then figure out a way to convert those to various output formats.&lt;/p&gt;&lt;p&gt;I have some experience writing TeX / LaTeX documents, and I knew that would probably be sufficient. For example, I knew I could convert TeX into HTML and PDF fairly easily.&lt;/p&gt;&lt;p&gt;However, while TeX files are somewhat human-readable, they aren't really presentable in that format. The big advantage of formats such as Markdown is that the source document can stand on it's own. That way if I needed an ASCII version of my markdown document I could just use the source file. That wouldn't work for TeX.&lt;/p&gt;&lt;p&gt;As I mentioned, I wanted to try something interesting and fairly new for this exercise, and Markdown certainly is the lightweight markup language of choice these days. Lots of blogging tools support Markdown as a native format, so if I pasted chunks of my resume onto websites they would magically &lt;a href="http://liresume.blogspot.com/2010/12/pro-tip-format-your-resume-using.html"&gt;show up as formatted text&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Finally, Markdown has &lt;em&gt;momentum&lt;/em&gt;. There are lots of other perfectly good alternatives like &lt;a href="https://en.wikipedia.org/wiki/Textile_%28markup_language%29"&gt;textile&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Restructured_text"&gt;reSTructured Text&lt;/a&gt;. However, none of them are the default &lt;a href="http://github.github.com/github-flavored-markdown/"&gt;markdown language on github&lt;/a&gt;.&lt;/p&gt;&lt;h2 id="tools"&gt;Tools&lt;/h2&gt;&lt;p&gt;I'm a hardcore emacs user so I knew I wanted to write my markdown in Emacs. Naturally there's a &lt;a href="http://jblevins.org/projects/markdown-mode/"&gt;Markdown mode for Emacs&lt;/a&gt;. I run Emacs 23.3.1 on my Mac, by the way, and I recommend it. Getting markdown-mode running was as simple as copying the .el file and adding a few bits to my .emacs:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;(autoload 'markdown-mode &amp;quot;markdown-mode.el&amp;quot;
  &amp;quot;Major mode for editing Markdown files&amp;quot; t)
(setq auto-mode-alist
   (cons '(&amp;quot;\\.md&amp;quot; . markdown-mode) auto-mode-alist))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once that was installed I could open a &lt;code&gt;.md&lt;/code&gt; file, make my edits, and preview it as html in my browser with a simple &lt;code&gt;C-c C-c p&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Next I tackled the problem of converting markdown to pdf. Luckily &lt;a href="http://twitter.com/solarce"&gt;Brandon Burton&lt;/a&gt; happened to mention the free markup converter &lt;a href="http://johnmacfarlane.net/pandoc/"&gt;Pandoc&lt;/a&gt; on a mailing list I subscribe to. Pandoc comes with a &lt;a href="http://johnmacfarlane.net/pandoc/README.html#markdown2pdf"&gt;markdown2pdf&lt;/a&gt; script. Based on the name of that script I was pretty sure pandoc could do what I needed.&lt;/p&gt;&lt;p&gt;I installed pandoc from &lt;a href="http://www.macports.org"&gt;macports&lt;/a&gt;, and I should mention that I also had TeX installed from the same source. This is important because pandoc uses TeX in the markdown-&amp;gt;pdf pipeline.&lt;/p&gt;&lt;h2 id="constructing-a-resume"&gt;Constructing A Resume&lt;/h2&gt;&lt;p&gt;Now that I had some tools for creating and processing Markdown documents, I needed to figure out how to structure my resume in Markdown. For this I just stared googling &amp;quot;markdown resume&amp;quot; until I found something I liked. &lt;a href="http://icco.github.com/Resume/"&gt;Nathaniel Welch's resume&lt;/a&gt; is an excellent place to start. I ended up using a similar although somewhat simplified design. I basically just used standard headers and paragraphs all the way down: my name in the largest header size, major headings one size smaller, etc. Something like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# Philip J. Hollenback

philiph@pobox.com

## Overview

I do stuff.

## Experience

### SysAdvent, On The Interwebs

#### Blogger, 2010 to present

Wrote some awesome and some mediocre SysAdvent articles.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;et cetera, et cetera. Note that you should consider using &lt;a href="http://daringfireball.net/projects/markdown/syntax#link"&gt;reference links&lt;/a&gt; for readability as this puts the links at the end of your Markdown document for a more natural text flow.&lt;/p&gt;&lt;h2 id="generating-output"&gt;Generating Output&lt;/h2&gt;&lt;h3 id="tex-time"&gt;TeX Time&lt;/h3&gt;&lt;p&gt;Once you have your resume written in Markdown, it's time to produce some output. I put my &lt;code&gt;resume.md&lt;/code&gt; file in a separate &lt;code&gt;resume&lt;/code&gt; directory and wrote a makefile to handle generating the html and pdf versions. I'll go over that later in this article.&lt;/p&gt;&lt;p&gt;To generate a pdf from my markdown source, I first tried this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;markdown2pdf resume.md
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;which produced a usable pdf file, but there were some problems:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;there was a page number at the bottom of every page&lt;/li&gt;
&lt;li&gt;the margins were much too large&lt;/li&gt;
&lt;li&gt;the font looked like garbage when I printed the pdf file&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;After reading the pandoc documentation and &lt;a href="https://github.com/mwhite/resume"&gt;researching on the web&lt;/a&gt; I figured out how to fix the first two problems. You just need to include a TeX header file to control how the output gets generated. Here's a good starting point for that header:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;\usepackage[top=1in, bottom=1in, left=1.25in, right=1.25in]{geometry}
\pagestyle{empty}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That gave me a resume with much more narrow margins, and turned off the page numbers. All I had to do was include the header file when running markdown2pdf:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;markdown2pdf -o resume.pdf -H header.tex resume.md
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;that fixed all my problems except the ugly font. Fixing that turned out to be a little more complicated: I had to turn on XeTeX and create an entirely new output template. That's the only way I could find to easily select new fonts for TeX on my mac. I'm hoping people who read this have better solutions.&lt;/p&gt;&lt;p&gt;I ran &lt;code&gt;fc-list&lt;/code&gt; to find all my available system fonts. I ended up using that delightful old standard, Times New Roman.&lt;/p&gt;&lt;p&gt;Next, I dumped out the LaTeX template that pandoc uses by default to use as a starting point:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;pandoc -D latex &amp;gt; resume-template.tex
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;and modified that file to incorporate my header changes and add font selection to the xetex section. I also forced the font size to 11 points:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;@@ -1,9 +1,16 @@
-\documentclass$if(fontsize)$[$fontsize$]$endif${article}
+\documentclass[fontsize=11pt]{article}
 \usepackage{amssymb,amsmath}
+
+\usepackage[top=1in, bottom=1in, left=1.25in, right=1.25in]{geometry}
+\pagestyle{empty}
+
$if(xetex)$
 \usepackage{ifxetex}
 \ifxetex
 \usepackage{fontspec,xltxtra,xunicode}
+  \setmainfont{Times New Roman}
+  \setsansfont{Verdana}
+  \setmonofont{Courier New}
 \defaultfontfeatures{Mapping=tex-text,Scale=MatchLowercase}
 \else
 \usepackage[mathletters]{ucs}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;(you can &lt;a href="http://www.hollenback.net/personal/resume-template.tex"&gt;download the full template from my website&lt;/a&gt;).&lt;/p&gt;&lt;p&gt;The final step was to use the &lt;code&gt;--xetex&lt;/code&gt; option to &lt;code&gt;markdown2pdf&lt;/code&gt; to use xetex and enable my custom font. My markdown2pdf invocation thus became:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;markdown2pdf --template=resume-template.tex --xetex resume.md
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;the end result of this is a &lt;a href="http://www.hollenback.net/personal/resume-phil.pdf"&gt;nice clean pdf file&lt;/a&gt;. Compare this to the &lt;a href="http://www.hollenback.net/personal/resume-phil.md"&gt;original Markdown&lt;/a&gt;.&lt;/p&gt;&lt;h3 id="html"&gt;HTML&lt;/h3&gt;&lt;p&gt;The final piece of the output puzzle is HTML. Convert the markdown to html by invoking pandoc directly:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;pandoc -o resume.html resume.md
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That results in a serviceable but not very attractive html file. Fortunately pandoc allows you to also use a css file to change the html output. I used &lt;a href="https://github.com/mwhite/resume"&gt;Michael White's css file&lt;/a&gt; as starting point and tweaked the colors slightly. Then, I invoke pandoc as follows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;pandoc -t html -o resume.html -c resume.css resume.md
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can see the final result &lt;a href="http://www.hollenback.net/personal/resume-phil.html"&gt;on my website&lt;/a&gt;. I'm quite happy with the fit and finish.&lt;/p&gt;&lt;h3 id="automation"&gt;Automation&lt;/h3&gt;&lt;p&gt;No SysAdvent article is complete without a section on automation. To wrap this whole thing together, I &lt;a href="http://www.hollenback.net/personal/Makefile"&gt;wrote a makefile&lt;/a&gt; (as I mentioned earlier). I also wrote a perl script to automatically update my resume files on my website when they changed. First, the makefile:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;all:    resume.html resume.pdf

%.html: %.md
    pandoc -t html -o $@ $&amp;lt; -c resume.css
    ./resume-uploader $@ $&amp;lt; resume.css

%.pdf:  %.md
    markdown2pdf --template=resume-template.tex --xetex $&amp;lt;
    ./resume-uploader $@ $&amp;lt; resume-template.tex

clean:
    rm -f *~ *.html *.log *.pdf
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The perl script is a bit too long to post here, but you can find it &lt;a href="http://www.hollenback.net/personal/resume-uploader"&gt;on my website as well&lt;/a&gt;. This script takes a filename as input, and checks if the file exists on the server. If it exists and the version on the server is older than the one just generated, the script uploads the file. Note that authentication is handled via the &lt;code&gt;.netrc&lt;/code&gt; file.&lt;/p&gt;&lt;p&gt;Now I have a one-stop mechanism for updating my resume. I just load up my &lt;code&gt;resume.md&lt;/code&gt; file in emacs and use markdown-mode to edit it. I can hit &lt;code&gt;C-c C-c p&lt;/code&gt; at any time to view it in html in my browser. When I'm happy with the contents, I run my makefile with &lt;code&gt;C-x C-k&lt;/code&gt; to build fresh html and pdf versions. Finally, the uploader script uploads the new versions of my resume and support files to my website as appropriate.&lt;/p&gt;&lt;p&gt;Another enhancement to consider is putting this whole thing in git and hosting it on &lt;a href="http://pages.github.com/"&gt;github pages&lt;/a&gt; (did I mention that github supports Markdown as a native format)?&lt;/p&gt;&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;&lt;p&gt;So there you have my account of how I converted my resume to Markdown. In general, I'm very pleased with this approach: I get the simplicity of a text source file combined with decent HTML and PDF output. With a little bit of sysadmin-compatible automation, I've turned this whole thing in to a repeatable workflow. The best thing about this approach is the flexibility. If I want to host my resume on github, it's ready to go. If I want to change my fonts or html output, I just have to make a few simple changes to the input files. Most importantly, I'm not locked in to a proprietary format like Microsoft Word.&lt;/p&gt;&lt;p&gt;If you are interested in using my resume as a starting point for your own, I suggest you consider some &lt;a href="https://github.com/tels7ar/resume"&gt;hardcore github forking action&lt;/a&gt;.&lt;/p&gt;&lt;h2 id="further-reading"&gt;Further Reading&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;This one got me started: &lt;a href="https://github.com/icco/Resume/blob/master/resume.md"&gt;icco's markdown resume on github&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Ycombinator: &lt;a href="http://news.ycombinator.com/item?id=1863688"&gt;maintain your resume on github with markdown&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;edrex has a &lt;a href="https://github.com/edrex/resume"&gt;markdown resume on github&lt;/a&gt; that include html css so it renders nicely for the web via pandoc.&lt;/li&gt;
&lt;li&gt;mwhite's &lt;a href="https://github.com/mwhite/resume"&gt;markdown resume on github&lt;/a&gt; - includes css &lt;em&gt;and&lt;/em&gt; a tex header so both formats render similarly.&lt;/li&gt;
&lt;li&gt;Here's some &lt;a href="https://github.com/claes/pandoc-templates"&gt;pandoc templates&lt;/a&gt; that illustrate how to override tex fonts.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;em&gt;Editor's note&lt;/em&gt;: This year's sysadvent posts are all written in markdown. They are converted them to HTML using pandoc for publishing on Blogger.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3615332969083650973-3583949727448230794?l=sysadvent.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/sysadvent/~4/mE9Z3i4dA5M" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sysadvent.blogspot.com/feeds/3583949727448230794/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=3615332969083650973&amp;postID=3583949727448230794" title="6 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/3583949727448230794?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/3583949727448230794?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/sysadvent/~3/mE9Z3i4dA5M/day-14-write-your-resume-in-markdown.html" title="Day 14 - Write Your Resume in Markdown Already!" /><author><name>Jordan Sissel</name><uri>http://www.blogger.com/profile/04677274979347685821</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>6</thr:total><feedburner:origLink>http://sysadvent.blogspot.com/2011/12/day-14-write-your-resume-in-markdown.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0MFSXo7fyp7ImA9WhRQF0s.&quot;"><id>tag:blogger.com,1999:blog-3615332969083650973.post-5583412889031600349</id><published>2011-12-13T00:36:00.000-08:00</published><updated>2011-12-13T00:36:58.407-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-13T00:36:58.407-08:00</app:edited><title>Day 13 - Test Driven Infrastructure with Vagrant, Puppet and Guard</title><content type="html">&lt;p&gt;This was written by &lt;a href="https://twitter.com/patrickdebois"&gt;Patrick Debois&lt;/a&gt; (&lt;a href="http://www.jedi.be/blog/"&gt;www.jedi.be&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;What's the next step once you implement some config management tools? Testing, right? Wouldn't you like to automate that with some nice clean and sandboxed environment for testing your config management changes?&lt;/p&gt;&lt;p&gt;You can use &lt;a href="http://vagrantup.com"&gt;Vagrant&lt;/a&gt; to help ease the VM management. It is &lt;a href="http://www.slideshare.net/jedi4ever/vagrant-devopsdays-mountain-view-2011"&gt;a great tool&lt;/a&gt; often used as a sandbox environment to develop Chef recipes or Puppet manifests, and this workflow usually looks like this:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;you create a vagrant vm&lt;/li&gt;
&lt;li&gt;share some puppet/chef files via a shared directory&lt;/li&gt;
&lt;li&gt;edit some files locally&lt;/li&gt;
&lt;li&gt;run a &lt;code&gt;vagrant provision&lt;/code&gt; to see if this works&lt;/li&gt;
&lt;li&gt;and if you are happy with it, commit it to your favorite version control repository&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Specifically for puppet, thanks to the great work by &lt;a href="http://twitter.com/nistude"&gt;Nikolay Sturm&lt;/a&gt; and &lt;a href="http://twitter.com/rodjek"&gt;Tim Sharpe&lt;/a&gt;, we can now also complement this with tests written in &lt;a href="https://github.com/rodjek/rspec-puppet"&gt;rspec-puppet&lt;/a&gt; and &lt;a href="https://github.com/nistude/cucumber-puppet"&gt;cucumber-puppet&lt;/a&gt;. You can find more info at &lt;a href="http://www.jedi.be/blog/2011/12/05/puppet-unit-testing-like-a-pro/"&gt;Puppet unit testing like a pro&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;So we got code, and we got tests, what else are we missing? &lt;strong&gt;Automation&lt;/strong&gt; of this process: it's funny, if you think of it, that we automate the hell out of server installations, but we haven't automated the previous described process.&lt;/p&gt;&lt;p&gt;The need to manually run &lt;code&gt;vagrant provision&lt;/code&gt; or &lt;code&gt;rake rspec&lt;/code&gt; actually breaks my development flow: I have to leave my editor to run a shell command and then come back to it depending on the output.&lt;/p&gt;&lt;p&gt;Would it not be great if we could automate this whole cycle? And have it run tests and provision whenever files change?&lt;/p&gt;&lt;h2 id="how"&gt;How&lt;/h2&gt;&lt;p&gt;The first tool I came across is &lt;a href="https://github.com/autotest/autotest"&gt;autotest&lt;/a&gt;: it allows one to automatically re-execute tests depending on filesystem changes. Downside is that it could either run cucumber tests or rspec tests.&lt;/p&gt;&lt;p&gt;So enter &lt;a href="https://github.com/guard/guard"&gt;Guard&lt;/a&gt;; it describes itself as &lt;em&gt;a command line tool to easily handle events on file system modifications (FSEvent / Inotify / Polling support)&lt;/em&gt;. Just what we wanted!&lt;/p&gt;&lt;p&gt;Installing Guard is pretty easy, you require the following gems in your Gemfile&lt;/p&gt;&lt;pre&gt;&lt;code&gt;gem 'guard'
gem 'rb-inotify', :require =&amp;gt; false
gem 'rb-fsevent', :require =&amp;gt; false
gem 'rb-fchange', :require =&amp;gt; false
gem 'growl', :require =&amp;gt; false
gem 'libnotify', :require =&amp;gt; false
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As you can tell by the names, it uses different strategies to detect changes in your directories. It uses growl (if correctly setup) on Mac OS X and libnotify on Linux to notify you if your tests pass or fail. Once installed you get a command &lt;code&gt;guard&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Guard uses a configuration file &lt;code&gt;Guardfile&lt;/code&gt;, which can be created by &lt;code&gt;guard init&lt;/code&gt;. In this file you define different guards based on different helpers: for example there is &lt;a href="http://github.com/guard/guard-rspec"&gt;guard-rspec&lt;/a&gt;, &lt;a href="http://github.com/guard/guard-cucumber"&gt;guard-cucumber&lt;/a&gt; and &lt;a href="http://github.com/guard"&gt;many more&lt;/a&gt;. There is even a &lt;a href="http://github.com/guard/guard-puppet"&gt;guard-puppet&lt;/a&gt;(which we will not use because it works only for local provisioning)&lt;/p&gt;&lt;p&gt;To install one of these helpers you just include it in your Gemfile. We are using only two here:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;gem 'guard-rspec'
gem 'guard-cucumber'
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Each of these helpers has a similar way of configuring themselves inside a Guardfile. A vanilla guard for a ruby gem with rspec testing would look like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;guard 'rspec' do
  watch(%r{^spec/.+_spec\.rb$})
  watch(%r{^lib/(.+)\.rb$})     { |m| &amp;quot;spec/lib/#{m[1]}_spec.rb&amp;quot; }
  watch('spec/spec_helper.rb')  { &amp;quot;spec&amp;quot; }
end
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Whenever a file that matches a watch expression changes, it would run an rspec test. By default if no block is supplied, the file itself is run. You can alter the path in a block as in the example.&lt;/p&gt;&lt;p&gt;Once you have a &lt;code&gt;Guardfile&lt;/code&gt; you simply run &lt;code&gt;guard&lt;/code&gt; (or &lt;code&gt;bundle exec guard&lt;/code&gt;) to have it watch changes. Simple hu?&lt;/p&gt;&lt;h2 id="vagrant-setup"&gt;Vagrant setup&lt;/h2&gt;&lt;p&gt;Enter our sample puppet/vagrant project. You can find the full source at &lt;a href="http://github.com/jedi4ever/vagrant-guard-demo"&gt;&lt;code class="url"&gt;http://github.com/jedi4ever/vagrant-guard-demo&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;It's a typical vagrant project with the following tree structure:(only 3 levels shown)&lt;/p&gt;&lt;pre&gt;&lt;code&gt;├── Gemfile
├── Gemfile.lock
├── Guardfile
├── README.markdown
├── Vagrantfile
├── definitions # Veewee definitions
│   └── lucid64
│       ├── definition.rb
│       ├── postinstall.sh
│       └── preseed.cfg
├── iso # Veewee iso
│   └── ubuntu-10.04.3-server-amd64.iso
└── vendor
    └── ruby
        └── 1.8
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="puppet-setup"&gt;Puppet setup&lt;/h2&gt;&lt;p&gt;The project follows Jordan Sissel's idea of &lt;a href="http://www.semicomplete.com/blog/geekery/puppet-nodeless-configuration"&gt;puppet nodeless configuration&lt;/a&gt;. To specify the classes to apply to a host, we use a fact called: &lt;code&gt;server_role&lt;/code&gt;. We read this from a file &lt;code&gt;data/etc/server_tags&lt;/code&gt; via &lt;a href="https://github.com/jedi4ever/vagrant-guard-demo/blob/master/puppet-repo/modules/truth/lib/facter/server_tags.rb"&gt;a custom fact&lt;/a&gt; (inspired by &lt;a href="http://nuknad.com/2011/02/11/self-classifying-puppet-nodes/"&gt;self-classifying puppet node&lt;/a&gt;).&lt;/p&gt;&lt;p&gt;This allows us to only require one file, &lt;code&gt;site.pp&lt;/code&gt;. And we don't have to fiddle with our hostname to get the correct role. Also if we want to test multiple roles on this one test machine, just add another role to the &lt;code&gt;data/etc/server_tags&lt;/code&gt; file.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;├── data
│   └── etc
│       └── server_tags

$ cat data/etc/server_tags
role:webserver=true
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The puppet modules and manifests can be found in &lt;code&gt;puppet-repo&lt;/code&gt;. It has class &lt;code&gt;role::webserver&lt;/code&gt; which includes class &lt;code&gt;apache&lt;/code&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;puppet-repo
├── features # This is where the cucucumber-puppet catalog policy feature lives
│   ├── catalog_policy.feature
│   ├── steps
│   │   ├── catalog_policy.rb
│   └── support
│       ├── hooks.rb
│       └── world.rb
├── manifests
│   └── site.pp #No nodes required
└── modules
    ├── apache
    |    &amp;lt;module content&amp;gt;
    ├── role
    │   ├── manifests
    │   │   └── webserver.pp # Corresponds with the role specified
    │   └── rspec
    │       ├── classes
    │       └── spec_helper.rb
    └── truth # Logic of puppet nodeless configuration
        ├── lib
        │   ├── facter
        │   └── puppet
        └── manifests
            └── enforcer.pp
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="puppet---vagrant-setup"&gt;Puppet - Vagrant setup&lt;/h3&gt;&lt;p&gt;These are the settings we use in our Vagrant file to make puppet work:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;config.vm.share_folder &amp;quot;v-data&amp;quot;, &amp;quot;/data&amp;quot;, File.join(File.dirname(__FILE__), &amp;quot;data&amp;quot;)
# Enable provisioning with Puppet stand alone.  Puppet manifests
# are contained in a directory path relative to this Vagrantfile.
config.vm.provision :puppet, :options =&amp;gt; &amp;quot;--verbose&amp;quot;  do |puppet|
  puppet.module_path = [&amp;quot;puppet-repo/modules&amp;quot;]
  puppet.manifests_path = &amp;quot;puppet-repo/manifests&amp;quot;
  puppet.manifest_file  = &amp;quot;site.pp&amp;quot;
end
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="puppet-tests-setup"&gt;Puppet tests setup&lt;/h3&gt;&lt;p&gt;The cucumber-puppet tests will check if the catalog compiles for role &lt;code&gt;role::webserver&lt;/code&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Feature: Catalog policy
  In order to ensure basic correctness
  I want all catalogs to obey my policy

  Scenario Outline: Generic policy for all server roles
    Given a node with role &amp;quot;&amp;lt;server_role&amp;gt;&amp;quot;
    When I compile its catalog
    Then compilation should succeed
    And all resource dependencies should resolve

    Examples:
      | server_role |
      | role::webserver |
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The rspec-puppet tests will check if the package &lt;code&gt;http&lt;/code&gt; gets installed&lt;/p&gt;&lt;pre&gt;&lt;code&gt;require &amp;quot;#{File.join(File.dirname(__FILE__),'..','spec_helper')}&amp;quot;
describe 'role::webserver', :type =&amp;gt; :class do
  let(:facts) {{:server_tags =&amp;gt; 'role:webserver=true',
      :operatingsystem =&amp;gt; 'Ubuntu'}}
  it { should include_class('apache') }
  it { should contain_package('httpd').with_ensure('present') }
end
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="guard-setup"&gt;Guard setup&lt;/h3&gt;&lt;p&gt;To make Guard work with a setup like our &lt;code&gt;puppet-repo&lt;/code&gt; directory we need to change some things.&lt;/p&gt;&lt;p&gt;This has mostly to do with conventions used in development projects where Guard is normally used.&lt;/p&gt;&lt;h4 id="fixing-guard-cucumber-to-read-from-puppetrepofeatures"&gt;Fixing Guard-Cucumber to read from puppetrepo/features&lt;/h4&gt;&lt;p&gt;The first problem is that the Guard-Cucumber gem standard reads it's features from &lt;code&gt;features&lt;/code&gt; directory. This is actually hardcoded in the gem. But nothing a little monkey patching can't solve:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;require 'guard/cucumber'

# Inline extending the ::Guard::Cucumber
# Because by default it only looks in the ['features'] directory
# We have it in ['puppet-repo/features']
module ::Guard
  class ExtendedCucumber &amp;lt; ::Guard::Cucumber
    def run_all
      passed = Runner.run(['puppet-repo/features'], options.merge(options[:run_all] || { }).merge(:message =&amp;gt; 'Running all features'))

      if passed
        @failed_paths = []
      else
        @failed_paths = read_failed_features if @options[:keep_failed]
      end

      @last_failed = !passed

      throw :task_has_failed unless passed
    end
  end
end

# Monkey patching the Inspector class
# By default it checks if it starts with /feature/
# We tell it that whatever we pass is valid
module ::Guard
  class Cucumber
    module Inspector
      class &amp;lt;&amp;lt; self
        def cucumber_folder?(path)
          return true
        end
      end
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="orchestration-of-guard-runs"&gt;Orchestration of guard runs&lt;/h4&gt;&lt;p&gt;The second problem was to have Guard only execute the Vagrant provision when &lt;strong&gt;BOTH&lt;/strong&gt; the cucumber and rspec tests would be OK.&lt;/p&gt;&lt;p&gt;Inspired by &lt;a href="https://github.com/guard/guard/issues/189#issuecomment-3097145"&gt;the comments&lt;/a&gt; of &lt;a href="https://github.com/netzpirat"&gt;Netzpirat&lt;/a&gt;, I got it working so that the block &lt;code&gt;vagrant provision&lt;/code&gt; would only execute on both tests being complete.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# This block simply calls vagrant provision via a shell
# And shows the output
def vagrant_provision
  IO.popen(&amp;quot;vagrant provision&amp;quot;) do |output|
    while line = output.gets do
      puts line
    end
  end
end

# So determine if all tests (both rspec and cucumber have been passed)
# This is used to only invoke the vagrant_provision if all tests show green
def all_tests_pass
  cucumber_guard = ::Guard.guards({ :name =&amp;gt; 'extendedcucumber', :group =&amp;gt; 'tests'}).first
  cucumber_passed = cucumber_guard.instance_variable_get(&amp;quot;@failed_paths&amp;quot;).empty?
  rspec_guard = ::Guard.guards({ :name =&amp;gt; 'rspec', :group =&amp;gt; 'tests'}).first
  rspec_passed = rspec_guard.instance_variable_get(&amp;quot;@failed_paths&amp;quot;).empty?
  return rspec_passed &amp;amp;&amp;amp; cucumber_passed
end
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="guard-matchers"&gt;Guard matchers&lt;/h3&gt;&lt;p&gt;With all the correct guards and logic setup, it's time to specify the correct options to our Guards.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;group :tests do

  # Run rspec-puppet tests
  # --format documentation : for better output
  # :spec_paths to pass the correct path to look for features
  guard :rspec, :version =&amp;gt; 2, :cli =&amp;gt; &amp;quot;--color --format documentation&amp;quot;, :spec_paths =&amp;gt; [&amp;quot;puppet-repo&amp;quot;]  do
    # Match any .pp file (but be carefull not to include any dot-temporary files)
    watch(%r{^puppet-repo/.*/[^.]*\.pp$}) { &amp;quot;puppet-repo&amp;quot; }
    # Match any .rb file (but be carefull not to include any dot-temporary files)
    watch(%r{^puppet-repo/.*/[^.]*\.rb$}) { &amp;quot;puppet-repo&amp;quot; }
    # Match any _rspec.rb file (but be carefull not to include any dot-temporary files)
    watch(%r{^puppet-repo/.*/[^.]*_rspec.rb})
  end

  # Run cucumber puppet tests
  # This uses our extended cucumber guard, as by default it only looks in the features directory
  # --strict        : because otherwise cucumber would exit with 0 when there are pending steps
  # --format pretty : to get readable output, default is null output
  guard :extendedcucumber, :cli =&amp;gt; &amp;quot;--require puppet-repo/features --strict --format pretty&amp;quot; do

    # Match any .pp file (but be carefull not to include any dot-temporary files)
    watch(%r{^puppet-repo/[^.]*\.pp$}) { &amp;quot;puppet-repo/features&amp;quot; }

    # Match any .rb file (but be carefull not to include any dot-temporary files)
    watch(%r{^puppet-repo/[^.]*\.rb$}) { &amp;quot;puppet-repo/features&amp;quot; }

    # Feature files are monitored as well
    watch(%r{^puppet-repo/features/[^.]*.feature})

    # This is only invoked on changes, not at initial startup
    callback(:start_end) do
      vagrant_provision if all_tests_pass
    end
    callback(:run_on_change_end) do
      vagrant_provision if all_tests_pass
    end
  end

end
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The full &lt;a href="http://github.com/jedi4ever/vagrant-guard-demo/Guardfile"&gt;Guardfile is on github&lt;/a&gt;&lt;/p&gt;&lt;h3 id="run-it"&gt;Run it&lt;/h3&gt;&lt;p&gt;From within the top directory of the project type&lt;/p&gt;&lt;pre&gt;&lt;code&gt;$ guard
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now open a second terminal and change some of the files and watch the magic happen.&lt;/p&gt;&lt;h1 id="final-remarks"&gt;Final remarks&lt;/h1&gt;&lt;p&gt;The setup described is an idea I only recently started exploring. I'll probably enhance this in the future or may experience other problems.&lt;/p&gt;&lt;p&gt;For the demo project, I only call &lt;code&gt;vagrant provision&lt;/code&gt;, but this can of course be extended easily. Some ideas:&lt;/p&gt;&lt;ol style="list-style-type: decimal"&gt;&lt;li&gt;Inspired by &lt;a href="http://paperairoplane.net/?p=240"&gt;Oliver Hookins - How we use Vagrant as a throwaway testing environment&lt;/a&gt;:&lt;/li&gt;
&lt;li&gt;use &lt;a href="http://github.com/jedi4ever/sahara"&gt;sahara&lt;/a&gt; to create a snapshot just before the provisioning&lt;/li&gt;
&lt;li&gt;have it start from a clean machine when all tests pass&lt;/li&gt;
&lt;li&gt;Turn this into a guard-vagrant gem, to monitor files and tests&lt;/li&gt;
&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3615332969083650973-5583412889031600349?l=sysadvent.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/sysadvent/~4/wSx7ykM_iEU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sysadvent.blogspot.com/feeds/5583412889031600349/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=3615332969083650973&amp;postID=5583412889031600349" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/5583412889031600349?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/5583412889031600349?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/sysadvent/~3/wSx7ykM_iEU/day-13-test-driven-infrastructure-with.html" title="Day 13 - Test Driven Infrastructure with Vagrant, Puppet and Guard" /><author><name>Jordan Sissel</name><uri>http://www.blogger.com/profile/04677274979347685821</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>4</thr:total><feedburner:origLink>http://sysadvent.blogspot.com/2011/12/day-13-test-driven-infrastructure-with.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUUFQXw6cCp7ImA9WhRQFko.&quot;"><id>tag:blogger.com,1999:blog-3615332969083650973.post-5713261230923234482</id><published>2011-12-12T00:00:00.000-08:00</published><updated>2011-12-12T00:06:50.218-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-12T00:06:50.218-08:00</app:edited><title>Day 12 - Reverse-engineer servers with Blueprint</title><content type="html">&lt;br /&gt;
&lt;p&gt;This was written by &lt;a href="https://twitter.com/rcrowley"&gt;Richard Crowley&lt;/a&gt; (&lt;a href="http://rcrowley.org/"&gt;rcrowley.org&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;The benefits of using a configuration management tool can be felt by even the smallest of teams, yet most still rely on wiki pages and handwritten shell scripts to keep things straight because the barriers to entry in traditional configuration management tools are too high, both mentally and technically.&lt;/p&gt;&lt;p&gt;You could be comfortable with how to configure a server by hand, but you don't know how to use the right tools to automate that configuration. &lt;a href="http://devstructure.com/"&gt;We&lt;/a&gt; set out to build a configuration management tool that addresses common problems in an approachable way.&lt;/p&gt;&lt;p&gt;&lt;a href="https://github.com/devstructure/blueprint"&gt;Blueprint&lt;/a&gt; is a simple configuration management tool that reverse-engineers servers. It figures out what you've done manually, stores it locally in a Git repository, generates code that's able to recreate your efforts, and helps you deploy those changes to production.&lt;/p&gt;&lt;p&gt;Once you get it &lt;a href="http://devstructure.github.com/blueprint/#installation"&gt;installed&lt;/a&gt;, creating a blueprint takes but a single command:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;blueprint create example
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;where 'example' is the name of your blueprint. Blueprint will list packages managed by APT, Yum, RubyGems, Python's &lt;code&gt;easy_install&lt;/code&gt; and &lt;code&gt;pip&lt;/code&gt;, PHP's PEAR and PECL, and Node.js' NPM. It will also determine which configuration files in &lt;code&gt;/etc&lt;/code&gt; have been added or modified from their packaged versions and also collect files in &lt;code&gt;/usr/local&lt;/code&gt; that are part of any software packages installed from source. Finally, it will build a list of conditions under which System V init or Upstart services should be restarted, including package upgrades and configuration changes.&lt;/p&gt;&lt;p&gt;Blueprints are stored in a local Git repository as JSON and tarballs and from there you can turn them into shell scripts, Puppet modules, or Chef cookbooks.&lt;/p&gt;&lt;p&gt;Generate a shell script rendering of this blueprint as &lt;code&gt;example/bootstrap.sh&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;blueprint show -S example
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Generate a puppet module named 'example'; the main manifest will be &lt;code&gt;example/manifests/init.pp&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;blueprint show -P example
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Generate a bit of chef with the recipe as &lt;code&gt;example/recipes/default.rb&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;blueprint show -C example
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The generated shell scripts can render &lt;a href="https://github.com/rcrowley/mustache.sh"&gt;&lt;code&gt;mustache.sh&lt;/code&gt;&lt;/a&gt; templates to tailor configuration to the hardware: &lt;code&gt;{{CORES}}&lt;/code&gt; for the number of CPU cores in the system, &lt;code&gt;{{MEM}}&lt;/code&gt; for total memory, &lt;code&gt;{{FQDN}}&lt;/code&gt; for the fully-qualified domain name, and more. See &lt;a href="http://devstructure.github.com/blueprint/blueprint-template.7.html"&gt;&lt;code&gt;blueprint-template(7)&lt;/code&gt;&lt;/a&gt; for the complete list and &lt;a href="http://devstructure.github.com/blueprint/blueprint-template.1.html"&gt;&lt;code&gt;blueprint-template&lt;/code&gt;(1)&lt;/a&gt; to learn how to add your own template data.&lt;/p&gt;&lt;p&gt;There will undoubtedly come a time when you want to use a bit more finesse in configuration management than Blueprint's automatic reverse-engineering can provide, even with hints in &lt;code&gt;/etc/blueprintignore&lt;/code&gt;. For that time, there are rules files that enumerate only the resources you want included in the blueprint. Take, for example, &lt;code&gt;example.blueprint-rules&lt;/code&gt; describing a tiny Sinatra application:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;:source:/usr/local

/etc/init/example.conf
/etc/nginx/sites-*/example
/etc/unicorn.conf.rb

:package:apt/libmysqlclient-dev
:package:apt/mysql-client-5.1
:package:apt/nginx-common
:package:apt/nginx-light
:package:apt/ruby-dev
:package:apt/rubygems
:package:rubygems/*

:service:sysvinit/nginx
:service:upstart/example
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Regardless of what else is installed on the system, &lt;em&gt;example&lt;/em&gt; will only contain these resources:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;blueprint rules example.blueprint-rules
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Blueprint still extracts file content and metadata, package versions, and service dependencies from the system but instead of including everything and the kitchen sink, stays focused only on the resources you declare.&lt;/p&gt;&lt;p&gt;There's much more detail in the newly-revamped &lt;a href="http://devstructure.github.com/blueprint/"&gt;documentation&lt;/a&gt; and copious &lt;a href="http://devstructure.github.com/blueprint/#man"&gt;man pages&lt;/a&gt;. Give Blueprint a spin and send questions to &lt;script type="text/javascript"&gt;
&lt;!--
h='&amp;#x67;&amp;#x6f;&amp;#x6f;&amp;#x67;&amp;#108;&amp;#x65;&amp;#x67;&amp;#114;&amp;#x6f;&amp;#x75;&amp;#112;&amp;#x73;&amp;#46;&amp;#x63;&amp;#x6f;&amp;#x6d;';a='&amp;#64;';n='&amp;#98;&amp;#108;&amp;#x75;&amp;#x65;&amp;#112;&amp;#114;&amp;#x69;&amp;#110;&amp;#116;&amp;#x2d;&amp;#x75;&amp;#x73;&amp;#x65;&amp;#114;&amp;#x73;';e=n+a+h;
document.write('&lt;a h'+'ref'+'="ma'+'ilto'+':'+e+'"&gt;'+'&lt;code&gt;'+e+'&lt;/code&gt;'+'&lt;\/'+'a'+'&gt;');
// --&gt;
&lt;/script&gt;&lt;noscript&gt;&amp;#98;&amp;#108;&amp;#x75;&amp;#x65;&amp;#112;&amp;#114;&amp;#x69;&amp;#110;&amp;#116;&amp;#x2d;&amp;#x75;&amp;#x73;&amp;#x65;&amp;#114;&amp;#x73;&amp;#32;&amp;#x61;&amp;#116;&amp;#32;&amp;#x67;&amp;#x6f;&amp;#x6f;&amp;#x67;&amp;#108;&amp;#x65;&amp;#x67;&amp;#114;&amp;#x6f;&amp;#x75;&amp;#112;&amp;#x73;&amp;#32;&amp;#100;&amp;#x6f;&amp;#116;&amp;#32;&amp;#x63;&amp;#x6f;&amp;#x6d;&lt;/noscript&gt;.&lt;/p&gt;&lt;h2 id="further-reading"&gt;Further Reading&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;Blueprint can generate &lt;a href="http://docs.puppetlabs.com/"&gt;puppet&lt;/a&gt; modules and &lt;a href="http://www.opscode.com/chef/"&gt;chef&lt;/a&gt; recipes and could be your gateway drug into learning more automation tools.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://vagrantup.com/"&gt;Vagrant&lt;/a&gt; could be used to help you automate testing of your blueprints.&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3615332969083650973-5713261230923234482?l=sysadvent.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/sysadvent/~4/Ob2opXiT1W8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sysadvent.blogspot.com/feeds/5713261230923234482/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=3615332969083650973&amp;postID=5713261230923234482" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/5713261230923234482?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/5713261230923234482?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/sysadvent/~3/Ob2opXiT1W8/day-12-reverse-engineer-servers-with.html" title="Day 12 - Reverse-engineer servers with Blueprint" /><author><name>Jordan Sissel</name><uri>http://www.blogger.com/profile/04677274979347685821</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>1</thr:total><feedburner:origLink>http://sysadvent.blogspot.com/2011/12/day-12-reverse-engineer-servers-with.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkICQ3o9fip7ImA9WhRQFUQ.&quot;"><id>tag:blogger.com,1999:blog-3615332969083650973.post-5790533311884871582</id><published>2011-12-11T00:00:00.000-08:00</published><updated>2011-12-11T00:02:42.466-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-11T00:02:42.466-08:00</app:edited><title>Day 11 - Simple Disk-Based Server Backups with rsnapshot</title><content type="html">&lt;p&gt;This was written by &lt;a href="http://www.twitter.com/philiph"&gt;Phil Hollenback&lt;/a&gt; (&lt;a href="http://www.hollenback.net"&gt;www.hollenback.net&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;I've helped a friend administer the network at his engineering firm for about 10 years. For most of that time, they've used a standard &lt;a href="http://www.amanda.org/"&gt;Amanda&lt;/a&gt; configuration with a separate backup server and a DAT drive. That setup worked well enough as long as someone remembered to swap the tape every day and occasionally run the cleaning tape.&lt;/p&gt;&lt;p&gt;However, this backup system suffered from a few serious limitations:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Annoyingly difficult to retrieve individual backup files for users&lt;/li&gt;
&lt;li&gt;20GB tape size required small filesystems and other 'creative' tricks&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;There was also a desire to simplify the configuration and eliminate the standalone backup server. With those goals in mind I set out to find a suitable replacement system.&lt;/p&gt;&lt;h2 id="physical-media"&gt;Physical Media&lt;/h2&gt;&lt;p&gt;The big change compared to ten years ago is the easy availability of very large and inexpensive external hard drives. This site is a small company so price is a major factor, and larger tape systems were just too expensive compared to large drives. At the time this system was being considered (2008), you could buy 500GB SATA drives for around US$120. Compare this to many thousands of dollars for a larger capacity tape system and media.&lt;/p&gt;&lt;p&gt;Based on this, we purchased two 500GB SATA drives and two external SATA/USB enclosures for the backup system. The plan was to rotate the drives every few days and assign someone to take the spare drive home in case the office burned down.&lt;/p&gt;&lt;p&gt;I should also note that this site was and is connected to the internet with consumer DSL, so the very slow upload speeds prevented use of network backups.&lt;/p&gt;&lt;h2 id="backup-software"&gt;Backup Software&lt;/h2&gt;&lt;p&gt;We knew we wanted a backup mechanism that would copy important user data to the external drive at regular intervals. Bare metal restore capability was judged unimportant because the main server that was being backed up was a stock CentOS box. Backing up user data and system config files was enough of a safeguard, especially since the filesystems were already running on RAID1.&lt;/p&gt;&lt;p&gt;Of course the other criteria was that the software needed to be free as in beer and free as in Richard Stallman. Google quickly told us we should look at &lt;a href="http://rsnapshot.org/"&gt;rsnapshot&lt;/a&gt;.&lt;/p&gt;&lt;h2 id="hot-swap"&gt;Hot Swap&lt;/h2&gt;&lt;p&gt;This small site did not have a lot of technical expertise other than my friend who served as a part-time sysadmin. We needed to find a solution that was as simple and bulletproof as possible, particularly for swapping out the external drives. This meant hot swap and a system that could handle being unplugged any time backups weren't actually running. I decided to use the autofs automounter to keep the drive unmounted when it was inactive.&lt;/p&gt;&lt;p&gt;We had hoped to use eSATA as the hot swap mechanism to maximize throughput, but that proved unworkable. The CentOS 4 server would autodetect the drive and load the appropriate storage drivers. However, it refused to disconnect the drive and would spew console errors if you then manually unplugged it. While I suspected this might be fixed in a newer Linux distro, we didn't want to go through the effort of upgrading the server to something like CentOS 5. Instead, we tried a USB2.0 connection since the external drive enclosures we had purchased supported that as well as eSATA. USB hotplug worked just fine - as long as the filesystem wasn't mounted, you could plug and unplug all day long. Since this was just a backup system on a small office server, the speed of USB2.0 has proven to be just fine.&lt;/p&gt;&lt;h2 id="configuring-file-systems-and-the-automounter"&gt;Configuring File Systems and the Automounter&lt;/h2&gt;&lt;p&gt;For simplicity and robustness, I configured the external backup drives with one large, journaled, ext3 filesystem. Again, I didn't try to optimize for performance, the goal was just to create a large backup filesystem that wasn't terribly slow. Note, I did label the filesystem on each disk as &lt;code&gt;backup1&lt;/code&gt; and &lt;code&gt;backup2&lt;/code&gt; respectively. This is important because it provides an easy programmatic way to determine which drive is connected. The format command was thus:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;mkfs -t ext3 -j -L backup1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;By default CentOS detects the usb device and mounts it as &lt;code&gt;/media/&amp;lt;label&amp;gt;&lt;/code&gt;. This is not desirable for backups as I wanted to filesystem paths to always be consistent. Remember, I also wanted to use the automounter so that the disk could be physically removed any time it was not being actively used.&lt;/p&gt;&lt;p&gt;Automounter setup was as follows:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;Enable autofs with &lt;code&gt;/sbin/chkconfig autofs on&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In &lt;code&gt;/etc/auto.master&lt;/code&gt; configure mounts under &lt;code&gt;/misc&lt;/code&gt; with a very short timeout of 5 seconds:&lt;/p&gt;&lt;p&gt;&lt;code&gt;/misc  /etc/auto.misc --timeout=5&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In &lt;code&gt;/etc/auto.misc&lt;/code&gt; specify that &lt;code&gt;/dev/sdd1&lt;/code&gt; will be mounted under &lt;code&gt;/misc/backup&lt;/code&gt;:&lt;/p&gt;&lt;p&gt;&lt;code&gt;backup -fstype=ext3    :/dev/sdd1&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;After that I was able to verify the configuration by running &lt;code&gt;/etc/autofs start&lt;/code&gt;, plugging in one of the backup drives, and verifying I could access it as &lt;code&gt;/misc/backup&lt;/code&gt;.&lt;/p&gt;&lt;h2 id="time-for-rsnapshot"&gt;Time for rsnapshot&lt;/h2&gt;&lt;p&gt;&lt;a href="http://www.rsnapshot.org"&gt;rsnapshot&lt;/a&gt; uses rsync and hard links to make snapshots of a filesystem. Since it uses hard links, only files actually changed in each snapshot take up additional room. While rsnapshot does not come with CentOS 4, I was able to install one from &lt;a href="http://dag.wieers.com/rpm/"&gt;DAG&lt;/a&gt; with no difficulty. It was then necessary to make a few edits to &lt;code&gt;/etc/rsnapshot&lt;/code&gt; as follows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# where to make backups
snapshot_root   /misc/backup
# if the usb drive isn't mounted, don't create the root dir!
no_create_root  1
# number of each level of snapshot to keep
# tune so you don't fill up the backup drive too fast
retain  hourly  3
retain  daily   7
retain  weekly  4
retain  monthly 3
# extra verbosity, for analysis scripts
verbose 4
loglevel 4
logfile /var/log/rsnapshot
# collect stats for later analysis
rsync_long_args --delete --numeric-ids --relative --delete-excluded --stats
# don't cross filesystem boundaries
one_fs          1
# filesystem trees to back up
backup  /home/          localhost/
backup  /etc/           localhost/
backup  /usr/           localhost/
backup  /var/           localhost/
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That was about the extent of the customizations I needed. After this, I was able to enable hourly rsnapshot backups with an entry in &lt;code&gt;/etc/crontabs&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;0 */8 * * * root /usr/bin/rsnapshot hourly 2&amp;gt;&amp;amp;1 | tee \
/tmp/rsnapreport-hourly.log | /usr/bin/rsnapreport.pl \
&amp;gt;&amp;gt;/root/cron/rsnapshot-hourly.log &amp;amp;&amp;amp; /bin/echo &amp;gt;&amp;gt; \
/root/cron/rsnapshot-hourly.log
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I realize that is kind of an ugly cronjob but I'll come back to why I set up that complicated job. Also note that we are only doing backups of the main server currently, but we did do backups of an additional machine via rsync over ssh which rsnapshot supports fully.&lt;/p&gt;&lt;p&gt;Finally I needed a few other crontab entries to perform the daily, weekly, and monthly backups. Note that all other backups past the hourly are really just renaming the latest hourly snapshot.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;50 23 * * * root /usr/bin/rsnapshot daily &amp;gt; /tmp/rsnapreport-daily.log
40 23 * * 6 root /usr/bin/rsnapshot weekly &amp;gt; /tmp/rsnapshot-weekly.log
30 23 1 * * root /usr/bin/rsnapshot monthly &amp;gt; /tmp/rsnapshot-monthly.log
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then finally I wanted to run a daily disk usage report &lt;a href="http://www.hollenback.net/junk/rsnapshot-du"&gt;script that I wrote&lt;/a&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;1 6 * * * root /usr/local/sbin/rsnapshot-du
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That script is the reason for the ugly hourly crontab entry. On each hourly run I do two things:&lt;/p&gt;&lt;ol style="list-style-type: decimal"&gt;&lt;li&gt;&lt;p&gt;Run the hourly log through the supplied &lt;code&gt;/usr/bin/rsnapreport.pl&lt;/code&gt; script to produce a report for the individual backup run showing how long the run took and how much was backed up from each filesystem.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Collect all hourly reports each day so they can be combined with additional housekeeping information into one simple mailed report. This report includes information such as how much space remains on the attached backup drive, and the filesystem label for the drive.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;This nightly email report is critical because it allows us to monitor the amount of free space on the backup drive and verify that the drives get switched out on a regular basis.&lt;/p&gt;&lt;p&gt;Here's what the &lt;a href="http://www.hollenback.net/junk/rsnapshot-usage.txt"&gt;daily report looks like&lt;/a&gt;.&lt;/p&gt;&lt;h2 id="in-practice"&gt;In Practice&lt;/h2&gt;&lt;p&gt;This backup system has now been running on the main server for about two years, with great success. In particular the ability for non-admins to recover files from the snapshots has been very popular.&lt;/p&gt;&lt;p&gt;Initially we started with two 500GB external drives in the rotation. After one year, one of these drives failed, and space was getting a little tight, so we upgraded to 2TB drives. The modular nature of the automounted disk backups makes this change (and switching backup drives in general) very simple. All you need to tell an untrained operator is to wait until the drive light isn't flashing, unplug it, and plug in the new drive. When we added the new larger drives to the rotation we just had to format the filesystems ad label them backup3 and backup4. Then we just rotated the new drives in and rotated the one remaining non-failed old drive out.&lt;/p&gt;&lt;p&gt;While this sort of hotswapping of backup drives may sound a little risky, in a small office environment the backup drives end up sitting idle most of the time. We have had no problems with rotating drives every few days, and the external cables and drive enclosures have held up just fine.&lt;/p&gt;&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;&lt;p&gt;The key things I want to emphasize about this setup are that it is cheap and reliable. While this design would probably not scale well in a more demanding environment, for a small office it works just fine. The daily management load is substantially reduced from using a tape drive, and reliability has been excellent. I found it particularly satisfying that we were able to assemble this backup tool using existing software, off the shelf external drives, and a bit of custom scripting. This did not take a huge amount of time, and the hardware investment was only a few hundred dollars. This modularity illustrates the greatest strength of open source software.&lt;/p&gt;&lt;h2 id="further-reading"&gt;Further Reading&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;The &lt;a href="http://www.rsnapshot.org"&gt;rsnapshot home page&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3615332969083650973-5790533311884871582?l=sysadvent.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/sysadvent/~4/_SNjq7G57bI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sysadvent.blogspot.com/feeds/5790533311884871582/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=3615332969083650973&amp;postID=5790533311884871582" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/5790533311884871582?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/5790533311884871582?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/sysadvent/~3/_SNjq7G57bI/day-11-simple-disk-based-server-backups.html" title="Day 11 - Simple Disk-Based Server Backups with rsnapshot" /><author><name>Jordan Sissel</name><uri>http://www.blogger.com/profile/04677274979347685821</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>3</thr:total><feedburner:origLink>http://sysadvent.blogspot.com/2011/12/day-11-simple-disk-based-server-backups.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkIAQH48cCp7ImA9WhRQFUQ.&quot;"><id>tag:blogger.com,1999:blog-3615332969083650973.post-199328077835995807</id><published>2011-12-10T12:01:00.000-08:00</published><updated>2011-12-11T00:02:21.078-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-11T00:02:21.078-08:00</app:edited><title>Day 10 - Analyzing Logs with Pig and Elastic MapReduce</title><content type="html">&lt;p&gt;This was written by &lt;a href="http://agiletesting.blogspot.com"&gt;Grig Gheorghiu&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Parsing and analyzing individual log files can be done fairly easily with standard Unix tools such as find, grep, sed, awk, wc etc. The difficult part is doing this at scale when you are dealing with large quantities of logs. &lt;a href="http://http://hadoop.apache.org/"&gt;Hadoop&lt;/a&gt; is a suite of technology that has proven itself to be capable of scaling well as you are throwing more and more data at it. What makes it even better is the large ecosystem that has grown around it with tools such as Hive and Pig which offer high-level programming constructs that make your life easier as a &amp;quot;data analyst.&amp;quot; I chose Pig for this article because I find it a bit more friendly for programmers, whereas Hive is more appropriate for SQL die-hards.&lt;/p&gt;&lt;p&gt;Pig is an Apache project which, as the &lt;a href="http://pig.apache.org"&gt;official documentation&lt;/a&gt; says, “is a platform for analyzing large data sets that consists of a high-level language for expressing data analysis programs, coupled with infrastructure for evaluating these programs. The salient property of Pig programs is that their structure is amenable to substantial parallelization, which in turns enables them to handle very large data sets.”&lt;/p&gt;&lt;p&gt;Pig runs on top of Hadoop. You could build out your own Hadoop cluster, but for quick experimentation you would be advised to choose a platform like Amazon’s &lt;a href="http://aws.amazon.com/elasticmapreduce/"&gt;Elastic MapReduce&lt;/a&gt; (EMR) which abstracts away the operational details of a Hadoop cluster and lets you focus on writing your data analysis scripts. In the rest of the article, I’ll show you how to launch an EMR cluster running Pig, how to use Pig to analyze sendmail log files, and how to terminate the cluster once your data analysis is finished.&lt;/p&gt;&lt;p&gt;A major help in my experiments with Pig was an AWS article called &lt;a href="http://aws.amazon.com/articles/2729"&gt;Parsing Logs with Apache Pig and Elastic MapReduce&lt;/a&gt;. Although the article deals with Apache logs and not mail logs, the techniques it presents are the same.&lt;/p&gt;&lt;h2 id="launching-an-emr-cluster-with-pig-interactively"&gt;Launching an EMR cluster with Pig interactively&lt;/h2&gt;&lt;p&gt;In the examples that follow, I’ll use the &lt;code&gt;elastic-mapreduce&lt;/code&gt; Ruby command-line tool. Here are the steps you need to install and configure this tool:&lt;/p&gt;&lt;h2 id="installing-the-emr-ruby-cli"&gt;Installing the EMR Ruby CLI&lt;/h2&gt;&lt;p&gt;Download the zip file from &lt;a href="http://aws.amazon.com/developertools/Elastic-MapReduce/2264"&gt;here&lt;/a&gt;, then unzip it somewhere on an EC2 instance where you can store your AWS credentials (a management-type instance usually). I installed in /opt/emr on one of our EC2 instances. At this point, it's also a good idea to become familiar with the &lt;a href="http://docs.amazonwebservices.com/ElasticMapReduce/latest/DeveloperGuide/"&gt;EMR Developer Guide&lt;/a&gt;, which has examples of various elastic-mapreduce use cases. I also found a good &lt;a href="https://github.com/tc/elastic-mapreduce-ruby"&gt;README&lt;/a&gt; on GitHub. , Next, create a &lt;code&gt;credentials.json&lt;/code&gt; file containing some information about your AWS credentials and the keypair that will be used when launching the EMR cluster. The format of this JSON file is:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;access-id&amp;quot;: &amp;quot;YOUR_AWS_ACCESS_ID&amp;quot;,
  &amp;quot;private-key&amp;quot;: &amp;quot;YOUR_AWS_SECRET_KEY&amp;quot;,
  &amp;quot;key-pair&amp;quot;: &amp;quot;YOUR_EC2_KEYPAIR_NAME&amp;quot;,
  &amp;quot;key-pair-file&amp;quot;: &amp;quot;PATH_TO_PRIVATE_SSH_KEY_CORRESPONDING_TO_KEYPAIR&amp;quot;,
  &amp;quot;region&amp;quot;: &amp;quot;us-east-1&amp;quot;,
  &amp;quot;log-uri&amp;quot;: &amp;quot;s3://somebucket.yourcompany.com/logs&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="running-pig-on-an-emr-cluster"&gt;Running Pig on an EMR Cluster&lt;/h2&gt;&lt;p&gt;Here’s a script that will launch an EMR cluster with 1 master instance and 2 slave instances, all m1.large, and will install Hadoop 0.20 and Pig. The cluster can be accessed interactively via ssh because &lt;code&gt;--pig-interactive&lt;/code&gt; is specified as an option to elastic-mapreduce:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;#!/bin/bash
# File name: run_emr_pig.sh

TIMESTAMP=`date &amp;quot;+%Y%m%d%H%M&amp;quot;`
EMR_DIR=/opt/emr
LOG_FILE=$EMR_DIR/run_emr_pig.log.$TIMESTAMP

START=`date &amp;quot;+%Y-%m-%d %H:%M&amp;quot;`

echo $START &amp;gt; $LOG_FILE

SSH_KEY=/root/.ssh/emrdw.pem
NAME=piggish
CREDENTIALS=/opt/emr/credentials.json
NUM_INSTANCES=3
MASTER_INSTANCE_TYPE=m1.large
SLAVE_INSTANCE_TYPE=m1.large

CMD=&amp;quot;$EMR_DIR/elastic-mapreduce -c $CREDENTIALS --create --name &amp;quot;$NAME&amp;quot; --alive --num-instances $NUM_INSTANCES --master-instance-type $MASTER_INSTANCE_TYPE --slave-instance-type $SLAVE_INSTANCE_TYPE --hadoop-version 0.20 --pig-interactive&amp;quot;

echo Launching EMR cluster with command $CMD &amp;gt;&amp;gt; $LOG_FILE

JOBID=`$CMD| egrep 'j-.*' -o`
echo JOBID: $JOBID &amp;gt;&amp;gt; $LOG_FILE
while true;  do
   $EMR_DIR/elastic-mapreduce --list | grep $JOBID | grep WAITING
   if [ $? = 0 ]; then
       break
   fi
   sleep 10
done

$EMR_DIR/elastic-mapreduce --list | grep $JOBID &amp;gt;&amp;gt; $LOG_FILE

MASTER=`$EMR_DIR/elastic-mapreduce --jobflow $JOBID --describe | grep MasterPublicDnsName | egrep 'ec2.*com' -o`
echo Master node: $MASTER
echo Master node: $MASTER &amp;gt;&amp;gt; $LOG_FILE
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="using-pig-interactively"&gt;Using Pig Interactively&lt;/h2&gt;&lt;p&gt;To ssh into the EMR master node, you can do:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;$ ssh -i $SSH_KEY hadoop@$MASTER
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;One note: the version of Pig available in EMR currently is 0.6.&lt;/p&gt;&lt;p&gt;Once on the master node, you have the option to run pig in either local mode (which will not go against Hadoop/HDFS) or in Hadoop mode. The local mode is recommended for trying out your scripts against small data sets. In my case, I wanted to analyze gzipped log files stored in S3, and there’s a bug in Pig which prevents copying files from S3 to the local file system. The only way I could test my scripts was to run Pig in regular Hadoop mode, and point it to files in S3.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;$ pig

grunt&amp;gt; RAW_LOGS = LOAD 's3://pig.mycompany.com/mail/test.maillog.gz' as (line:chararray);
grunt&amp;gt; DUMP RAW_LOGS;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The above lines load a gzipped mail log file stored in S3 into what is called a Pig relation (a collection of tuples) which I named &lt;code&gt;RAW_LOGS&lt;/code&gt;. Then the &lt;code&gt;DUMP&lt;/code&gt; statement prints the relation to standard out. Each element of the relation is a tuple with 1 element called line:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;(Nov  8 18:39:58 mail1 sendmail[18842]: pA8Ndwvm018842: from=&amp;lt;sender@example.com&amp;gt;, size=9549, class=0, nrcpts=1, msgid=&amp;lt;201111082339.pA8Ndwvm018842@mail1.example.com&amp;gt;, proto=ESMTP, daemon=MTA, relay=relay1 [10.10.10.152])
(Nov  8 18:39:58 mail2 sendmail[18784]: pA8Ndtvm018781: to=&amp;lt;recipient@somedomain.net&amp;gt;, delay=00:00:03, xdelay=00:00:03, mailer=esmtp, pri=107011, relay=relay.somedomain.net. [A.B.C.D], dsn=2.0.0, stat=Sent (pA8NduK8032663 Message accepted for delivery))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note that the file test.maillog.gz contains just a small subset of a regular mail log file. This is the recommended way of experimenting with your data before analysing it at scale: &lt;strong&gt;start small, understand the structure of your data, play with it&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;There is not much analysis we can do at this point unless we refine the parsing of the log file. Pig supports regular expressions, so we’ll use that. Before I go into more detailed examples, let me say that I advise you to become familiar with the &lt;a href="https://cwiki.apache.org/PIG/pigtutorial.html"&gt;Pig tutorial&lt;/a&gt; and the Pig Latin reference manuals, (&lt;a href="http://pig.apache.org/docs/r0.7.0/piglatin_ref1.html"&gt;manual 1&lt;/a&gt; and &lt;a href="http://pig.apache.org/docs/r0.7.0/piglatin_ref2.html"&gt;manual 2&lt;/a&gt;).&lt;/p&gt;&lt;p&gt;We first register the &amp;quot;piggybank&amp;quot;, which is a collection of useful Pig functions exposed via a jar file which gets installed automatically with Pig in EMR. We import the &lt;code&gt;EXTRACT&lt;/code&gt; function which allows us to use regular expressions for parsing the Pig relation. We then go through each tuple of the relation (with the &lt;code&gt;FOREACH&lt;/code&gt; statement) and split it into fields by means of a threatening-looking regular expression which will match all lines that contain a destination email address (a &amp;quot;to&amp;quot; field).&lt;/p&gt;&lt;p&gt;Note that you need to escape the backslash everywhere. We save the result of this processing into another relation called &lt;code&gt;LOGS_BASE&lt;/code&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;grunt&amp;gt; REGISTER file:/home/hadoop/lib/pig/piggybank.jar;
grunt&amp;gt; DEFINE EXTRACT org.apache.pig.piggybank.evaluation.string.EXTRACT();
grunt&amp;gt; LOGS_BASE = FOREACH RAW_LOGS GENERATE
&amp;gt;&amp;gt; FLATTEN(
&amp;gt;&amp;gt;  EXTRACT(line, '(\\S+)\\s+(\\d+)\\s+(\\S+)\\s+(\\S+)\\s+sendmail\\[(\\d+)\\]:\\s+(\\w+):\\s+to=&amp;lt;([^@]+)\\@([^&amp;gt;]+)&amp;gt;,\\s+delay=([^,]+),\\s+xdelay=([^,]+),.*relay=(\\S+)\\s+\\[\\S+\\],\\s+dsn=\\S+,\\s+stat=(.*)')
&amp;gt;&amp;gt; )
&amp;gt;&amp;gt; AS (
&amp;gt;&amp;gt;  month: chararray,
&amp;gt;&amp;gt;  day: chararray,
&amp;gt;&amp;gt;  time: chararray,
&amp;gt;&amp;gt;  mailserver: chararray,
&amp;gt;&amp;gt;  pid: chararray,
&amp;gt;&amp;gt;  sendmailid: chararray,
&amp;gt;&amp;gt;  dest_user: chararray,
&amp;gt;&amp;gt;  dest_domain: chararray,
&amp;gt;&amp;gt;  delay: chararray,
&amp;gt;&amp;gt;  xdelay: chararray,
&amp;gt;&amp;gt;  relay: chararray,
&amp;gt;&amp;gt;  stat: chararray
&amp;gt;&amp;gt; );
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If we dump the LOGS_BASE relation to stdout (via the &lt;code&gt;DUMP&lt;/code&gt; statement), we see something like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;(Nov,8,18:39:58,mail2,18817,pA8Ndvvm018807,user1,yahoo.com,00:00:00,00:00:00,mta6.am0.yahoodns.net.,Sent (ok dirdel))
()
()
()
()
(Nov,8,18:39:58,mail1,13466,pA8NdvaN013451,user2,gmail.com,00:00:00,00:00:00,gmail-smtp-in.l.google.com.,Sent (OK 1320795598 v8si2644603yhm.107))
(Nov,8,18:39:58,mail1,13389,pA8NdtaN013380,user3,me.com,00:00:02,00:00:02,mx.me.com.akadns.net.,Sent (Ok, envelope id   0LUD00A9Q8EK4BQ0@smtpin135.mac.com))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Each tuple of the relation is either empty (if the regular expresssion doesn’t match the line) or contains the elements we specified in the &lt;code&gt;EXTRACT&lt;/code&gt; statement (month, day, time, mailserver etc).&lt;/p&gt;&lt;p&gt;Now that we have the lines split into individual fields, we can start thinking about analysing the data. One thing that you’ll find when doing data analysis is that often one of the hardest things to do is to ask meaningful questions. There may be wonderful stories waiting to be told by the data, but you need to be able to extract those stories.&lt;/p&gt;&lt;p&gt;In the example that follows, I want to see the most common scenarios where mail did not get sent correctly. I will be looking for a status that does not start with &amp;quot;Sent,&amp;quot; and I will want to see the top mail domains that were involved in non-successful mail delivery.&lt;/p&gt;&lt;p&gt;First we select only the mail domain and the status from the &lt;code&gt;LOGS_BASE&lt;/code&gt; relation:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;grunt&amp;gt; DOMAIN_STAT = FOREACH LOGS_BASE GENERATE dest_domain, stat;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We filter out the empty tuples:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;grunt&amp;gt; NOT_NULL = FILTER DOMAIN_STAT BY NOT $0 IS NULL;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We also filter out tuples with the ‘stat’ field not starting with &amp;quot;Sent&amp;quot;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;grunt&amp;gt; NOT_SENT = FILTER NOT_NULL BY NOT stat MATCHES 'Sent.*';
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We now group the remaining tuples by domain and status, which are indicated by their position in the NOT_SENT tuple (0 and 1 respectively):&lt;/p&gt;&lt;pre&gt;&lt;code&gt;grunt&amp;gt; GROUPED = GROUP NOT_SENT by ($0, $1);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;GROUPED&lt;/code&gt; relation contains tuples of this form:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;((alumni.myuniversity.edu,Deferred: 451 Requested mail action not taken: mailbox unavailable),{(alumni.myuniversity.edu,Deferred: 451 Requested mail action not taken: mailbox unavailable),(alumni.myuniversity.edu,Deferred: 451 Requested mail action not taken: mailbox unavailable)})
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The first element of a tuple in the &lt;code&gt;GROUPED&lt;/code&gt; relation is the tuple we grouped by containing the original fields &lt;code&gt;$0&lt;/code&gt; and &lt;code&gt;$1&lt;/code&gt;. The second element is what is called a &amp;quot;bag of tuples&amp;quot; which is a set of tuples denoted by &lt;code&gt;{}&lt;/code&gt; bracketing. This bag contains as many instances of the group as were found in the &lt;code&gt;GROUPED&lt;/code&gt; relation. This allows us to count those instances, sort by count in decreasing order and mail domain in increasing order (as specified by ‘ORDER COUNT BY num DESC, $0’), then limit to the top 50 results:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;grunt&amp;gt; COUNT = FOREACH GROUPED GENERATE FLATTEN(group), COUNT($1) as num;
grunt&amp;gt; SORTED = LIMIT(ORDER COUNT BY num DESC, $0) 50;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The result looks something like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;(austin.rr.com,User unknown,2L)
(comcast.net,User unknown,2L)
(aol.com,User unknown,1L)
(earthinlink.net,Deferred: Connection timed out with earthinlink.net.,1L)
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="making-a-pig-script"&gt;Making a Pig Script&lt;/h2&gt;&lt;p&gt;After experimenting with the &lt;code&gt;grunt&lt;/code&gt; command line tool, it’s time to put together a script. You can find the statements you ran from the command line in the &lt;code&gt;~/.pig_history file&lt;/code&gt; on the master node. You can simply copy and paste them into a script. I called mine &lt;code&gt;mail_domain_stat.pig&lt;/code&gt;. I also parameterized the input and output of the script by including the variables &lt;code&gt;$INPUT&lt;/code&gt; and &lt;code&gt;$OUTPUT&lt;/code&gt; which will be passed to the script when it will be called. Here’s the script:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;REGISTER file:/home/hadoop/lib/pig/piggybank.jar;
DEFINE EXTRACT org.apache.pig.piggybank.evaluation.string.EXTRACT();
RAW_LOGS = LOAD '$INPUT' as (line:chararray);
LOGS_BASE = FOREACH RAW_LOGS GENERATE
FLATTEN(
    EXTRACT(line, '(\\S+)\\s+(\\d+)\\s+(\\S+)\\s+(\\S+)\\s+sendmail\\[(\\d+)\\]:\\s+(\\w+):\\s+to=&amp;lt;([^@]+)\\@([^&amp;gt;]+)&amp;gt;,\\s+delay=([^,]+),\\s+xdelay=([^,]+),.*relay=(\\S+)\\s+\\[\\S+\\],\\s+dsn=\\S+,\\s+stat=(.*)')
)
AS (
    month: chararray,
    day: chararray,
    time: chararray,
    mailserver: chararray,
    pid: chararray,
    sendmailid: chararray,
    dest_user: chararray,
    dest_domain: chararray,
    delay: chararray,
    xdelay: chararray,
    relay: chararray,
    stat: chararray
);
DOMAIN_STAT = FOREACH LOGS_BASE GENERATE dest_domain, stat;
NOT_NULL = FILTER DOMAIN_STAT BY NOT $0 IS NULL;
NOT_SENT = FILTER NOT_NULL BY NOT stat MATCHES 'Sent.*';
GROUPED = GROUP NOT_SENT by ($0, $1);
COUNT = FOREACH GROUPED GENERATE FLATTEN(group), COUNT($1) as num;
SORTED = LIMIT(ORDER COUNT BY num DESC, $0) 50;
STORE SORTED INTO '$OUTPUT';
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now I can call the script from the command line like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;$ TIMESTAMP=`date &amp;quot;+%Y%m%d%H%M&amp;quot;`
$ pig -p INPUT=&amp;quot;s3://pig.mycompany.com/mail/test.maillog.gz&amp;quot; -p OUTPUT=&amp;quot;s3://pig.mycompany.com/mail/output/run_$TIMESTAMP&amp;quot; mail_domain_stat.pig
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The output will consist of a series of files called &lt;code&gt;part-NN&lt;/code&gt; (00, 01, etc.) stored in the S3 bucket specified as the &lt;code&gt;OUTPUT&lt;/code&gt; parameter. To get back the final result, download the partial files and concatenate them.&lt;/p&gt;&lt;p&gt;Note that &lt;code&gt;INPUT&lt;/code&gt; can be an expression such as &lt;code&gt;INPUT=&amp;quot;s3://pig.mycompany.com/mail/2011*.maillog.gz&amp;quot;&lt;/code&gt; which would automatically read in all files in S3 matching the expression &lt;code&gt;2011*.maillog.gz&lt;/code&gt;. As a matter of curiosity, when I ran the above Pig script against all our mail logs for 2011, the most common errors where of the form &lt;code&gt;&amp;quot;someisp.com     User unknown&amp;quot;&lt;/code&gt; followed by &lt;code&gt;&amp;quot;Some mail server     Connection timed out&amp;quot;&lt;/code&gt;.&lt;/p&gt;&lt;h2 id="asking-more-questions-of-the-data"&gt;Asking more questions of the data&lt;/h2&gt;&lt;p&gt;Here are some more questions you can ask from a mail log, together with short Pig examples:&lt;/p&gt;&lt;p&gt;Who are the top mail recipients?&lt;/p&gt;&lt;pre&gt;&lt;code&gt;RAW_LOGS = LOAD '$INPUT' as (line:chararray);
LOGS_BASE = FOREACH RAW_LOGS GENERATE
FLATTEN(
    EXTRACT(line, '(\\S+)\\s+(\\d+)\\s+(\\S+)\\s+(\\S+)\\s+sendmail\\[(\\d+)\\]:\\s+(\\w+):\\s+to=&amp;lt;([^&amp;gt;]+)&amp;gt;,\\s+delay=([^,]+),\\s+xdelay=([^,]+),.*relay=(\\S+)\\s+\\[\\S+\\],\\s+dsn=\\S+,\\s+stat=(.*)')
)
AS (
    month: chararray,
    day: chararray,
    time: chararray,
    mailserver: chararray,
    pid: chararray,
    sendmailid: chararray,
    dest: chararray,
    delay: chararray,
    xdelay: chararray,
    relay: chararray,
    stat: chararray
);
DEST = FOREACH LOGS_BASE GENERATE dest;
DEST_FILTERED = FILTER DEST BY NOT $0 IS NULL;
DEST_COUNT = FOREACH (GROUP DEST_FILTERED BY $0) GENERATE $0, COUNT($1) as num;
DEST_COUNT_SORTED = LIMIT(ORDER DEST_COUNT BY num DESC) 50;
STORE DEST_COUNT_SORTED INTO ‘$OUTPUT’;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;At what hours of the day do we send the most mail?&lt;/p&gt;&lt;pre&gt;&lt;code&gt;RAW_LOGS = LOAD '$INPUT' as (line:chararray);
LOGS_BASE = FOREACH RAW_LOGS GENERATE
FLATTEN(
    EXTRACT(line, '(\\S+)\\s+(\\d+)\\s+(\\d+):(\\d+):(\\d+)\\s+\\S+\\s+sendmail\\[\\d+\\]:\\s+\\w+:\\s+\\S+=&amp;lt;[^&amp;gt;]+&amp;gt;')
)
AS (
    month: chararray,
    day: chararray,
    hour: chararray,
    minute: chararray,
    second: chararray
);
HOUR = FOREACH LOGS_BASE GENERATE hour;
HOUR_FILTERED = FILTER HOUR BY NOT $0 IS NULL;
HOUR_COUNT = FOREACH (GROUP HOUR_FILTERED BY $0) GENERATE $0, COUNT($1) as num;
HOUR_COUNT_SORTED = ORDER HOUR_COUNT BY num DESC;
STORE HOUR_COUNT_SORTED INTO '$OUTPUT';
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Which mail servers are sending the most email (they should be almost equal if you are using a round-robin mechanism):&lt;/p&gt;&lt;pre&gt;&lt;code&gt;RAW_LOGS = LOAD '$INPUT' as (line:chararray);
LOGS_BASE = FOREACH RAW_LOGS GENERATE
FLATTEN(
    EXTRACT(line, '(\\S+)\\s+(\\d+)\\s+(\\S+)\\s+(\\S+)\\s+sendmail\\[(\\d+)\\]:\\s+(\\w+):\\s+from=&amp;lt;([^&amp;gt;]+)&amp;gt;,\\s+size=(\\d+),\\s+class=(\\d+),\\s+nrcpts=(\\d+),\\s+msgid=&amp;lt;([^&amp;gt;]+)&amp;gt;.*relay=(\\S+)')
)
AS (
    month: chararray,
    day: chararray,
    time: chararray,
    mailserver: chararray,
    pid: chararray,
    sendmailid: chararray,
    src: chararray,
    size: chararray,
    classnumber: chararray,
    nrcpts: chararray,
    msgid: chararray,
    relay: chararray
);
RELAY = FOREACH LOGS_BASE GENERATE relay;
RELAY_FILTERED = FILTER RELAY BY NOT $0 IS NULL;
RELAY_COUNT = FOREACH (GROUP RELAY_FILTERED BY $0) GENERATE $0, COUNT($1) as num;
RELAY_COUNT_SORTED = LIMIT(ORDER RELAY_COUNT BY num DESC) 50;
STORE RELAY_COUNT_SORTED INTO '$OUTPUT';
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;One other interesting question I am working on is finding out how fast we deliver a given piece of mail. This will involve defining two relations, one for lines containing mail sources and one for lines containing mail destinations, then joining them together based on the sendmail ID.&lt;/p&gt;&lt;h2 id="the-elasticity-in-emr"&gt;The Elasticity in EMR&lt;/h2&gt;&lt;p&gt;One of the nice things about using EMR is that you can launch an EMR cluster at night, process your data, then terminate the cluster, thus paying only for the period of time the cluster was in use. Here’s a script that does that.&lt;/p&gt;&lt;p&gt;It launches a cluster, it copies your Pig scripts to the master node, then it runs a Pig script. When that finishes, it terminates the cluster. The results of the Pig script will have been stored in S3 at that point.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;#!/bin/bash

TIMESTAMP=`date &amp;quot;+%Y%m%d%H%M&amp;quot;`
EMR_DIR=/opt/emr
LOG_FILE=$EMR_DIR/run_emr_pig.log.$TIMESTAMP

START=`date &amp;quot;+%Y-%m-%d %H:%M&amp;quot;`

echo $START &amp;gt; $LOG_FILE

SSH_KEY=/root/.ssh/emrdw.pem
NAME=piggish
CREDENTIALS=/opt/emr/credentials.json
NUM_INSTANCES=5
MASTER_INSTANCE_TYPE=m1.large
SLAVE_INSTANCE_TYPE=m1.xlarge

CMD=&amp;quot;$EMR_DIR/elastic-mapreduce -c $CREDENTIALS --create --name &amp;quot;$NAME&amp;quot; --alive --num-instances $NUM_INSTANCES --master-instance-type $MASTER_INSTANCE_TYPE --slave-instance-type $SLAVE_INSTANCE_TYPE --hadoop-version 0.20 --pig-interactive&amp;quot;

echo Launching EMR cluster with command $CMD &amp;gt;&amp;gt; $LOG_FILE

JOBID=`$CMD| egrep 'j-.*' -o`
echo JOBID: $JOBID &amp;gt;&amp;gt; $LOG_FILE
while true;  do
   $EMR_DIR/elastic-mapreduce --list | grep $JOBID | grep WAITING
   if [ $? = 0 ]; then
       break
   fi
   sleep 10
done

$EMR_DIR/elastic-mapreduce --list | grep $JOBID &amp;gt;&amp;gt; $LOG_FILE

MASTER=`$EMR_DIR/elastic-mapreduce --jobflow $JOBID --describe | grep MasterPublicDnsName | egrep 'ec2.*com' -o`
echo Master node: $MASTER
echo Master node: $MASTER &amp;gt;&amp;gt; $LOG_FILE

scp -i $SSH_KEY -r $EMR_DIR/pigscripts hadoop@$MASTER:
ssh -i $SSH_KEY hadoop@$MASTER &amp;quot;cd pigscripts; ./mail_domain_stat.sh&amp;quot; &amp;gt;&amp;amp; /tmp/emr_pig.log

cat /tmp/emr_pig.log  &amp;gt;&amp;gt; $LOG_FILE
$EMR_DIR/elastic-mapreduce --jobflow $JOBID --terminate
STOP=`date &amp;quot;+%Y-%m-%d %H:%M&amp;quot;`
echo $STOP &amp;gt;&amp;gt; $LOG_FILE
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Using this script (which launches 1 m1.large master node and 4 m1.xlarge slave nodes) I was able to process 100 GB worth of compressed mail logs in a little under 4 hours.&lt;/p&gt;&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;&lt;p&gt;The combination Apache Pig + Elastic MapReduce is a pretty powerful one when it comes to doing large-scale data analysis. The learning curve for doing simple, but useful, data analysis with Pig Latin is not very steep. Elastic MapReduce has the advantage of abstracting away the operational details of a Hadoop cluster, and it also makes sense financially if you only use it a few hours per day.&lt;/p&gt;&lt;h2 id="resources"&gt;Resources&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="https://cwiki.apache.org/confluence/display/PIG/Index"&gt;Apache Pig Wiki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://docs.amazonwebservices.com/ElasticMapReduce/latest/DeveloperGuide/"&gt;Elastic MapReduce Developer Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://ofps.oreilly.com/titles/9781449302641/"&gt;Programming Pig&lt;/a&gt; - O’Reilly book by Alan Gates&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3615332969083650973-199328077835995807?l=sysadvent.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/sysadvent/~4/zwxIr7imreU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sysadvent.blogspot.com/feeds/199328077835995807/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=3615332969083650973&amp;postID=199328077835995807" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/199328077835995807?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/199328077835995807?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/sysadvent/~3/zwxIr7imreU/day-10-analyzing-logs-with-pig-and.html" title="Day 10 - Analyzing Logs with Pig and Elastic MapReduce" /><author><name>Jordan Sissel</name><uri>http://www.blogger.com/profile/04677274979347685821</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>5</thr:total><feedburner:origLink>http://sysadvent.blogspot.com/2011/12/day-10-analyzing-logs-with-pig-and.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C04CRno6eCp7ImA9WhRQFE8.&quot;"><id>tag:blogger.com,1999:blog-3615332969083650973.post-7539615604454516421</id><published>2011-12-09T00:55:00.001-08:00</published><updated>2011-12-09T01:12:47.410-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-09T01:12:47.410-08:00</app:edited><title>Day 9 - Data in the Shell</title><content type="html">&lt;p&gt;This was written by &lt;a href="http://twitter.com/jordansissel"&gt;Jordan Sissel&lt;/a&gt; (&lt;a href="http://semicomplete.com"&gt;semicomplete.com&lt;/a&gt;).&lt;/p&gt;&lt;p&gt;The shell is where I live most days, and the shell has pipes. Pipes simply transmit plain text, and many pieces of data I deal with are structured data where staples like grep and sed are not the best.&lt;/p&gt;&lt;p&gt;Fortunately, there are lots of tools available to help you deal with these structured formats.&lt;/p&gt;&lt;h2 id="delimited-data"&gt;Delimited Data&lt;/h2&gt;&lt;p&gt;I'll start with data you're probably already familiar with, simple text delimited by some characters like spaces or commas. The general tools used here are awk and cut. I often want to get some simple stats using awk, so I keep some handy shell functions to help me do sums and counts by field.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;function _awk_col() {
  echo &amp;quot;$1&amp;quot; | egrep -v '^[0-9]+$' || echo &amp;quot;\$$1&amp;quot;
}

function sum() {
  [ &amp;quot;${1#-F}&amp;quot; != &amp;quot;$1&amp;quot; ] &amp;amp;&amp;amp; SP=${1} &amp;amp;&amp;amp; shift
  [ &amp;quot;$#&amp;quot; -eq 0 ] &amp;amp;&amp;amp; set -- 0
  key=&amp;quot;$(_awk_col &amp;quot;$1&amp;quot;)&amp;quot;
  awk $SP &amp;quot;{ x+=$key } END { printf(\&amp;quot;%d\n\&amp;quot;, x) }&amp;quot;
}

function sumby() {
  [ &amp;quot;${1#-F}&amp;quot; != &amp;quot;$1&amp;quot; ] &amp;amp;&amp;amp; SP=${1} &amp;amp;&amp;amp; shift
  [ &amp;quot;$#&amp;quot; -lt 0 ] &amp;amp;&amp;amp; set -- 0 1
  key=&amp;quot;$(_awk_col &amp;quot;$1&amp;quot;)&amp;quot;
  val=&amp;quot;$(_awk_col &amp;quot;$2&amp;quot;)&amp;quot;
  awk $SP &amp;quot;{ a[$key] += $val } END { for (i in a) { printf(\&amp;quot;%d %s\\n\&amp;quot;, a[i], i) } }&amp;quot;
}

function countby() {
  [ &amp;quot;${1#-F}&amp;quot; != &amp;quot;$1&amp;quot; ] &amp;amp;&amp;amp; SP=${1} &amp;amp;&amp;amp; shift
  [ &amp;quot;$#&amp;quot; -eq 0 ] &amp;amp;&amp;amp; set -- 0
  key=&amp;quot;$(_awk_col &amp;quot;$1&amp;quot;)&amp;quot;
  awk $SP &amp;quot;{ a[$key]++ } END { for (i in a) { printf(\&amp;quot;%d %s\\n\&amp;quot;, a[i], i) } }&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It's not exactly pretty, but it lets me do things like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# Simply sum the first field
% seq 10 | sum 1
55

# Sum one field grouping by another
% printf &amp;quot;hello:1\nworld:1\nworld:3\n&amp;quot; |sumby -F: 1 2
4 world
1 hello

# Count instances by field
% printf &amp;quot;hello:1\nworld:1\nworld:3\n&amp;quot; | countby -F: 1
2 world
1 hello
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="fex"&gt;fex&lt;/h3&gt;&lt;p&gt;Over the years, I've found awk to be useful for some cases, but the majority of my uses of cut (with cut -d) and awk were essentially setting the field separator and picking out a few fields. I wanted something with better syntax to solve some more complex field selection problems, so I wrote a tool called &lt;a href="http://semicomplete.com/projects/fex/"&gt;fex&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;The best example I can give of fex's power is by chopping up an apache log entry:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;% echo '208.36.144.8 - - [22/Aug/2007:23:39:05 -0400] &amp;quot;GET /hello/world HTTP/1.0&amp;quot; 200 3595' \
  | fex 1 '&amp;quot;2 2'

208.36.144.8 /hello/world
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The above asks for two values, the client ip and the request path. The first field (default delimiter is space) and the 2nd field (delimited by doublequote), then inside that the 2nd field by space. I believe this tight syntax is still readable while being expressive enough to get most field splitting and selections done. Doing the same thing in awk (or worse, cut) would require much more effort to capture.&lt;/p&gt;&lt;h2 id="structured-data"&gt;Structured Data&lt;/h2&gt;&lt;p&gt;Awk, cut, and fex are useful, but there are better tools to use when dealing with structured data formats.&lt;/p&gt;&lt;h2 id="json"&gt;JSON&lt;/h2&gt;&lt;p&gt;Lots of web APIs speak JSON these days, so there's an increasing likelihood that you will have to mangle it on the command line at some point&lt;/p&gt;&lt;p&gt;For this format, I use &lt;a href="https://github.com/psy1337/JSON-Grep"&gt;jgrep&lt;/a&gt; to search json data.&lt;/p&gt;&lt;p&gt;Let's use it to look for github public gists with comments:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;% curl -s https://api.github.com/gists | jgrep 'comments&amp;gt;0' -s url     
https://api.github.com/gists/1450770
https://api.github.com/gists/1450726
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Boom, only seeing gist urls with comments.&lt;/p&gt;&lt;h2 id="xml"&gt;XML&lt;/h2&gt;&lt;p&gt;Lots of programs use XML for configuration and data transport, so it is helpful to have a tool you can use to quickly poke at this data format.&lt;/p&gt;&lt;p&gt;Many of the tools in this area use a language called XPath, which I originally learned from &lt;a href="http://zvon.org/xxl/XPathTutorial/General/examples.html"&gt;here&lt;/a&gt;. The XPath language is in most cases fairly simple to learn. The other language you will likely see in these XML processing tools is XSLT, but that is a bit outside the scope of this article.&lt;/p&gt;&lt;p&gt;Tool: &lt;a href="http://xmlstar.sourceforge.net/"&gt;xmlstarlet&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Data: Hadoop [core-default.xml](https://github.com/jordansissel/sysadvent/blob/master/2011/09/code/core-default.xml&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# Get all the properties set in this file:
% xmlstarlet sel -t -v '/configuration/property/name' core-default.xml

# The above can also be written:
% xmlstarlet sel -t -v '//name' core-default.xml
hadoop.common.configuration.version
hadoop.tmp.dir
io.native.lib.available
...

# Read the 'hadoop.tmp.dir' property:
% xmlstarlet sel -t -v '//property[name/text() = &amp;quot;hadoop.tmp.dir&amp;quot;]/value' *.xml  
/tmp/hadoop-${user.name}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This kind of thing is very useful when you need to feed other tools from data in xml config files (like monitoring settings, etc)&lt;/p&gt;&lt;h2 id="csv"&gt;CSV&lt;/h2&gt;&lt;p&gt;In most cases, awk, cut, or fex will be sufficient in dealing with CSV or other delimited format files. There are edge cases in CSV (like quoting) that make using normal delimiter tools insufficient.&lt;/p&gt;&lt;p&gt;In this case, there's nicely a whole suite of tools for mangling CSV data, csvkit:&lt;/p&gt;&lt;p&gt;Tool: &lt;a href="https://github.com/onyxfish/csvkit"&gt;csvkit&lt;/a&gt;&lt;/p&gt;&lt;p&gt;csvkit comes with tools to cut, grep, sort, and more to csv data sets.&lt;/p&gt;&lt;p&gt;For a fun example, let's look outside the sysadmin world at some UK Government data on &lt;a href="http://www.ons.gov.uk/ons/rel/international-transactions/mergers-and-acquisitions-involving-uk-companies/q3-2011/dd-am-dataset.html"&gt;mergers and acquisitions&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;After downloading the &lt;a href="http://www.ons.gov.uk/ons/datasets-and-tables/downloads/csv.csv?dataset=am"&gt;csv&lt;/a&gt; data, let's get some stats on quarterly domestic acquisitions (listed as code AIHA in the csv data):&lt;/p&gt;&lt;p&gt;First, since there's both yearly and quarterly data, we'll need to grep for only quarterly data, second we'll want to get stats on column 2 (AIHA):&lt;/p&gt;&lt;pre&gt;&lt;code&gt;% python csvgrep -c 1 -r &amp;quot;.*Q[1-4]&amp;quot; AM_CSDB_DS.csdb.csv | python csvstat -c 2            
  2. AIHA
        &amp;lt;type 'int'&amp;gt;
        Nulls: No
        Min: 56
        Max: 464
        Sum: 27967
        Mean: 164
        Median: 140.5
        Unique values: 116
        5 most frequent values:
                120:    5
                139:    4
                148:    4
                124:    3
                88:     3

Row count: 170

# What quarter had 464 acquisitions?
% python csvgrep -c 2 -r '^464$' AM_CSDB_DS.csdb.csv  | python csvcut -c 1
1988 Q3

# What quarter had only 56?
% python csvgrep -c 2 -r '^56$' AM_CSDB_DS.csdb.csv  | python csvcut -c 1
1975 Q1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This tool is pretty slick. As a tip, if csvkit has features you like (column selection/grep, stats, etc), remember that if your data set is not already in csv, it might be easy to convert to csv for use with csvkit.&lt;/p&gt;&lt;h2 id="parting-thoughts"&gt;Parting Thoughts&lt;/h2&gt;&lt;p&gt;Sysadmins are often required to deal with multiple systems that speak different languages - one might log in plain text, another expose data over a HTTP API and send JSON, etc.&lt;/p&gt;&lt;p&gt;You're not alone, and having the right data mangling tools in your toolbox will help you answer the right questions faster without spending energy fighting with data formats. This lets you be more effective, and have more time at the pub with friends, at home with the family, or wherever you happen to find happiness.&lt;/p&gt;&lt;h2 id="further-reading"&gt;Further Reading&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.devco.net/archives/2011/07/29/rich-data-on-the-cli.php"&gt;good coverage of jgrep&lt;/a&gt; and reasons why it was created.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://csvkit.readthedocs.org/en/latest/index.html"&gt;csvkit docs&lt;/a&gt; which are excellent&lt;/li&gt;
&lt;li&gt;&lt;a href="http://semicomplete.com/blog/articles/week-of-unix-tools/day-3-awk.html"&gt;Week of Unix Tools - Day 3: awk&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3615332969083650973-7539615604454516421?l=sysadvent.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/sysadvent/~4/FKcID9zIax4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sysadvent.blogspot.com/feeds/7539615604454516421/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=3615332969083650973&amp;postID=7539615604454516421" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/7539615604454516421?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/7539615604454516421?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/sysadvent/~3/FKcID9zIax4/day-9-data-in-shell.html" title="Day 9 - Data in the Shell" /><author><name>Jordan Sissel</name><uri>http://www.blogger.com/profile/04677274979347685821</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>3</thr:total><feedburner:origLink>http://sysadvent.blogspot.com/2011/12/day-9-data-in-shell.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkUARXk_eyp7ImA9WhRQE04.&quot;"><id>tag:blogger.com,1999:blog-3615332969083650973.post-5033116707392315664</id><published>2011-12-08T01:42:00.001-08:00</published><updated>2011-12-08T01:57:24.743-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-08T01:57:24.743-08:00</app:edited><title>Day 8 - Running services with systemd</title><content type="html">&lt;p&gt;This was written by &lt;a href="http://twitter.com/jordansissel"&gt;Jordan Sissel&lt;/a&gt; (&lt;a href="http://semicomplete.com"&gt;semicomplete.com&lt;/a&gt;).&lt;/p&gt;&lt;p&gt;In the past three years of sysadvent, I've covered various process monitor tools like &lt;a href="http://sysadvent.blogspot.com/2008/12/day-3-babysitting.html"&gt;monit&lt;/a&gt;, &lt;a href="http://sysadvent.blogspot.com/2009/12/day-15-replacing-init-scripts-with.html"&gt;supervisord&lt;/a&gt;, and &lt;a href="http://sysadvent.blogspot.com/2010/12/day-19-upstart.html"&gt;upstart&lt;/a&gt;. A little while back I put Fedora 15 on my laptop and found a new one, systemd.&lt;/p&gt;&lt;p&gt;Since every tool seems to invent different terminology for the same things, for the purposes of this article when I say 'process' or 'service' I mean the same thing - a systemd service.&lt;/p&gt;&lt;p&gt;So let's dig in a bit.&lt;/p&gt;&lt;h2 id="a-first-look"&gt;A First Look&lt;/h2&gt;&lt;p&gt;The first thing you'll need to know is how to interact with systemd: starting and stopping things - the usual business. The main tool for this is systemctl. Run it with no arguments, and it gives you a list of all services. You can also ask for status:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;% systemctl status rsyslog.service
rsyslog.service - System Logging Service
          Loaded: loaded (/lib/systemd/system/rsyslog.service)
          Active: active (running) since Tue, 06 Dec 2011 22:32:58 -0800; 1 day and 1h ago
        Main PID: 803 (rsyslogd)
          CGroup: name=systemd:/system/rsyslog.service
                  └ 803 /sbin/rsyslogd -n -c 5
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;From the above, you can see a few notable and strange things:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;it's called &amp;quot;rsyslog.service&amp;quot; not simply &amp;quot;rsyslog&amp;quot;&lt;/li&gt;
&lt;li&gt;the config file is in /lib/systemd/system/rsyslog.service&lt;/li&gt;
&lt;li&gt;it's running, and for how long&lt;/li&gt;
&lt;li&gt;the pid, and that it said 'main pid' indicating it might support child processes somehow&lt;/li&gt;
&lt;li&gt;it uses cgroups.&lt;/li&gt;
&lt;li&gt;and finally, the process command and arguments&lt;/li&gt;
&lt;/ul&gt;&lt;h2 id="a-sample-service"&gt;A Sample Service&lt;/h2&gt;&lt;p&gt;What's the config file look like?&lt;/p&gt;&lt;pre&gt;&lt;code&gt;[Unit]
Description=System Logging Service

[Service]
EnvironmentFile=-/etc/sysconfig/rsyslog
ExecStartPre=/bin/systemctl stop systemd-kmsg-syslogd.service
ExecStart=/sbin/rsyslogd -n $SYSLOGD_OPTIONS
Sockets=syslog.socket
StandardOutput=null

[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Most of the above should be fairly straight forward, though the 'unit' and 'install' sections have strange names. I'll explain it from top to bottom.&lt;/p&gt;&lt;p&gt;The 'Unit' section is documented in the &lt;code&gt;systemd.unit(5)&lt;/code&gt; manpage. This section seems to cover things like ordering and dependencies. There's a separate section for defining the rsyslog service itself because systemd supports many more things than simply services, according to &lt;code&gt;systemd.unit(5)&lt;/code&gt; -&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;A unit configuration file encodes information about a service, a socket, a device, a mount point, an automount point, a swap file or partition, a start-up target, a file system path or a timer controlled and supervised by systemd(1).&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;That's a lot of stuff, but I'm mainly interested in how to run things in systemd.&lt;/p&gt;&lt;h2 id="controlling-a-service"&gt;Controlling a Service&lt;/h2&gt;&lt;p&gt;Let's stop it.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;% sudo systemctl stop rsyslog.service
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There's no output. If you run 'stop' again, it will again have no output and will still exit with success - a nice touch making scripted management easier. Check the status:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;% sudo systemctl status rsyslog.service
rsyslog.service - System Logging Service
          Loaded: loaded (/lib/systemd/system/rsyslog.service)
          Active: inactive (dead) since Thu, 08 Dec 2011 00:36:46 -0800; 3s ago
        Main PID: 803 (code=exited, status=0/SUCCESS)
          CGroup: name=systemd:/system/rsyslog.service
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Remember that stop and start only affect the current run-time and don't impact other events that might cause rsyslog to start (like the system booting). For that, you'll want enable and disable. You can disable things fairly intuitively:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;% sudo systemctl disable rsyslog.service
rm '/etc/systemd/system/multi-user.target.wants/rsyslog.service'
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And enable it again:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;% sudo systemctl enable rsyslog.service 
ln -s '/lib/systemd/system/rsyslog.service' '/etc/systemd/system/multi-user.target.wants/rsyslog.service'
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It's an odd thing. The 'stop' and 'start' commands output nothing, but 'enable' and 'disable' output shell commands? Additionally 'disable' and 'enable' do those actions for you, so I don't know what I am supposed to do with the output. Is systemd trying to encourage me to use those commands myself instead of using systemctl? By the way, if you enable an already-enabled service, you get no output and success. Same for disabling. Confusing!&lt;/p&gt;&lt;h2 id="add-your-own-service"&gt;Add Your Own Service&lt;/h2&gt;&lt;p&gt;Here's the config file I used, and I put it in &lt;code&gt;/lib/systemd/system/fizzle.service&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;[Unit]
Description=Hello World

[Service]
ExecStart=/bin/sh -c 'echo Hello World; sleep 5'
StandardOutput=syslog
StandardError=syslog

[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Nothing else is required to make systemctl aware of our new service, so we can start it normally like shown before:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;% sudo systemctl start fizzle.service
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And since I told it to take stdout and ship it over syslog, &lt;code&gt;/var/log/messages&lt;/code&gt; has the output:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;% grep 'Hello' /var/log/messages
Dec  7 17:45:26 nightfall sh[22616]: Hello World
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That's a nice feature and is similar to supervisord and daemontools (with multilog).&lt;/p&gt;&lt;p&gt;Checking the status, I see it has died:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;% systemctl status fizzle.service
fizzle.service - Hello World
          Loaded: loaded (/lib/systemd/system/fizzle.service)
          Active: inactive (dead)
          CGroup: name=systemd:/system/fizzle.service
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I don't want it to be dead. As described in past sysadvents covering process monitoring, &amp;quot;if it dies, restart it.&amp;quot; What can be done? The &lt;code&gt;systemd.service(5)&lt;/code&gt; manpage says to add '&lt;code&gt;Restart=always&lt;/code&gt;' to the 'Service' section.&lt;/p&gt;&lt;p&gt;Once that is added, starting 'fizzle.service' again will get it rolling. After 5 seconds it will die and be started by systemd:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;% sudo systemctl status fizzle.service | grep 'Active'
          Active: activating (auto-restart) since Thu, 08 Dec 2011 00:52:50 -0800; 5s ago
% sudo systemctl status fizzle.service
          Active: active (running) since Thu, 08 Dec 2011 00:52:55 -0800; 771ms ago
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="supporting-odd-services"&gt;Supporting Odd Services&lt;/h2&gt;&lt;p&gt;I originally wanted to use nagios as the example, not rsyslog as above, but when installing nagios on Fedora 15, I received the usual &lt;code&gt;/etc/init.d/nagios&lt;/code&gt; startup script. However, when I ran it, I saw this output:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;% sudo /etc/init.d/nagios start
Starting nagios (via systemctl):                           [  OK  ]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Huh? I thought that was strange, and when I dug into the script, I saw no mention of systemctl. It loads &lt;code&gt;/etc/init.d/functions&lt;/code&gt; which, by default, seems to pass itself into systemctl. Asking systemctl what's up, it says:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;% systemctl status nagios.service
nagios.service - SYSV: Starts and stops the Nagios monitor
          Loaded: loaded (/etc/rc.d/init.d/nagios)
          Active: active (running) since Thu, 08 Dec 2011 00:59:22 -0800; 31s ago
         Process: 23567 ExecStop=/etc/rc.d/init.d/nagios stop (code=exited, status=0/SUCCESS)
         Process: 23590 ExecStart=/etc/rc.d/init.d/nagios start (code=exited, status=0/SUCCESS)
        Main PID: 23601 (nagios)
          CGroup: name=systemd:/system/nagios.service
                  └ 23601 /usr/sbin/nagios -d /etc/nagios/nagios.cfg
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Funky, though it shows two features of systemd. First, it supports old sysv init scripts. Second, having explicit 'ExecStart' and 'ExecStop' settings can be useful in putting together systemd and software that insists on being run with its own management tools to stop and start.&lt;/p&gt;&lt;h2 id="concerns"&gt;Concerns&lt;/h2&gt;&lt;p&gt;This section is more sourced from my feelings on systemd than from facts, so take this with a grain of salt.&lt;/p&gt;&lt;p&gt;My first problem with systemd is the huge feature list. It looks to be trying to replace &lt;code&gt;/sbin/init&lt;/code&gt;, SYSV init scripts and runlevels, inetd, udevd, automount, and supports cgroups, inotify, and more. That's a pretty big feature space, and it reflects in the size of the code base. At this time of writing, the lines of code in systemd around 82000 lines of code. Of those, only about 1000 are tests. Only 1.2% of the code appears to be tests? Yikes.&lt;/p&gt;&lt;p&gt;Further, systemd is a major consolidation of several components of the system. I don't really want software replacing major components (/sbin/init, cron, etc) with practically no tests and a fixation on problems I don't have. A bug in cron doesn't crash init, but now it just might.&lt;/p&gt;&lt;p&gt;Lastly, systemd relies on DBus. It's pretty rare for sysadmins to have work experience with DBus. It's another layer to debug when things break. Are there debugging tools? I hope I'm just failing to google for this, but I always come up empty when looking for a decent tracing tool for DBus messages - all the ones I run across are graphical.&lt;/p&gt;&lt;p&gt;The above problems are not terrible things on desktops which tend to have much looser expectations on software reliability and more flexibility on outages. However, put these on a server, and what do you have? DBus usage, major software consolidation into a single binary, 82000 lines of code and basically no tests - all this adds up to great worry and concern. How long until systemd ships with RHEL or your preferred production Linux distribution?&lt;/p&gt;&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;&lt;p&gt;As stated, please take my opionated concerns detailed above for what they are - they are not facts. I'm not writing off systemd as a failure by any stretch.&lt;/p&gt;&lt;p&gt;Systemd itself has some fairly nice features for running services. Pretty much anything you'd want to configure for a service is available: cgroups, user, oom tuning, output logging, cpu and I/O tuning, etc. The command line tools and documentation are also pretty good. It's the default on all Fedora 15 and newer releases, and you can get it on many other Linux distributions, so go on and play with it!&lt;/p&gt;&lt;h2 id="further-reading"&gt;Further Reading&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://0pointer.de/public/systemd-lca2011.pdf"&gt;Slides&lt;/a&gt; giving an overview of systemd&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3615332969083650973-5033116707392315664?l=sysadvent.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/sysadvent/~4/tgiVGN-Ik7s" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sysadvent.blogspot.com/feeds/5033116707392315664/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=3615332969083650973&amp;postID=5033116707392315664" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/5033116707392315664?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/5033116707392315664?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/sysadvent/~3/tgiVGN-Ik7s/day-8-running-services-with-systemd.html" title="Day 8 - Running services with systemd" /><author><name>Jordan Sissel</name><uri>http://www.blogger.com/profile/04677274979347685821</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>1</thr:total><feedburner:origLink>http://sysadvent.blogspot.com/2011/12/day-8-running-services-with-systemd.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ak8EQ3s8fCp7ImA9WhRQEkk.&quot;"><id>tag:blogger.com,1999:blog-3615332969083650973.post-8617450094838617918</id><published>2011-12-07T00:55:00.001-08:00</published><updated>2011-12-07T01:06:42.574-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-07T01:06:42.574-08:00</app:edited><title>Day 7 - Change and Proximity of Communication</title><content type="html">&lt;p&gt;This was written by &lt;a href="https://twitter.com/anichols/"&gt;Aaron Nichols&lt;/a&gt; (&lt;a href="http://www.opsbs.com/"&gt;www.opsbs.com&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;Whether we like it or not, we sysadmins are all about &amp;quot;me&amp;quot;. When someone else sends an email about something, our interest in that thing is inversely proportional to the length of the email. When stuff breaks at 3AM and we &lt;em&gt;know&lt;/em&gt; there was an email sent out about that, we go looking and can't find it - so we call the person who sent it.&lt;/p&gt;&lt;p&gt;There is a better way.&lt;/p&gt;&lt;p&gt;This post is about communication and documentation for day-to-day stuff. This is not the documentation you dig through when you have all day to sort out a problem. This is the stuff you want, now, without getting intimate with a search engine.&lt;/p&gt;&lt;p&gt;Pop the hood on any car, and you'll see an example of this documentation. Right there - all the stuff you are most likely to care about when you are looking under the hood. It doesn't matter if you've never driven this car or not: the documentation is placed as close to the problem as it can get.&lt;/p&gt;&lt;p&gt;Here are some examples.&lt;/p&gt;&lt;h2 id="communication-in-your-config-management-system"&gt;Communication in your Config Management System&lt;/h2&gt;&lt;p&gt;Today, most of your configuration-related changes should be distributed through some sort of CM system. Since this is where most folks will go looking for the status of things when they break, this is a great place to document the status of things as they are changing. Here are some examples using puppet as the CM.&lt;/p&gt;&lt;h3 id="identify-files-that-are-managed-by-the-cm-system"&gt;Identify files that are managed by the CM system&lt;/h3&gt;&lt;p&gt;The top of every file managed by your config management system should have a line that looks something like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# This file is managed by puppet - any changes made to this file
# directly will be deleted
# Source: puppet/modules/wibble/templates/wibble_a.erb
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This makes it perfectly clear where to find this file if you want to edit it.&lt;/p&gt;&lt;p&gt;If you are moving things from one CM to another, tag the old ones too so people can tell which system is the source for a particular file.&lt;/p&gt;&lt;h3 id="tell-users-about-things-they-should-know-when-they-are-running-the-cm-manually-or-debugging-it"&gt;Tell users about things they should know when they are running the CM manually or debugging it&lt;/h3&gt;&lt;p&gt;If there's important information you want people to know about when they run your CM in debug mode (presumably looking for problems) you can usually add notifications. Comments in the code are great if someone is looking in the right place, but messaging like below directs them to that place:&lt;/p&gt;&lt;p&gt;In puppet you can use &lt;a href="http://docs.puppetlabs.com/references/stable/type.html#notify"&gt;'notify'&lt;/a&gt; for this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;class java {
  notify { &amp;quot;WARNING: This module (java) is experimental and may break things!&amp;quot;:; }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And you should see something like this when you run the manifests by hand:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;notice: WARNING: This module (java) is experimental and may break things!
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="send-short-email-messages-when-things-are-changing-with-links-to-more-details"&gt;Send short email messages when things are changing, with links to more details&lt;/h3&gt;&lt;p&gt;If you are making major changes to your CM and you need people to be aware, send a short email out with a link to details. Make sure you include enough keywords to make it searchable later on, but short enough to ensure people read it:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Everyone,&lt;/p&gt;&lt;p&gt;I am renaming the 2 puppet modules 'wibble' and 'wobble'. I will be working on this over the next 2 days. This work is being done on the 'wibblewobble' branch. An explanation of these changes can be found here: http://wiki/why-wibblewobble-must-change&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;And add a notify to those modules warning users. Add comments to those modules where users will see them. Put links to your wiki in both places.&lt;/p&gt;&lt;h3 id="leverage-your-motd"&gt;Leverage your MOTD&lt;/h3&gt;&lt;p&gt;It's very common to be working on a system and need to let people know you do not want them making changes to the system or disturbing the state of things. It's fine to send email about this, but don't get stabby when someone forgets about your email at 4AM and &amp;quot;fixes&amp;quot; the system.&lt;/p&gt;&lt;p&gt;My preference is always to put a message in the MOTD telling them about this. Let's say I have stopped httpd on a system for a few days &amp;amp; I've acked the alert in Nagios - I will also add this to the MOTD:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;***************************************************
NOTE: This system has httpd stopped for maintenance
We are resolving some network problems on this host
contact anichols@example.com if you have questions. 
Time of this message: 2011-12-06 13:55:01Z
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="if-a-script-shouldnt-be-run---break-it"&gt;If a script shouldn't be run - break it&lt;/h3&gt;&lt;p&gt;In the above example we had httpd turned off because we were resolving some issues. If you are in an environment where enabling httpd would cause a service impacting event - make that harder to do.&lt;/p&gt;&lt;p&gt;If I want a script to stop running, and this includes init scripts or others, I will typically disable the script with an exit along with a message to let the user know:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;start() {
        # SCRIPT DISABLED - anichols 2011/11/11
        # See http://wiki/why-I-disabled-httpd-on-this-host for more info
        echo &amp;quot;This script is disabled, see http://wiki/why-I-disabled-httpd-on-this-host for more detail&amp;quot;
        exit
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So when someone tries to use it they see this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;[~]$ sudo /etc/init.d/httpd start
This script is disabled, see http://wiki/why-I-disabled-httpd-on-this-host for more detail
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can do fancy stuff like conditionals so certain users can run the script even, but the point is: Communicate what's going on and do it where the user will come in contact with the change.&lt;/p&gt;&lt;h3 id="if-you-have-migrated-to-using-a-new-tool-put-a-header-on-the-old-one"&gt;If you have migrated to using a new tool, put a header on the old one&lt;/h3&gt;&lt;p&gt;As an example, you are moving some graphing from cacti to Graphite, and you want users to know about it. You want to keep the old cacti instance up because it has valuable historical data you haven't moved yet, but you want folks to stop asking you why the cacti graphs aren't accurate.&lt;/p&gt;&lt;p&gt;At the top of cacti, in the header, put something like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;font color=red&amp;gt;
&amp;lt;strong&amp;gt;This cacti data is no longer updated&amp;lt;/strong&amp;gt;&amp;lt;br&amp;gt;
This data is here for archival purposes, for current data visit
http://awesome-graphite-url
&amp;lt;/font&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It's simple and people will see it. If they don't - you can safely ignore their pleas of ignorance.&lt;/p&gt;&lt;h2 id="you-get-the-point-the-point-is..."&gt;You get the point, the point is...&lt;/h2&gt;&lt;p&gt;When you are working on systems and making changes, think about the other people who will come in contact with your change and how you can make it painfully obvious to them what is going on. Think about your own thoughtless patterns when you are bleary eyed at 3am: what would make it easy for you to know what is going on?&lt;/p&gt;&lt;h2 id="further-reading"&gt;Further Reading&lt;/h2&gt;&lt;p&gt;Like the example of car engine documentation being near the related area. There are many lessons we, as sysadmins and operations folks, can learn from other industries.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.kitchensoap.com/2011/07/18/systems-engineering-great-definition/"&gt;John Allspaw on &amp;quot;Systems Engineering&amp;quot;&lt;/a&gt; which links to a &lt;a href="http://education.ksc.nasa.gov/esmdspacegrant/Documents/NASA%20SP-2007-6105%20Rev%201%20Final%2031Dec2007.pdf"&gt;NASA Handbook&lt;/a&gt; on the subject.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://sysadvent.blogspot.com/2009/12/day-13-redundancy.html"&gt;Matt Simmons on &amp;quot;Redundancy&amp;quot;&lt;/a&gt; wisdom we can learn from the airline industry, from SysAdvent 2009.&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3615332969083650973-8617450094838617918?l=sysadvent.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/sysadvent/~4/pfWQXzpGvQs" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sysadvent.blogspot.com/feeds/8617450094838617918/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=3615332969083650973&amp;postID=8617450094838617918" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/8617450094838617918?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/8617450094838617918?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/sysadvent/~3/pfWQXzpGvQs/day-7-change-and-proximity-of.html" title="Day 7 - Change and Proximity of Communication" /><author><name>Jordan Sissel</name><uri>http://www.blogger.com/profile/04677274979347685821</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>2</thr:total><feedburner:origLink>http://sysadvent.blogspot.com/2011/12/day-7-change-and-proximity-of.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C08GSX49cSp7ImA9WhRQFE8.&quot;"><id>tag:blogger.com,1999:blog-3615332969083650973.post-6896170446088924050</id><published>2011-12-06T00:01:00.001-08:00</published><updated>2011-12-09T01:10:28.069-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-09T01:10:28.069-08:00</app:edited><title>Day 6 - Always Be Hacking</title><content type="html">&lt;p&gt;This was written by &lt;a href="https://twitter.com/lusis"&gt;John Vincent&lt;/a&gt; (&lt;a href="http://blog.lusis.org/"&gt;blog.lusis.org&lt;/a&gt;).&lt;/p&gt;&lt;p&gt;I got my start in computers a long time ago. Our school library had, if I recall correctly, a few Apple IIe systems in a room off to the side. I remember pretty clearly the first time I wrote something in BASIC. It was so awesome watching this machine do exactly what I told it to do. I was in second or third grade at the time.&lt;/p&gt;&lt;p&gt;The only computer I ever had at home as a kid was a Mattel Aquarius; thankfully it had the cassette add-on.&lt;/p&gt;&lt;p&gt;I dabbled in some form of 'programming' over the years from typing code using edlin out of Byte magazine and writing little gui apps in Tk to do most of the system administration related stuff I write today.&lt;/p&gt;&lt;p&gt;Evidently, I've always been hacking, and you should be, too.&lt;/p&gt;&lt;h2 id="sysadmins-of-the-future"&gt;Sysadmins of the Future&lt;/h2&gt;&lt;p&gt;&lt;a href="http://www.thesimplelogic.com/"&gt;Adam Fletcher&lt;/a&gt; did an open space at &lt;a href="http://devopsdays.org/"&gt;DevOps Days&lt;/a&gt; Boston this year about what system administrators would look like ten years from now. The truth is that sysadmins in the future will look a lot like they did twenty or thirty years ago - programmers.&lt;/p&gt;&lt;p&gt;It wasn't too long ago that the same guy who ran the system also wrote the code that ran on the system.&lt;/p&gt;&lt;p&gt;That's what sysadmins need to get back to, today, if they have any hope of being employable in the future. I'm not necessarily talking about writing the application code that runs your business, but I'm not ruling it out either. Of course, you might be thinking, &amp;quot;I'm not a programmer! I can't do that stuff&amp;quot;. In the words of &lt;a href="http://agilesysadmin.net/"&gt;Stephen Nelson-Smith&lt;/a&gt;, &amp;quot;With all due respect, I call bullshit&amp;quot;.&lt;/p&gt;&lt;h2 id="you-already-program"&gt;You Already Program&lt;/h2&gt;&lt;p&gt;When someone tells me that they can't program, I ask them to show me the last shell script they wrote. Look back over the scripts you've taken from company to company (you know you've done it). Look at the hyperconvoluted pipelines of awk, sed, grep, sort, cut and uniq. Look at the command-line argument handling. Look at the bash functions. Look at the loop constructs. Look at the subprocess you spawn.&lt;/p&gt;&lt;p&gt;I want you to listen to something. It's a talk that Uncle Bob Martin gave at RailsConf 2010. It's called &lt;a href="http://itc.conversationsnetwork.org/shows/detail4566.html"&gt;&amp;quot;Twenty Five Zeros&amp;quot;&lt;/a&gt;. Once you do, come back and reread this. If you weren't convinced before, you will be after listening. Bob Martin talks mainly about traditional programming languages and history in, but also talks about interpreted languages and the ever-common ground of sequence (assignment), selection (conditionals), and iteration (loops) - you think you don't do all of those in a shell script?&lt;/p&gt;&lt;p&gt;The fact is that, as a system administrator, you probably know more about the inner workings of the systems you manage than anyone at your company. Not only do you understand the details of what happens at the OS level but more often than not, you're the one person with the most holistic view of how all the components fit together, probably because you or your team were the ones to glue all those components together.&lt;/p&gt;&lt;h2 id="but-why"&gt;But Why?&lt;/h2&gt;&lt;p&gt;There are several reasons &lt;em&gt;WHY&lt;/em&gt; you should learn to program, but here are the three main ones in my mind.&lt;/p&gt;&lt;h2 id="so-that-youre-self-sufficient"&gt;So That You're Self-Sufficient&lt;/h2&gt;&lt;p&gt;Remember the first time you found a newsgroup post or web site that told you the exact solution to the problem you were having and how to solve it?&lt;/p&gt;&lt;p&gt;No? Me either.&lt;/p&gt;&lt;p&gt;That's not to say it hasn't happened, but what is MORE common is that you found a solution that ALMOST fit, and you munged it until it worked.&lt;/p&gt;&lt;p&gt;Then you left it there hoping that it never stopped working. Just to be sure you could always find it again you bookmarked the page, printed a copy and also ran a &lt;code&gt;wget -m&lt;/code&gt; (because there might be pictures) and saved that somewhere. In fact, I probably still have most of &lt;a href="http://cuddletech.com/blog://twitter.com/benr"&gt;Ben Rockwood&lt;/a&gt;'s &lt;a href="http://cuddletech.com/blog/?category_name=solaris"&gt;Solaris&lt;/a&gt; articles from &lt;a href="http://cuddletech.com/blog/"&gt;Cuddletech&lt;/a&gt; saved off somewhere on a dead hard drive.&lt;/p&gt;&lt;p&gt;But really, wouldn't it be more awesome if you could be the person who WROTE the solution that someone else found? Adding Epic to Awesome, imagine if you could just write it yourself from SCRATCH?&lt;/p&gt;&lt;h2 id="so-youll-have-a-job-in-the-next-five-years"&gt;So you'll have a job in the next five years&lt;/h2&gt;&lt;p&gt;This one is probably the most painful for folks. The fact of the matter is that sysadmins who CAN'T program and contribute more than just keeping the lights on are going to find themselves less and less employable. The times, they are a changing. Fish or cut bait. Insert other random phrase here. You aren't likely to have a choice in the matter. This isn't a DevOps thing. It's just the reality of the situation. The 'new' crop of operations folk are already ahead of you.&lt;/p&gt;&lt;p&gt;You might disagree with the above, and the future might prove it wrong, but what if you find you like writing code? What if having programming skills improves your business value and thus reflects positively upon your salary and benefits?&lt;/p&gt;&lt;h2 id="career-flexiblity"&gt;Career Flexiblity&lt;/h2&gt;&lt;p&gt;I wish I had someone tell me the stuff I'm telling you years ago. Interests change. People change. Economies change. You may discover that you enjoy programming more and want to make a career shift. Better to learn that now than when you're unable to switch financially or you find yourself out of work in your originally chosen field.&lt;/p&gt;&lt;h2 id="learning-to-program"&gt;Learning To Program&lt;/h2&gt;&lt;p&gt;We've already established that you can program and why you should learn more, so how do you go about it? I'm going to make some assumptions, here, based on my experience and how I learn.&lt;/p&gt;&lt;h2 id="tight-feedback-loop"&gt;Tight Feedback Loop&lt;/h2&gt;&lt;p&gt;I was never formally educated in computer science, and I find myself missing quite a bit. One thing that made learning to program easier was using a language with very quick feedback - how quickly can you try something? How many steps does it take? I don't care what the language is that you start with, but my personal preference is either Python or Ruby.&lt;/p&gt;&lt;p&gt;Both of these languages can work in the style you're familiar with in shell scripts, but also ease you into both functional and object-oriented programming concepts. They're very easy to get started with on any modern distro and have amazingly rich REPLs (Read-Eval-Print Loop, an interactive shell for Ruby, Python, etc). I'm not ashamed to say that I learned what I know about both languages more via &lt;a href="http://ipython.org/"&gt;IPython&lt;/a&gt; and &lt;a href="http://en.wikipedia.org/wiki/Interactive_Ruby_Shell"&gt;irb&lt;/a&gt; respectively than ever cracking open &lt;a href="http://www.vim.org/"&gt;vim&lt;/a&gt;. REPLs also provide instant gratification when you do something right. This provides encouragement to keep going.&lt;/p&gt;&lt;h2 id="scratch-an-itch"&gt;Scratch an Itch&lt;/h2&gt;&lt;p&gt;Regardless of which language you pick, don't start theoretical. Find a problem you have or recently had and solve it using that language. I like to credit the phrase 'Hate Driven Development' to &lt;a href="https://twitter.com/jordansissel"&gt;Jordan Sissel&lt;/a&gt;, but really sysadmins are more likely to practice NDD: Neccessity driven development. We don't typically sit down and write an application just to write one (not that you shouldn't do that).&lt;/p&gt;&lt;p&gt;Once you've solved the problem, rewrite it to be a bit more flexible. Throw in some parameterization and configurability. Try and make it a little more generic.&lt;/p&gt;&lt;p&gt;Don't just write one-offs.&lt;/p&gt;&lt;p&gt;I know for a fact that a few scripts that I thought were one-offs are still being used, today, to keep some core business functionality running.&lt;/p&gt;&lt;p&gt;Don't screw yourself with bad solutions. Don't screw the person who comes after you.&lt;/p&gt;&lt;h2 id="practice"&gt;Practice&lt;/h2&gt;&lt;p&gt;Rewriting and &lt;a href="http://en.wikipedia.org/wiki/Code_refactoring"&gt;refactoring&lt;/a&gt; also helps you practice the language. The next time you have a problem, don't immediately reach for bash. Take a few minutes and think about possibly writing it in your new language. If you have to solve the problem immediately, go back and rewrite the solution to be less fragile.&lt;/p&gt;&lt;h2 id="publish-and-share"&gt;Publish and Share&lt;/h2&gt;&lt;p&gt;Create public repo on &lt;a href="https://github.com/"&gt;Github&lt;/a&gt; or &lt;a href="https://bitbucket.org/"&gt;Bitbucket&lt;/a&gt; and start putting what you write there. Share it with the world.&lt;/p&gt;&lt;p&gt;Don't let ego get in the way.&lt;/p&gt;&lt;p&gt;You never know who you'll be helping down the road, and you never know who will be helping you in the future.&lt;/p&gt;&lt;h2 id="next-steps"&gt;Next Steps&lt;/h2&gt;&lt;p&gt;Remember, you should always be hacking. Here are a few things that I think we as sysadmins need to know beyond the basics&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Perl&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Every sysadmin should know some Perl. It's on every system. You might not be able to get the latest and greatest dynamic language on the system, but Perl will always be there.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;At least one functional language&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Don't know what 'functional' means? &lt;a href="http://www.haskell.org/haskellwiki/Functional_programming"&gt;This might help&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Functional programming concepts provide a new way to look at problems and often can be a better way to solve them. If you're an emacs user and you've ever modified your config, there's no reason you shouldn't go learn &lt;a href="http://clojure.org/"&gt;Clojure&lt;/a&gt; right now. Otherwise, it probably doesn't matter which language you use.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Learn to read Java&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;You don't have to learn to WRITE Java, but you should be able to navigate your way through the basics of a Java program. Additionally, the JVM is becoming a popular platform outside of Java with projects like Scala, JRuby, Clojure and others. Those languages will often be coupled with leveraging the existing Java ecosystem. Quite honestly, you should be able to at least read whatever language your company writes its applications in.&lt;/p&gt;&lt;p&gt;The dirty secret is this: once you learn one language, the others become easier.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;C&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Yes, you should learn C at some point. It's not going anywhere.&lt;/p&gt;&lt;p&gt;These things won't happen overnight, and I don't know all of these languages, either, but if we can manage to keep track of the differences between 5 different Linux distros and 3 flavors of Unix, we can handle learning a few different languages.&lt;/p&gt;&lt;p&gt;Remember that inside your head is the most amazing computer ever. Use it to the fullest and remember:&lt;/p&gt;&lt;p&gt;Always Be Hacking.&lt;/p&gt;&lt;h2 id="futher-reading"&gt;Futher Reading&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://learnpythonthehardway.org/"&gt;Learn Python the Hard Way&lt;/a&gt; - Free online; teaches you how to program in Python&lt;/li&gt;
&lt;li&gt;&lt;a href="http://ruby.learncodethehardway.org/"&gt;Learn Ruby the Hard Way&lt;/a&gt; - Same as above, but with Ruby.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://c.learncodethehardway.org/"&gt;Learn C the Hard Way&lt;/a&gt; - Same as above, but with C.&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3615332969083650973-6896170446088924050?l=sysadvent.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/sysadvent/~4/gUARkDSSOz8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sysadvent.blogspot.com/feeds/6896170446088924050/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=3615332969083650973&amp;postID=6896170446088924050" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/6896170446088924050?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/6896170446088924050?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/sysadvent/~3/gUARkDSSOz8/day-6-always-be-hacking.html" title="Day 6 - Always Be Hacking" /><author><name>Jordan Sissel</name><uri>http://www.blogger.com/profile/04677274979347685821</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>5</thr:total><feedburner:origLink>http://sysadvent.blogspot.com/2011/12/day-6-always-be-hacking.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUUBSHw9eyp7ImA9WhRQEEo.&quot;"><id>tag:blogger.com,1999:blog-3615332969083650973.post-287490983648233286</id><published>2011-12-05T00:01:00.000-08:00</published><updated>2011-12-05T00:20:59.263-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-05T00:20:59.263-08:00</app:edited><title>Day 5 - Tracking Requests with Request Tracker</title><content type="html">&lt;br /&gt;
&lt;p&gt;This was written by &lt;a href="https://twitter.com/cwebber"&gt;Christopher Webber&lt;/a&gt; (&lt;a href="http://cwebber.ucr.edu/"&gt;cwebber.ucr.edu&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;One of the first things that &lt;a href="http://everythingsysadmin.com/"&gt;Tom Limoncelli&lt;/a&gt; talks about in the &lt;a href="http://www.amazon.com/Practice-System-Network-Administration/dp/0201702711"&gt;&amp;quot;Practice of System and Network Administration&amp;quot;&lt;/a&gt; is getting a request tracking system in place. While you are not always tracking requests per-say, I have never seen an environment that didn't have tasks, work orders, requests, tickets or some other variant on that theme. Because my roles have always been rather customer facing, whether internal or external, the idea of a ticket system always appealed to me, so here are my reasons you should dive head first into a ticket system, why I chose Request Tracker by Best Practical, and a quick HOWTO on getting RT up and running on Ubuntu/Debian.&lt;/p&gt;&lt;h2 id="why-a-ticket-system"&gt;Why a Ticket System?&lt;/h2&gt;&lt;p&gt;A ticket system is important because email, conversations, sticky notes, etc get lost and forgotten. That is it, plain and simple, but that just scratches the surface of the real power of a ticket system. For starters, tickets make great collaboration spaces for dealing with issues. Everyone involved gets updated when new info is available, and it provides a great blow by blow when you are dealing with the postmortem. The other thing that comes from all of this effort is metrics: Metrics about which users or customers make the most requests, what kinds of requests happen with what frequency, and how long it takes to get certain tasks accomplished. These metrics provide an easy way to justify the need for improvements in ticket throughput like more staff or more staff in specific areas.&lt;/p&gt;&lt;h2 id="why-rt"&gt;Why RT?&lt;/h2&gt;&lt;p&gt;&lt;a href="http://bestpractical.com/rt/"&gt;Request Tracker&lt;/a&gt;, or RT, is fairly simple to get up and running and is extremely easy to bend to meet the needs of most environments. I personally chose RT because of the general ease of setup and the feature fit: Debian has packages for it, it is email based, it has a REST API, and there is a mobile interface and iPhone app.&lt;/p&gt;&lt;p&gt;Sure, there are a bunch of other way cool things you can do with it, but the above are my personal reasons. Being email-based is a huge advantage because of the limited amount of training required to get people started. Instead of sending mail to you, they are going to send to a special email address you create. The REST API is rather new, and I am looking forward to being able to automate ticket creation from scripts and events from Nagios and Munin.&lt;/p&gt;&lt;h2 id="getting-dirty"&gt;Getting Dirty&lt;/h2&gt;&lt;p&gt;The easiest way to get this up and running is using Vagrant. If you aren't familiar with Vagrant, I highly suggest taking a look at &lt;a href="http://vagrantup.com/"&gt;the Vagrant site&lt;/a&gt; to get it running, as it is much easier to use Vagrant to start playing with RT. Otherwise, I will provide instructions below on getting it installed on Ubuntu and Debian.&lt;/p&gt;&lt;h2 id="with-vagrant"&gt;With Vagrant&lt;/h2&gt;&lt;p&gt;Once you have Vagrant installed, perform the following steps:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;git clone git://github.com/cwebberOps/rt-vagrant.git
cd rt-vagrant
vagrant up
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;From there, wait for the instance to come up all the way and browse to http://10.0.0.10/rt&lt;/p&gt;&lt;p&gt;Vagrant is neat, isn't it?&lt;/p&gt;&lt;h2 id="without-vagrant-the-manual-way"&gt;Without Vagrant (The Manual Way)&lt;/h2&gt;&lt;p&gt;If you are using Debian Squeeze, you will need the backports repo installed and setup. To do this, please see &lt;a href="http://backports-master.debian.org/Instructions/"&gt;the backports setup instructions&lt;/a&gt; for more details.&lt;/p&gt;&lt;ol style="list-style-type: decimal"&gt;&lt;li&gt;&lt;p&gt;Install the packages listed below. Add the password as you see fit. On Debian, you will need the '-t squeeze-backport' option to be passed to &lt;code&gt;apt-get&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;apt-get install rt4-db-mysql rt4-clients rt4-apache2 mysql-server postfix request-tracker4
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Running the following commands to create all the necessary symlinks.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;ln -s /etc/request-tracker4/apache2-modperl2.conf /etc/apache2/sites-enabled/001-rt4
ln -s /etc/apache2/mods-available/actions.conf /etc/apache2/mods-enabled/actions.conf
ln -s /etc/apache2/mods-available/actions.load /etc/apache2/mods-enabled/actions.load
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Restart Apache&lt;/p&gt;&lt;pre&gt;&lt;code&gt;/etc/init.d/apache2 restart
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add the aliases for the 'general' ticket queue to /etc/aliases and run &lt;code&gt;newaliases&lt;/code&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;general: &amp;quot;|/usr/bin/rt-mailgate-4 general --action correspond --url http://localhost/rt&amp;quot;
general-comment: &amp;quot;|/usr/bin/rt-mailgate-4 general --action comment --url http://localhost/rt&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Now you should be able to browse to http://[systemname]/rt to get started.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;h2 id="first-steps"&gt;First Steps&lt;/h2&gt;&lt;p&gt;If you used Vagrant to bootstrap a test instance, the default password is root:password. Once you are in there are a few things that are worth taking a look at:&lt;/p&gt;&lt;ol style="list-style-type: decimal"&gt;&lt;li&gt;Start by going to &lt;code&gt;Tools &amp;gt; Configuration &amp;gt; Global &amp;gt; Group Rights&lt;/code&gt; and granting 'Everyone' the right to create tickets. This will allow for people to send mail and create tickets. You may want to revisit this setting once you have a better handle on how you are going to break up your ticket queues.&lt;/li&gt;
&lt;li&gt;Add a group for administrative users. The interface for this can be found at: &lt;code&gt;Tools &amp;gt; Configuration &amp;gt; Groups &amp;gt; Create&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Go back to &lt;code&gt;Tools &amp;gt; Configuration &amp;gt; Global &amp;gt; Group Rights&lt;/code&gt;. Type the name of the new group you just created in the box below 'Add Groups'. Then grant all of the permissions to that group as you see fit.&lt;/li&gt;
&lt;li&gt;Add a user for yourself. The interface for this is located &lt;code&gt;Tools &amp;gt;    Configuration &amp;gt; Users &amp;gt; Create&lt;/code&gt;. Once the user is created, you will likely want to visit the Memberships tab and add the user to the new admin group.&lt;/li&gt;
&lt;li&gt;Goto &lt;code&gt;Tools &amp;gt; Configuration &amp;gt; Queues &amp;gt; Select&lt;/code&gt;. From there, choose the 'General' queue and then the 'Watchers' tab. Find the new user you created and add them as an 'AdminCC'. This will cause that user to receive email messages when tickets are created or updated.&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;And welcome to RT!&lt;/p&gt;&lt;h2 id="notes-about-going-production"&gt;Notes About Going Production&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;Make sure that the &lt;code&gt;$rtname&lt;/code&gt; setting (what shows up in the ticket subject when dealing with mail) is set correctly. It breaks things if you change it after you have a number of tickets in the system.&lt;/li&gt;
&lt;li&gt;The postfix configuration that makes this all work will likely need a bit of tweaking to fit into any given environment.&lt;/li&gt;
&lt;li&gt;The actual configuration file for RT is located in the &lt;code&gt;/etc/request-tracker4/RT_SiteConfig.d&lt;/code&gt; directory. The &lt;code&gt;update-rt-siteconfig-4&lt;/code&gt; command builds the `/etc/request-tracker4/RT_SiteConfig.pmq file.&lt;/li&gt;
&lt;li&gt;Because of the credentials and other potentially sensitive information, you should get an Apache vhost setup with SSL for this site.&lt;/li&gt;
&lt;/ul&gt;&lt;h2 id="further-reading"&gt;Further Reading&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.redmine.org/"&gt;Redmine&lt;/a&gt; - a project and ticket tracking system (open source)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.bugzilla.org/"&gt;Bugzilla&lt;/a&gt; - another ticket system (open source)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.atlassian.com/software/jira/overview"&gt;Jira&lt;/a&gt; - also a ticket system! (commercial, on-premise or hosted available)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://en.wikipedia.org/wiki/Comparison_of_issue-tracking_systems"&gt;List of Issue Tracking Systems on Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3615332969083650973-287490983648233286?l=sysadvent.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/sysadvent/~4/g8ryikqd9N8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sysadvent.blogspot.com/feeds/287490983648233286/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=3615332969083650973&amp;postID=287490983648233286" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/287490983648233286?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/287490983648233286?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/sysadvent/~3/g8ryikqd9N8/day-5-tracking-requests-with-request.html" title="Day 5 - Tracking Requests with Request Tracker" /><author><name>Jordan Sissel</name><uri>http://www.blogger.com/profile/04677274979347685821</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>3</thr:total><feedburner:origLink>http://sysadvent.blogspot.com/2011/12/day-5-tracking-requests-with-request.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0MFR3gyfSp7ImA9WhRQEEw.&quot;"><id>tag:blogger.com,1999:blog-3615332969083650973.post-715693567498386248</id><published>2011-12-04T01:25:00.001-08:00</published><updated>2011-12-04T09:23:36.695-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-04T09:23:36.695-08:00</app:edited><title>Day 4 - A Guide to Packaging Systems</title><content type="html">&lt;p&gt;This was written by &lt;a href="http://twitter.com/jordansissel"&gt;Jordan Sissel&lt;/a&gt; (&lt;a href="http://semicomplete.com"&gt;semicomplete.com&lt;/a&gt;).&lt;/p&gt;&lt;p&gt;Software packaging systems are a strange phenomenon. They all seem to aim at solving the general problem of shipping some named, versioned collection of files to the world. Yet, this common ground seems insufficient given the overwhelming number of incompatible packaging systems in the wild.&lt;/p&gt;&lt;p&gt;The &amp;quot;overwhelming&amp;quot; part of above is what we need to talk about. Specifically, despite having similar goals, one package system is rather unlikely to have anything in common with another. Different terminologies, different tools, different technologies, different distribution mechanisms, different policies. The side effect with this phenomenon of &amp;quot;similar goal, nothing meaningful in common&amp;quot; is that you get punished.&lt;/p&gt;&lt;p&gt;Know how to use Red Hat's package system? Knowledge of rpm and its world doesn't help you much with the Solaris packaging system. Each solves roughly the same problem but with fundamentally different tools, techniques, policies, and terminology. Even package tools with historical ties like Gentoo Portage and FreeBSD Ports have strong divergence in technology, policies, and distribution.&lt;/p&gt;&lt;p&gt;I've had enough frustration over the years in dealing with learning new packaging systems, so today's post aims to give you a general guide to this strange world of packaging. As a caveat, the information detailed here is to the best of my knowledge and assisted with some googling. For the sake of brevity, I'm going to leave out (for now) language or non-OS platform packaging systems like Ruby's rubygems, Python's eggs, etc.&lt;/p&gt;&lt;h2 id="terminology"&gt;Terminology&lt;/h2&gt;&lt;p&gt;Terminology first, because knowing the right terms will help you find answers in search engines faster.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Tool terms:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Red Hat: rpm, rpmbuild, spec file, yum&lt;/li&gt;
&lt;li&gt;Debian: dpkg, debuild, control file, dh_make, apt-get&lt;/li&gt;
&lt;li&gt;FreeBSD: ports, make, pkg_add&lt;/li&gt;
&lt;li&gt;Solaris: pkg, pkgadd, pkgmk, pkgtrans&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Versioning terms:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Red Hat: epoch, version, release&lt;/li&gt;
&lt;li&gt;Debian: version&lt;/li&gt;
&lt;li&gt;FreeBSD: epoch, version, revision&lt;/li&gt;
&lt;li&gt;Solaris: version&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Relationship terms:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Red Hat: requires, obsoletes, provides&lt;/li&gt;
&lt;li&gt;Debian: depends, conflicts, provides, replaces&lt;/li&gt;
&lt;li&gt;FreeBSD: depends, requires&lt;/li&gt;
&lt;li&gt;Solaris: &lt;em&gt;nothing as far as I can tell&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Scripted action terms:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Red Hat: %pre, %post, %preun, %postun&lt;/li&gt;
&lt;li&gt;Debian: preinst, postinst, prerm, postrm&lt;/li&gt;
&lt;li&gt;FreeBSD: pkg-install, pkg-deinstall, pkg-req&lt;/li&gt;
&lt;li&gt;Solaris: preinstall, postinstall, checkinstall&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Support or repo tools&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Red Hat: mrepo, createrepo&lt;/li&gt;
&lt;li&gt;Debian: apt-ftparchive, reprepro, apt-file&lt;/li&gt;
&lt;li&gt;FreeBSD: portmaster, portupgrade, portsnap,&lt;/li&gt;
&lt;li&gt;Solaris: pkgbuild&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;As you can see, some things have very similar terms, some do not. FreeBSD calls 'pkg-req' what Solaris calls 'checkinstall' but Red Hat and Debian lack this specific feature (though it can be implemented in pre-installation scripts). Red Hat and FreeBSD agree on 'epoch' in meaning but use 'release' and 'revision' to refer to the same feature.&lt;/p&gt;&lt;p&gt;In Solaris, to do dependency checking, you are supposed to do this in a &lt;a href="http://www.ibiblio.org/pub/packages/solaris/sparc/html/creating.solaris.packages.html"&gt;script named 'checkinstall'&lt;/a&gt; and bail out if the dependencies are not installed.&lt;/p&gt;&lt;h2 id="distributing-packages"&gt;Distributing Packages&lt;/h2&gt;&lt;p&gt;Many package systems support network installation and dependency resolution. It is these two features that makes 'yum install foo' fetch and install all dependencies of 'foo,' possibly aborting due to dependency conflicts, version mismatches, or other relationship problems.&lt;/p&gt;&lt;p&gt;A common practice here is to mirror packages locally in your infrastructure. This enables you to survive upstream failures (like package removal, outages, etc). Tools like mrepo (for rpm) and rsync will help you here as most package repositories support rsync.&lt;/p&gt;&lt;p&gt;Rolling your own package repository can be done with mrepo and creatrepo for rpm/yum. For Debian (dpkg/apt-get), apt-ftparchive or reprepro are useful.&lt;/p&gt;&lt;h2 id="building-packages"&gt;Building Packages&lt;/h2&gt;&lt;p&gt;Have you ever needed a piece of software (or specific version) not available in your upstream package repository? You don't want to be at the mercy and whims of upstream. Your own software policies and needs are likely to be different (and even in conflict), for example, with the packaging policies and cultures of Debian, Red Hat, FreeBSD, Ubuntu, or whoever is your package provider.&lt;/p&gt;&lt;p&gt;Given this likely mismatch in your needs and upstream's available software, it is worth your time learning how to build packages and how to host them internally in your own package repository.&lt;/p&gt;&lt;p&gt;Learning to build packages can be a frustrating experience and, at least for me, has resulted in many &lt;a href="http://en.wiktionary.org/wiki/yak_shaving"&gt;shaved yaks&lt;/a&gt; and &lt;a href="http://xkcd.com/349/"&gt;lost hours&lt;/a&gt;, but there are resources available to help you out:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.rpm.org/max-rpm/"&gt;Maximum RPM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.debian.org/doc/manuals/maint-guide/"&gt;Debian New Maintainer's Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.freebsd.org/doc/en_US.ISO8859-1/books/porters-handbook/"&gt;FreeBSD Porter's Handbook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.ibiblio.org/pub/packages/solaris/sparc/html/creating.solaris.packages.html"&gt;Creating Solaris Packages&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;h2 id="converting-packages"&gt;Converting Packages&lt;/h2&gt;&lt;p&gt;Using multiple package systems on the same platform can make for a confusing array of moving parts. For example, using both rpms and rubygems. The confusion stems largely from the huge differences already described - it's yet another learning curve you must ascend. What if you converted those rubygems packages you needed to rpms? Your team and your tools could then manage ruby libraries the same way as the rest of your software, and that sounds promising.&lt;/p&gt;&lt;p&gt;This idea of converting package types is not new. The common ground between package systems is common enough that there's often a tool available to help translating one package type to another.&lt;/p&gt;&lt;p&gt;What package translating tools are there?&lt;/p&gt;&lt;ul&gt;&lt;li&gt;FreeBSD has 'BSDPAN' for automatically registering perl modules installed with CPAN in the FreeBSD package system. This allows you to uninstall them later with standard FreeBSD tools.&lt;/li&gt;
&lt;li&gt;Python setuptools (the 'setup.py' stuff) usually responds nicely to 'python setup.py bdist_rpm'&lt;/li&gt;
&lt;li&gt;Ruby has &lt;a href="http://rubygems.org/gems/gem2rpm"&gt;gem2rpm&lt;/a&gt; will help you build rpms from gems&lt;/li&gt;
&lt;li&gt;Perl has &lt;a href="http://perl.arix.com/cpan2rpm/"&gt;cpan2rpm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;h2 id="build-packages-easier"&gt;Build Packages Easier&lt;/h2&gt;&lt;p&gt;It's pretty lame needing one tool for every conversion method you want to use. While one tool doing one task well is deeply rooted in &lt;a href="http://www.faqs.org/docs/artu/ch01s06.html"&gt;Unix philosophy&lt;/a&gt;, there's still lots of overhead in requiring many tools to do similar jobs.&lt;/p&gt;&lt;p&gt;Further, the conversion tools don't often help you build the packages; for example, gem2rpm emits an 'rpm spec' which requires you know how to turn that into an rpm. All of this sums up to a burden of knowledge I think is silly.&lt;/p&gt;&lt;p&gt;Luckily, there are projects addressing this problem. Two tools, in particular, aim to solve exactly the 'how to I build a package of this thing?' question.&lt;/p&gt;&lt;p&gt;First, there is &lt;a href="http://asic-linux.com.mx/~izto/checkinstall/"&gt;CheckInstall&lt;/a&gt; (which bears no relation to the Solaris packaging term). This project tries to make package creation a side effect of the normal &amp;quot;make install&amp;quot; task, certainly a nice touch if you are looking to keep you software building workflow the same. Using the same basic workflow that ends with 'make install,' CheckInstall will produce an rpm or deb for you.&lt;/p&gt;&lt;p&gt;Second, there is &lt;a href="https://github.com/jordansissel/fpm#readme"&gt;fpm&lt;/a&gt;. The goal of fpm (caveat: I am the author) is to make a common tool for building packages from any source to any target. For example, you can turn a directory into an rpm, or a rubygem into a deb - both tasks can be done with very little knowledge of how each source (rubygem, directory) or target (deb, rpm) work.&lt;/p&gt;&lt;p&gt;At this time, fpm can create Solaris, rpm, and deb packages. To show you the simplicity, the following command lines will create a package of the 'boto' AWS library for Python:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;% fpm -s python -t rpm boto
% fpm -s python -t solaris boto

# Example with 'deb' target:
% fpm -s python -t deb boto
Trying to download boto (using: easy_install)
...
Downloading http://pypi.python.org/packages/source/b/boto/boto-2.1.1.tar.gz
...
Created /tmp/z/python-boto_2.1.1_all.deb
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now you have a native deb, rpm, and solaris package of the python boto library and you never had to learn how to build packages on each of those platforms.&lt;/p&gt;&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;&lt;p&gt;For any given platform I am operating, I've always had to learn the native packaging system, build my own custom packages, and host my own package repo. That's why I wrote fpm, after all, to &lt;a href="http://sysadvent.blogspot.com/2011/12/day-3-share-skills-and-permissions-with.html"&gt;codify my knowledge and skills&lt;/a&gt; into a tool that I and others can reuse.&lt;/p&gt;&lt;p&gt;Hopefully this guide has shed some light on a confusing and frustrating area of systems administration and given you the tools and terms to wield whatever packaging systems you are using.&lt;/p&gt;&lt;h2 id="further-reading"&gt;Further Reading&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://dag.wieers.com/rpm/"&gt;rpmforge&lt;/a&gt; - A 3rd party rpm repository that might save you from having to roll your own packages on Red Hat and related platforms.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://fedoraproject.org/wiki/EPEL"&gt;EPEL&lt;/a&gt; - A similar project to rpmforge but helps bridge packages from Fedora into distributions like CentOS and Scientific Linux.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.netbsd.org/docs/software/packages.html"&gt;pkgsrc&lt;/a&gt; - The NetBSD pkgsrc system is supported on many different platforms including Solaris and Linux.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.gentoo.org/proj/en/gentoo-alt/prefix/"&gt;Gentoo Prefix&lt;/a&gt; - The Gentoo Portage system but supported on other platforms (similar in idea to pkgsrc).&lt;/li&gt;
&lt;li&gt;&lt;a href="http://mxcl.github.com/homebrew/"&gt;Homebrew&lt;/a&gt; - A popular package manager for OS X.&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3615332969083650973-715693567498386248?l=sysadvent.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/sysadvent/~4/Ry5l-QMs3vo" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sysadvent.blogspot.com/feeds/715693567498386248/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=3615332969083650973&amp;postID=715693567498386248" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/715693567498386248?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/715693567498386248?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/sysadvent/~3/Ry5l-QMs3vo/day-4-guide-to-packaging-systems.html" title="Day 4 - A Guide to Packaging Systems" /><author><name>Jordan Sissel</name><uri>http://www.blogger.com/profile/04677274979347685821</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>4</thr:total><feedburner:origLink>http://sysadvent.blogspot.com/2011/12/day-4-guide-to-packaging-systems.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkEGQXo6eyp7ImA9WhRRGU0.&quot;"><id>tag:blogger.com,1999:blog-3615332969083650973.post-9146537196253375606</id><published>2011-12-03T00:40:00.001-08:00</published><updated>2011-12-03T01:30:20.413-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-03T01:30:20.413-08:00</app:edited><title>Day 3 - Share skills and permissions with code</title><content type="html">&lt;p&gt;This was written by &lt;a href="http://twitter.com/jordansissel"&gt;Jordan Sissel&lt;/a&gt; (&lt;a href="http://semicomplete.com"&gt;semicomplete.com&lt;/a&gt;).&lt;/p&gt;&lt;p&gt;Last year, I said, &lt;a href="http://sysadvent.blogspot.com/2010/12/day-13-dont-be-human-keyboard.html"&gt;&amp;quot;Don't be a human keyboard,&amp;quot;&lt;/a&gt; and recommended avoiding this situation by building a dashboard for others to consume instead of consuming your time and energy. Going deeper, why not stem 'human keyboard' requests by providing a very simple terminal- or web-based tool?&lt;/p&gt;&lt;p&gt;Before we dive into solutions, we need to go over some of the problems. What are some common reasons someone might invoke you as their computer interface? In general, you probably have something that this person does not, such as skills or permissions.&lt;/p&gt;&lt;p&gt;Both skills and permissions problems can be solved in the same way. You can codify these things into a tool or application that someone other than yourself can use: turn your knowledge and access into a tool usable by others!&lt;/p&gt;&lt;p&gt;The simplest of these tools could be a script in the terminal or a button in the web browser. Buttons are an important interface element in this situation because of exactly the skill mismatch that I mentioned above - not everyone has the skill to use the terminal (ssh, ssh keys, the shell, etc). Going with the 'button' idea, there are nice options for those in the terminal with tools like &lt;a href="http://invisible-island.net/dialog/"&gt;dialog&lt;/a&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;#!/bin/sh

dialog --yesno &amp;quot;Do you want to do this?&amp;quot; 0 0
result=$?
echo

if [ $result -eq 0 ] ; then
  # Do your thing here...
  echo &amp;quot;Doing it.&amp;quot;
else
  echo &amp;quot;Cancelled&amp;quot;
  exit 1
fi
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Running this script will give you a nice simple terminal tool. This kind of terminal interface is good for some situations, but not all. The &lt;a href="http://invisible-island.net/dialog/"&gt;dialog&lt;/a&gt; tool itself has many more features I won't discuss here, but you will benefit from at least playing with this tool. Anyway, despite this kind of visual interface in the terminal, the permissions problem isn't solved since this script is targeted at users other than you, and you might need to share your permissions.&lt;/p&gt;&lt;p&gt;Permissions are more easily shared over remote interfaces because many access control models lack the detail to express what you want to allow. In a human keyboard situation, the 'server' is you and the 'client' is the person asking for action, which maps pretty well to something like HTTP, so how about exposing permissions &lt;em&gt;and&lt;/em&gt; skills through the web browser?&lt;/p&gt;&lt;p&gt;One caveat with doing this with the web is the number of technologies required to make it happen. In the terminal, a button was as easy as a simple &lt;code&gt;dialog&lt;/code&gt; invocation, but you'll need a web server, html, and some code to make it happen. Luckily there's lots of open source tools available to make building this quicker.&lt;/p&gt;&lt;p&gt;I'll start with a simple example that has a button restart Apache. I chose Ruby, &lt;a href="http://www.sinatrarb.com/"&gt;Sinatra&lt;/a&gt; and &lt;a href="http://twitter.github.com/bootstrap/"&gt;Bootstrap&lt;/a&gt; for this, but you can use any tool that helps you get the job done.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;require &amp;quot;rubygems&amp;quot;
require &amp;quot;sinatra&amp;quot;
require &amp;quot;logger&amp;quot;

logger = Logger.new(STDOUT)

# The main page
get &amp;quot;/&amp;quot; do
  haml :index
end

# Handle form submission
post &amp;quot;/&amp;quot; do
  logger.info(&amp;quot;Bouncing apache by request from #{request.ip}&amp;quot;)

  # Should use Ruby's Open3 here, but let's keep it simple.
  @output = system(&amp;quot;sudo apachectl graceful 2&amp;gt;&amp;amp;1&amp;quot;)
  exitcode = $?.exitstatus
  @status = (exitcode == 0) ? &amp;quot;success&amp;quot; : &amp;quot;error&amp;quot;

  # Render it.
  haml :result
end
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There are &lt;a href="https://github.com/jordansissel/sysadvent/tree/master/2011/03/code/bounce-it"&gt;support files&lt;/a&gt; (the templates and such) for this available &lt;a href="https://github.com/jordansissel/sysadvent/tree/master/2011/03/code/bounce-it"&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;The above is pretty short in terms of code. Serve up the button (get &amp;quot;/&amp;quot;) and handle the form submission (post &amp;quot;/&amp;quot;). The deployment scenario for this is that since your permissions are what are needed, it should run as your user (or as something with appropriate permissions if you are shipping it to production).&lt;/p&gt;&lt;p&gt;The results look something like this:&lt;/p&gt;&lt;div class="figure"&gt;&lt;img src="https://github.com/jordansissel/sysadvent/raw/master/2011/03/media/main-page.png" alt="Main page" /&gt;&lt;p class="caption"&gt;Main page&lt;/p&gt;&lt;/div&gt;&lt;hr /&gt;&lt;div class="figure"&gt;&lt;img src="https://github.com/jordansissel/sysadvent/raw/master/2011/03/media/success.png" alt="A success after clicking the button" /&gt;&lt;p class="caption"&gt;A success after clicking the button&lt;/p&gt;&lt;/div&gt;&lt;hr /&gt;&lt;div class="figure"&gt;&lt;img src="https://github.com/jordansissel/sysadvent/raw/master/2011/03/media/failure.png" alt="A failure after clicking the button" /&gt;&lt;p class="caption"&gt;A failure after clicking the button&lt;/p&gt;&lt;/div&gt;&lt;p&gt;Keep in mind the point of all of this is to code your way out of the human keyboard situation. Don't know ruby? There's hope! Ruby, Python, Perl, and other communities have lots of ways to serve the web. Beyond that, you probably have a friend who can help you build the parts you aren't able. Lastly, there's lots of resource online for learning how to build simple web applications like this.&lt;/p&gt;&lt;p&gt;Of course, a simple button is just the start. With slightly more effort, you could build a simple interface that allows users to, for example, pick a database backup to restore to their own dev database, or perhaps exposing a one-click 'silence all active nagios alerts for 30 minutes' feature for your on-call folks?&lt;/p&gt;&lt;p&gt;A complex, real-world example of a skills and permissions sharing tool is &lt;a href="https://github.com/etsy/deployinator"&gt;Etsy's Deployinator&lt;/a&gt;. Deployment in many organizations is a complex mixing of different teams, skills, permissions, and goals. A tool like Deployinator allows the operations team to expose deployment functionality outside the operations team while hiding the necessary complexities of the infrastructure. This allows ops to share its permissions and skills by putting those in code and exposing it in the web browser. Because of this, deployments no longer require the full attention of the operations team, development doesn't block on operations, and no one is used as a human keyboard.&lt;/p&gt;&lt;p&gt;Definitely a brilliant win-win situation for everyone.&lt;/p&gt;&lt;p&gt;Proxying permissions and embedding your skills into code allows other people to act with your permissions and skills in a controlled and repeatable way without the burden of knowledge or complex access controls. Reducing the knowledge required to perform a task will greatly reduce the amount of documentation you need to write, too, since you can reduce most things to &amp;quot;If this, then click this button&amp;quot; instead of documenting the complicated orchestra of steps and knowledge.&lt;/p&gt;&lt;h2 id="further-reading"&gt;Further Reading&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;Simple web frameworks in Python: &lt;a href="http://denied.immersedcode.org/"&gt;Denied&lt;/a&gt;, &lt;a href="http://flask.pocoo.org/"&gt;Flask&lt;/a&gt;, &lt;a href="http://bottlepy.org/docs/dev/"&gt;Bottle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Simple web frameworks in Perl: &lt;a href="http://perldancer.org/"&gt;Dancer&lt;/a&gt;, &lt;a href="http://www.mojolicious.org/"&gt;Mojolicious&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://codeascraft.etsy.com/"&gt;&amp;quot;Code as Craft&amp;quot; by Etsy Engineering&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3615332969083650973-9146537196253375606?l=sysadvent.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/sysadvent/~4/7W5dYLS-JVk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sysadvent.blogspot.com/feeds/9146537196253375606/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=3615332969083650973&amp;postID=9146537196253375606" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/9146537196253375606?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/9146537196253375606?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/sysadvent/~3/7W5dYLS-JVk/day-3-share-skills-and-permissions-with.html" title="Day 3 - Share skills and permissions with code" /><author><name>Jordan Sissel</name><uri>http://www.blogger.com/profile/04677274979347685821</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://sysadvent.blogspot.com/2011/12/day-3-share-skills-and-permissions-with.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0EHRHo4cCp7ImA9WhRRGEk.&quot;"><id>tag:blogger.com,1999:blog-3615332969083650973.post-8182931390555893787</id><published>2011-12-02T01:28:00.001-08:00</published><updated>2011-12-02T08:00:35.438-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-02T08:00:35.438-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="java deployment maven rpm fpm service management" /><title>Day 2 - Strategies for Java Deployment</title><content type="html">&lt;p&gt;This article was written by &lt;a href="https://twitter.com/krisbuytaert"&gt;Kris Buytaert&lt;/a&gt; (&lt;a href="http://www.krisbuytaert.be/blog/"&gt;www.krisbuytaert.be/blog&lt;/a&gt;).&lt;/p&gt;&lt;p&gt;After years of working in Java-based environments, there are a number of things that I like to implement together with the teams I`m working with - the application doesn't matter much, whether it's plain java, Tomcat, JBoss, etc, these deployment strategies will help your ops and dev teams build more managable services.&lt;/p&gt;&lt;h2 id="packaging"&gt;Packaging&lt;/h2&gt;&lt;p&gt;The first step is to have the native operating system packages as build artifacts rolling out of your &lt;a href="http://martinfowler.com/articles/continuousIntegration.html"&gt;continuous integration&lt;/a&gt; server - No .ear, .war or .jar files: I want to have rpms or debs. With things like &lt;a href="https://github.com/jordansissel/fpm#readme"&gt;fpm&lt;/a&gt; or the &lt;a href="http://mojo.codehaus.org/rpm-maven-plugin/"&gt;maven rpm&lt;/a&gt; plugin this should not be an extra hassle, and the advantages you get from doing this are priceless.&lt;/p&gt;&lt;p&gt;What advantages? Most native package systems support dependency resolution, file verification, and upgrades (or downgrades). These are things you would have to implement yourself or cobble together from multiple tools. As a bonus, your fellow sysadmins are likely already comfortable with the native package tool used on your systems, so why not do it?&lt;/p&gt;&lt;h2 id="proxied-not-running-as-root"&gt;Proxied, not running as root&lt;/h2&gt;&lt;p&gt;&lt;em&gt;Shaken, not stirred&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Just like any other daemon, for security reasons, I prefer to run run Tomcat or JBoss as its own user, rather than as root. In most cases, however, only root can bind to ports below 1024, so you need to put a proxy in front. This is a convenient requirement because proxying (with something like Apache) can be used to terminate SSL connections, give improved logging (access logs, etc), and provides the ability to run multiple java application server instances on the same infrastructure.&lt;/p&gt;&lt;h2 id="service-management"&gt;Service Management&lt;/h2&gt;&lt;p&gt;Lots of Java application servers have a semi functional shell script that allows you to start the service. Often, these services don't daemonize in a clean way, so that's why I prefer to use the &lt;a href="http://wrapper.tanukisoftware.com/doc/english/download.jsp"&gt;Java Service wrapper&lt;/a&gt; from Tanuki to manage most Java based services. With a small config file, you get a clean way to stop and start java as a service and even the possibility to add some more monitoring to it.&lt;/p&gt;&lt;p&gt;However, there are some problems the Java Service wrapper leaves unsolved. For example, after launching the service, the wrapper can return back with a successful exit code while your service is not ready yet. The application server might be ready, but your applications themselves are still starting up. If you are monitoring these applications (e.g for High Availability), you really only want to treat them as 'active' when the application is ready, so you don't want your wrapper script to return, &amp;quot;OK,&amp;quot; before the application has been deployed and ready. Otherwise, you end up with false positives or nodes that failover before the application has ever started. It's pretty easy to create a ping-pong service flapping scenario on a cluster this way.&lt;/p&gt;&lt;h2 id="one-application-per-host"&gt;One application per host&lt;/h2&gt;&lt;p&gt;I prefer to deploy one application per host even though you can easily deploy multiple applications within a single Java VM. With one-per-host, management becomes much easier. Given the availability and popularity of good virtualization, the overhead of launching multiple Linux VM's for different applications is so low that there are more benefits than disadvantages.&lt;/p&gt;&lt;h2 id="configuration"&gt;Configuration&lt;/h2&gt;&lt;p&gt;What about configuration of the application? Where should remote API urls, database settings, and other tunables go? A good approach is to create a standard location for all your applications, like &lt;code&gt;/etc/$vendor/app/&lt;/code&gt;, where you place the appropriate configuration files. Volatile application configuration must be outside the artifact that comes out the build (.ear , .jar, .war, .rpm). The content of these files should be managed by a configuration management tool such as puppet, chef, or cfengine. The developers should be given a basic training so they can provide the systems team with the appropriate configuration templates.&lt;/p&gt;&lt;h2 id="logs"&gt;Logs&lt;/h2&gt;&lt;p&gt;Logs are pretty important too, and very easy to neglect. There are plenty of alternative tools around to log from a Java application: Log4j, Logback, etc .. Use them and make sure that they are configured to log to syslog, then they can be collected centrally and parsed by tools much easier than if they were spread all over the filesystem.&lt;/p&gt;&lt;h2 id="monitoring"&gt;Monitoring&lt;/h2&gt;&lt;p&gt;You also want your application to have some ways to monitor it besides just checking if it is running - it is usually insufficient to simply check if a tcp server is listening. A nice solution is to have a simple plain text page with a list of critical services and whether they are OK or not (true/false), for example:&lt;/p&gt;&lt;pre class="PROGRAMLISTING"&gt;&lt;code&gt;someService: true
otherService: false
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This benefits humans as well as machines. Tools like &lt;a href="https://mon.wiki.kernel.org/"&gt;mon&lt;/a&gt;, &lt;a href="http://linux-ha.org/wiki/Heartbeat"&gt;heartbeat&lt;/a&gt; or loadbalancers can just grep for &amp;quot;false&amp;quot; in the file. If the file contains false, it reports a failure and fails over. This page should live on a standard location for all your applications, maybe a pattern like this http://&lt;span class="math"&gt;&lt;em&gt;h&lt;/em&gt;&lt;em&gt;o&lt;/em&gt;&lt;em&gt;s&lt;/em&gt;&lt;em&gt;t&lt;/em&gt; / &lt;/span&gt;servicename/health.html and an example &amp;quot;http://10.0.129.10:8080/mrs-controller/health.html&amp;quot;. The page should be accessible as soon as the app is deployed.&lt;/p&gt;&lt;p&gt;This true/false health report should not be a static HTML file; it should be a dynamically generated page. Text means that you can also use curl, wget, or any command-line tool or browser to check the status of your service.&lt;/p&gt;&lt;p&gt;The 'health.html' page should report honestly about health, executing any code necessary to compute 'health' before yielding a result. For example, if your app is a simple calculator, it should verify health by doing tests internally like adding up some numbers before sharing 'myCalculator:true' in the health report.&lt;/p&gt;&lt;p&gt;The 'health.html' page should report honestly about health, executing any code necessary to compute 'health' before yielding a result. For example, if your app is a simple calculator, then before reporting health it should put two and two together and get four.&lt;/p&gt;&lt;p&gt;This kind of approach could also be used to provide you with metrics you can't learn from the JVM, such as number of concurrent users or other valid application metadata for measurement and trending purposes.&lt;/p&gt;&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;&lt;p&gt;If you can't convince your developers, then maybe more data can help: Check out Martin Jackson's (presentation on java deployments)[http://www.slideshare.net/actionjackx/automated-java-deployments-with-rpm]&lt;/p&gt;&lt;p&gt;With good strategies in packaging, deployment, logging, and monitoring, you are in a good position to have an easily manageable, reproducible, and scalable environment. You'll give your developers the opportunity to focus on writing the application, they can use the same setup on their local development boxes (e.g. by using vagrant) as you are using on production.&lt;/p&gt;&lt;p&gt;By the way, I will be giving a talk titled &lt;a href="http://www.usenix.org/events/lisa11/tech/tech.html#Buytaert"&gt;DevOps: The past and future are here. It's just not evenly distributed (yet)&lt;/a&gt;. at this year's LISA in Boston!&lt;/p&gt;&lt;h2 id="further-reading"&gt;Further Reading&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://jenkins-ci.org/"&gt;Jenkins CI Server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://docs.oracle.com/javase/6/docs/api/java/util/Properties.html"&gt;Simple configuration files in Java with Properties&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3615332969083650973-8182931390555893787?l=sysadvent.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/sysadvent/~4/tyR1-KEbgbY" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sysadvent.blogspot.com/feeds/8182931390555893787/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=3615332969083650973&amp;postID=8182931390555893787" title="7 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/8182931390555893787?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/8182931390555893787?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/sysadvent/~3/tyR1-KEbgbY/day-2-strategies-for-java-deployment.html" title="Day 2 - Strategies for Java Deployment" /><author><name>Jordan Sissel</name><uri>http://www.blogger.com/profile/04677274979347685821</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>7</thr:total><feedburner:origLink>http://sysadvent.blogspot.com/2011/12/day-2-strategies-for-java-deployment.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUENRnszfip7ImA9WhRRF08.&quot;"><id>tag:blogger.com,1999:blog-3615332969083650973.post-6073041791146940630</id><published>2011-12-01T00:14:00.000-08:00</published><updated>2011-12-01T00:21:37.586-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-01T00:21:37.586-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="perl subprocess output bash shell errors" /><title>Day 1 - Don't bash your process outputs</title><content type="html">&lt;p&gt;This article was written by &lt;a href="http://www.twitter.com/philiph"&gt;Phil Hollenback&lt;/a&gt; (&lt;a href="http://www.hollenback.net"&gt;www.hollenback.net&lt;/a&gt;)&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Like most veteran bash scripters, I have a tendency to abuse shell scripting. In particular, I often fall into the trap of collecting and manipulating process output. This works fine for the simple case of one or two lines of stdout, but falls down horribly for complex cases. Pretty soon a simple&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;output=`some_command`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;turns in to a huge conditional block where you have to check exit codes, redirect stderr in to temporary files, and generally hate yourself.&lt;/p&gt;
&lt;h2 id="welcome-to-perl"&gt;Welcome to Perl&lt;/h2&gt;
&lt;p&gt;I should warn you from the start: I've got a secret agenda. My goal is to convince all sysadmins to ditch the bash scripts and use perl instead. With perl you get both the ubiquity of shell scripting and access to a rich data manipulation environment.&lt;/p&gt;
&lt;p&gt;Unfortunately, the default perl backtick mechanism for capturing process output isn't a whole lot better than what you get with bash. The good news is that thanks to the &lt;a href="http://www.cpan.org"&gt;CPAN&lt;/a&gt;, there are many ways to deal with gathering process input. I'll cover two here: &lt;a href="http://search.cpan.org/~dagolden/IO-CaptureOutput-1.1102/lib/IO/CaptureOutput.pod"&gt;IO::CaptureOutput&lt;/a&gt; and &lt;a href="http://search.cpan.org/~pjf/IPC-System-Simple-1.21/lib/IPC/System/Simple.pm"&gt;IPC::System::Simple&lt;/a&gt;. There's some overlap between these two modules but I find I often end up using them both for different tasks. In particular, IO::CaptureOutput is best if you need to deal with both STDERR and STDOUT, and IPC::System::Simple is best if you just need something friendlier than the default perl &lt;code&gt;system&lt;/code&gt; call or backticks operator.&lt;/p&gt;
&lt;h2 id="iocaptureoutput"&gt;IO::CaptureOutput&lt;/h2&gt;
&lt;p&gt;IO::CaptureOutput is the more comprehensive solution to the problem of capturing output and determining whether the external process ran correctly. Here's how I typically use it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/perl -w
use IO::CaptureOutput qw/capture_exec/;

my ($JobStdout, $JobStderr, $JobSuccess, $JobExitCode) =
         capture_exec(&amp;quot;my_command&amp;quot;);
if ( ! $JobSuccess )
{
    print &amp;quot;my_command failed with exit code &amp;quot; . ($JobExitCode &amp;gt;&amp;gt; 8);
    print &amp;quot; and printed on stderr:\n&amp;quot;;
    print $JobStderr;
    die;
}

my @JobResults = split(/^/,$JobStdout);

my $number = 1;
foreach my $line (@JobResults) {
  print &amp;quot;$number: $line&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This provides a comprehensive record of what happened when you ran &lt;code&gt;my_command&lt;/code&gt; and is appropriate if any failure is grounds for terminating your script. Note that it also preserves and outputs stderr in case of failure, which is a friendly touch.&lt;/p&gt;
&lt;p&gt;After running &lt;code&gt;capture_exec&lt;/code&gt; this way, every line of your external command output is sitting there in the array &lt;code&gt;@JobResults&lt;/code&gt;, ready for further processing. For example, you could do this next to remove beginning line numbers from your output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# perl arrays are always references so this is in-place
# editing!
map { s/^\s*\d+\s// } @JobResults;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or, say you wanted to print all the non-comment lines from the output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;print grep(!/^#/, @JobResults);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another problem that capture_exec solves nicely is the issue of external commands that print failures to stderr but then return a successful exit code. To trap this, check the contents of &lt;code&gt;$JobStderr&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/perl -w
use IO::CaptureOutput qw/capture_exec/;
use List::Util qw/first/;

my $command = &amp;quot;echo hello world and some error text&amp;quot;;
my ($JobStdout, $JobStderr, $JobSuccess, $JobExitCode) =
        capture_exec($command);
my @JobResults = split(/^/,$JobStdout);

if ( first { /some error text/ } @JobResults )
{
    # found an occurence of the error text
    print &amp;quot;'$command' command failed&amp;quot;;
    print &amp;quot; and printed on stderr:\n&amp;quot;;
    print $JobStderr;
    die;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let's put that all together in a trivial data munging script that calls &lt;code&gt;lsof&lt;/code&gt;(1) and finds all the open files from processes owned by my user:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;use IO::CaptureOutput qw/capture_exec/;
my $username = &amp;quot;philiph&amp;quot;;
my $command = &amp;quot;lsof&amp;quot;;

my ($JobStdout, $JobStderr, $JobSuccess, $JobExitCode) =
    capture_exec($command);
if ( ! $JobSuccess )
{
    print &amp;quot;$command failed with exit code &amp;quot; . ($JobExitCode &amp;gt;&amp;gt; 8);
    print &amp;quot; and printed on stderr:\n&amp;quot;;
    print $JobStderr;
    die;
}
# split lines in to an array
my @JobResults = split(/^/,$JobStdout);

print $#JobResults + 1;
print &amp;quot; total open files on the system, here's what is owned by $username:\n&amp;quot;;
print grep(/^\S+\s+\S+\s+$username/, @JobResults);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That will print something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1687 processes owned by philiph:
loginwind    47 philiph  cwd      DIR       14,2       1530        2 /
loginwind    47 philiph  txt      REG       14,2    1754160  3902953 blah
loginwind    47 philiph  txt      REG       14,2     113744    35203 foo
loginwind    47 philiph  txt      REG       14,2     425504 14968261 quux
..etc..
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The power of IO::CaptureOutput is that it provides a consistent way to capture stderr, stdout, and the return code when calling an external process. In turn that makes it trivially easy to transform the process output into an array and perform operations on that array.&lt;/p&gt;
&lt;h2 id="ipcsystemsimple"&gt;IPC::System::Simple&lt;/h2&gt;
&lt;p&gt;If you don't actually care about separately capturing the stdout, stderr and exit value from a process then IO::CaptureOutput is a bit of a heavyweight. Instead, for the ultimate in simplicity you should look at IPC::System::Simple. This CPAN module provides a number of extremely convenient replacements for the perl system and backticks operators. For example, if you just want to run a process and die on failure, you can do this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;use IPC::System::Simple qw(system);
system(&amp;quot;my_program&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's it. If anything goes wrong with calling &lt;code&gt;my_program&lt;/code&gt;, this replacement for the perl system builtin will just call die to exit your script with a descriptive error message. Of course, you can always catch the exception instead of just die()ing. The big advantage of this version of &lt;code&gt;system&lt;/code&gt; is it prints much more descriptive messages on exit, including the real return code.&lt;/p&gt;
&lt;p&gt;This module also includes a &lt;code&gt;capture&lt;/code&gt; function which is a replacement for the default backticks operator in perl. It works similarly to IO::CaptureOutput &lt;code&gt;capture_exec&lt;/code&gt;, although note you can only capture STDOUT, not STDERR. If you don't care at all about STDERR you could make my earlier example much simpler:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/perl -w
use IPC::System::Simple qw(capture);

my $username = &amp;quot;philiph&amp;quot;;
my $command = &amp;quot;lsof&amp;quot;;

my @JobResults = capture($command);

print $#JobResults + 1;
print &amp;quot; total files on this system, the following are opened by by $username:\n&amp;quot;;
print grep(/^\S+\s+\S+\s+$username/, @JobResults);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If anything goes wrong, IPC::System::Simple will just bomb out with a descriptive message, &lt;em&gt;and&lt;/em&gt; set an appropriate exit value automatically. This saves you from having to bit shift the system result codes to figure out what the hell actually happened. An illustration:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# the standard way
my $command = &amp;quot;crontab -l -u root&amp;quot;;
system(&amp;quot;$command&amp;quot;) == 0 or die &amp;quot;$command failed: $?&amp;quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;if you aren't running as root, this produces the following baffling error:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;crontab: must be privileged to use -u
crontab -l -u root failed: 256 at /tmp/test.pl line 6.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;that '256' actually has to be bitshifted (&lt;code&gt;$?&amp;gt;&amp;gt;8&lt;/code&gt;) to get the real exit value, which is 1. Also, you have to remember to manually call die on a non-zero return from the default perl &lt;code&gt;system&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;If you replace the default system fucntion with the IPC::System::Simple one, that is all taken care of automatically:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Replace system with IPC::System::Simple
use IPC::System::Simple qw(system);
my $command = &amp;quot;crontab -l -u root&amp;quot;;
system(&amp;quot;$command&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;this time you get the real failure:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;crontab: must be privileged to use -u
&amp;quot;crontab -l -u root&amp;quot; unexpectedly returned exit value 1 at /tmp/test.pl line 6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And you don't even have to remember to call die! Of course, you can trap the failure in an eval block if you don't want your script to die.&lt;/p&gt;
&lt;p&gt;As a further refinement, say you know that there is a list of exit codes that are acceptable when calling an external process. IPC::System::Simple has a shorthand for that:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Replace system with IPC::System::Simple
use IPC::System::Simple qw(system);
my $command = &amp;quot;crontab -l -u root&amp;quot;;
# return codes 0 or 1 are acceptable
system([0,1], &amp;quot;$command&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, the script will continue on past the system call as long as it returns an exit code of 0 or 1. Otherwise it will die with a descriptive error message the same way as before.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Dealing with process output in bash scripts is hard! Doing it correctly and safely can quickly end up with you shaving yaks.&lt;/p&gt;
&lt;p&gt;Stock perl with the system function and backtick operator is an improvement. You gain access to perl's rich data manipulation features, but you still have to do unpleasant things like bitshifts to determine the real process exit code.&lt;/p&gt;
&lt;p&gt;As usual, CPAN comes to the rescue. If you just want to replace system or backticks with more convenient functions, IPC::System::Simple is an excellent choice. If you want to capture all of STDOUT, STDERR, and the process return code, then IO::CaptureOutput is the way to go. Also, note that these modules are portable across unix, mac, and windows.&lt;/p&gt;
&lt;h2 id="further-reading"&gt;Further Reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The code samples in this post are available at &lt;a href="https://github.com/jordansissel/sysadvent/tree/master/2011/01/code"&gt;github.com/jordansissel/sysadvent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A StackOverflow post that &lt;a href="http://stackoverflow.com/questions/109124/how-do-you-capture-stderr-stdout-and-the-exit-code-all-at-once-in-perl"&gt;discusses capturing stdout and stderr in perl&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.perlmonks.org/?node_id=454715"&gt;Capturing stdout, stderr, and exit code in Perl&lt;/a&gt; from Perl Monks.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://docs.python.org/library/subprocess.html"&gt;Subprocess output and more in Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://ruby-doc.org/stdlib-1.9.3/libdoc/open3/rdoc/Open3.html"&gt;Subprocess output and more in Ruby&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://php.net/manual/en/function.proc-open.php"&gt;Subprocess output and more in PHP&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3615332969083650973-6073041791146940630?l=sysadvent.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/sysadvent/~4/-zxTNKl8GI4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://sysadvent.blogspot.com/feeds/6073041791146940630/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=3615332969083650973&amp;postID=6073041791146940630" title="14 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/6073041791146940630?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/3615332969083650973/posts/default/6073041791146940630?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/sysadvent/~3/-zxTNKl8GI4/day-1-dont-bash-your-process-outputs.html" title="Day 1 - Don't bash your process outputs" /><author><name>Jordan Sissel</name><uri>http://www.blogger.com/profile/04677274979347685821</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>14</thr:total><feedburner:origLink>http://sysadvent.blogspot.com/2011/12/day-1-dont-bash-your-process-outputs.html</feedburner:origLink></entry></feed>

