Advertise with Googlier.com Allan Feid - devops https://allanfeid.com/category/tags/devops en Doing 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/* &gt; /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}&amp;channel=#{CHANNEL}" + "&amp;username=#{USERNAME}&amp;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:&nbsp;</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 +0000 Allan Feid 77 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&lt;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&lt;Base def body result = { :agentlist =&gt; [], :facts =&gt; [], :classes =&gt; [] } 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 =&gt; true, :type =&gt; 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>&lt;authorizationEntry queue="mcollective.&gt;" write="admins" read="admins" admin="admins" /&gt; </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&lt;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:&nbsp;</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 +0000 Allan Feid 75 at https://allanfeid.com Tracking 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 =&gt; '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 &lt;&lt; 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:&nbsp;</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 +0000 Allan Feid 74 at https://allanfeid.com Using 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&amp;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 &amp;&gt;&gt; $log apt-get install -y ruby ruby1.8-dev build-essential wget libruby-extras libruby1.8-extras git-core &amp;&gt;&gt; $log cd /tmp wget http://production.cf.rubygems.org/rubygems/rubygems-1.3.7.tgz &amp;&gt;&gt; $log tar zxf rubygems-1.3.7.tgz &amp;&gt;&gt; $log cd rubygems-1.3.7 ruby setup.rb --no-format-executable &amp;&gt;&gt; $log gem install ohai chef --no-rdoc --no-ri --verbose &amp;&gt;&gt; $log mkdir -p /var/chef/cache git clone -b deploy git://github.com/crazed/cookbooks.git /var/chef/cookbooks &amp;&gt;&gt; $log mkdir /etc/chef cat &lt;&lt; EOF &gt; /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 &lt;&lt; EOF &gt; /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 &amp;&gt;&gt; $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 &amp;&gt;&gt; $log tar zxf rubygems-1.3.7.tgz &amp;&gt;&gt; $log cd rubygems-1.3.7 ruby setup.rb --no-format-executable &amp;&gt;&gt; $log mkdir -p /var/chef/cache git clone -b deploy git://github.com/crazed/cookbooks.git /var/chef/cookbooks &amp;&gt;&gt; $log gem install ohai chef --no-rdoc --no-ri --verbose &amp;&gt;&gt; $log chef-solo &amp;&gt;&gt; $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} &lt;file&gt;" exit 1 end def escape(string) parse = JSON.parse({ 'json' =&gt; string }.to_json) parse['json'] end data = '' File.open(ARGV[0]) {|f| data &lt;&lt; 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 &amp;&amp; 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 &lt;&lt; f.read} cf = Fog::AWS::CloudFormation.new( :aws_access_key_id =&gt; config[:aws_access_key_id], :aws_secret_access_key =&gt; 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' =&gt; 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} &lt;stack name&gt;" exit 1 end config = YAML::load(File.open(ENV['HOME']+'/.fog'))[:default] cf = Fog::AWS::CloudFormation.new( :aws_access_key_id =&gt; config[:aws_access_key_id], :aws_secret_access_key =&gt; config[:aws_secret_access_key] ) events = cf.describe_stack_events('StackName' =&gt; 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:&nbsp;</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 +0000 Allan Feid 73 at https://allanfeid.com Scaling 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&amp;utm_medium=RDSLandingPage&amp;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:&nbsp;</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 +0000 Allan Feid 71 at https://allanfeid.com Setting 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&gt;&amp;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 (&lt;project&gt;.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 &lt;Directory /usr/share/gitweb&gt; Options FollowSymLinks +ExecCGI AddHandler cgi-script .cgi &lt;/Directory&gt; 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 &lt;Directory /usr/share/gitweb&gt; Options FollowSymLinks +ExecCGI AddHandler cgi-script .cgi &lt;/Directory&gt; </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:&nbsp;</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 +0000 Allan Feid 70 at https://allanfeid.com Continuous 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">&amp;&amp;</code>, on success of the tests, it will launch a deploy using capistrano.</p> <pre><code>rake RAILS_ENV=test gems:build db:migrate \ &amp;&amp; spec ./spec --format specdoc &amp;&amp; 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&gt;/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:&nbsp;</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 +0000 Allan Feid 67 at https://allanfeid.com