Templating with GeoEngineer

A couple of months ago I joined the Infra team at Coinbase. One of the first projects I worked on was codifying all of our cloud infrastructure. We evaluated a number of different options for how to codify that infrastructure, and ended up using a thin wrapper called GeoEngineer (Geo for short) over Terraform. My colleagues Graham Jenson and Rob Witoff have written and spoken about GeoEngineer previously, and I think they’ve done a good job explaining why we built it and the purpose it serves. Today I’m going to talk about templating with Geo, why templates are useful and how they compare to Terraform modules.

At a high level, Geo is organized around environments. These correspond to the particular AWS account or VPC that you are planning and/or applying your resources into. Additionally, you can further organize your code via projects, which is a logical grouping of resources within an environment. These concepts help you think about where to define your resources and how to group them, but as you add more and more resources, you need additional logic to help keep things simple and maintainable. This is where templates come into play — they let you codify best practices.

For example, as we were migrating towards what we’re calling consolidated login and codifying all of the resources roles, a common pattern was defining a role for some project, letting some Amazon service assume that role, and giving that role some permissions.

Sample role for a lambda function

In most of these cases, the assume_role_policy for each service was the same, just with a different service name, i.e. vpc-flow-logs.amazonaws.com or lambda.amazonaws.com . Additionally, the role-name , policy-name , and attachment-name were similarly the same, or derived from the same base. And the only purpose of the aws_iam_policy_attachment is to connect the two resources we just created, it doesn’t require any additional information. So in order to create those 3 resources, all we really need to know is the role name, the service for the assume role policy, and a list of policy files. It’s a complex implementation with a simple interface.

This allowed us to create a RoleWithPolicies template, which really simplified things.

Using this new template, we can codify the role from earlier as follows:

Sample role codified with Geo, demonstrating attaching multiple policies

The great thing about this particular template is that the more policies you need to attach to a single role, the more lines of code you save. We’ve easily saved hundreds of lines using templates for users, roles, and groups. Another benefit is that the naming ends up being more consistent, which just satisfies my inner OCD.


Now if you’ve already worked with Terraform quite a bit, you might be thinking: “I can do that same thing using Terraform modules — why should I use Geo?”. And indeed, they do very similar things — Geo just makes it easier to do more while smoothing over some of Terraform’s rough edges. (I’d also like to point out that we’re very excited about the progress demonstrated in Terraform 0.8, and hope that one day we won’t need Geo anymore — but until then, Geo all the way!).

Lets look at another example where we’ve used templates at Coinbase: VPC routing. In our VPCs, we often have one or more route table that needs to be connected to various interfaces like NAT gateways, peering connections, and VPN gateways. With Terraform modules, I was able to create a module for each interface type. For example, with VPN gateways, we wanted 2 routes for each route table attached to the gateway:

Example Terraform Module

Great. Now we have a nifty module we can invoke anywhere we’d like via:

Which is certainly better than the raw terraform. However, you’ll notice a couple things:

  • You need a new module for each interface type: VPN gateway, NAT gateway, VPC Peering connection, Internet gateway, etc…
  • We’ve already got notes about “work arounds” — this particular one relates to restrictions on how module outputs can be used as inputs to other modules
  • There’s no validation that the end user provided the data in the correct format
  • We have limited functions available for data manipulation

Enter Geo. With Geo, we can achieve the same functionality AND address some of the shortcomings with modules. Let’s see what that same VPN Gateway route module looks like as a Geo template:

Which can be used like this:

With the above Geo example we’ve added validations, cleaner error handling and combined 5 copy and paste modules into a more flexible and powerful tool. Having the full power of the Ruby language means that we can do things that simply aren’t possible with Terraform modules — in this case, switch statements and input validation.

This is a just a brief demonstration of the power of Geo templates to keep your infrastructure code simple and re-usable, and if I’ve piqued your interest, tune in for part 2 for more about template design and best practices.

Resources:

P.S. If any of this sounds interesting, or you’re into Bitcoin — Coinbase is hiring.