Migrating Installer Images to AWS S3 and CloudFront

This is the second in a series of posts which describe the adventures encountered while sticking our heads even further in the clouds.

  • The first article is mostly Project Introduction & Background, describes what we’re doing and why in higher level terms
  • This post is about getting our static data hosted at AWS as our first steps to using the AWS cloud
  • Then we’ll talk about Migrating Site Local MySQL database to AWS RDS
  • And the big kahuna part 1: Establishing the Ruby on Rails Web App Environment on AWS EBS

Ok firstly let’s think about the types of static content we want to serve from AWS, and then look at how this impacts our web app and then the build and distribution workflows for the Drum Score Editor app itself.

We’ve got 2 types of static content we want served, firstly there’s the Drum Score App installer images for each platform, plus example scores and PDFs. We’re going to store these in an AWS S3 bucket primarily to reduce the size of the web app so it can be deployed in later steps using AWS Elastic Beanstalk, which has an untweakable hard maximum web app size it can deal with (500MB at last look).

Secondly there’s the Rails asset pipeline, all the static css, javascript etc that goes with an app. Some reading of the various opinions on the inter web reveals that AWS CloudFront is the CDN which caches copies of static assets closer to the user. You simply specify the origin of the files and it does it’s magic to make them appear. That origin could be the S3 bucket containing the installer images etc, or the origin could be your web site itself, or the assets can be precompiled to an S3 bucket also. We’ll look at the options and why we chose the solution we did later in this article.

Step 1 – getting the installer images into an S3 bucket

Before we can put anything in a bucket, we need to acquire that bucket. Should we just the use AWS Console as this is a one off operation, or maybe we want to have different buckets for UAT and Production separation and so should create a reusable script for their creation. Do we need that complexity? Probably not at this stage given our overall use case.

First we create a bucket for these resources, imaginatively called drumscoreportal-resources, and we upload our installer image into it using the console (or AWS command line tools) and make it public, by right clicking on the uploaded filename. Selecting properties will show the public name of the file, so to test this works we copied the link presented and pasted it into a command line and used curl to pull it down. Remember it’s a common installer we want to be available to everybody, no need to control access to it so no need to work out any permission stuff. This all just worked, so in theory we can tweak the download links in the appropriate page in the web app and off we go.

However, everybody does local development right? You really don’t want to be running up your AWS network costs by pulling the copies down when in an iterative dev/test cycle. So we need to somehow make the development environment use local copies while UAT and production use the S3 objects.

Given all we’re doing though is pulling down the objects via http, we don’t need anything more clever than a local web server and the files in a similar URL so the app can switch through Rails environment specific initialisers. The secrets.yml file is already used to separate out which hosts are used for each environment for things like the Facebook and Paypal integration. Might be impure, but popping a line in there for the resource_host and using that in the link tags might be viable.

The download link in the view then becomes

<a href="#{Rails.application.secrets.resource_host}/drumscoreportal-resources/DrumScoreEditor-2.23.dmg" class="button radius" download>Download For Mac OS X</a>

The secrets.yml entry for the environment then specifies https://s3-eu-west-1.amazonaws.com  for production and UAT and http://localhost:8080 for development. Why that URL for development, well every Mac comes with python and from the directory you want to serve files from, the command below works well.

python -m SimpleHTTPServer 8080

Simple really, in summary our web app just serves up a link to the resource in the S3 bucket rather than from it’s own host. Really need a Rails guru to chime in and say what the best way to set the resource_host variable would be though. I’m sure secrets.yml isn’t meant for this!

Last thought before moving on, putting that installer image in the S3 bucket cost money, for transfer in fees, every download costs money, and we’ve made it publicly available – this worries me.

Step 2 – moving the rails assets pipeline to S3 & CloudFront

I’ve chosen not to do this at this stage. What? I thought this was an article about how to do that! Here’s the deal, if I follow the pretty simple advice out there to simply use CloudFront, e.g. https://www.happybearsoftware.com/use-cloudfront-and-the-rails-asset-pipeline-to-speed-up-your-app.html, then I’m simply running up my costs further.

Well that’s how it seems at the moment, with no control over bandwidth costs due to user behaviour or any other malevolent person choosing to do so, we’re exposed enough already. I’ll park this for now, and return when I understand better what techniques are available for controlling exposure here.

For reference, my current setup in the single VM, which hosts both the UAT and Production website in Apache virtual sites, and the database is different schemas in a single MySQL instance, costs less than a tenner a month, and has unlimited (subject to fair use) bandwidth. For sure this project is about adding resilience and scalability but as always there’s a decision about costs versus value. We don’t understand our total costs yet, perhaps there’s a way of modelling and understanding potential costs based on apache and mysql logs, but also need to understand the behaviours on costs that EBS, RDS, S3 and maybe eventually CloudFront add to this.

Advertisements

Home Lab – Ruby on Rails Web Server on Ubuntu 14.04.1

Objective is to end up with a reusable template of a VM that can be cloned for testing ruby on rails app deployment, upgrades etc. This post builds Ubuntu 14.04 LTS, nginx as the web server, passenger as the app server and mysql for the database. This is more of a memo for me, hence not explaining every single command.

Linux

Download server image from Ubuntu, http://www.ubuntu.com/download/server and set up your VM according to cloud providers process, or local vmware / fusion etc.

Get yourself ssh access https://help.ubuntu.com/community/SSH/OpenSSH/Configuring to install the ssh daemon, then copy your public key file to authorized_keys on the new server. Much easier than console access only from this point!

sudo apt-get update
sudo apt-get install openssh-server
sudo apt-get install avahi-daemon avahi-discover

Name Services

If you’re using Fusion or some such local virtual machine technology that allocates IP addresses using DHCP, get some naming going to make it easier to connect to your VM. Enable bonjour protocols using sudo apt-get install avahi-daemon avahi-discover. Change the hostname, sudo vi /etc/hostname and sudo vi /etc/hosts to change 127.0.1.1 entry then reboot, eg use a single hostname such as labweb1

BROKEN – cannot set up multiple local virtual hosts using avahi, mimicking how this is done in regular DNS to allow nginx server blocks (or apache virtual hosts) to work. Airtonix has scripts but couldn’t get them to work

SSH Access

With name services now set up,  Get yourself ssh access  – copy your public key file to .ssh/authorized_keys on the new server. Much easier than console access only from this point!

Web Server

Install nginx https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-14-04-lts

sudo apt-get install nginx

Ruby

sudo apt-get install git
git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
echo 'export PATH=&amp;quot;$HOME/.rbenv/bin:$PATH&amp;quot;' &amp;amp;gt;&amp;amp;gt; ~/.bash_profile
echo 'eval &amp;quot;$(rbenv init -)&amp;quot;' &amp;amp;gt;&amp;amp;gt; ~/.bash_profile
echo 'export PATH=&amp;quot;$HOME/.rbenv/bin:$PATH&amp;quot;' &amp;amp;gt;&amp;amp;gt; ~/.bashrc
echo 'eval &amp;quot;$(rbenv init -)&amp;quot;' &amp;amp;gt;&amp;amp;gt; ~/.bashrc

restart your shell

git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
sudo apt-get install build-essential libssl-dev
rbenv install 2.1.3
rbenv global 2.1.3
echo &amp;quot;gem: --no-document&amp;quot; &amp;amp;gt;&amp;amp;gt; ~/.gemrc

App Server

sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 561F9B9CAC40B2F7
sudo apt-get install apt-transport-https ca-certificates

create file /etc/apt/sources.list.d/passenger.list and insert deb https://oss-binaries.phusionpassenger.com/apt/passenger trusty main

sudo chown root /etc/apt/sources.list.d/passenger.list
sudo chmod 600 /etc/apt/sources.list.d/passenger.list
sudo apt-get update
sudo apt-get install nginx-extras passenger

Edit /etc/nginx/nginx.conf and uncomment passenger_root and passenger_ruby

Note this will mean passenger launches with the default ruby installed by ubuntu in /usr/bin/ruby – this is OK!

Edit /etc/nginx/nginx.conf to look similar to this:

server {
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;

    root /var/www/<INSERT YOUR RAILS APP NAME>/current/public;
    passenger_ruby /Users/alanwhite/.rbenv/versions/2.1.3/bin/ruby
    passenger_enabled on;
    passenger_set_cgi_param SECRET_KEY_BASE "blah whatever it was in your ruby on rails app";
}

This will ensure passenger uses your installed ruby version and not the system installed one. There’s still a flaw here in that to change ruby version you have to edit this file as root.

Database

Going to implement a mysql database here too, it’s really a web server, but one of the application use cases I need to experiment with relies on a mysql database local to the web server, rather than on a separate (virtual or otherwise) machine.

sudo apt-get install mysql-server

I use the password labsql1 usually for the mysql root user. This is a transient lab that gets torn down.

sudo mysql_install_db

Might be a transient lab but let’s get rid of the defaults.

sudo mysql_secure_installation

Now we have nginx, passenger and mysql running but haven’t deployed an app to it. Probably a good time to stop and use your virtualisation managers clone or snapshot facility, whatever works best, to create a known starting point for testing apps.

One of the areas I want to investigate further – needing all the dev libraries to install gems – seems wrong

sudo apt-get install libmysqlclient-dev

Prepare For Deployment

I’d like to deploy my test app to /var/www/my_app_name. Unfortunately Ubuntu 14 LTS doesn’t allow package deployers to point anything there so the installed nginx points to /usr/share/nginx/html

sudo mkdir /var/www
sudo chgrp www-data /var/www
sudo chmod 775 /var/www

Don’t forget to put you git/bitbucket credentials for deploy on the new server if you’re using a capistrano recipe that pulls down from one of those repo hosts.

Ruby on Rails Installation on Ubuntu 12.04.5

Objective is to have a server where multiple ruby or rails projects could be hosted, and the app could be simply installed from github without any faffing about.

What do I mean by faffing about? Not needing root access to the server in any way in order to deploy and run your app. Sure, for some baseline system packages like apache and plugins, and to define the virtual host to apache you may need sudo but once configured, deploying your app, updates and even changing ruby and rails versions should require no system privileges.

Prepping the server

It’s been about a while, a few aborted attempts to install various things linger. First step is to tidy up. My preference these days is rbenv for ruby version management, and bundler for gem mgmt, with gems local to each app, not system installed.

RVM was already installed from a previous adventure, time to remove it

Removing rvm

The process was to run rvm implode, and remove references in the startup scripts and make sure the gem was gone. Please note I only removed the .z* files as all they had in them were the rvm references. Check yours before doing that. Also removed all references to rvm in .bashrc and .bash_profile.

Install Ruby Environment Manager

Next install rbenv from github, avoiding the linux distro’s package manager. NB If you’re behind an enterprise firewall that spoofs certificates so ssh/ssl doesn’t work, you’ll have to use one of the other documented methods for cloning from github, e.g. http and plain text user/passwords – frustrating when experts do this in the name of security and end up with plain text credentials traversing the internet.

installrbenv

Exit the shell and start a new one so the rbenv command is available, then install the ruby-build plugin which allows you to build new ruby versions when needed.

installbuild

Install Ruby

Then use rbenv to install ruby!

installruby

Set the default ruby version for this user to the newly installed one, e.g. rbenv global 2.1.3

This process is directly from the rbenv github readme, which contains all the commands in a ready to cut’n’paste format.

Install Rails

Installing rails now becomes as simple as gem install rails but you may wish to tell gem to not bother installing all the documentation if you’re intending this install to be a server only, in which case enter echo "gem: --no-document" >> ~/.gemrc beforehand.

Web & Application Server

Next we need a web server hosting an application server that knows how to run ruby on rails apps. I’d usually go for nginx as a web server, but as this linux VM is already running apache2 with a bunch of other web sites we’ll stick with that. Still need to add the application server – for this we’re installing Phusion Passenger following their instructions.

First we establish the authentication of the Phusion repos.

preprepo

Then we add them to the list of repos Ubuntu uses.

addphusion

Then a quick sudo apt-get update and ubuntu has the latest phusion passenger software ready to install.

installpass

OK, now it’s all installed, the software needs enabled in apache. Works as per the Passenger install guide using sudo a2enmod passenger and then restart apache sudo service apache2 restart

Apache Virtual Host

You’ll need a virtual host for your web site.

<VirtualHost *:80>
    ServerName blog.blahblah.org
    DocumentRoot /var/www/blog/current/public
    PassengerRuby /home/arw/.rbenv/shims/ruby
    SetEnv SECRET_KEY_BASE 1389....
    <Directory /var/www/blog/current/public>
        Allow from All
        Options -MultiViews
    </Directory>
 </VirtualHost>

Note the use of the PassengerRuby directive or it will try to use the system installed ruby, bundler etc and it will all go horribly wrong!

Next Steps

If you’ve developed your ruby on rails app, followed all the capistrano guides and deployed it, in this case to /var/www/blog, and remembered to set up your production database of choice, and ran the rake db:migrate on production ….. it just works! Honest.