I build Cloud Native applications. That is, I develop applications that run in the Cloud and leverage Cloud services. This means I write application code and deploy Cloud infrastructure. I believe there are exactly 1,034,343 ways to deploy Cloud infrastructure. There is a wide range of tools available from a variety of vendors that want to help you deploy your Cloud infrastructure and get it connected to your application.
More often than not, these days, deploying Cloud infrastructure means writing Infrastructure-as-Code. If you are still doing anything in the console we can’t be friends. I recently became aware of Ampt and the new (to me) concept of Infrastructure-from-Code (IfC). I’m really struggling with how I feel about it and thought I should try to solidify my feelings in a blog post.
The thing I really want to talk about is abstraction. That is, each of the 1,034,343 solutions that help build Cloud infrastructure provide a layer of abstraction on top of foundational ways to build Cloud infrastructure. This layer of abstraction is often titled towards a particular use case to make it easier to build the infrastructure that satisfies the use case. Some solutions are focused on declarative infrastructure, some on support for multiple clouds and some on building applications in the Cloud.
The level of abstraction varies pretty widely across different solutions. Let’s get into it and I think (hope) you will see what I mean. And, keep in mind my background, and interest, is with tools that help build infrastructure for building applications, and I do that primarily on AWS.
Too Little Abstraction - CloudFormation
In the AWS world CloudFormation is the OG when it comes to IaC. In fact, in AWS CloudFormation is really the base layer IaC, or you might think of it as no abstraction. That is, all of the other solutions we will discuss after this build their abstraction on top of CloudFormation.
The lack of abstraction with CloudFormation is why no one uses it, except for specific things they can’t do with the abstraction tool they have selected. That is, it gets used as a fallback when the abstraction tool doesn’t provide a desired capability. Generally, it just takes too much time and effort to cobble together and maintain CloudFormation. The abstraction tools exist for a reason. They make it easier and faster to build stuff on AWS.
- very easily understood (probably should be the foundation for anyone working on AWS)
- everything AWS (almost - custom resource escape hatch)
What doesn’t quite work:
- no abstractions
- too much work to get to the finish line
Too Much Abstraction - AWS Amplify
First there was CloudFormation and almost immediately after someone said we can make this infrastructure deployment stuff way easier. Some other smart person, or maybe the same smart person, said if we figure out the use case we want to support we don’t have to support everything like CloudFormation does.
One of the solutions that came along, far from the first, was AWS Amplify, and I’m specifically talking about the Amplify CLI (Amplify has come to mean so many things). Amplify was created with the web and mobile application developer in mind. By running CLI commands you can define your application’s backend, things like APIs , databases, storage, etc. Each of these things maps to one or more AWS Services that is provisioned to build the related infrastructure that can then be used by the application being developed. In fact, the Amplify CLI builds CloudFormation that provides the desired infrastructure.
The challenge with using Amplify is the opposite challenge to using CloudFormation. Amplify provides a little too much abstraction. Sometimes it is hard to know what exactly will be provisioned, and maybe more critically, when things break it can be hard to fix them. It is important to remember that applications get deployed and operationalized.
This lack of direct relationship between Amplify and the provisioned infrastructure can also lead to users stepping past the invisible boundaries of the anticipated use case and pushing the limits on the amount of infrastructure being deployed; which comes at the cost of deployment time and maintainability.
- speed to solution is great
What doesn’t quite work:
- too much mystery, too hard to follow abstraction - hard to troubleshoot
- perception (maybe not reality) that the amount of abstraction makes it difficult to scale - too slow, too many things created, etc
Just Right Abstraction - Serverless Framework
The Serverless Framework was one of the first IaC solutions I became aware of and I expect its long history has served it well. The solution has a large and loyal following because it works and I believe hits the sweet spot for the right amount of abstraction. When you use the Serverless Framework you know what is being deployed and, at the same time, you get real time savings over using CloudFormation.
The key thing that Serverless Framework really nailed was building an API. That is, they made it easy for the application developer to define and link all of the infrastructure needed to build the API that provides the frontend of the application access to the backend of the application. This involves tying together API Gateway and Lambda and the required underlying permissions. It has evolved into much more, including being able to easily adjust the myriad of settings available and inclusion of additional services.
Traditionally and largely still today, Serverless Framework leverages config files to define IaC. Most often this was yaml, sometimes json (depending on the team), and now includes support for different programming languages. However, even with this I would suggest it still feels like you are building config files, and you don’t really know if they are set up correctly until you run them to deploy your code.
- solved a really specific challenge - build a REST API - pull together the required bits
- evolved into allowing add-on of other required bits
- first IaC that really thought like a developer, not a sysadmin (probably a role/title that doesn’t exist anymore)
What doesn’t quite work:
- static files, not compiled
- additional of typed languages reliant on type definitions, not actual implementation
- linters don’t quite get there
- too easy to deploy stuff that doesn’t work as configured (coded)
- can use CloudFormation to get everything which is only okay
- granular permissions can get messy (requires add-ons)
Just Right'er Abstraction - CDK
One of the well-known, and respected, influencers in the ‘serverless’ space is Alex Debrie, he literally wrote the book on how to use DynamoDB effectively. He has some history with Serverless Framework and recently penned this article on his blog - Why I (Still) Like the Serverless Framework over the CDK. Know two things, Alex is a much smarter guy than myself, and I couldn't disagree with someone more.
In my world, CDK (another solution from AWS) came along and really nailed some key improvements to Serverless Framework. There are two things that really sets CDK apart. One, CDK uses typed languages (I use Typescript) and you can build your IaC in a familiar coding paradigm. This comes with the benefits of type safety and intellisense; which means your IaC is much more likely to work when you deploy it. It also makes it easy to build your own abstractions. The second thing that CDK improves on is the ability to easily assign permissions.
The big miss with CDK is that it has felt like it has constantly been under construction for years. There was a first version that I would suggest didn’t do very well and then a second version that has done better. The challenge I have personally experienced several times are breaking changes. That is, you update to a new version and your solution no longer works as it did. This was so bad in version one of CDK that I gave up on it. It has surfaced again in version two but not as often. Although that is a negative, my willingness to continue to live with it should be seen as a testament to how much I like it versus the alternatives.
- different level of abstractions, but not giant leaps
- easily traced through cloudformation
- can still use cloudformation - when writing, when wanting to kill something
- everything in AWS
Improvements over Serverless:
- type safety - get configuration right prior to deployment
- intellisense - help with configuration while building the code
- real programming language - easy to create further abstractions (most useful for Functions where 95%+ config is the same for every function)
- simplified permissions
What could be better:
- new stuff is alpha for too long
- history of breaking changes
Too Early to Tell - Ampt
Now, we have Ampt. It comes from the Serverless Framework team. The idea is you write your application code and it will figure out what infrastructure to deploy, hence, infrastructure-from-code (IfC). Currently, it supports AWS.
My initial reaction is that this is too much abstraction. That is, now I am completely separated from the infrastructure I (and others) will have to support when our application moves into production. I do expect that this will reduce the time to build an application but that just isn’t the whole picture. I’m going to need to troubleshoot things when they go wrong. I’m going to need to reconfigure things. I may have to migrate the application to another platform. These things feel like they will be harder.
What looks good:
- at the end of the day I’m just trying to create an application, this makes sense
Where I have questions:
- is it just lambda/serverless
- what about containers
Keys to the Right Level of Abstraction
These are my thoughts on how I access the level of abstraction provided by an IaC/IfC solution.
- I can easily follow how the abstraction works - for troubleshooting and to understand what has been deployed. I feel like the operational side of the application sometimes gets overlooked. When we build an application we need to remember that the application needs to be secured, it needs to be backed up, it needs to be maintained, etc. Probably, the critical question is - what do you do when things stop working?
- The abstraction saves time and reduces errors. That is, the abstraction helps get the right things deployed, and correctly links them (securely).
- I can keep my application from getting intertwined with the abstraction. That is, I can still fool myself into thinking I have a ‘somewhat’ portable application and I can undo and redo the abstraction as needed to support another platform. Lots of teams seem willing to look past this but that feels very dangerous to me.
I’m going to try to keep an eye on Ampt and not be too judgy but that is tough for me. For now, I continue marching forward with CDK.