<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Cloudformation on Geoff Baskwill</title>
    <link>https://geoffbaskwill.ca/tags/cloudformation/</link>
    <description>Geoff Baskwill (Cloudformation)</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-ca</language>
    <lastBuildDate>Mon, 13 Nov 2023 00:00:00 +0000</lastBuildDate>
    
    <atom:link href="https://geoffbaskwill.ca/tags/cloudformation/index.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
      <title>Custom CloudFormation resource types</title>
      <link>https://geoffbaskwill.ca/posts/custom-cloudformation-resource-types/</link>
      <pubDate>Mon, 13 Nov 2023 00:00:00 +0000</pubDate>
      
      <guid>https://geoffbaskwill.ca/posts/custom-cloudformation-resource-types/</guid>
      <description>&lt;p&gt;I recently had the opportunity to explore building &lt;a href=&#34;https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-types.html&#34;&gt;custom CloudFormation resource types&lt;/a&gt; as a mechanism to support a stream-aligned team. It was intriguing to play with this new-to-me tool and see how it could reduce complexity for the team.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve written before about &lt;a href=&#34;../cloudformation-custom-resources-with-step-functions/&#34;&gt;using Step Functions to build custom resources in CloudFormation&lt;/a&gt;. This builds on the &lt;a href=&#34;https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html&#34;&gt;old-school custom resources mechanism&lt;/a&gt; where you provide a Lambda handler as part of your stack. This is convenient for very small or one-off duct-tape situations, but if you need more complex resource logic or if you want to provide a simple resource interface to a bunch of teams, there is a better way.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&#34;https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/registry.html&#34;&gt;AWS CloudFormation registry&lt;/a&gt; and the &lt;a href=&#34;https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/what-is-cloudformation-cli.html&#34;&gt;CloudFormation command-line interface&lt;/a&gt; are this better way. They allow you to write your own resource provider that will be exposed using the exact same mechanisms that CloudFormation uses for many AWS-provided resource types.&lt;/p&gt;
&lt;p&gt;You can do all kinds of super-neat things with resource types, and there is a &lt;a href=&#34;https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-develop-stabilize.html&#34;&gt;neat pattern&lt;/a&gt; to report progress when your resource may take a while to create or when there are multiple
steps to create your resource.&lt;/p&gt;
&lt;h2&gt;Batteries not always included&lt;/h2&gt;&lt;p&gt;Resource providers can &lt;a href=&#34;https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-model.html#resource-type-howto-configuration&#34;&gt;expose configuration data&lt;/a&gt;; this is super-handy if you want to attach &amp;ldquo;account-level&amp;rdquo; configuration (really region-level configuration) to your type&amp;rsquo;s handler. Unfortunately, while an API exists to programmatically set the configuration data, tragically and ironically there is no CloudFormation support for setting the configuration data on a resource type.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Because of the super-cool open way that AWS CloudFormation resource providers are built,
a person could theoretically open a pull request on the &lt;a href=&#34;https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-cloudformation&#34;&gt;aws-cloudformation/aws-cloudformation-resource-providers-cloudformation&lt;/a&gt; repository, duplicating the &lt;code&gt;aws-cloudformation-hooktypeconfig&lt;/code&gt; folder and refactoring it to implement &lt;code&gt;AWS::CloudFormation::ResourceTypeConfig&lt;/code&gt;, but I haven&amp;rsquo;t gone there yet. Probably should open a &lt;a href=&#34;https://github.com/aws-cloudformation/cloudformation-coverage-roadmap/issues&#34;&gt;roadmap issue&lt;/a&gt; for discussion first.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I used an old-school custom resource to set the configuration for my custom types. 🤦‍♂️&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Using Step Functions to build CloudFormation custom resources</title>
      <link>https://geoffbaskwill.ca/posts/cloudformation-custom-resources-with-step-functions/</link>
      <pubDate>Sat, 24 Apr 2021 00:00:00 +0000</pubDate>
      
      <guid>https://geoffbaskwill.ca/posts/cloudformation-custom-resources-with-step-functions/</guid>
      <description>&lt;p&gt;&lt;img src=&#34;images/serban-mestecaneanu-1Cwj7JowUHA-unsplash.jpg&#34; alt=&#34;Steps leading up&#34; title=&#34;Steps leading up&#34;&gt;
&lt;em&gt;Photo by
&lt;a href=&#34;https://unsplash.com/@meste?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&#34;&gt;Serban
Mestecaneanu&lt;/a&gt; on
&lt;a href=&#34;https://geoffbaskwill.ca/s/photos/steps?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&#34;&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;My team uses &lt;a href=&#34;https://aws.amazon.com/cloudformation/&#34;&gt;AWS CloudFormation&lt;/a&gt; to
provision our cloud infrastructure using code. Most of the time we can get what
we need with the set of resources that AWS provides.&lt;/p&gt;
&lt;p&gt;However, sometimes CloudFormation support for a service or a particular feature
takes a while to arrive, and we need to fill in the gap ourselves.
CloudFormation gives us the ability to fill these gaps by building
&lt;a href=&#34;https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html&#34;&gt;&amp;ldquo;custom resources&amp;rdquo;&lt;/a&gt;
that can literally run any logic you need in
&lt;a href=&#34;https://aws.amazon.com/lambda/&#34;&gt;AWS Lambda&lt;/a&gt;, and we&amp;rsquo;ve used these when we
needed to.&lt;/p&gt;
&lt;h2&gt;Sometimes Lambda isn&amp;rsquo;t the right answer&lt;/h2&gt;&lt;p&gt;Sometimes even Lambda fails us, though, as some resources can potentially take a
long time to set up, and we don&amp;rsquo;t particularly want to have a Lambda function
sitting idle or potentially timing out halfway through the resource setup.&lt;/p&gt;
&lt;p&gt;The great thing about Lambda functions is that you only pay for them when
they&amp;rsquo;re running. This makes them great for places where you need to run a quick
task or handle an API request. You lose some of the benefits when your Lambda
function looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;start_something()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; not_done():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    time&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sleep(&lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;finish()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;because you&amp;rsquo;re paying for the Lambda to run while you&amp;rsquo;re waiting for your
operation to complete. Worse, Lambda functions have a maximum lifetime of 15
minutes, so if your process takes longer than 15 minutes, you have to do weird
hacks to make it work with a pure Lambda solution.&lt;/p&gt;
&lt;p&gt;When we found out that
&lt;a href=&#34;https://github.com/aws-cloudformation/aws-cloudformation-coverage-roadmap/issues/57&#34;&gt;CloudFormation didn&amp;rsquo;t have support for creating DynamodDB Global Tables&lt;/a&gt;,
our first thought was &amp;ldquo;we know how to do this, a Lambda function custom resource
can handle it.&amp;rdquo; However, as we dug into the details and tried it out, we quickly
learned that we could get into a scenario where creating the initial replica set
or updating the replicas could easily exceed the 15-minute Lambda timeout.&lt;/p&gt;
&lt;h2&gt;Step Functions to the rescue!&lt;/h2&gt;&lt;p&gt;Here&amp;rsquo;s where &lt;a href=&#34;https://aws.amazon.com/step-functions/&#34;&gt;AWS Step Functions&lt;/a&gt; comes
in. Step Functions make the task of orchestrating processes easier. They have
built-in support for looping, waiting, and integrating with different functions
and services, which makes them perfect for this sort of thing.&lt;/p&gt;
&lt;p&gt;One of our team members put together this Step Function definition for creating
a DynamoDB global table. It starts out by checking the state of the table,
waiting until the table is ready for updates, then comparing the set of replicas
with the desired set. You can only add one replica at a time, so the step
function repeats the &lt;code&gt;UpdateReplicas&lt;/code&gt; step until the actual state matches the
desired state. Each step is very small and self-contained, usually only one or
two API calls, and all of the waiting is done by Step Functions instead of in
the Lambda function, so we&amp;rsquo;re not paying for idle time! Best of all, Step
Functions can run for up to a week, so we didn&amp;rsquo;t need to worry about the
15-minute timeout any more.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;images/replication-state-machine.png&#34; alt=&#34;Replication state machine&#34; title=&#34;Replication state machine&#34;&gt;&lt;/p&gt;
&lt;h2&gt;There&amp;rsquo;s a small catch&amp;hellip;&lt;/h2&gt;&lt;blockquote&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;I wish I could use Step Functions directly to build CloudFormation custom resources instead of having to have a Lambda function that triggers the Step Function. #awswishlist&lt;/p&gt;&amp;mdash; Geoff Baskwill (@geoff_baskwill) February 24, 2021&lt;/blockquote&gt;
&lt;p&gt;CloudFormation doesn&amp;rsquo;t support direct integration with Step Functions as a
custom resource provider yet, but we can use our old Lambda function trick to
trigger the Step Function execution, and send the response back to
CloudFormation when we get to an end state.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;images/step-function-overview.png&#34; alt=&#34;Invoking Step Functions as a custom resource using Lambda as a shim&#34; title=&#34;Invoking Step Functions as a custom resource using Lambda as a shim&#34;&gt;&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;&lt;p&gt;When you love infrastructure-as-code and need a custom resource for something
that CloudFormation doesn&amp;rsquo;t support, Lambda is usually a great solution. When
you need a bigger hammer for complex orchestration or operations with lots of
idle time, Step Functions can help get you there.&lt;/p&gt;
&lt;p&gt;The goal is to retire this particular resource soon, as AWS tells us that
they&amp;rsquo;ll have built-in support in CloudFormation for DynamoDB Global Tables
&lt;a href=&#34;https://github.com/aws-cloudformation/aws-cloudformation-coverage-roadmap/issues/57#issuecomment-758200567&#34;&gt;in the very near future&lt;/a&gt;.
That said, my team is happy that we were able to deliver an initial
implementation with this workaround and provide value to our customers!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>CloudFormation, Route 53, and ... EKS?</title>
      <link>https://geoffbaskwill.ca/posts/cloudformation-route53-and-eks/</link>
      <pubDate>Thu, 22 Apr 2021 00:00:00 +0000</pubDate>
      
      <guid>https://geoffbaskwill.ca/posts/cloudformation-route53-and-eks/</guid>
      <description>&lt;p&gt;&lt;img src=&#34;images/down-the-rabbit-hole.jpg&#34; alt=&#34;Falling down the rabbit hole into Wonderland&#34; title=&#34;Falling down the rabbit hole into Wonderland&#34;&gt;&lt;/p&gt;
&lt;p&gt;The other day I got halfway through writing a &lt;em&gt;very irate&lt;/em&gt; support ticket to
AWS, stopped to do some fact checking, and learned something deeply annoying.&lt;/p&gt;
&lt;p&gt;One of the teams I work manages a bunch of services. One of these “services” is
some common &lt;a href=&#34;https://aws.amazon.com/route53/&#34;&gt;Amazon Route 53&lt;/a&gt; infrastructure
that is set up using
&lt;a href=&#34;https://aws.amazon.com/cloudformation/&#34;&gt;AWS CloudFormation&lt;/a&gt;, and over the
history of the project the deployment in the development account the team uses
has been a little flaky. Each time we hit a deployment problem, the problem
always turned out to be rate limiting. It never happened in production, so the
flakiness didn&amp;rsquo;t quite get the attention that it could have.&lt;/p&gt;
&lt;h2&gt;Rate limiting in Route 53&lt;/h2&gt;&lt;p&gt;Some background: Route 53 has a hard limit of 5 control plane requests per second per AWS
account. For most folks, this is fine. However, it wasn&amp;rsquo;t working well for this
team.&lt;/p&gt;
&lt;p&gt;The team had raised support tickets and gotten advice like “CloudFormation will
attempt to create your resources in parallel. One option to avoid rate limiting
is to add &lt;code&gt;DependsOn&lt;/code&gt; links to serialize the resource creation.” We weren&amp;rsquo;t
super-happy with that answer, and there&amp;rsquo;s a
&lt;a href=&#34;https://github.com/aws-cloudformation/aws-cloudformation-coverage-roadmap/issues/573&#34;&gt;CloudFormation roadmap item&lt;/a&gt;
to fix it, but we needed something in the interim, and it worked &amp;hellip; mostly.&lt;/p&gt;
&lt;p&gt;On this day a deployment had failed again, and after going after the usual
suspects and making sure that the resources were properly serialized, I got
&lt;em&gt;very irate&lt;/em&gt;. I was halfway through writing a support ticket:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We are still encountering Route 53 rate limits and our CloudFormation stack
deployment / updates are intermittently failing, sometimes after only two
resources are created. There are no Route 53 API calls being made by our
applications, only through CloudFormation.&lt;/p&gt;
&lt;p&gt;We are &lt;em&gt;quite frustrated&lt;/em&gt; at this point and would like to request a session
with a solution architect to help us understand how we should be doing this
and&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;and I paused.&lt;/p&gt;
&lt;h2&gt;Check yourself before you wreck yourself&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;“There is no point in using the word &amp;lsquo;impossible&amp;rsquo; to describe something that
has clearly happened.” — Douglas Adams, &lt;em&gt;Dirk Gently&amp;rsquo;s Holistic Detective
Agency&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;“Is it true that there are no other Route 53 API calls being made?” I asked
myself. A quick jaunt into &lt;a href=&#34;https://aws.amazon.com/cloudtrail/&#34;&gt;AWS CloudTrail&lt;/a&gt;
told me the answer, and also opened a gaping pit beneath my feet.&lt;/p&gt;
&lt;p&gt;There were &lt;strong&gt;436&lt;/strong&gt; Route 53 API calls made in the 2-minute period surrounding our
CloudFormation failure. If you do the math, that&amp;rsquo;s 3.6 requests per second on
average, so it&amp;rsquo;s not at all surprising that we maybe tipped over the limit of 5
at some point in there.&lt;/p&gt;
&lt;p&gt;“But where are these coming from?” was my immediate question, and it was
immediately answered.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;images/cloudtrail-route53.png&#34; alt=&#34;CloudTrail console showing Route 53 calls from an EC2 instance&#34; title=&#34;CloudTrail console showing Route 53 calls from an EC2 instance&#34;&gt;&lt;/p&gt;
&lt;p&gt;Virtually all of these requests were being made by an EC2 instance that was part
of an &lt;a href=&#34;https://aws.amazon.com/eks/&#34;&gt;Amazon Elastic Kubernetes Service (EKS)&lt;/a&gt;
cluster.&lt;/p&gt;
&lt;p&gt;Talking through this with some other folks, I learned that they&amp;rsquo;d configured
&lt;a href=&#34;https://github.com/kubernetes-sigs/external-dns&#34;&gt;external-dns&lt;/a&gt; on the cluster,
and that this behaviour is
&lt;a href=&#34;https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/aws.md#throttling&#34;&gt;actually documented&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The production account doesn&amp;rsquo;t have the EKS cluster, so it&amp;rsquo;s not overwhelmed
with Route 53 API calls, which explains why deployment never failed there.&lt;/p&gt;
&lt;h2&gt;Buh-bye&lt;/h2&gt;&lt;p&gt;I wanted to decommission the cluster immediately, but unfortunately some teams
still need it, so I wasn&amp;rsquo;t able to.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;external-dns&lt;/code&gt; documentation says that one workaround for the issue of the
controller eating your entire Route 53 request budget is to extend the interval
that the controller reconciliation loop runs at. In this particular cluster, the
reconciliation loop was running &lt;em&gt;every minute&lt;/em&gt; (the default!) to reconcile a set
of records that change approximately never. I followed the instructions, set the
&lt;code&gt;interval&lt;/code&gt; to a week, and settled in to see what happened.&lt;/p&gt;
&lt;p&gt;The first thing I noticed was that immediately the calls to Route 53 stopped. Not
surprising, but great to see the confirmation. Several hours after the change,
there were still no calls from the previously-misbehaving cluster.&lt;/p&gt;
&lt;p&gt;All is well now, and I get to put away my detective hat for another day.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“The light works,” he said, indicating the window, “the gravity works,” he
said, dropping a pencil on the floor. “Anything else we have to take our
chances with.” — Douglas Adams, &lt;em&gt;Dirk Gently&amp;rsquo;s Holistic Detective Agency&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;What I learned&lt;/h2&gt;&lt;p&gt;First, CloudTrail was instrumental here. I&amp;rsquo;m still a novice, but I&amp;rsquo;m learning
how powerful a tool it is. Once I knew what to look for, it was &lt;em&gt;immediately&lt;/em&gt;
obvious what the source of the rate limiting was. The events in CloudTrail
identified the EC2 instance and even made it clear that the source of the
requests was in an EKS cluster.&lt;/p&gt;
&lt;p&gt;Second, I was reminded that Kubernetes is not a get-out-of-ops-free card. There
is a lot of expertise involved in running Kubernetes well, even when you&amp;rsquo;re
using a managed service like EKS. I knew this before, but this was an example of
a cluster I didn&amp;rsquo;t even know existed (don&amp;rsquo;t worry: someone more responsible did
know!) having side effects way outside its scope.&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
