Posts Tagged ‘EC2’

Getting your tunnel on with EC2 and OpenVPN (or wrap it before you ride it)

Tuesday, November 16th, 2010

I worry about data. I worry about whether it’ll be lost, compromised, or corrupted. I worry whether or not I am doing enough: did I check the backups, are they “good”, did any fail to run, do I have a “plan”, will it work? It is easier for me to get a handle on my fretting with regards to the server I tend but what about those assets that get to see the wider world and networks other than my own?

As a company, we are traveling more and it isn’t unusual for people to work from airports, coffee shops, clients, taxi cabs, or bars. Its just how business is getting done. But what about that data I worry about so much, both company and personal? Things like Firesheep intrigue me at the same time as leave me with the feeling that I need to do more for the people on the road.

The most obvious solution is to implement a VPN but which one, how, and moreover could we run it in our EC2 stack. I experimented with PPTP and loved the ease and simplicity of setting it up and it worked great with the built in OS X VPN client. However, once I left the free love environment that is my home network it was all but useless as I encountered plenty of public WiFi that disallowed TCP/1723. Next up was Openswan’s IPSec implementation, a complex beast full of options and configurations. It enjoyed wide support on the public networks I tried but, sadly, I seemingly could not get the built in OS X VPN client to consistently play nice. Last up was OpenVPN.

OpenVPN is easy to setup, configure, and roll out. We use it on our production servers as part of the CohesiveFT VPN-Cubed product that allows us to easily throw up IPSec tunnels with clients and hook it back stack so we know it is rock solid. The only drawback that I encountered with OpenVPN is that neither the iPad nor the iPhone support it so this does limit the devices that we can secure at the moment but life is about compromise.

Eric Hammond wrote an excellent article as a proof-of-concept for running OpenVPN over TCP/80 on EC2. Per usual he made it dead simple and easy to implement and with the slightest bit of finessing it can be adapted for rolling out to small teams.

For this I chose a micro instance running Ubuntu 10.04 for both the typical miserly and familiarity reasons. Prepping I ran with Hammond’s opening tasks:

sudo apt-get update && sudo apt-get upgrade -y
sudo apt-get install openvpn -y
sudo modprobe iptable_nat
echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward
sudo iptables-save > /some/place/you/can/recall
sudo iptables -t nat -A POSTROUTING -s 10.4.0.1/2 -o eth0 -j MASQUERADE

Once that inital prep work was out of the way I hoped over to the official Ubuntu OpenVPN documentation where we can set up OpenVPN to use asymmetrical keys instead.

But first we need to make a couple of tweaks to the server config (/etc/openvpn/server.conf). Look for the following and adjust them:

port 80
proto tcp
server 10.4.0.0 255.255.255.0

The port and protocol is so that our tunnel will run over TCP/80 and this, in theory, let you toss up your tunnel on nearly any network that has access to the Internet. That said, I’ve tested this on my free love network, a tighter administered open WiFi where my daughter goes to school (this is the one that disallows TCP/1723), and my T-Mobile data plan and while it worked on all three your mileage may vary.

The next step is to create a CA for OpenVPN and generate keys for clients and the server. Rather than do that work in /etc/openvpn/ as suggested in the documentation I opted to perform all that work in another directory so we can easily manage it with tools like git or puppet.

sudo mkdir /mnt/easy-rsa/
sudo cp -r /usr/share/doc/openvpn/examples/easy-rsa/2.0/* /mnt/easy-rsa/
sudo chown -R $USER /mnt/easy-rsa/

Adjust the variables in /mnt/easy-rsa/vars to best fit how you want things to look:

export KEY_COUNTRY="US"
export KEY_PROVINCE="CT"
export KEY_CITY="Hartford"
export KEY_ORG="Awesome Pancake, LLP"
export KEY_EMAIL="muffin.man@awepancake.com"

Then, because the devs and maintainers are awesome, just run some handy scripts and you’ll be all set:

cd /mnt/easy-rsa/
source vars
./clean-all
./build-dh
./pkitool --initca
./pkitool --server server
cd keys
openvpn --genkey --secret ta.key
sudo cp server.crt server.key ca.crt dh1024.pem ta.key /etc/openvpn/

Now we just need to generate some client certs:

cd /mnt/easy-rsa/
source vars
./pkitool jimmy

Replace “jimmy” with whatever you want to name your client, I recommend easy to remember nicknames that will cause you to snicker when you use them.

Now for the clients, it all depends on the what you’ll be using to connect to the server with. For testing I tried Tunnelblick and while it’s FLOSS I found it to be a little harder to configure than Viscosity, which is an affordable commercial client. The additional benefit to Viscosity is that it will allow you create easy to distribute connection bundles that include the CA cert, client key, and client cert that you can distribute to your clients for importing.

Setting up Viscosity is about as easy as it gets, you’ll need the CA.crt, and the host key and cert that you created above. Since pictures are a more valuable currency:

Once that is all set, connect to your tunnel and marvel at your secure connection. To check how things look on the client you can check ifconfig to ensure that the tunnel was added:

Also, do a quick nslookup to ensure that DNS is going to EC2 rather than to the local network:

Viscosity as a nifty detail page for visualizing the traffic over the tunnel:

On the server you can use tcpdump to watch the traffic and make sure that it is flowing like you want:

sudo tcpdump -i tun0 | grep "ip-10-4-0-6.ec2.internal"

Where 10-4-0-6 is the same as the ip address given by the tunnel.

That’s pretty much it, very easy thanks to Eric Hammond and the Ubuntu documentation team. From here you could set up connections in Viscosity for each of your users, export a zipped connection and distribute it (securely, of course!).

The next big test will be setting up all our road warriors and asking them to try it while they are traveling and report back with concerns, problems, or suggestions because in the end I want it to be easy as possible as they’ll be more inclined to use it.

Using ELB to Serve Multiple Domains Over SSL on EC2 for Giggles and Unicorns

Wednesday, December 23rd, 2009

One of the complaints about EC2 is that you only have one IP address allocated per instance which makes it difficult to host, in a clean manner, multiple domains that require SSL certs.  Where you have control over IP allocation you could punch down a couple for one server and then set up your domains and SSL certs by IPs. That method is a no go so you are left with the ugly method of allocating those certs by port, something that Joe and Jane public are a little skittish about (https://superawesomefuntime.com is cool but https://superawesomefuntime.com:8443 smells phishy). Thankfully, ELB makes for a great proxy to hide the hideousness of the port-based workaround.

Essentially, as solutions go, this one is stupid easy; for each domain that you need to handle SSL for create a load balancer. Many balancers to one or a pool of instances. That’s it. Here’s a sloppy diagram…

For this example, we’ll be serving superawesomefuntime.com and unicorns-unlimited.com:


elb-create-lb superawesome --headers --listener "lb-port=443,instance-port=8443,protocol=TCP" --listener "lb-port=80,instance-port=80,protocol=http" --availability-zones us-east-1c


elb-create-lb unicorns --headers --listener "lb-port=443,instance-port=8445,protocol=TCP" --listener "lb-port=80,instance-port=80,protocol=http" --availability-zones us-east-1c

Take note of the address which are returned to you as need that for the cname portion of the game. They’ll look something like this: superawesome-123456789.us-east-1.elb.amazonaws.com and unicorns-987654321.us-east-1.elb.amazonaws.com.

The next step would be to add your instance to those load balancers:


elb-register-instances-with-lb superawesome --instances i-12ab3c45


elb-register-instances-with-lb unicorns --instances i-12ab3c45

Over on the server set up your virtual hosts in a way that hopefully is as hasty as mine…


NameVirtualHost *:8443
<VirtualHost *:8443>
ServerName superawesomefuntime.com
ServerAlias *.superawesomefuntime.com
SSLEngine On
SSLCertificateFile /etc/ssl/superawesome.crt
SSLCertificateKeyFile /etc/ssl/superawesome.key
RequestHeader set X_FORWARDED_PROTO 'https'
## Directories, Includes, Rewrites, oh my...
</VirtualHost>


<VirtualHost *:8445>
ServerName unicorns-unlimited.com
ServerAlias *.unicorns-unlimited.com
SSLEngine On
SSLCertificateFile /etc/ssl/unicorn.crt
SSLCertificateKeyFile /etc/ssl/unicorn.key
RequestHeader set X_FORWARDED_PROTO 'https'
## Directories, Includes, Rewrites, oh my...
</VirtualHost>

Then all you need to do is set up the cname for each domain to point at the load balancer address, in this example the cnames would be for *.superawesomefuntime.com = superawesome-123456789.us-east-1.elb.amazonaws.com and *.unicorns-unlimited = unicorns-987654321.us-east-1.elb.amazonaws.com.

The only loose end is configuring your healthcheck but that is a deeply personal decision best left for you and your app to work out.

EC2 Elastic Load Balancing for Fun and Profit

Monday, November 23rd, 2009

This one is so easy I don’t know why I haven’t gotten around to it sooner. It took my dev to be all, “OMG! The servers! How will we do a rolling deploy without impacting uptime!1!!eleven!” Thankfully, Amazon is there with Elastic Load Balancing (ELB) so I didn’t really need to get off my ass and be excited. Amazon Web Services: Doing the hard work so you don’t have to.

Before we dive in, a little background on operations. We’ve been making use of the elastic IP feature since it came out along with round robin DNS to handle the distribution of traffic over the web servers. Sure, it is crude and primitive but it works and I am a big fan of implementing the simplest possible solution since more moving parts results in exponentially more headaches. And, yes, we tried the HAProxy and the Nginx route but the cost+plus heartburn factor was too high for what we needed. Anyway, things have worked just fine with a minimum of effort on our part but our needs are changing so we need a solution that is both flexible and forgiving. Brittle is only good with peanuts.

First things first, create an ELB…

elb-create-lb eeniemeenie --headers --listener "lb-port=443,instance-port=8443,protocol=TCP" \
--listener "lb-port=80,instance-port=80,protocol=http" --availability-zones us-east-1c

Now you might be saying to yourself, “WTF, James. What is with the 443 => 8443 when 80 => 80?” Simple: “Currently, Elastic Load Balancing does not have SSL termination capability.” That basically means your application servers need to handle the SSL part but honestly this is fine because the way ELB is engineered the traffic between the balancer and your instances outside the firewall so you’ll want that traffic passing over SSL anyway.

Next, configure health checks…

elb-configure-healthcheck eeniemeenie --headers --target "TCP:8443" --interval 30 --timeout 20 \
--unhealthy-threshold 2 --healthy-threshold 2

elb-configure-healthcheck eeniemeenie --headers --target "HTTP:80/up.html" --interval 10 --timeout 5 \
--unhealthy-threshold 2 --healthy-threshold 2

These two health checks are intended to do different things. The one for TCP:8443 is to see if our application is up and serving (we force all traffic over SSL) while the latter is intended for rolling deploys, when we begin a deploy we’ll rename that file with the intention that the balancer sees it go missing and pulls the instance out of the pool. When everything is done and the deploy was successful we plop up.html back in and the balancer throws the instance back in the pool.

On the Apache side of things I decided to do a little mod_rewrite mumbo jumbo to deal with the new up.html file. Since I want all application related traffic forced to SSL the monitoring of that file by ELB presents a little wrinkle to my normal sledgehammer approach to things. Easily addressed though:

RewriteEngine on
RewriteCond %{REQUEST_URI} !(up.html)
RewriteRule ^(.*)$ https://%{SERVER_NAME}$1 [L,R]

Now, because I am a grumpy bastard I really don’t want up.html to be available over SSL.

RewriteEngine on
RewriteCond %{REQUEST_URI} (up.html)
RewriteRule ^(.*)$ http://%{SERVER_NAME}$1 [L,R]

That is basically it. Mind you, I have not punched this down for production but our initial tests have been very promising, in particular the rolling deployment feature.

(Parts of this solution were gratefully cribbed from Serk and Lead Thinking as well as the ever popular RTFM)

Re-Sizing EBS Volumes for Fun and Profit on EC2

Thursday, September 17th, 2009

This is one you can file under easy and obvious but since I have a memory like a sieve I am going to write about it. There are times when I set up EBS volumes and think to myself, “I’ll never need anymore than nGB, ever!”, only to find out some months down the road my estimates were woefully short for the growth trend. Turns out resizing is pretty easy and can be accomplished in a matter of minutes. For this example, I’m going to resize a single EBS volume using XFS and where a MySQL database hangs out doing its thing. I am going to attach it to a separate block device than the original volume so that we can quickly revert back if we find the apocalypse happening ahead of schedule.

  1. Put up a maintenance page or just halt activity to the DB so that your life is slightly more elegant.
  2. Stop MySQL
  3. Make a snapshot of the original EBS volume
  4. Create a new volume from that snapshot specifying the size you want and make sure that it is in the same availability zone as the instance you want to attach it to
  5. Attach the new EBS volume to your instance on a different block device (if the original is on /dev/sdh then attach the new one to /dev/sdi)
  6. Edit /etc/fstab to reflect the new changes:

    #/dev/sdh /vol xfs noatime 0 0
    /dev/sdi /vol xfs noatime 0 0

  7. Mount the drive
  8. Resize it with “xfs_growfs -d /vol”
  9. Start MySQL and let it run its checks
  10. Take down the maintenance pages and sit back with a sense of smug self-satisfaction

Like I said, super easy. However, this is for a single EBS volume I haven’t really played around with resizing or generally manipulating RAID sets so that is a post for another day.

EC2, BIND9, DNS, Pancakes and You!

Wednesday, August 12th, 2009

I finally reached that place where maintaining versions of host files just didn’t seem to cut it andfound myself mucking about with BIND9 to solve the problem of keeping track of all those internal IP addresses when I bring instances online and take them offline. This seemed to be the quickest and easiest way to handle fairly basic networking needs though it does introduce yet another point of failure so I’ll need to tackle primary and secondary DNS servers in the near future and maybe develop a doomsday host file backup but if the latter is invoked I’m sure that I have bigger problems on my hands.

Two basic tutorials helped me hack together the start of a solution, HowTo update DNS hostnames automatically for your Amazon EC2 instances and the ever useful Ubuntu wiki’s BIND9 Server Howto. Working back and forth between those two write-ups and a little manpage searching answered 95% of my questions so getting started isn’t as much of a headache as it might seem. Deploying it on a large scale? That is a whole other matter.

A caveat before we dive in, more to remind me when I have to revisit this some 18 months later. A couple of iterations in I thought it would be cute to set up the DNS zone for the full domain (*.awesome.com) but that seemingly added to my headaches as I tried to examine resources that did not have a DNS entry just yet. Since this is for internal use only we can pretty much set the FQDN to be whatever we want it to be, in this case pancake.man is our working domain.

Set things up…

apt-get install apt-get install bind9 dnsutils
sudo mkdir /etc/bind/zone/
sudo mkdir /some/where/other/than/etc/bind
sudo cp /etc/bind/db.local /etc/bind/zone/db.pancake.man
sudo cp /etc/bind/db.127 /etc/bind/zone/db.10
sudo chown -Rv bind:bind /etc/bind/zones/

EDIT: A couple of things occurred to me while I mulled this over. Keeping the zone files in /etc/bind while sounding like a neat idea doesn’t take into account the ephemeral nature of instances so I moved ours into an EBS volume. Additionally, I neglected to add that if your distro uses app armor you’ll have to grant rw permissions to the folder where you are keeping the db files. That can be done by editing /etc/apparmor.d/usr.sbin.named and adding the following:

/some/where/other/than/etc/bind/** rw,

The you can just reload apparmor and be on your happy way.

Generate a key pair….
This is really a CYA implementation but it should ensure that only the servers you want updating the LAN DNS can do it.

dnssec-keygen -a HMAC-MD5 -b 512 -n USER pancake.man

…and copy the secret from the public key as you’ll need it in the next section.

Set up a Zone…
Edit /etc/bind/named.conf.local and add the following:

key pancake.man. {
algorithm HMAC-MD5;
secret "Gzr11.....==";
};

The above block sets up the key that you created earlier and it will be used in the following block that defines the zone.

zone "pancake.man"
{
type master;
file "/etc/bind/zone/db.pancake.man";
allow-update { key pancake.man.; };
allow-query { any; };
};

Next is to work on the zone file so edit /etc/bind/zone/db.pancake.man and change the localhost/127.0.0.1 entries to fit your environment.

$TTL 604800
@ IN SOA ns.pancake.man. me.pancake.man. (
2 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Negative Cache TTL
;
@ IN NS ns.pancake.man.
@ IN A 10.0.0.1 ; this is your local IP address

When you restart BIND9 this file will change a bit here’s what I see once it is running…

$ORIGIN .
$TTL 604800 ; 1 week
pancake.man IN SOA ns.pancake.man. me.pancake.man. (
3 ; serial
604800 ; refresh (1 week)
86400 ; retry (1 day)
2419200 ; expire (4 weeks)
604800 ; minimum (1 week)
)
NS ns.pancake.man.
A 10.208.41.206
$ORIGIN pancake.man.
$TTL 60 ; 1 minute

One thing that tripped me up for a little bit was the formation of the SOA line, pancake.man IN SOA ns.pancake.man. me.pancake.man.. I had been reading ns.pancake.man. me.pancake.man. as FQDN when in fact they are a FQDN and and email address where the ‘@’ has been replaced with a ‘.’; file this one under RIF: Reading Is Fundamental.

Next up is a reverse zone file which is pretty much the same process as what we just did, just edit /etc/bind/zone/db.10 and replace the localhost/127.0.0.1 values with your own.

;
; BIND reverse data file for local loopback interface
;
$TTL 604800
@ IN SOA ns.pancake.man. root.pancake.man. (
2 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Negative Cache TTL
;
@ IN NS ns.
1.0.0 IN PTR ns.pancake.man.

Test your new DNS server
First things first, let’s test things out by inserting an entry using nsupdate (this piece is gratefully cribbed from Marius). Edit /etc/resolv.conf and put the IP address of your DNS server above the ec2 nameserver.

search compute-1.internal
nameserver 10.1.2.3
nameserver 172.1.2.3

Now lets punch an entry in using our shiny set of keys. I found making a script was easier for testing purposes, create a file called dnsupdate.sh and add the following:

#!/bin/bash
cat << EOF | /usr/bin/nsupdate -k Kpancake.man.+157+46088.private -v
server 127.0.0.1
zone pancake.man
update delete $1 A
update add $1 60 A $2
show
send
EOF

The private key is the one we made way back in the beginning of this exercise, your's might be about waffles. After making the file executable, just ./dnsupdate test.pancake.man 10.1.2.3. Ideally, you should get something like the following back:

Outgoing update query:
;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id: 0
;; flags: ; ZONE: 0, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0
;; ZONE SECTION:
;pancake.man. IN SOA


;; UPDATE SECTION:
test.pancake.man. 0 ANY A
test.pancake.man. 60 IN A 10.1.2.3

Implement your shiny new DNS server
With all that done now we and testing showing that everything works as expected we want to share it with the whole LAN but there's a potential snag. When you work up the next morning still heady from your success you noticed that when DHCP decided to check on its lease it wrote over /etc/resolv.conf with the stock EC2 values, showing absolutely no love for your handiwork. Not what we wanted to happen but there are a couple of steps we can take to make sure that your shiny new DNS persists (this is mainly geared toward Ubuntu so your mileage may vary). The AWS forums has a great writeup about this though I'm only implementing some what was discussed.

Since there is the chance of a DNS server changing, instance goes down or a new one is swapped in, I'm going the route of appending the DNS server to /etc/hosts at boot time and then adding the FQDN to /etc/dhcp3/dhclient.conf like so...

prepend domain-name-servers ns.pancake.man;

What that will do is punch in the IP address specified in /etc/hosts into /etc/resolv.conf when DHCP does it's little dance.

Loose ends...
While everything should be working decently on a small scale there are still plenty of things still left to do like setting up a secondary DNS server for failover, implementing EC2 internal zones for forwarding as described in the AWS post, and working out an elegant solution for updating clients about the DNS server. As for the latter, I'm still playing with scripts to fetch the local IP address of the DNS server, from using an empty security group as a tag to just keeping an updated list in S3 that is maintained by the DNS servers themselves. That, among the other things, will be a post for another day when I finally stumble on something that isn't so ham-fisted.

nginx + HAProxy + Thin + FastCGI + PHP5 = Load Balanced Rails with PHP Support

Tuesday, July 15th, 2008

This was probably one of the more radical switches in architecture that we’ve made in the recent past.  For the past 7 months we have been successfully running Apache + mod_proxy + mongrel with some limited PHP applications bolted on but the whole set up felt a tad bloated and a little more than unstable as we tested various scaling scenarios.  With the rails community chatting about the hotness that is thin, nginx, and HAProxy we decided to see what it would take to migrate.

The catch with our infrastructure though is that we have broken apart our static assets from rails so the usual localhost simplicity isn’t there which, unfortunately, is how most of the tutorials are aimed at.  In our case, the application sits in a pool of servers and one of the things that we wanted to do was leverage HAProxy to balance each nginx instance over a group of primary and secondary application servers with the primary and secondary status staggered between each nginx instance. Igvita’s post was the inspiration for this and our goal is to create a more fault tolerant environment built on shared services rather than our current setup of largely discrete stacks.

The first thing I tackled was setting up nginx by breaking apart the rails application and any PHP applications into separate virtual hosts. First up is the rails config…

upstream thin {
server 127.0.0.1:8700;
}

server {
listen       80;
server_name  first.server.name;
rewrite ^/(.*) https://what.ever.you.want/$1 permanent;
}

server {
listen 443;
ssl on;
ssl_session_timeout  5m;
ssl_protocols  SSLv2 SSLv3 TLSv1;
ssl_ciphers  ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
ssl_prefer_server_ciphers   on;

# path to your certificate
# if you have an intermediate cert then you need to add the contents to the end of the cert file
ssl_certificate /where/your/cert/is.pem;

# path to your ssl key
ssl_certificate_key /where/your/key/is.key;

# standard rails configuration goes here.
root /location/of/your/site/root;

#        rewrite_log on;

if (-f $document_root/system/maintenance.html) {
rewrite  ^(.*)$  /system/maintenance.html last;
break;
}

location ~ ^/$ {
if (-f /index.html){
rewrite (.*) /index.html last;
}
proxy_pass  http://thin;
}

location / {
if (!-f $request_filename.html) {
proxy_pass  http://thin;
}
rewrite (.*) $1.html last;
}

location ~ .html {
root /location/of/your/site/root;
}

location ~* ^.+\.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|pdf|txt|js|mov)$ {
root  /location/of/your/site/root;
}

location / {
proxy_pass  http://thin;
proxy_redirect     off;
proxy_set_header   Host             $host;
proxy_set_header   X-Real-IP        $remote_addr;
proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
proxy_set_header X-FORWARDED_PROTO https;
}
}

And our PHP config…

server {
### PHP Support ###
listen       80;
server_name  second.server.name;
access_log  /location/of/your/site/root/logs/blog-access.log;
error_log  /location/of/your/site/root/logs/blog-error.log;

if (!-e $request_filename) {
rewrite ^([_0-9a-zA-Z-]+)?(/wp-.*) $2 last;
rewrite ^([_0-9a-zA-Z-]+)?(/.*\.php)$ $2 last;
rewrite ^ /index.php last;
}

location / {
root / /location/of/your/site/root;
index index.html index.php index.htm;
}

location ~* ^.+\.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|pdf|txt|js|mov)$ {
root /location/of/your/site/root;
}

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000

location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_FILENAME  /location/of/your/site/root/$fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
}
}

Next up is the HAProxy configuration…

global
	log 127.0.0.1	local0
	log 127.0.0.1	local1 notice
	nbproc		1
	pidfile		/var/run/haproxy.pid
	#debug
	#quiet
	user haproxy
	group haproxy

defaults
	log		global
	mode		http
	option		httplog
	option		dontlognull
	retries		15
	redispatch
	contimeout	60000
	clitimeout	150000
	srvtimeout	60000
	option          httpclose     # disable keepalive (HAProxy does not yet support the HTTP keep-alive mode)
	option          abortonclose  # enable early dropping of aborted requests from pending queue
	option          httpchk       # enable HTTP protocol to check on servers health

listen	thin *:8700
	option httpchk
        mode http
        option forwardfor except 127.0.0.1/8
	balance roundrobin
        server web01 hostname-of-server:8100 weight 1 minconn 1 maxconn 6 check inter 40000
        etc....

There are a couple of things to note here: to get HAProxy to fetch content from servers other than localhost you’ll need to chuck in a wildcard: listen thin *:8700, and to get logging running you’ll need to edit /etc/syslog.conf adding the following lines:

# Save HA-Proxy logs
	local0.*                                                /var/log/haproxy_0.log
	local1.*                                                /var/log/haproxy_1.log

As well as edit /etc/default/syslogd:

# For remote UDP logging use SYSLOGD="-r"
SYSLOGD="-r"

One last thing that drove me almost to the brink of madness is that HAProxy, at least in the build on Ubuntu 8.04, is finicky about how the configuration file is laid out. Each section default, global, and listen has to have the parameters defined with a tab preceding each and while HAProxy would start and accept request from nginx with anything else it would not fetch from the thin server pool.

So that is our front-end, what about the application pool? Turns out that Thin is just as easy to set up as a mongrel cluster and only took a minimum of effort on our part to get it dialed in with God and serving upstream. We edited the stock init script to reflect where we store the yamls and massaged God for the changes in clustering.

Here’s our init script:

#!/bin/sh
### BEGIN INIT INFO
# Provides:          thin
# Required-Start:    $local_fs $remote_fs
# Required-Stop:     $local_fs $remote_fs
# Default-Start:     2 3 4 5
# Default-Stop:      S 0 1 6
# Short-Description: thin initscript
# Description:       thin
### END INIT INFO

# Original author: Forrest Robertson

# Do NOT "set -e"

DAEMON=/usr/bin/thin
SCRIPT_NAME=/etc/init.d/thin
CONFIG_PATH=/location/of/your/yamls

# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0

case "$1" in
  start)
	$DAEMON start --all $CONFIG_PATH
	;;
  stop)
	$DAEMON stop --all $CONFIG_PATH
	;;
  restart)
	$DAEMON restart --all $CONFIG_PATH
	;;
  *)
	echo "Usage: $SCRIPT_NAME {start|stop|restart}" >&2
	exit 3
	;;
esac

:

And here’s a sample yaml:

---
user: user-which-runs
group: group-which-runs
chdir: /location/of/your/app
log: log/thin.log
port: 8100
environment: staging
pid: /location/of/your/pids.pid
servers: 3

God is very similar to what we had been running with a mongrel cluster:

RAILS_ROOT = "/location/of/your/app"

%w{8100 8101 8102}.each do |port|
 God.watch do |w|
    w.group = 'pack_01'
    w.name = "thin-#{port}"
    w.interval = 30.seconds # default
    w.start = "thin start -C /location/of/your.yaml -o #{port}"
    w.stop = "thin stop -C /location/of/your.yaml -o #{port}"
    w.restart = "thin stop -C/location/of/your.yaml -o #{port} && thin start -C /location/of/your.yaml -o #{port}"
    w.start_grace = 15.seconds
    w.restart_grace = 15.seconds
    w.pid_file = "/location/of/your/pids.#{port}.pid"

    w.behavior(:clean_pid_file)

    w.start_if do |start|
      start.condition(:process_running) do |c|
        c.interval = 5.seconds
        c.running = false
      end
    end

    w.restart_if do |restart|
      restart.condition(:memory_usage) do |c|
        c.above = 150.megabytes
        c.times = [3, 5] # 3 out of 5 intervals
      end

      restart.condition(:cpu_usage) do |c|
        c.above = 50.percent
        c.times = 5
      end
    end

    # lifecycle
    w.lifecycle do |on|
      on.condition(:flapping) do |c|
        c.to_state = [:start, :restart]
        c.times = 5
        c.within = 5.minute
        c.transition = :unmonitored
        c.retry_in = 10.minutes
        c.retry_times = 5
        c.retry_within = 2.hours
      end
    end
  end
end

There you have it, a completely rebuilt stack leveraging lean, fast, and stable services.

Gratefully cribbed from HowtoForgeJohn Yerhot, and  Igvita.