Advertise with Googlier.comAllan Feid
https://allanfeid.com
enLack of updates
https://allanfeid.com/content/lack-updates
<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>For anyone that finds this page, I apologize if some of the information has gone stale. It's hard to find time for blogging about the things I find interesting, and more recently my focus has been on my job. I've been writing a lot of Go to "bridge the gap" between pre-cloud and cloud-native environments, more specifically protocols like FTPS, SFTP, SMTP, and gRPC. Hopefully I find time to put some more here.</p>
</div></div></div>Sun, 08 Apr 2018 20:02:19 +0000Allan Feid78 at https://allanfeid.comDoing 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.comGetting through OpenStack Quantum with Open vSwitch
https://allanfeid.com/content/getting-through-openstack-quantum-open-vswitch
<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>Over the last couple days I've been working on setting up OpenStack Quantum on my Folsom cluster. I'm using CentOS 6.3, which is by far not the best supported platform to run OpenStack on. I figure I'll try to cover the problems I went through getting this running, and hopefully explain a bit about how Quantum works. I'll assume you have installed an OpenStack cluster from the EPEL provided packages already, and are familiar with the basics of OpenStack's services architecture.</p>
<h2>Open vSwitch</h2>
<p>Before I go on to actually configuring Quantum, you will want to build the <em>openvswitch</em> and <em>kmod-openvswitch</em> RPMs. I've <a href="https://bugzilla.redhat.com/show_bug.cgi?id=910633">opened a bug with RedHat</a> to get this package in EPEL, and it also gives you an idea of how to build the package for yourself. For reference, something as simple as this should work for you:</p>
<pre class="no-code">
$ wget https://bugzilla.redhat.com/attachment.cgi?id=696698 -O $HOME/rpmbuild/SOURCES/openvswitch-1.7.3-el6.patch
$ wget http://openvswitch.org/releases/openvswitch-1.7.3.tar.gz -O $HOME/rpmbuild/SOURCES/openvswitch-1.7.3.tar.gz
$ wget https://bugzilla.redhat.com/attachment.cgi?id=696699 -O $HOME/rpmbuild/SPECS/openvswitch.spec
$ wget \
http://openvswitch.org/cgi-bin/gitweb.cgi?p=openvswitch;a=blob_plain;f=rhel/openvswitch-kmod-rhel6.spec.in;hb=branch-1.7 \
-O $HOME/rpmbuild/SPECS/openvswitch-kmod.spec
$ rpmbuild -vs -D "kversion `uname -r`" $HOME/rpmbuild/SPECS/openvswitch.spec
$ rpmbuild -vs -D "kversion `uname -r`" $HOME/rpmbuild/SPECS/openvswitch-kmod.spec
$ mock $HOME/SRPMS/openvswitch-1.7.3-1.src.rpm
$ mock $HOME/SRPMS/openvswitch-kmod-1.7.3-1.el6.src.rpm
</pre><p>I won't go over the intricacies of RPM building since there is plenty of information available on <a href="http://fedoraproject.org/wiki/How_to_create_an_RPM_package">the Fedora project's wiki</a>. Once you have the package ready, you'll want to install it on your controller and all of your compute nodes (hypervisors). The Open vSwitch project will replace a lot of the networking functionality in the kernel, especially the stuff provided by the bridge module. The openvswitch deamon actually won't start up properly unless you reboot after the install.</p>
<p>When you're up and running again and the <em>openvswitch</em> service is running, you will need to create two bridge. One bridge is for external access and the other provides internal networking for your VMs. The cool thing is that OVS supports the standard network scripts (to an extent) available on EL based distros. An example being:</p>
<pre class="no-code">
# ifcfg-br-ext
DEVICE=br-ext
ONBOOT=yes
BOOTPROTO=static
DEVICETYPE=ovs
IPADDR=10.10.20.30
NETMASK=255.255.254.0
TYPE=OVSBridge
GATEWAY=10.10.20.1
# ifcfg-em1
DEVICE=em1
ONBOOT=yes
BOOTPROTO=none
DEVICETYPE=ovs
HWADDR=FF:FF:FF:FF:FF:FF
TYPE=OVSPort
OVS_BRIDGE=br-ext
# ifcfg-br-int
DEVICE=br-int
ONBOOT=yes
BOOTPROTO=none
DEVICETYPE=ovs
TYPE=OVSBridge
</pre><p>Perform a network service restart, and you should be in a pretty good position. You should now see a network similar to the following:</p>
<pre class="no-code">
# ovs-vsctl show
1b0f63cf-d024-488e-872e-09a4389a067c
Bridge br-int
Port br-int
Interface br-int
type: internal
Bridge br-ext
Port br-ext
Interface br-ext
type: internal
Port "em1"
Interface "em1"
ovs_version: "1.7.3"
</pre><p>You should get your controller and compute nodes to this point before continuing, since this is probably the most difficult part.</p>
<h2>Quantum configuration</h2>
<p>Getting quantum going takes some thought and configuration on both your network gear and your compute/controller nodes. In my case, I'm trunking a set of VLANs down to my compute nodes and adding the gateway IPs to my core network. I'll be using the <em>LibvirtOpenVswitchDriver</em> driver for nova, which does not support security groups or other security filtering features. This is fine for my internal cluster, but if you're a service provider you probably want something else. I can mention now that you will need the <a href="http://openvswitch.org/cgi-bin/gitweb.cgi?p=openvswitch;a=blob_plain;f=INSTALL.bridge;hb=branch-1.7">OVS bridge compatibility daemon</a> if you plan to use CentOS 6.3 and the <em>LibvirtHybridOVSBridgeDriver</em>.</p>
<p>You will want to follow the <a href="http://docs.openstack.org/folsom/openstack-network/admin/bk-quantum-admin-guide-folsom.pdf">Quantum Admin Guide</a> to get a bare minimal configuration going.</p>
<p>I'll point out a couple things that were not obvious for me. The first being you want to install the <em>openstack-quantum-openvswitch</em> package, which provides you with the necessary python bits to bridge the Quantum API to actual OVS commands/configuration. Next you want to ensure the quantum configuration file is correct, mine looks like this:</p>
<pre class="no-code">
# cat /etc/quantum/plugins/openvswitch/ovs_quantum_plugin.ini
[DATABASE]
sql_connection = mysql://username:password@dbhost:3306/ovs_quantum
reconnect_interval = 2
[OVS]
tenant_network_type = vlan
network_vlan_ranges = openstack:1001:2000
bridge_mappings = openstack:br-ext
[AGENT]
polling_interval = 2
root_helper = sudo quantum-rootwrap /etc/quantum/rootwrap.conf
</pre><p>In this case I'll be allowing vlan IDs 1001 to 2000 to attach to a named network called openstack. This name will be used when adding networks to the quantum API later, so keep that in mind. The next important piece is the bridge_mappings, which will map your named network to a bridge that can handle these VLANs. This file needs to be the same on your controller and your compute nodes which will all play an important part in managing your network.</p>
<p>The second most unobvious part, is the init script for <em>quantum-server</em>. It passes <em>--config /etc/quantum/plugin.ini</em>, which doesn't exist by default with the package. I simply symlink this to the plugin I'm using, which happens to be the OVS plugin. Example:</p>
<pre><code>ln -s /etc/quantum/plugins/openvswitch/ovs_quantum_plugin.ini /etc/quantum/plugin.ini
</code></pre><h3>l3_agent and dhcp_agent configuration</h3>
<p>Generally the <em>l3_agent</em> and <em>dhcp_agent</em> will run on your controller node. CentOS 6.3 does not have a version of the iproute2 utilities that supports namespacing. The kernel does support it, but not the userland utilities which Quantum relies on to configure certain aspects. In order to properly work, you need to disable namespacing:</p>
<pre><code>use_namespaces = False
</code></pre><p>This should exist in in both agent's configuration files (<em>/etc/quantum/l3_agent.ini</em> and <em>/etc/quantum/dhcp_agent.ini</em> respectively). You will also want to ensure a proper <em>external_network_bridge</em> value for the <em>l3_agent</em> (br-ext in my example bridges above). Once you have the two agents running on your controller, you should be in great shape. However, the <em>dnsmasq</em> command used by the <em>dhcp_agent</em> is incorrect for the version of <em>dnsmasq</em> included with CentOS 6.3. I've filed <a href="https://bugs.launchpad.net/quantum/+bug/1123624">a bug report</a> with a patch that you can apply to get things going.</p>
<h3>libvirt configuration changes</h3>
<p>At a bare minimum, your libvirt qemu.conf file needs to include the following:</p>
<pre><code>vnc_listen = "0.0.0.0"
user = "root"
group = "root"
cgroup_device_acl = [
"/dev/null", "/dev/full", "/dev/zero",
"/dev/random", "/dev/urandom",
"/dev/ptmx", "/dev/kvm", "/dev/kqemu",
"/dev/rtc", "/dev/hpet", "/dev/net/tun",
]
clear_emulator_capabilities = 0
</code></pre><p>More information can be found on this on the <a href="http://wiki.libvirt.org/page/Guest_won't_start_-_warning:_could_not_open_/dev/net/tun_('generic_ethernet'_interface)">libvirt API page</a>. Reason we need this is for the libvirt driver we will be using for Nova. If you aren't using <em>LibvirtOpenVswitchDriver</em> you likely won't need this, but there is probably some other libvirt changes you will need.</p>
<h2>Network configuration</h2>
<h3>Juniper</h3>
<p>As I mentioned before, you need to configure both networking hardware and quantum to get things going. I'm running Juniper gear, so here's an example of setting up a vlan with gateway:</p>
<pre><code>set interfaces vlan unit 1002 family inet address 10.10.10.1/24
set vlans openstack-net1 vlan-id 1002
set vlans openstack-net1 l3-interface vlan.1002
commit
</code></pre><p>This is on my core network switch, so you will need to make sure the vlan is trunked down to your access switch aka the one or many that are connected to your compute nodes. On the access switches you can do something like this:</p>
<pre><code>set vlans openstack-net1 vlan-id 1002
set groups openstack-configuration interfaces <*> ether-options link-mode full-duplex
set groups openstack-configuration interfaces <*> ether-options speed 1g
set groups openstack-configuration interfaces <*> unit 0 family ethernet-switching port-mode trunk
set groups openstack-configuration interfaces <*> unit 0 family ethernet-switching vlan members openstack-net1
set groups openstack-configuration interfaces <*> unit 0 family ethernet-switching native-vlan-id 80
set interface ge-0/0/10 apply-groups openstack-configuration
set interface ge-0/0/11 apply-groups openstack-configuration
commit
</code></pre><p>An important thing to note here, is that I'm setting the native-vlan-id to 80, which is the vlan ID that the IP address I assigned to br-ext on my compute nodes can communicate on. This is essentially the same as assigning the port in access mode to the vlan id 80, since any 802.1q tagged packets will be sent here. My <em>openstack-net1</em> vlan will be trunked down to the ports <em>ge-0/0/10</em> and <em>ge-0/0/11</em> which means I can tag packets from my compute nodes onto vlan id 1002.</p>
<h2>Quantum</h2>
<p>From a node with the quantum cli tool installed, you can finally add a new network that will us ethe vlan ID 1002.</p>
<pre><code>$ quantum net-create --shared openstack-net1 --provider:network_type vlan \
--provider:physical_network openstack --provider:segmentation_id 1002
</code></pre><p>See the docs for specifics, but in this case we're creating a network that is shared amongst tenants. The various <em>--provider</em> flags are specific to the OVS plugin. Here we are saying the <em>network_type</em> is vlan (OVS plugin also supports GRE based networks), that we want to use the named network openstack (remember we mapped it to br-ext earlier?), and that our vlan_id is 1002. I don't know why they chose to use <em>segmentation_id</em> here, it's a bit confusing, but to each his own.</p>
<h2>Conclusion</h2>
<p>Getting OpenStack Quantum running on a Folsom based cluster using CentOS 6.3 is challenging. Especially since the docs mostly assume you're using Ubuntu or running the dev-stack. It is possible to do, you just have to work through problems one at a time and utilize config management to ensure consistency in configuration. Hopefully the couple bug reports I mentioned throughout will get addressed in a timely manner so it will be easier for future installs. If you have any questions, I'm generally around in the <em>#openstack</em> channel on <em>freenode</em> and can probably help out to an extent. I'd like to point out there's a bunch of great people there that contributed to my own understanding of the Quantum network service.</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/openstack" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">openstack</a></li><li class="field-item odd"><a href="/category/tags/quantum" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">quantum</a></li><li class="field-item even"><a href="/category/tags/junos" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">junos</a></li><li class="field-item odd"><a href="/category/tags/juniper" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">juniper</a></li><li class="field-item even"><a href="/category/tags/centos" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">centos</a></li><li class="field-item odd"><a href="/category/tags/openvswitch" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">openvswitch</a></li></ul></section>Wed, 13 Feb 2013 05:55:59 +0000Allan Feid76 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.comImproving varnish hit rates for Drupal 7
https://allanfeid.com/content/improving-varnish-hit-rates-drupal-7
<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've recently moved my site to a new VPS and decided to upgrade to Drupal 7 at the same time. It was an interesting move, since a lot of the modules I was using have not yet been ported to D7. One of the main reasons I moved over was for native external caching support, meaning I no longer needed to depend on Pressflow. I ran into a few issues actually getting this to work properly with Varnish, so I wanted to cover how I got this working.</p>
<h2>Changes to Drupal's settings.php</h2>
<p>Just like in Pressflow, you need to make sure the site is aware it is being accessed from behind a reverse proxy:</p>
<pre><code>$conf['reverse_proxy'] = TRUE;
$conf['reverse_proxy_addresses'] = array('127.0.0.1');
</code></pre><p>The next option needed is specific to Drupal 7, and there's a <a href="http://drupal.org/node/804864" title="Clarify the relation between external caching and page_cache_invoke_hooks">bug report</a> to clarify the relation between external caching and <em>page_cache_invoke_hooks</em>. This was a confusing and frustrating part of getting Drupal to set the <em>Cache-Control</em> headers properly.</p>
<pre><code>$conf['page_cache_invoke_hooks'] = FALSE;
</code></pre><p>When you set <em>page_cache_invoke_hooks</em> to false, the cache settings in the UI will be taken into account when sending out the appropriate cache related headers. Without this, you will see <em>Cache-Control: max-age=0</em>, which basically tells Varnish not to cache anything. The bug report explains more in depth as to why this happens.</p>
<h2>A basic VCL for Drupal</h2>
<p>Cookies are usually the biggest issue when it comes to properly caching a website. To find out what cookies were being sent to my backend from Varnish, I started with no custom configuration and picked a URL to test against.</p>
<pre><code>varnishlog -b -o TxURL /content/scaling-your-application-cloud
</code></pre><p>Visit the URL path, and look for a line similar to the following:</p>
<pre><code>11 TxHeader b Cookie: __utmz=148462695.1292604478.3.2.utmcsr=linkedin.com|utmccn=(referral)
|utmcmd=referral|utmcct=/profile/edit; Drupal.toolbar.collapsed=0; has_js=1;
__utma=148462695.668390275.1288311613.1298230598.1298310610.28; __utmc=148462695; __utmb=148462695.11
</code></pre><p>These cookies will cause bad cache hit rates because of the <em>Vary: Cookie</em> header, which tells a compliant reverse proxy, to vary the cache based off of the <em>Cookie</em> header. You can see how this will become an issue, because every browser and client is going to send a different <em>Cookie</em> header. To remove them, you need a line similar to this in the <em>vcl_recv</em> portion of your configuration:</p>
<pre><code>set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(__[a-z]+|has_js|Drupal.toolbar.collapsed)=[^;]*", "");
</code></pre><p>Most of this comes from the default Pressflow VCL, but a new addition in Drupal 7 is the <em>Drupal.toolbar.collapsed</em> cookie. This cookie seems to be set in order to control toolbar visibility, per the <a href="http://api.drupal.org/api/drupal/modules--toolbar--toolbar.module/7/source" title="toolbar.module documentation">module documentation</a>.</p>
<p>My full VCL is as follows:</p>
<pre><code>backend default {
.host = "127.0.0.1";
.port = "8080";
}
sub vcl_recv {
remove req.http.X-Forwarded-For;
set req.http.X-Forwarded-For = client.ip;
// from https://wiki.fourkitchens.com/display/PF/Configure+Varnish+for+Pressflow?focusedCommentId=13828160
// Remove has_js, Google Analytics __* and Drupal.toolbar.collapsed cookies.
set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(__[a-z]+|has_js|Drupal.toolbar.collapsed)=[^;]*", "");
// Remove a ";" prefix, if present.
set req.http.Cookie = regsub(req.http.Cookie, "^;\s*", "");
// Remove empty cookies.
if (req.http.Cookie ~ "^\s*$") {
unset req.http.Cookie;
}
// fix compression per http://www.varnish-cache.org/trac/wiki/FAQ/Compression
if (req.http.Accept-Encoding) {
if (req.url ~ "\.(jpg|png|gif|gz|tgz|bz2|tbz|mp3|ogg)$") {
# No point in compressing these
remove req.http.Accept-Encoding;
} elsif (req.http.Accept-Encoding ~ "gzip") {
set req.http.Accept-Encoding = "gzip";
} elsif (req.http.Accept-Encoding ~ "deflate" && req.http.user-agent !~ "MSIE") {
set req.http.Accept-Encoding = "deflate";
} else {
# unkown algorithm
remove req.http.Accept-Encoding;
}
}
}
sub vcl_hash {
if (req.http.Cookie) {
set req.hash += req.http.Cookie;
}
}
</code></pre><p>Once I dropped this VCL in, I started seeing much higher hit rates, which can be viewed on <a href="http://hendrix.crazed.pl/crazed.pl/hendrix.crazed.pl/varnish_hit_rate.html" title="Varnish hit rates">my munin stats page</a>. Prior to implementing the VCL, I was seeing low rates (< 10%) but didn't have the time to troubleshoot until this weekend. I learned a bunch about using <em>varnishtop</em> and <em>varnishlog</em> to find signs as to why my requests weren't being properly cached. I highly recommend checking out the <a href="http://www.varnish-cache.org/docs/2.1/tutorial/increasing_your_hitrate.html" title="Achieving a high hitrate">documentation on achieving higher hit rates</a>.</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/sysadmin" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">sysadmin</a></li><li class="field-item odd"><a href="/category/tags/varnish" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">varnish</a></li><li class="field-item even"><a href="/category/tags/cache" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">cache</a></li><li class="field-item odd"><a href="/category/tags/drupal" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Drupal</a></li></ul></section>Mon, 21 Feb 2011 19:25:55 +0000Allan Feid72 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