Background
A couple months ago, we took over a Grails project for a client. When we took over the project, it has never been deployed outside the previous developer’s laptop - obviously not ideal.
So we convinced the client that getting it deployed sooner. She then picked Amazon Web Services (AWS) as the platform where she would like her app to be deployed.
Although, we haven’t had any experience with AWS before, we were quite happy to accomodate her requirements as we have heard good things about AWS and it’s a good opportunity for us to learn about AWS.
Load Grails application to AWS
To start of, my colleague followed Inoneo’s article: deploying a grails appliction on AWS to setup and deploy our AWS instance.
In hindsight, I would have not recommended that article, as it missed a very crucial step which is setting up an SSH keypair. Without it you won’t be able to SSH into your instance and when SSH is unavaiable you can pretty much kiss automation goodbye.
I haven’t read the following article thoroughly, but from my quick glance, it looks like it’s more thorough: Load a Grails or Java application to Elastic Beanstalk on AWS.
If you know a more authoritative article on setting up a Grails project on AWS - please do leave a comment.
So basically our setup on AWS is: Grails app packaged as a WAR file deployed on Tomcat server (I believe this is not the only option though, more on this later).
Manual upload of 82MB WAR is not fun
So all well and good, getting the Grails app to AWS wasn’t too hard at all. Client can now actually use the app and see the progress of the app while it is being developed.
But after awhile, I felt the deployment process is time consuming and frustrating. If you read the article above, deploy an app is basically done by manually upload the application WAR via this page on AWS:
Having to go to that AWS page all the time is already painful, let alone having to upload the gargantuan WAR file that Grails produces.
Side note, when it was on Grails 2.3, the WAR size was around 43 MB - but when we upgraded to Grails 3.0.14, WAR file is now around 82 MB.
Being a Ruby on Rails developer, I really missed the mina deploy to=production
command, that takes care of all the things for me.
Automated Grails deployment
So how can we automate the process? I am thinking few options.
Option 1 - Stand alone Grails on AWS
We could install Grails on AWS and on deployment, we would do the following:
- upload the source code - which would be small as it will be just our code not including Grails files and friends
- or trigger source code pull from repo (eg: GitHub)
- restart Grails
Option 2 - App only WAR, exclude all the JARs
When running grails war
, you could pass in a -nojars
option which will exclude all JARs from Grails and its
dependencies being bundled into the resulting war (read more here: Create much smaller grails war file.
The idea is you only need to deploy your app only, the Grails, plugins etc do not need to be re-uploaded. Of course, if you install new Grails plugin - you need to remember to deploy it once.
Option 3 - Explode WAR and RSync
Similar to the option above, what if we could do explode (an awful word - but basically it means unzip) war then rsync the individual files to the Tomcat site directory.
This way, if you install new plugin for instance - rsync will pick it up and upload it too.
This is the option that I went with.
For a comparison sake, I tried to time a manual deploy of my 82 MB war file to AWS and it took a whopping 18 minutes!
Using rysnc, deployment time is now just a mere 62 seconds!
sent 491,205 bytes received 616,276 bytes 51,510.74 bytes/sec
total size is 101,083,561 speedup is 91.27
./scripts/staging-deploy.sh 61.09s user 6.23s system 51% cpu 2:10.05 total
Ok - how to explode WAR you ask? Well, apparently Grails used to have the ability to produce an exploded WAR but it no longer has that, see this github issue.
So I ended up following the workaround described on that issue, adding the following gradle task:
// Usage: ./gradlew explodedWar -Pd0=grails.env=prod
File explodedDir = "$buildDir/libs/deployment" as File
task explodedWar(dependsOn: war) {
ant.unzip(src: war.archivePath, dest: explodedDir)
}
And so there you have it - I am pretty happy with the state of my grails deployment for the moment, maybe one day I’ll give the option 1 a go - as it actually makes the most sense to me.
Bonus round
Here is my staging deployment script - it’s not perfect - open for suggestion:
grails -Dgrails.env=staging war
./gradlew explodedWar -Pd0=grails.env=staging
rsync --no-perms --omit-dir-times -rve "ssh -i my-amazon-key" build/libs/deployment/ ec2-user@my-amazon-url:/usr/share/tomcat7/webapps/ROOT
ssh -i my-amazon-key ec2-user@my-amazon-url "sudo service tomcat7 restart"
Request for Comment
I have only been doing Grails for few months - so it is pretty likely that I am doing it wrong somewhere. Please do let me know if there is a better way for Grails deployment to AWS (by commenting below or email / tweet me at @cemeng).