Posted on Aug 8, 2017 7 mins read
The workout tracker program is a super dead simple little golang app that I've been working on over the past year or so. It started out with a simple design as a way to help me keep track of my workouts. Easy enough, right? Well, it turns out that the golang stuff was actually a bit of a flop. I ended up solving the problem using google sheets and a little ruby code.
However, what I learned during this process is that my little go binary was super good at doing one very simple thing in that I could wrap up the bin into a docker container and use it to do many POC things with AWS ECS. In fact, I used this little fella extensively at a client to show how ECS deployments worked.
This was the workflow for demonstrating how we could do b/g deployments in ECS:
The output would look something like this:
{ "version": "0.4.0" }
{ "version": "0.4.0" }
{ "version": "0.4.0" }
{ "version": "0.4.1" }
{ "version": "0.4.0" }
{ "version": "0.4.1" }
{ "version": "0.4.0" }
{ "version": "0.4.1" }
{ "version": "0.4.1" }
{ "version": "0.4.1" }
{ "version": "0.4.1" }
{ "version": "0.4.1" }
{ "version": "0.4.1" }
Both versions are running until the new version is completely switched over and all traffic has switched over to the new task. ECS, like most systems like this, has free b/g deployments. Pretty neat, huh?
The intent with this post is to show how to create a dead simple container like this that can deploy a simple application that does a simple thing. Simply.
Let's start this process by laying out what we want to accomplish. For now, I'm going to skip the DNS integration. I might do that in a later post as to not confuse the simplicity of this project.
The devops project contains everything I need to launch a stack that does exactly this. This is something that I've been putting together for years based on my work with many different clients representing many different business models. Here's how I launch my stack:
krogebry@ubuntu-secure:~/dev/devops/aws$ rake cf:launch['workout-tracker, 0.0.8, WorkoutTracker']
D, [2017-08-08T15:32:38.329970 #3245] DEBUG -- : FS(profile_file): cloudformation/profiles/workout-tracker.yml
D, [2017-08-08T15:32:38.970751 #3245] DEBUG -- : MgtCidr - {"type"=>"vpc_cidr", "tags"=>{"Name"=>"main"}}
D, [2017-08-08T15:32:38.971575 #3245] DEBUG -- : KeyName - devops-1
D, [2017-08-08T15:32:38.972031 #3245] DEBUG -- : DockerImageName - {"type"=>"docker_image_name", "image_name"=>"workout-tracker"}
D, [2017-08-08T15:32:38.972579 #3245] DEBUG -- : VpcId - {"type"=>"vpc", "tags"=>{"Name"=>"main"}}
D, [2017-08-08T15:32:38.973277 #3245] DEBUG -- : Zones - {"type"=>"zones", "tags"=>[{"Name"=>"public"}, {"Name"=>"private"}]}
D, [2017-08-08T15:32:38.974151 #3245] DEBUG -- : Subnets - {"type"=>"subnets", "tags"=>[{"Name"=>"public"}, {"Name"=>"private"}]}
D, [2017-08-08T15:32:38.977673 #3245] DEBUG -- : Loading module: cloudformation/templates/modules/base.json
D, [2017-08-08T15:32:38.981519 #3245] DEBUG -- : Loading module: cloudformation/templates/modules/vpc.json
D, [2017-08-08T15:32:39.093917 #3245] DEBUG -- : Stack exists(WorkoutTracker-0-0-8): false
D, [2017-08-08T15:32:39.094576 #3245] DEBUG -- : Creating stack
Most of this is debugging info that I use to keep track of what's going on. The most important part here is the rake command, which consists of 4 parts:
The name of the CF stack ends up being WorkoutTracker-0-0-8. Now I can use the version as a sort of "point in time" reference for where I'm at with the infrastructure code. This is the most difficult way of producing CF stacks because it's forcing people to think of infrastructure as a gathering of versioned things, which is different than what we're used to, which is something that lives in reality as a 1u component in a rack. I personally prefer this model because I can create things faster and better here. However, not everyone agrees with me on this one.
This does have it's draw backs, of course. However, in one particular case called the blast radius argument, it's very easy to see how this would be better than traditional "non-versioned" layouts. An example that demonstrates this would be how someone might argue that we should breakout the security groups for a given stack into their own stack. This is fine, but the problem is that if they are broken out, and multiple services are using a single SG, then if someone changes that SG, then everything that touches that SG is exposed. This is a very short example of how one change can have a "blasting" effect which "radiates" to more than we intend to. Keeping the SGs inside the stack ensures that if something is changed, then the only thing impacted by that change is the thing that is contained within the stack.
I've used my code to create a stack, now we can start investigating what has been created. First off I want to test to see if my ALB is working as I intend.
krogebry@ubuntu-secure:~/dev/workout_tracker$ curl Worko-EcsAL-W72TLYSV79IS-1289125696.us-east-1.elb.amazonaws.com/version
{"version":"0.5.0","build_time":"2017-08-08T13:52:02-0700","hostname":""}
We can see here that the ALB end point is working from my workstation here at home.
The stack and params file are available for this demo. Here's a detailed list of what we're creating here.
The workout tracker implements JSON logging output, which is being sent through ECS to cloudwatch logs. This doesn't do me any good at the moment, but having json logs is just good stuff all around.