Some more fun and games with deployment of Play to EC2. Just to set the scene, I have a Play 2 app which was built locally using Java 1.7. I connect it to an Amazon RDS MySQL instance. All this works just fine when running from localhost on my machine.
In theory, and according to the Play Documentation, I should be able to
- create a standalone Play distributable using play dist
- copy that up to my EC2 instance
- explode it
- run start
So that’s what I tried and it didn’t work. For a start the standard Linux AMI at Amazon ships with Java 1.6 as the default, so I had to upgrade that (see here for how to do that).
Then I had to install a set of tools to give me access to my EC2 instance. I like WinSCP which gives me a file browser and removes all the sweat of uploading and editing files on the Linux server, and does it through SFTP so I’m safe. You can get WinSCP here.
I also needed PuTTY, or more accurately PuTTYgen to translate from the .pem file that EC2 creates for you when you make your security group in which your instances run, to a .ppk file which PuTTY and WinSCP need. You can get the download from here but I confess I found that page very confusing. Basically you want the one called “A Windows installer for everything except PuTTYtel“, and get the first one on the list since there seem to be two things with the same label.
Having done that using PuTTYgen is straightforward and you have a .ppk that you can use in WinSCP. This blog has a nice write-up about that bit, in fact it’s worth a read in general.
Lots of steps to get to the basic point of being able to connect a file browser to my EC2 instance. One thing to remember is that the DNS entry for your instance will change every time you restart it, so your saved connection in WinSCP will go stale as soon as you restart in EC2. The way round that is to assign an Elastic IP address, but beware, that costs money.
Having connected I can very simply upload my Play distributable to EC2. That distributable is just a zip file, so you need to unzip it when it gets there. It doesn’t really matter where you put it on the server, but it’s simplest to put it in your ec2-user folder as you will have all the permissions you need to do that.
At this point you need a shell on your EC2 instance so you can run commands, including the start script that comes with your dist. I tend to use the default shell that EC2 offers, but having done all that downloading you could (perhaps should) use PuTTY to do the same thing. If you are like me (a bit lazy), then click on your instance in the EC2 console and choose “Connect” from the “Actions” menu at the top and then select “Use Java SSH client” and you’ll get a shell.
Now navigate to the folder in which you dropped your zipped dist file and unzip it. That couldn’t be easier, just type “unzip your-file-name.zip” and it’ll explode it to the same folder you are currently in.
Now, in theory you should just be able to type ./start and your app should be running.
That didn’t work for me. I spent many hours while the script was silently timing out in the background googling aimlessly trying to figure out what might be wrong. It’s worth noting that a /logs folder is created with an application.log in it, which does give some clue. In my case I was getting the error
2013-06-10 01:43:13,855 - [ERROR] - from com.jolbox.bonecp.hooks.AbstractConnectionHook in main Failed to obtain initial connection Sleeping for 0ms and trying again. Attempts left: 0. Exception: java.net.ConnectException: Connection timed out
This means “I can’t connect to your database, so I can’t get started”. I was getting an exception thrown in the SSH client window after a very long timeout, but it actually took me quite a while to spot it. That had a slightly less cryptic message and told me that I could not connect to my database [default].
That led me down a garden path of believing that it was not trying to connect to the right database and that the application.conf file packed into the dist was wrong or missing. A few things on stackoverflow (this one and this one and this one and this one) helped, but they basically led me down the wrong path, although the experimentation was a useful learning curve.
So, the real problem was simply that I needed to make sure that my EC2 instance was allowed to talk to my RDS instance. Turns out you can do this all through AWS. I had a default security group on my RDS instance and a default security group on EC2, all I had to do was add the EC2 group to the RDS group, which you do through the RDS console by selecting Security Groups and adding an EC2 Security Group, and choosing the one you are using for your EC2 instance.
I re-ran my start script and it happily connected to the database and ran my app.
I still wasn’t done. When I hit the page from the outside world (having assigned an elastic IP) I got a server connection error from the browser. The last step is to make sure that the appropriate port is open to the wide world from your EC2 instance. In the case of a Play app the default port is 9000, although you can change that, but I hadn’t, so I had to add it to the list of open ports in the EC2 security group. You do that from the EC2 console, select Security Groups, choose the one you are using, select the “Inbound” tab and add a custom TCP rule typing 9000 as the port.
I tried to open this up on standard ports (80 and 8080) and then running the app using “./start -Dhttp.port=8080” but I couldn’t get that to work. I suspect there are some internals of my Play app which would need to change to reflect the port change, and I was so happy to just see it all working up in the cloud I stopped there.