Advertise with Googlier.comAllan Feid - devops
https://allanfeid.com/category/tags/devops
enDoing something useful with your SNMP traps
https://allanfeid.com/content/doing-something-useful-your-snmp-traps
<div class="field field-name-upload field-type-file field-label-hidden view-mode-rss"><div class="field-items"><div class="field-item even"><table class="sticky-enabled">
<thead><tr><th>Attachment</th><th>Size</th> </tr></thead>
<tbody>
<tr class="odd"><td><span class="file"><img class="file-icon" alt="Image icon" title="image/jpeg" src="/modules/file/icons/image-x-generic.png" /> <a href="https://allanfeid.com/sites/default/files/slack_pic.jpg" type="image/jpeg; length=7172">slack_pic.jpg</a></span></td><td>7 KB</td> </tr>
</tbody>
</table>
</div></div></div><div class="field field-name-body field-type-text-with-summary field-label-hidden view-mode-rss"><div class="field-items"><div class="field-item even" property="content:encoded"><p>Sometimes when you're trying to automate all the things, you have to revisit old technologies that you have long forgotten. In the networking world, SNMP is king for monitoring and metric gathering. I haven't seen a lot of information out there about setting up SNMP traps in a way that allows you to do what you want. Sure there's plenty of Network Management Systems out there, but I don't need all the bells and whistles.</p>
<p>I recently spent some time figuring out how to audit Juniper gear for configuration changes, get notified of flapping ports on the network, and check for failed SSH login attempts. I considered reinventing the wheel here, but came across <a href="http://snmptt.sourceforge.net/">snmptt</a>. While the project is on Sourceforge, and uses CVS, it still looks actively maintained. I looked into the configuration, and there are a ton of options with plenty of flexibility.</p>
<h2>Quick primer on net-snmp</h2>
<p>If you've never used the <a href="http://www.net-snmp.org/">net-snmp tools</a>, here's a quick guide to getting started. First, make sure you have the MIBs downloaded for your device and placed into <code>/etc/snmp/mibs</code>. Second, add the following to <code>/etc/snmp/snmp.conf</code>:</p>
<pre class="no-code">
mibdirs +/etc/snmp/mibs
mibs +ALL
</pre><p>This sets up <code>net-snmp</code> to work with your custom MIBs, feel free to puruse their documentation if you want to know more.</p>
<h2>Get your mibs in the snmptt format</h2>
<p>In order to get <code>snmptt</code> configured with your MIBs, they include a useful tool called <code>snmpttconvertmib</code>. It's quick enough to use like so:</p>
<pre><code>mkdir /tmp/snmptt_configs
for mibfile in /etc/snmp/mibs/*.mib; do
snmpttconvertmib --in $mibfile \
--out /tmp/snmptt_configs/$(basename $mibfile) \
--net_snmp_perl
done
cat /tmp/snmptt_configs/* > /etc/snmp/snmptt_vendor.conf
</code></pre><p>This takes all of those mib files and lets you translate them into simple text strings. The conversion isn't perfect, but it's easy to go in and tweak the ones you care about and execute actions.</p>
<h2>Intercept the traps and send them to snmptt</h2>
<p>I'm not going to go over setting up <code>snmptt</code> in daemon mode, you can follow their documentation, or if you're on CentOS <code>yum install snmptt</code> to get init scripts. Once you do have your daemon running, you have to set up <code>snmptrapd</code> to execute <code>snmptthandler</code> like so:</p>
<pre class="no-code">
# cat /etc/snmp/snmptrapd.conf
traphandle default /usr/sbin/snmptthandler
disableAuthorization yes
</pre><p>To send SNMP traps to <code>snmptrapd</code> from a Juniper device, you need a configuration like this:</p>
<pre class="no-code">
set snmp trap-group default version v2
set snmp trap-group default categories chassis
set snmp trap-group default categories link
set snmp trap-group default categories routing
set snmp trap-group default categories startup
set snmp trap-group default categories configuration
set snmp trap-group default categories chassis-cluster
set snmp trap-group default targets $snmptrapd_ip
set event-options policy login-traps events sshd_login_failed
set event-options policy login-traps events login_failed
set event-options policy login-traps events login_pam_authentication_error
set event-options policy login-traps then raise-trap
</pre><h2>A couple tips about snmptt</h2>
<p>If you're receiving a lot of traps, I recommend setting <code>threads_enable = 1</code> in your <code>snmptt.ini</code> file. This allows EXEC statements to be launched in a new thread, preventing a build up of traps while you wait for the previous EXEC. I also recommend <code>net_snmp_perl_enable = 1</code> which will convert integers to their proper names if you have <code>net-snmp</code> set up properly. In the example below, this is what converts a 2 into the word "cli," which is way more informative to us humans.</p>
<h2>Do something useful with your traps</h2>
<p><a href="http://snmptt.sourceforge.net/docs/snmptt.shtml#LoggingDatabase-MySQL">You can store all these traps in MySQL</a>, which I highly recommend for analytics and historical reporting. (Protip: use DATETIME rather than VARCHAR to query by time easily). But what if you have a bunch of Juniper gear and happen to use <a href="https://slack.com/">Slack</a>? I find it useful to know when people are committing changes to the network, especially since we have a handful of engineers all working on different tasks, and I can't keep up with all the network changes.</p>
<p>First you have to know which trap you really care about. For me it's <code>jnxCmCfgChange</code>, which gets triggered on configuration changes. Looking through my previously converted mib to snmptt config, I found the event and changed it to look like this:</p>
<pre class="no-code">
EVENT jnxCmCfgChange .1.3.6.1.4.1.2636.4.5.0.1 "Status Events" Normal
FORMAT Configuration change by $4 (reason: $5, from: $3)
EXEC send_slack_notification "$A - Configuration change by $4 (reason: $5, from: $3)"
..snip (took out description for simplicity)..
</pre><p>In my <code>snmptt</code> log, anytime this gets fired I get a line like this:</p>
<pre class="no-code">
10.1.10.1 - Configuration change by afeid (from: cli, reason: look a change!)
</pre><p>You may have noticed the EXEC statement. That launches an arbitrary command and can use the same string format as the FORMAT string. So for me, I created a dumb little script to post a message to a slack channel like so:</p>
<pre><code>#!/usr/bin/env ruby
require 'uri'
require 'net/https'
TOKEN='yourtokenhere'
CHANNEL='#yourchannelhere'
USERNAME='SNMP Traps'
def usage
puts "#{$0} message"
end
text = ARGV.join(' ')
usage if text.nil?
uri = URI.parse("https://slack.com/")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
params = URI.encode("token=#{TOKEN}&channel=#{CHANNEL}" +
"&username=#{USERNAME}&text=#{text}")
request = Net::HTTP::Post.new("/api/chat.postMessage?#{params}")
http.request(request)
</code></pre><p>The end result:</p>
<p><img src="http://allanfeid.com/sites/default/files/slack_pic.jpg" /></p>
<p>Now this is a very simple use case, there are plenty of powerful things you can do like <a href="http://paulgporter.net/2013/09/16/nagios-snmp-traps/">create alerts in nagios using passive checks</a> or trigger a pager when an interface goes down. What do you do with your SNMP traps? I'd be interesting in finding out.</p>
</div></div></div><section class="field field-name-taxonomy-vocabulary-1 field-type-taxonomy-term-reference field-label-above view-mode-rss"><h2 class="field-label">Tags: </h2><ul class="field-items"><li class="field-item even"><a href="/category/tags/juniper" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">juniper</a></li><li class="field-item odd"><a href="/category/tags/snmp" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">snmp</a></li><li class="field-item even"><a href="/category/tags/netops" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">netops</a></li><li class="field-item odd"><a href="/category/tags/devops" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">devops</a></li></ul></section>Fri, 25 Apr 2014 01:14:44 +0000Allan Feid77 at https://allanfeid.com Create an inventory tool using MCollective's registration feature
https://allanfeid.com/content/create-inventory-tool-using-mcollectives-registration-feature
<div class="field field-name-body field-type-text-with-summary field-label-hidden view-mode-rss"><div class="field-items"><div class="field-item even" property="content:encoded"><p>I started working at a company (Shutterstock) that had <a href="http://docs.puppetlabs.com/mcollective/" title="mcollective home page">MCollective</a> implemented a few months ago. We use it to perform various functions across a wide range of servers. Things such as rolling the application servers to pick up new code, clearing caches, checking replication lag on databases, telling our puppetmaster to sign certificates, and even doing code deployment. There are endless possibilites with this system, and it is so simple to write your own agent/client utilities, that I'm not sure why more people aren't using MCollective. One of the things we were lacking was a central inventory of our systems in an easy to view manner. Thankfully, <a href="http://www.devco.net/" title="R.I. Pienaar's Blog">R.I. Pienaar's</a> MCollective has a great feature called registration. Essentially, it causes your nodes to blast out to an ActiveMQ/rabbitmq topic at a set interval, and any node configured to listen can read that data and perform actions based on that data. With that in mind, I set out to create a database of information.</p>
<h2>Setting up a registration plugin</h2>
<p>Taking a look at <a href="https://github.com/puppetlabs/mcollective-plugins/blob/master/registration/meta.rb" title="mcollective-plugins on github">this registration plugin</a>, you can begin to see how easy generating your data set is. From this example, you get MCollective's agentlist, facts, and classes sent during registration. I wanted to also report on virtual machines running on hypervisors with an in-house libvirtd agent. To achieve that, your agent needs to have a simple method that can be called without the request/reply data structures. An example of that would be this:</p>
<pre><code>module MCollective
module Agent
class Custom<RPC::Agent
def some_method
# gather some data and return it
end
end
end
end
</code></pre><p>Once your agent has a method that can simply be called, you can call this method inside your registration plugin with something like this:</p>
<pre><code>module MCollective
module Registration
class Meta<Base
def body
result = {
:agentlist => [],
:facts => [],
:classes => []
}
cfile = Config.instance.classesfile
if File.exists?(cfile)
result[:classes] = File.readlines(cfile).map {|i| i.chomp}
end
result[:agentlist] = Agents.agentlist
result[:facts] = PluginManager["facts_plugin"].get_facts
if Agents.agentlist.include?("custom")
# because of how PluginManager works, you need to
# append _agent to the agent's name
result[:custom] = PluginManager["custom_agent"].some_method
end
result
end
end
end
end
</code></pre><p>This way your nodes can report back with literally any information that you'd like to include. I've had great success reporting on my hypervisor's available disk, memory, and CPU utilization as well as what virtual machines are running.</p>
<h2>What do you do with this data?</h2>
<p>With inspiration from <a href="https://github.com/puppetlabs/mcollective-plugins/blob/master/agent/registration-mongodb/registration.rb" title="mcollective-plugins on github">this mongodb registration agent</a>, I decided MongoDB was a pretty good way to store data that was dynamicly generated. I wanted to make it easy to store/retrieve data as I saw fit, so using <a href="http://mongoid.org/" title="Mongoid's home page">Mongoid</a> seemed like the way to go. Something as simple as this is all you really need:</p>
<pre><code>class Node
include Mongoid::Document
include Mongoid::Timestamps
field :hostname, :unique => true, :type => String
index :hostname
validates_uniqueness_of :hostname
end
</code></pre><h2>Improving performance with a queue</h2>
<p>After running registration for a bit, memory usage by mcollective began to skyrocket on the machine processing the data, and eventually I started seeing timeouts or pool limits being hit on my mongo connection. I was able to talk a bit with R.I. Pienaar on IRC, and he informed me that every request that a node recieves spawns a new thread for an agent to run in. This is not scalable for the registration process, and once you hit over about 100 nodes, you run into problems since your machine is constantly opening up new threads to handle registration data. He did offer a solution that he uses personally and has suggested to others. That is to use a queue rather than a topic, and to have a simple daemon dedicated to processing registration data. There's some <a href="http://devco.net/rip/mcollective-registration-receiver.rb">sample code available on his website</a>, and I'm sure this will be included in the main application at some point. In order to get this working I did need to change my ActiveMQ configuration a bit, but the change was simple and just required adding a new line similar to this:</p>
<pre><code><authorizationEntry queue="mcollective.>" write="admins" read="admins" admin="admins" />
</code></pre><p>Basically the mcollective user that you use from the client side needs to be able to read/write to the new queue. This also requires that your registration plugin sends the result out to the queue rather than a topic. Another simple change:</p>
<pre><code>module MCollective
module Registration
class Meta<Base
def body
...
PluginManager["connector_plugin"].connection.publish("/queue/mcollective.registration", result.to_json)
nil
end
end
end
end
</code></pre><p>By having the body method return nil, you won't send anything out to the standard registration topic, which would be redundant in this case.</p>
<h2>The end result</h2>
<p>With a bit of tweaking, I was able to get my registration daemon/agent saving data into mongo in less than a second per request recieved. Once I had all that data in place, I was able to implement a slick interface using Sinatra and Bootstrap that gives me good insight into our inventory. I'll probably go more into the frontend interface when it becomes more mature, but once you have all this data, it's up to you to decide what you want to do with it.</p>
</div></div></div><section class="field field-name-taxonomy-vocabulary-1 field-type-taxonomy-term-reference field-label-above view-mode-rss"><h2 class="field-label">Tags: </h2><ul class="field-items"><li class="field-item even"><a href="/category/tags/mcollective" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">MCollective</a></li><li class="field-item odd"><a href="/category/tags/ruby" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Ruby</a></li><li class="field-item even"><a href="/category/tags/linux" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Linux</a></li><li class="field-item odd"><a href="/category/tags/sysadmin" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">sysadmin</a></li><li class="field-item even"><a href="/category/tags/devops" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">devops</a></li></ul></section>Sat, 08 Oct 2011 22:05:09 +0000Allan Feid75 at https://allanfeid.comTracking stats in your application
https://allanfeid.com/content/tracking-stats-your-application
<div class="field field-name-body field-type-text-with-summary field-label-hidden view-mode-rss"><div class="field-items"><div class="field-item even" property="content:encoded"><p>Inspired by <a href="http://codeascraft.etsy.com/2011/02/15/measure-anything-measure-everything/" title="Measure antyhing, measure everything">Etsy's post</a> on keeping track of everything, I set out to achieve similar functionality on a Rails application. Essentially, you run <a href="http://graphite.wikidot.com/" title="Graphite">Graphite</a> somewhere and send UDP packets from your application to <a href="https://github.com/etsy/statsd" title="StatsD on Github">StatsD</a>. When you have Graphite and StatsD running, you can really get some awesome stats from your application and at the same time, since it's using UDP, the overhead is very minimal.</p>
<h2>Getting your graphite on</h2>
<p>I'm using Ubuntu 10.10 in this example, because it has a whisper package available, but you should be able to install this on any distro as all the sources are available.</p>
<p># apt-get install -y bzr python-cairo-dev python-django python-twisted python-whisper libapache2-mod-wsgi</p>
<p>Once you have the necessary packages installed, you can easily grab the graphite source from launchpad, verify you have all the dependencies and finally install the software.</p>
<pre><code># bzr branch lp:graphite
# cd graphite
# ./check-dependenicies.py
...
# python setup.py install
</code></pre><p>Graphite installs itself to <em>/opt/graphite</em>, with plenty of example configurations. You will need to copy a few in place to get started.</p>
<pre><code># cd /opt/graphite/conf
# cp carbon.conf.example carbon.conf
# cp storage-schemas.conf.example storage-schemas.conf
# cp graphite.wsgi.example graphite.wsgi
</code></pre><p>I added the following to my <em>storage-schemas.conf</em> which provides some sane defaults for use with StatsD, provided by Etsy:</p>
<pre><code>[stats]
priority = 110
pattern = ^stats\..*
retentions = 10:2160,60:10080,600:262974
</code></pre><p>The Graphite source comes with an example virtual host definition for Apache under the examples directory. I simply copied this over to <em>/etc/apache2/sites-available/graphite</em>, but had to comment out the <em>WSGCISocketPrefix</em> line. After the virtual host definition is in place, you will need to set up the initial database, then change up some permissions so Apache can write to the necessary directories.</p>
<pre><code># cd /opt/graphite/webapp/graphite
# python manage.py syncdb
# chown -R www-data:www-data /opt/graphite/storage/
# a2ensite graphite
# service apache2 restart
</code></pre><p>The final step is to start up <em>carbon</em> which is graphite's data aggregator.</p>
<pre><code># cd /opt/graphite/
# ./bin/carbon-cache.py start
</code></pre><p>If all went well, you should now have graphite up and running at <em>http://stats.example.com</em>. At some point I really need to look into writing a Chef cookbook for this, but looks like <a href="https://github.com/dje/cookbooks/tree/master/graphite" title="dje's graphite cookbook">dje</a> has written one already. I haven't checked it out yet, but I'm sure it's at the very least a good starting point.</p>
<h2>A simple node.js daemon: StatsD</h2>
<p>You can either compile node.js from source, or grab it from a PPA. The version included in the Ubuntu repositories are too old to run StatsD.</p>
<pre><code># add-apt-repository ppa:richarvey/nodester
# apt-get update
# apt-get install nodejs
</code></pre><p>With the required dependencies in place, clone the repo and move it to a more suitable location.</p>
<pre><code># git clone https://github.com/etsy/statsd.git
# mv statsd /opt/
# mkdir /etc/statsd
</code></pre><p>Create your configuration file, in this case graphite is running on the same machine as statsd:</p>
<pre><code># cat /etc/statsd/config.js
{
graphitePort: 2003
, graphiteHost: "localhost"
, port: 8125
}
</code></pre><p>So now you will probably want to use upstart to integrate the service properly in Ubuntu. Create a simple upstart service definition as follows:</p>
<pre><code># cat /etc/init/statsd.conf
description "statsd"
author "rdio"
start on startup
stop on shutdown
script
export HOME="/root"
exec sudo -u nobody nodejs /opt/statsd/stats.js /etc/statsd/config.js
end script
</code></pre><p>Now you can easily start statsd and verify it is running:</p>
<pre><code># start statsd
# status statsd
statsd start/running, process 22307
# netstat -nulp | grep nodejs
udp 0 0 0.0.0.0:8125 0.0.0.0:* 22307/nodejs
</code></pre><p>At this point you will be able to configure your application to send data at your StatsD instance. There's plenty of code out there for doing this in pretty much every language. In fact, when you cloned the StatsD repository, it came with PHP and Python examples. Below I'll show you how to implement a statsd client in a Rails application.</p>
<h2>Some basic stats in a Rails application</h2>
<p>To start out, you need to tell Rails you need the <a href="https://github.com/dawanda/statsd-client" title="Dawanda's StatsD Client">dawanda-statsd-client</a> gem, so add the following in your <em>environments.rb</em>:</p>
<pre><code>config.gem 'dawanda-statsd-client', :lib => 'statsd/client'
</code></pre><p>The default methods are pretty good, but getting your connection information in needs a bit of work so we override the <em>config</em> method in <em>initializers/statsd.rb</em>:</p>
<pre><code>class Statsd
class << self
def config
environments = YAML.load_file("#{Rails.root}/config/statsd-client.yml") || {}
environments[Rails.env]
end
end
end
</code></pre><p>Next you'll want to keep your StatsD connection information in a separate yaml file. You can define different settings per the running Rails environment. Set up <em>config/statsd-client.yml</em>:</p>
<pre><code>production:
host: stats.example.com
port: 8125
</code></pre><p>Now you're ready to increment any arbitrary stat at some place in your application:</p>
<pre><code>Statsd.increment('user.logins')
Statsd.timing('user.upload', end_time - start_time) # time is tracked in ms
</code></pre><h2>What stats to track?</h2>
<p>Well that's entirely up to you, but ideally you would be tracking as much as humanly possible. Things that you don't even know will matter, may matter at some point, so having a history of stats will help you troubleshoot some issues. You could track every CRUD operation in your models, specific actions in your controllers, or even user page load times. You will definitely want to keep track of timings of any external API calls and If you're using Capistrano to do deployments, it's easy to add a simple increment every time you do a deployment. This will allow you to overlay your arbitrary stats with code deployments. This is awesome for seeing the effects of your modifications and can be invaluable for showing progress to your management types.</p>
</div></div></div><section class="field field-name-taxonomy-vocabulary-1 field-type-taxonomy-term-reference field-label-above view-mode-rss"><h2 class="field-label">Tags: </h2><ul class="field-items"><li class="field-item even"><a href="/category/tags/ruby" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Ruby</a></li><li class="field-item odd"><a href="/category/tags/rails" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Rails</a></li><li class="field-item even"><a href="/category/tags/graphite" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">graphite</a></li><li class="field-item odd"><a href="/category/tags/devops" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">devops</a></li><li class="field-item even"><a href="/category/tags/statsd" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">statsd</a></li><li class="field-item odd"><a href="/category/tags/sysadmin" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">sysadmin</a></li></ul></section>Fri, 22 Apr 2011 17:59:26 +0000Allan Feid74 at https://allanfeid.comUsing Amazon's CloudFormation, cloud-init, chef and fog to automate infrastructure
https://allanfeid.com/content/using-amazons-cloudformation-cloud-init-chef-and-fog-automate-infrastructure
<div class="field field-name-body field-type-text-with-summary field-label-hidden view-mode-rss"><div class="field-items"><div class="field-item even" property="content:encoded"><p>Amazon recently announced their new CloudFormation API. This allows you to create what they call "stacks," which will bring up and provision various AWS resources. You are able to configure security groups, EC2 instances, RDS instances, and elastic load balancers just to name a few. With this level of support and ease of declaration, it makes you wonder how much longer things like RightScale are going to last.</p>
<p>In this post, I'm going to cover a couple different topics. Since there's a lot going on here, I may be brief to explain certain aspects. Hopefully I do a good job of getting the basic idea down, but just in case, here's a quick overview of what I'm attempting to accomplish:</p>
<ul><li>create EC2 user data that can be used with cloud-init</li>
<li>have the user data kick off chef-solo to configure an instance</li>
<li>setup a simple CloudFormation template for easy launching</li>
<li>use fog to launch a CloudFormation template</li>
</ul><h2>EC2 user data and cloud-init</h2>
<p><a href="http://aws.amazon.com/articles/1085?_encoding=UTF8&jiveRedirect=1">User data</a> can be associated with EC2 instances on boot. The data can really be any arbitrary value. It is up to you to handle what is done with this data, however, the Ubuntu developers came up with a pretty cool concept: <a href="https://help.ubuntu.com/community/CloudInit" title="Ubuntu CloudInit">cloud-init</a>. With the cloud-init package, you can format your user data to their specs, and have the instance perform various actions. This becomes extremely powerful. One of the most widely used features, is to setup a <em>User-Data Script</em>, which in cloud-init, means starting your userdata with "#!", the shebang. A user-data script will run around the same time as <em>rc.local</em> in the boot process making it a great place to perform service configuration.</p>
<p>What if you want more control, rather than relying on scripts? Some of the really cool parts of cloud-init include being able to install packages, specifying that you want apt-get to update, and even upgrade the system on boot. With the multi-part support, you use mime-types to combine multiple files together. This allows you to have both a <em>user-script</em> and a <em>cloud-config</em> portion of your EC2 user data, amongst other things.</p>
<p>With knowledge of cloud-init's user-scripts in mind, I started off with this basic script:</p>
<pre><code>#!/bin/bash
log='/tmp/init.log'
apt-get update &>> $log
apt-get install -y ruby ruby1.8-dev build-essential wget libruby-extras libruby1.8-extras git-core &>> $log
cd /tmp
wget http://production.cf.rubygems.org/rubygems/rubygems-1.3.7.tgz &>> $log
tar zxf rubygems-1.3.7.tgz &>> $log
cd rubygems-1.3.7
ruby setup.rb --no-format-executable &>> $log
gem install ohai chef --no-rdoc --no-ri --verbose &>> $log
mkdir -p /var/chef/cache
git clone -b deploy git://github.com/crazed/cookbooks.git /var/chef/cookbooks &>> $log
mkdir /etc/chef
cat << EOF > /etc/chef/solo.rb
file_cache_path "/var/chef/cache"
cookbook_path "/var/chef/cookbooks"
json_attribs "/etc/chef/node.json"
log_location "/var/chef/solo.log"
verbose_logging true
EOF
cat << EOF > /etc/chef/node.json
{
"www": {
"document_root": "/srv/http",
"server_name": "localhost"
},
"mysql": {
"user": "user",
"password": "user",
"hostname": "localhost",
"database": "drupal",
"rootpw": "root"
},
"drupal": {
"version": "7.0",
"modules": [ "pathauto", "token" ]
},
"run_list": [
"recipe[php-fpm]",
"recipe[mysql::new_database]",
"recipe[drupal::install]",
"recipe[drupal::nginx-config]"
]
}
EOF
chef-solo &>> $log
</code></pre><p>This was cool and all, but I wanted to really separate my node attributes and solo configuration from the user-script. So I stumbled upon the <em>part-handler</em> portion of cloud-init. This allows you to write a bit of python to handle additional mime-types inside the EC2 user data. Here's a <em>very</em> basic chunk that adds support for <em>text/chef-attributes</em> and <em>text/chef-solo</em> mime types:</p>
<pre><code>#part-handler
# vi: syntax=python ts=4
def list_types():
# return a list of mime-types that are handled by this module
return(["text/chef-attributes", "text/chef-solo"])
def handle_part(data,ctype,filename,payload):
# data: the cloudinit object
# ctype: '__begin__', '__end__', or the specific mime-type of the part
# filename: the filename for the part, or dynamically generated part if
# no filename is given attribute is present
# payload: the content of the part (empty for begin or end)
if ctype == "__begin__":
print "my handler is beginning"
return
if ctype == "__end__":
print "my handler is ending"
return
if ctype == "text/chef-attributes":
print "handling %s " % ctype
import os
attribs = '/etc/chef/node.json'
d = os.path.dirname(attribs)
if not os.path.exists(d):
os.makedirs(d)
f = open(attribs, 'w')
f.write(payload)
f.close()
return
if ctype == "text/chef-solo":
print "handling %s" % ctype
import os
solo = '/etc/chef/solo.rb'
d = os.path.dirname(solo)
if not os.path.exists(d):
os.makedirs(d)
f = open(solo, 'w')
f.write(payload)
f.close()
return
</code></pre><p>There's probably more effecient ways of doing the above, but this is all still a work in progress for me. Either way, continuing with the original script, I can also pull out the package installation and add this to a <em>cloud-config</em> portion of my user data using the following:</p>
<pre><code>#cloud-config
packages:
- ruby
- ruby1.8-dev
- build-essential
- wget
- libruby-extras
- libruby1.8-extras
- git-core
</code></pre><p>Note the <em>#cloud-config</em> line at the begining. This tells cloud-init to handle data in the file as part of the cloud-config specification. Now that I have a cloud-config portion installing some required packages, and a part-handler to handle additional mime-types, I can put my chef node attributes and chef solo configuration into their own files.</p>
<pre><code># cat node.json
{
"www": {
"document_root": "/srv/http",
"server_name": "localhost"
},
"mysql": {
"user": "user",
"password": "user",
"hostname": "localhost",
"database": "drupal",
"rootpw": "root"
},
"drupal": {
"version": "7.0",
"modules": [ "pathauto", "token" ]
},
"run_list": [
"recipe[php-fpm]",
"recipe[mysql::new_database]",
"recipe[drupal::install]",
"recipe[drupal::nginx-config]"
]
}
# cat solo.rb
file_cache_path "/var/chef/cache"
cookbook_path "/var/chef/cookbooks"
json_attribs "/etc/chef/node.json"
log_location "/var/chef/solo.log"
verbose_logging true
</code></pre><p>I'm not going to go over the <em>node.json</em> part all that much, but basically my chef cookbooks will read this data in order to determine how to configure my instance. I want to set my http document root to <em>/srv/http</em>, setup a new mysql database, and install drupal 7.0 with the <em>pathauto</em>, and <em>token</em> modules. The solo configuration points out where I will be storing my cookbooks, which my stripped down user-script will be checking out from my github repository:</p>
<pre><code>#!/bin/bash
log='/var/log/cloud-init.log'
cd /tmp
wget http://production.cf.rubygems.org/rubygems/rubygems-1.3.7.tgz &>> $log
tar zxf rubygems-1.3.7.tgz &>> $log
cd rubygems-1.3.7
ruby setup.rb --no-format-executable &>> $log
mkdir -p /var/chef/cache
git clone -b deploy git://github.com/crazed/cookbooks.git /var/chef/cookbooks &>> $log
gem install ohai chef --no-rdoc --no-ri --verbose &>> $log
chef-solo &>> $log
</code></pre><p>Now that I have all these files, I need to combine them using mime-multiparts. There's a script availabe in the cloud-utils package, but you won't really need to install this on your workstation/laptop. So just use <em>bzr</em> to checkout the latest version:</p>
<pre><code># bzr branch lp:ubuntu/cloud-utils
# cd cloud-utils
# ./write-mime-multipart --output=combined-userdata.txt \
part-handler.py:text/part-handler \
node.json:text/chef-attributes \
solo.rb:text/chef-solo \
cloud-config.txt \
user-script.sh
</code></pre><p><em>Note that I don't specify the mime-types for user-script.sh or cloud-config.txt, as they begin with the appropriate lines which cause cloud-init to handle them properly.</em></p>
<h2>CloudFormation templates</h2>
<p>So now that you have this slick user-data file, you're going to want to put more than one of these together to create a full infrastructure. This is where the CloudFormation "stacks" come into play. They are JSON based templates. This example will create you an EC2 Security Group and an EC2 instance.</p>
<pre><code>{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description": "Create a fully functioning Drupal installation."
"Parameters": {
"InstanceType": {
"Description": "Type of EC2 instance to launch",
"Type": "String",
"Default": "m1.small"
},
"WebServerPort": {
"Description": "TCP/IP port fo the webserver",
"Type": "String",
"Default": "80"
},
"KeyName": {
"Description": "Name of an existing EC2 KeyPair to enable SSH access to the instances",
"Type": "String"
}
},
"Mappings": {
"AWSInstanceType2Arch": {
"t1.micro" : { "Arch": "64" },
"m1.small" : { "Arch" : "32" },
"m1.large" : { "Arch" : "64" },
"m1.xlarge" : { "Arch" : "64" },
"m2.xlarge" : { "Arch" : "64" },
"m2.2xlarge" : { "Arch" : "64" },
"m2.4xlarge" : { "Arch" : "64" },
"c1.medium" : { "Arch" : "32" },
"c1.xlarge" : { "Arch" : "64" },
"cc1.4xlarge" : { "Arch" : "64" }
},
"AWSRegionArch2AMI": {
"us-east-1": { "32": "ami-3e02f257", "64": "ami-3202f25b" }
}
},
"Resources": {
"web1": {
"Type": "AWS::EC2::Instance",
"Properties": {
"InstanceType": { "Ref": "InstanceType" },
"SecurityGroups": [ { "Ref": "web" }, "default" ],
"KeyName": { "Ref": "KeyName" },
"ImageId": { "Fn::FindInMap" : [ "AWSRegionArch2AMI", { "Ref": "AWS::Region" },
{ "Fn::FindInMap": [ "AWSInstanceType2Arch", { "Ref": "InstanceType" }, "Arch" ] } ] },
"UserData": { "Fn::Base64": " SEE BELOW " }
}
},
"web": {
"Type": "AWS::EC2::SecurityGroup",
"Properties": {
"GroupDescription": "Web security group, allows port 80 in",
"SecurityGroupIngress": [ {
"IpProtocol": "tcp",
"FromPort": "22",
"ToPort": "22",
"CidrIp": "0.0.0.0/0"
},
{
"IpProtocol": "tcp",
"FromPort": { "Ref": "WebServerPort" },
"ToPort": { "Ref": "WebServerPort" },
"CidrIp": "0.0.0.0/0"
} ]
}
}
}
}
</code></pre><p>Taking a look at the template, you'll see it gets a bit messy in the user data portion of my instance. Essentially you need to make sure your userdata is compliant with the JSON format. To do that, I used this simple hackish ruby script:</p>
<pre><code># cat json_encode.rb
#!/usr/bin/ruby
require 'rubygems'
require 'json'
if ARGV.size != 1
puts "Usage: #{$0} <file>"
exit 1
end
def escape(string)
parse = JSON.parse({ 'json' => string }.to_json)
parse['json']
end
data = ''
File.open(ARGV[0]) {|f| data << f.read}
p escape(data)
# ./json_encode.rb combined-userdata.txt
...
</code></pre><h2>Manipulating stacks with fog</h2>
<p>Now you have a stack template that's fairly powerful. You get to configure a few things, such as what EC2 keypair to use and what instance type you want for the stack. You can even configure what web port will be opened on security group. How do you actually use this data? While Amazon provides command line api tools, I decided to add CloudFormation support to the <a href="https://github.com/geemus/fog" title="Fog">fog gem</a> and use this to create a basic tool for myself. Realistically, you could take fog and build a nice interface to launch or build your own templates using your own chef cookbooks/node attributes. As of this writing, CloudFormation is not included in the released fog gem, so you're going to have to checkout the latest code from github:</p>
<pre><code># mkdir projects && cd projects
# git clone git://github.com/geemus/fog.git
</code></pre><p>With everything in place, here's a simple script that will allow you to launch a stack:</p>
<pre><code>#!/usr/bin/env ruby
require 'optparse'
require "#{ENV['HOME']}/projects/fog/lib/fog"
options = {}
parser = OptionParser.new do |opts|
opts.banner = "Usage: #{$0} -n STACK_NAME -t TEMPLATE_FILE [options]"
options[:name] = nil
opts.on('-n', '--name NAME', 'Required name attribute') do |name|
options[:name] = name
end
options[:parameters] = {}
opts.on('-p', '--parameters KEY=VALUE', 'Set the parameters for a stack template',
'(can be comma separated key value pairs)') do |params|
params.split(',').each do |param|
k,v = param.split('=')
raise "Invalid parameter definition" unless v
options[:parameters][k] = v
end
end
options[:template] = nil
opts.on('-t', '--template FILE', 'Set the template file for this stack') do |file|
options[:template] = file
end
end
parser.parse!
[:name, :template].each do |p|
raise "Missing required parameter: --#{p}" unless options[p]
end
config = YAML::load(File.open(ENV['HOME']+'/.fog'))[:default]
template_body = ''
File.open(options[:template]) {|f| template_body << f.read}
cf = Fog::AWS::CloudFormation.new(
:aws_access_key_id => config[:aws_access_key_id],
:aws_secret_access_key => config[:aws_secret_access_key]
)
def aws_params(hash)
r = Hash.new
c = 1
hash.each do |k,v|
r["Parameters.member.#{c}.ParameterKey"] = k
r["Parameters.member.#{c}.ParameterValue"] = v
c += 1
end
r
end
cf.create_stack(options[:name], {'TemplateBody' => template_body}.merge!(aws_params(options[:parameters])))
</code></pre><p>You can use it as follows:</p>
<pre><code># ./create-stack.rb --name drupal --parameters KeyName=allan,InstanceType=m1.large --template drupal_dev.template
</code></pre><p>This will upload your template definition and have Amazon read and launch the various resources that you have defined. As these resources are launched, you can get status messages using the DescribeStackEvents API call. Again, here's a simple script that's using fog to pull down these messages:</p>
<pre><code>#!/usr/bin/env ruby
require 'rubygems'
require 'yaml'
require "#{ENV['HOME']}/projects/fog/lib/fog"
if ARGV.size != 1
puts "Usage: #{$0} <stack name>"
exit 1
end
config = YAML::load(File.open(ENV['HOME']+'/.fog'))[:default]
cf = Fog::AWS::CloudFormation.new(
:aws_access_key_id => config[:aws_access_key_id],
:aws_secret_access_key => config[:aws_secret_access_key]
)
events = cf.describe_stack_events('StackName' => ARGV[0]).body['Events']
events.each do |event|
puts "Timestamp: #{event['Timestamp']}"
puts "LogicalResourceId: #{event['LogicalResourceId']}"
puts "ResourceType: #{event['ResourceType']}"
puts "ResourceStatus: #{event['ResourceStatus']}"
puts "ResourceStatusReason: #{event['ResourceStatusReason']}" if event['ResourceStatusReason']
puts "--"
end
</code></pre><p>And what the output will look like:</p>
<pre><code># ./describe_stack_events.rb drupal
Timestamp: 2011-03-02T17:13:08Z
LogicalResourceId: drupal
ResourceType: AWS::CloudFormation::Stack
ResourceStatus: CREATE_COMPLETE
--
Timestamp: 2011-03-02T17:13:05Z
LogicalResourceId: web1
ResourceType: AWS::EC2::Instance
ResourceStatus: CREATE_COMPLETE
--
Timestamp: 2011-03-02T17:12:27Z
LogicalResourceId: web1
ResourceType: AWS::EC2::Instance
ResourceStatus: CREATE_IN_PROGRESS
--
Timestamp: 2011-03-02T17:12:27Z
LogicalResourceId: web
ResourceType: AWS::EC2::SecurityGroup
ResourceStatus: CREATE_COMPLETE
--
Timestamp: 2011-03-02T17:12:23Z
LogicalResourceId: web
ResourceType: AWS::EC2::SecurityGroup
ResourceStatus: CREATE_IN_PROGRESS
--
Timestamp: 2011-03-02T17:12:21Z
LogicalResourceId: drupal
ResourceType: AWS::CloudFormation::Stack
ResourceStatus: CREATE_IN_PROGRESS
ResourceStatusReason: User Initiated
--
</code></pre><p>Again you could simply use the provided Amazon tools, as they are probaly more feature-complete, but sometimes you want to be able to query for specific bits of data. Learning how to use fog will save you tons of time compared to parsing data that comes out of the Amazon tools. Fog really lets you do some powerful things with the Amazon APIs and is always gaining more features.</p>
<h2>Current shortcommings and summary</h2>
<p>This setup has a few issues that I currently don't know how to solve. Let's say you were to launch a stack that included two EC2 instances, a load balancer, and an RDS instance. During the creation phase, you want to import a basic DB schema into your RDS instance. Since you can't get shell on the RDS instance, you will have to use one of your EC2 instances to import the schema. You have a race condition here, how do you make sure your RDS instance is available before your EC2 instance attemps to import data? Right now the only thing I can think of is to have your chef recipe gracefully handle retries. It seems we need a way for resources of a stack to depend on other resources before being launched, or a notification system that could potentially kick off the chef run (SNS?).</p>
<p>Either way, I think CloudFormation, chef and fog can be used in conjuction with cloud-init to create quick deployments that are OS independent. This allows you to maintain your automation portions separately from your images, which as a consequence, promotes the use of vanilla images. With these basic ideas, you can easily swap out the <em>node.json</em> file for your own, and replace the user-script in such a way that it checks out your own cookbooks. The possibilities are really endless. Currently cloud-init supports Puppet out of the box, and I'd like to see some chef support here as well.</p>
</div></div></div><section class="field field-name-taxonomy-vocabulary-1 field-type-taxonomy-term-reference field-label-above view-mode-rss"><h2 class="field-label">Tags: </h2><ul class="field-items"><li class="field-item even"><a href="/category/tags/chef" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">chef</a></li><li class="field-item odd"><a href="/category/tags/cloudformation" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">CloudFormation</a></li><li class="field-item even"><a href="/category/tags/aws" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">aws</a></li><li class="field-item odd"><a href="/category/tags/fog" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">fog</a></li><li class="field-item even"><a href="/category/tags/cloud-init" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">cloud-init</a></li><li class="field-item odd"><a href="/category/tags/ubuntu" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Ubuntu</a></li><li class="field-item even"><a href="/category/tags/devops" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">devops</a></li><li class="field-item odd"><a href="/category/tags/sysadmin" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">sysadmin</a></li><li class="field-item even"><a href="/category/tags/ruby" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Ruby</a></li></ul></section>Thu, 03 Mar 2011 02:08:59 +0000Allan Feid73 at https://allanfeid.comScaling your application in the cloud
https://allanfeid.com/content/scaling-your-application-cloud
<div class="field field-name-body field-type-text-with-summary field-label-hidden view-mode-rss"><div class="field-items"><div class="field-item even" property="content:encoded"><p>Recently, I've been standing up a lot of infrastructure in Amazon's EC2. While auto-scaling the infrastructure aspects is fairly simple using configuration automation utilities such as Chef or Puppet, getting developers to understand these concepts may be an interesting task. I will try to outline most of the common issues I see when uplifting applications to an elastic cloud environment.</p>
<h2>Rules to live by</h2>
<h3>Infrastructure automation is critical</h3>
<p>You need to know how to build the infrastructure that runs your application, and you need to know how to do this repeatedly. Using tools such as <a href="/content/cooking-chef" title="Cooking with chef">Chef</a> or Puppet will allow you to do this efficiently and in a self-documenting way that also provides revision control to your software configurations. Being able to do this allows you to quickly bring up additional server resources as your application demands it.</p>
<h3>Revision control is not optional</h3>
<p><a href="/content/setting-your-own-git-repository" title="Setting up your own git repository">Choose your favorite source control system</a> and learn how to use every aspect of it. These tools were meant to keep track of your code changes. When dealing with the cloud, you're going to need to keep track of what is in you production environment vs your staging environment.</p>
<p>One of the biggest changes with a cloud based infrastructure, is that the underlying resources <em>will be changing</em>. There will be monitors that detect high traffic, CPU usage, memory usage, or anything really. This will cause additional virtual machines (instances) to launch, configure themselves (see above), check out the latest version of your code from your repository, and finally add itself to a load balancer. When your application's server infrastructure can build itself, you better be able to checkout the latest revision from your deployment branch and have it run with no human interaction.</p>
<h3>Don't rely on traditional shared storage</h3>
<p>I've seen many legacy applications rely on shared storage of some sort. This will not work in most cloud environments, and definitely will not work in Amazon. As of this writing, Amazon's Elastic Block Storage (EBS) volumes can only be attached to one server instance. This means you cannot utilize a clustered filesystem and standing up your own NFS server leaves for a single point of failure.</p>
<p>The solution? Put all of your site content that isn't revision controlled into an asset host or content delivery network (CDN). When on Amazon, this means using Amazon's Simple Storage Service (S3). By doing this you are storing your precious images, movies, and other content on storage that is 99.999999999% available and accessible anywhere over HTTP. As an added bonus, <a href="http://stackoverflow.com/questions/913208/pros-and-cons-of-a-separate-image-server-e-g-images-mydomain-com" title="Pros and Cons of a separate image server">you will be serving up images faster</a> and can achieve better edge network performance by enabling CloudFront, Amazon's CDN service.</p>
<h3>Relational databases aren't evil</h3>
<p><a href="http://www.xtranormal.com/watch/6995033/" title="MongDB is webscale">There's a lot of hype over NoSQL</a> databases such as MongoDB and CouchDB. They are touted as being "webscale" because you can add and remove instances from the database cluster as needed. These are great if you're starting a new application and have decided you need these features, but what if you have existing data in MySQL?</p>
<p>Amazon's Relational Database Service (RDS) supports MySQL databases that are both redundant and scalable to your needs. You can easily grow the database storage size, cpu, and memory resources. They also perform automatic backups in a timeslot that you configure. This makes transitioning from traditional database infrastructure to the cloud fairly painless. They have also announced plans to <a href="http://aws.amazon.com/rds/oracle/?utm_source=OraclePR&utm_medium=RDSLandingPage&utm_campaign=Oracle" title="Amazon plans to support Oracle 11g on RDS">support Oracle 11g</a> in the future on the same platform. So while scaling relational databases are not as trivial as using MongoDB, Amazon has really stepped up to the plate by taking care of this problem for you.</p>
<h2>Summary</h2>
<p>By utilizing asset hosts, maintaining an automated server build, and keeping strict revision control you will be setting yourself up for success in today's cloud environment, however, I cannot stress the importance of at least having a staging environment. Have unit test and force all commits to be tested, then deployed to your staging environment. This allows you to quickly catch any code issues. An added benefit to your staging environment, is that you can test infrastructure changes before deciding to put the changes in the wild.</p>
</div></div></div><section class="field field-name-taxonomy-vocabulary-1 field-type-taxonomy-term-reference field-label-above view-mode-rss"><h2 class="field-label">Tags: </h2><ul class="field-items"><li class="field-item even"><a href="/category/tags/cloud" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">cloud</a></li><li class="field-item odd"><a href="/category/tags/ec2" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">ec2</a></li><li class="field-item even"><a href="/category/tags/aws" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">aws</a></li><li class="field-item odd"><a href="/category/tags/devops" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">devops</a></li><li class="field-item even"><a href="/category/tags/sysadmin" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">sysadmin</a></li><li class="field-item odd"><a href="/category/tags/automation" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">automation</a></li></ul></section>Wed, 16 Feb 2011 02:55:26 +0000Allan Feid71 at https://allanfeid.comSetting up your own git repository
https://allanfeid.com/content/setting-your-own-git-repository
<div class="field field-name-body field-type-text-with-summary field-label-hidden view-mode-rss"><div class="field-items"><div class="field-item even" property="content:encoded"><h2>Setup a bare repository</h2>
<p>The first thing you'll need to do is create a new bare repository. A bare repository is one that does not contain a working directory, essentially it just contains everything in your standard <code class="prettyprint">.git</code> directory. I'll be serving all my repositories from <code class="prettyprint">/srv/git</code>, and assuming all the users you want to give access to this repository are in the <cite>git</cite> group.</p>
<pre><code># mkdir /srv/git
# git init --bare --shared=group /srv/git/my-awesome-application.git
# chgrp -R git /srv/git/my-awesome-application.git
</code></pre><p>If you're not going to be sharing this with multiple users, you won't need the <code class="prettyprint">--shared=group</code>. Later down the road, if you forgot to specify this option, all it really does is the following:</p>
<pre><code># chmod -R g+rws /srv/git/my-awesome-application.git
</code></pre><p>Then, you either need to change the default umask of each user to 002, or you can make <code class="prettyprint">/srv/git/my-awesome-application.git/config</code> look like this:</p>
<pre><code>[core]
repositoryformatversion = 0
filemode = true
bare = true
sharedrepository = 1
</code></pre><p>The key is the <code class="prettyprint">sharedrepository = 1</code> directive. This can be changed to a variety of options, but when set to <code class="prettyprint">true</code> or <code class="prettyprint">group</code> it ensures all file pushes will use a sane umask of 002. You can find more information about this setting in the <a href="http://www.kernel.org/pub/software/scm/git/docs/git-config.html" title="git-config(1)">git-config(1)</a> man page.</p>
<h2>Configuring user access</h2>
<p>I personally never setup a git repository without using ssh keys. So have your committers send your their public keys and create users for them. Add these keys to their <code class="prettyprint">authorized_keys</code> file, and have them confirm they can ssh in without any issues. Once this is done, you can have them add a new remote repository to git as follows:</p>
<pre><code># git remote add origin user@crazed.pl:/srv/git/my-awesome-application.git
</code></pre><p>That's it, they're ready to start pushing code in the normal ways:</p>
<pre><code># git push origin master
</code></pre><h2>Allowing read only access</h2>
<p>Sometimes it's necessary to allow read only access to your repositories, such as for use with Capistrano or performing continuous integration. In this case, using <a href="http://www.kernel.org/pub/software/scm/git/docs/git-daemon.html" title="git-daemon(1)">git-daemon</a> is easy and provides faster cloning/pulls than using HTTP or SSH. On Ubuntu, you can install the following package, which sets you up with a <code class="prettyprint">runit</code> configuration:</p>
<pre><code># apt-get install -y git-daemon-run
</code></pre><p>There's one thing you'll likely want to change in this daemon's arguments, and that is the <code class="prettyprint">--base-path</code> option. I use the following in <code class="prettyprint">/etc/sv/git-daemon/run</code>:</p>
<pre><code>#!/bin/sh
exec 2>&1
echo 'git-daemon starting.'
exec chpst -ugitdaemon \
/usr/lib/git-core/git-daemon --verbose --base-path=/srv/git /srv/git
</code></pre><p>If you took a look at the <code class="prettyprint">git-daemon(1)</code> man page, you will see that the base path essentially changes the root for git requests. So, in reality your application lives at <code class="prettyprint">/srv/git/my-awesome-application.git</code>, but when pulling over the git protocol, it's as easy as this:</p>
<pre><code># git clone git://crazed.pl/my-awesome-application.git
</code></pre><p>However, before you can even do that, there is one magic step that makes this all work.</p>
<pre><code># touch /srv/git/my-awesome-application.git/git-daemon-export-ok
</code></pre><p>Touching this file tells the git-daemon that you meant for this repository to be publicly accessible (assuming you have the proper ports open, <code class="prettyprint">tcp/9418</code>).</p>
<h2>Providing a web front end</h2>
<p>So your boss wants to see what you've been doing, but has no idea what git is. Why not give him a simple web interface? Luckily, Ubuntu again has a package for this:</p>
<pre><code># apt-get install gitweb
</code></pre><p><a href="https://git.wiki.kernel.org/index.php/Gitweb" title="Gitweb">Gitweb</a> is a simple web interface written in Perl and used as a CGI script. There may be better interfaces out there, but if it's good enough for <cite>kernel.org</cite>, it's good enough for me. To get this working, there is one simple change to the configuration file <code class="prettyprint">/etc/gitweb.conf</code>:</p>
<pre><code># path to git projects (<project>.git)
$projectroot = "/srv/git";
</code></pre><p>The Ubuntu package comes with a simple Apache configuration to get this working at <cite>fqdn.com/gitweb</cite>:</p>
<pre><code>Alias /gitweb /usr/share/gitweb
<Directory /usr/share/gitweb>
Options FollowSymLinks +ExecCGI
AddHandler cgi-script .cgi
</Directory>
root@ip-10-117-65-141:~# vi /etc/gitweb.conf
root@ip-10-117-65-141:~# cat /etc/apache2/conf.d/gitweb
Alias /gitweb /usr/share/gitweb
<Directory /usr/share/gitweb>
Options FollowSymLinks +ExecCGI
AddHandler cgi-script .cgi
</Directory>
</code></pre><p>That's it, you now have a private repository that can be shared amongst multiple users, as well as a web interface and read-only access to those that need it.</p>
</div></div></div><section class="field field-name-taxonomy-vocabulary-1 field-type-taxonomy-term-reference field-label-above view-mode-rss"><h2 class="field-label">Tags: </h2><ul class="field-items"><li class="field-item even"><a href="/category/tags/git" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">git</a></li><li class="field-item odd"><a href="/category/tags/sysadmin" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">sysadmin</a></li><li class="field-item even"><a href="/category/tags/devops" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">devops</a></li></ul></section>Thu, 03 Feb 2011 02:27:31 +0000Allan Feid70 at https://allanfeid.comContinuous integration with git, Capistrano, and Integrity
https://allanfeid.com/content/continuous-integration-git-capistrano-and-integrity
<div class="field field-name-body field-type-text-with-summary field-label-hidden view-mode-rss"><div class="field-items"><div class="field-item even" property="content:encoded"><h2>What is continuous integration (CI)?</h2>
<p>When working in an environment with multiple developers, the practice of continous integration can be tremendous for catching integration errors quickly. This means, as soon as code is checked in to your central repository, integration will be verified by an automated build / test suite. This requires that you have a single source repository and are writing proper tests for your application.</p>
<h2>Automated builds / tests with Integrity</h2>
<p><a href="http://integrityapp.com" title="Integrity: Continous Integration server">Integrity</a> will build your code, then run tests to make sure everything works as expected. Installation instructions can be found on their homepage, but once you do have it setup, it is quite easy to add extremely useful functionality. What I have used, is the email on completion functionality. To get this up and running, you simply need the following line in your <code class="prettyprint">init.rb</code> file:</p>
<pre><code>require "integrity/notifier/email"
</code></pre><p>Once you have added a new project, the repository URI, branch, and name should be straight forward to fill out. Your build script is what will be evaluated for a return value of 0, indicating everything went well and good. You can use this section to do some really cool things, what I've done is take a rails app and run some tasks using <code class="prettyprint">rake</code>. Then using <code class="prettyprint">&&</code>, on success of the tests, it will launch a deploy using capistrano.</p>
<pre><code>rake RAILS_ENV=test gems:build db:migrate \
&& spec ./spec --format specdoc && cap -f /srv/app/capfile deploy
</code></pre><p>I heavily advise against using this straight to your production web server, in this example I'm having <code class="prettyprint">cap</code> deploy to my staging servers. This is a great way to go from successful tests to deployment on your staging or QA servers. Now, to complete the full automated build to deploy, you need a way for your code check-ins to perform an action.</p>
<h2>Using git hooks to initiate a build</h2>
<p>Inside every git repository, there is a subdirectory called <code class="prettyprint">hooks</code>. This directory contains a series of specially named scripts that will get executed at various points whne interacting with git. For a full explanation of the scripts, check out the <a href="http://www.kernel.org/pub/software/scm/git/docs/githooks.html" title="githooks(5)">githooks(5) man page</a>.</p>
<pre><code>#!/bin/bash
echo "Received commit to branch: $1"
if [[ "$1" =~ ^(.*)/master$ ]]; then
echo "Starting Integrity build..."
curl -F 'build=build' http://ci.example.com/project/builds 2>/dev/null
else
echo "Current branch is not master, Integrity will not be started."
fi
</code></pre><p>Sending a simple POST request to your integrity server at the <code class="prettyprint">/:project/builds</code> URI will cause integrity to do its thing. This script checks the branch that was commited to, and only kicks off the POST request if it was master.</p>
<p>There you have it, a full commit, test, and deployment all automated. This forces your developers to use version control and write good tests, because as soon as you commit something, it will be tested and if it passes it's going straight to your servers. As a sysadmin, I've learned a lot about rails applications by setting something like this up.</p>
</div></div></div><section class="field field-name-taxonomy-vocabulary-1 field-type-taxonomy-term-reference field-label-above view-mode-rss"><h2 class="field-label">Tags: </h2><ul class="field-items"><li class="field-item even"><a href="/category/tags/continuous-integration" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">continuous integration</a></li><li class="field-item odd"><a href="/category/tags/devops" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">devops</a></li><li class="field-item even"><a href="/category/tags/sysadmin" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">sysadmin</a></li><li class="field-item odd"><a href="/category/tags/integrity" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">integrity</a></li><li class="field-item even"><a href="/category/tags/git" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">git</a></li><li class="field-item odd"><a href="/category/tags/capistrano" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Capistrano</a></li></ul></section>Thu, 03 Feb 2011 01:36:45 +0000Allan Feid67 at https://allanfeid.com