<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Eoin Shanaghy</title>
    <description>The latest articles on DEV Community by Eoin Shanaghy (@eoinsha).</description>
    <link>https://dev.to/eoinsha</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F116529%2Fbee08367-6062-4b28-9a45-765d121b940b.jpeg</url>
      <title>DEV Community: Eoin Shanaghy</title>
      <link>https://dev.to/eoinsha</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eoinsha"/>
    <language>en</language>
    <item>
      <title>How to use EventBridge as a Cross-Account Event Backbone</title>
      <dc:creator>Eoin Shanaghy</dc:creator>
      <pubDate>Tue, 24 May 2022 16:01:48 +0000</pubDate>
      <link>https://dev.to/eoinsha/how-to-use-eventbridge-as-a-cross-account-event-backbone-5fik</link>
      <guid>https://dev.to/eoinsha/how-to-use-eventbridge-as-a-cross-account-event-backbone-5fik</guid>
      <description>&lt;p&gt;Two of the emerging best practices in modern AWS applications are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use a separate AWS account per application&lt;/li&gt;
&lt;li&gt;Decouple communication between systems using events instead of point-to-point, synchronous communication.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This post will show how EventBridge can provide an ideal event backbone for applications in multiple AWS accounts, achieving both of these best practices with minimal complexity. To illustrate these concepts, we will use the example of an eCommerce application. We will simplify it and imagine just two services, each running in a separate AWS account. Simple applications do not need to have all components separated in their own accounts like this, but as the product grows and each service becomes sufficiently complex, with different teams involved, it becomes necessary to use dedicated accounts.&lt;/p&gt;

&lt;p&gt;Our eCommerce application has two services - the Order Service and the Delivery Service. There is a logical link between orders and the delivery of products being fulfilled, but these are regarded as separate services that should not be tightly coupled.&lt;/p&gt;

&lt;p&gt;In this multi-account setup, we will also dedicate an account for the event backbone itself. This gives us three accounts, with the potential to add more as we grow the number of services or applications.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8eab5jee6gieve4rc67y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8eab5jee6gieve4rc67y.png" alt="A simple diagram showing three AWS accounts for order service, delivery service and the event backbone"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before we dive into the solution, let's talk about why account separation and event-driven communication are regarded as best practices. We want to avoid the mistake of accepting 'best practices' without understanding the reasoning!&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate AWS accounts
&lt;/h2&gt;

&lt;p&gt;An AWS account is the ultimate boundary for permissions and quotas. IAM is used for fine-grained access control and is one of the most impressive and fundamental AWS services there is. There is no escaping the fact that enforcement of &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege" rel="noopener noreferrer"&gt;the principle of least privilege&lt;/a&gt; takes a lot of time and a solid understanding. If you mix applications, environments or teams in a single AWS account, you rely on permissions boundaries, complex IAM policies and strict policy change processes to avoid impacting others' resources. This will result in a significant overhead in engineering time as well as the risk of human error. By providing each application and environment with a separate account, you can rely more on the account boundary to protect against the workloads and actions of others. You can still enforce minimal privilege but in a much more agile way, by detecting and continually improving policies instead of strict enforcement up front. For development accounts, this is a big productivity boost.&lt;/p&gt;

&lt;p&gt;AWS quotas put limits on the number of requests you can make or resources you can utilise by default in an account. You have a mix of soft quotas, which can be raised if you ask, and hard quotas, which, more often than not, cannot budge. There may be some exceptions here if you have specific needs and engage directly with your AWS account manager. Once you mix workloads in an AWS account, those workloads share quotas. This can have the effect of limiting the scalability of each application or the more drastic result where one application can deny the other from operating because it is simply reaching a quota. Take AWS Lambda as an example. If your region has a default quota of 1,000 concurrent executions and one application reaches this, it will throttle any other workload from using Lambda. Using separate accounts removes the risk of this cross-application side-effect.&lt;/p&gt;

&lt;p&gt;The drawback of using separate accounts for everything will come with the account management overhead. If you end up with more than a handful of accounts (100's or 1000's of accounts is not uncommon!), account automation is a must. I recommend a solution based on &lt;a href="https://github.com/org-formation/org-formation-cli" rel="noopener noreferrer"&gt;org-formation&lt;/a&gt; for this. To read more about the benefits of a multi-account strategy, check out &lt;a href="https://cloudash.dev/blog/aws-multi-account-benefits" rel="noopener noreferrer"&gt;this post&lt;/a&gt; by Tomasz Łakomy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Event based communication
&lt;/h2&gt;

&lt;p&gt;The case for event-based communication is less clear cut  and more a case of nuanced trade-offs. With synchronous communication, an application will send a request to a known address and wait to receive a response. The advantage as a developer or architect is that you know what service you are addressing and you can follow the flow of logic and data clearly. There are many disadvantages, however. &lt;/p&gt;

&lt;p&gt;Synchronous communication means you have to know the address of the application or service that is processing your request. This is called &lt;em&gt;location coupling&lt;/em&gt;, and it means you have to have a mechanism to update the address in all clients if it changes. Service discovery solutions are used to solve this but not without their own complexity. With synchronous communication, you also get &lt;em&gt;temporal coupling&lt;/em&gt;, since the action of making a request is bound in time to the processing of that request. Temporal coupling has a greater impact, since it results in failures when the request processor is not online, not reachable, or just busy with other requests. Temporal coupling means that the receiver must scale exactly in line with the request volume.&lt;/p&gt;

&lt;p&gt;Asynchronous (event-driven) communication can remove these forms of coupling. Instead of sending events or requests to a known receiver, you send events to a bus, queue or topic. The receiver can scale independently and even delay processing. Message durability provided by the bus or queue can ensure that events don't get lost or undelivered, even if the event processor is temporarily offline.&lt;/p&gt;

&lt;p&gt;That said, asynchronous communication is harder to reason about. It becomes more difficult to follow the flow of data and logic. I would say it requires a mindset change for engineers and also means you need better observability tooling to capture event flows.&lt;/p&gt;

&lt;p&gt;While event-driven seems to be the more architecturally sound approach, there is still a case for going synchronous. We have all become used to integrating SaaS platforms using APIs and webhooks. This is essentially all synchronous communication. It has become a de-facto standard for SaaS product integration because it is easy for the consumer to understand, get started and troubleshoot. It shifts the burden to the SaaS provider who now has to ensure the API is always available, robust and scalable.&lt;/p&gt;

&lt;p&gt;Even though I'm a big fan of event-driven, I still think there's a valid case for good, well-documented, synchronous APIs where simplicity and clarity are more important than decoupled perfection. A well-balanced enterprise architecture might combine a small number of REST APIs at high-level boundaries across distinct applications with asynchronous messages for callbacks and updates as well as lower-level, inter-service communication.&lt;/p&gt;

&lt;p&gt;We have covered the reasons for these two underlying best practices. Let's now dive in to our cross-account event driven solution using EventBridge!&lt;/p&gt;

&lt;h2&gt;
  
  
  What is an Event Backbone?
&lt;/h2&gt;

&lt;p&gt;An Event Backbone is simply an event communication mechanism that serves multiple applications. The concept evolved from the idea of an Enterprise Service Bus (ESB) in the time of service-oriented architecture (SOA). ESBs have a broader set of capabilities however, including more complex routing, extensive transformation capabilities and even business rule execution. An event backbone is fundamentally simpler, focusing on scalable, reliable event transport and ensuring that business logic belongs to the services and not the bus.&lt;/p&gt;

&lt;p&gt;The term is &lt;a href="https://kgb1001001.github.io/cloudadoptionpatterns/Event-Based-Architecture/Event-Backbone/" rel="noopener noreferrer"&gt;commonly used&lt;/a&gt; for such systems based on Apache Kafka, since Kafka was one of the first technologies that enabled event backbones for microservice communication with massive scale and performance. Since Kafka was first released over a decade ago, cloud managed services have evolved to the degree where you don't need to Kafka to have a scalable, reliable event backbone. Amazon EventBridge is the most obvious example, since it has managed to pull off the amazing feat of having a large feature set and massive scalability while remaining one of the simplest cloud services there is.&lt;/p&gt;

&lt;p&gt;If you are a Kafka fan, there is effort being put in by AWS in reducing the complexity with the managed MSK, including the MSK Serverless version. I would compare MSK to EventBridge in the same way I would compare EKS to Fargate or Lambda. You get a lot more control and configurability but even with the AWS managed service, you still have plenty of complexity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx2jhf8xkqo29yoeb7x6l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx2jhf8xkqo29yoeb7x6l.png" alt="The three accounts showing EventBridge as the selected technology for the event backbone"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The beauty of something like EventBridge is that the investment is so low. If your needs evolve, you can adapt and use alternative options for specific cases. You are not stuck with it because of a large investment in infrastructure or training. If you need durability, add SQS! If you need lower latency, ordered streams, you can add Kinesis! It's possible to build a event backbone on Kinesis or SNS/SQS but EventBridge is still the best place to start, integrates with more services and has really good cross-account support.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cross-account EventBridge
&lt;/h2&gt;

&lt;p&gt;We already mentioned that EventBridge has good support for cross-account scenarios. With EventBridge, you can create a Rule with any other EventBridge &lt;em&gt;bus&lt;/em&gt; as a target. This bus can be in a different account.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feg5ec8dd6kky1pdwwqxu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feg5ec8dd6kky1pdwwqxu.png" alt="Cross account EventBridge"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For this to work, the target bus should have a policy that allows the source account to send events to it.&lt;/p&gt;

&lt;p&gt;Now, let's imagine this idea at a larger scale, where we have multiple accounts, each with their own applications or services. Where does each application need to send events? There are a few options here. If you want to explore all the options, take a look at &lt;a href="https://youtu.be/Wk0FoXTUEjo" rel="noopener noreferrer"&gt;this great talk from re:Invent 2020 on Building event-driven architectures&lt;/a&gt;.  In this article, I'll focus on my preferred option, referred to in that video as the "single-bus*, multi account-pattern". There are are in fact multiple buses, but a central bus in a dedicated account is used to route messages to multiple accounts, each with their own local bus.&lt;/p&gt;

&lt;p&gt;The important characteristics of this architecture are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every service send events to a global bus, a dedicated bus in a separate account&lt;/li&gt;
&lt;li&gt;Every service receives events from a local bus in its own account&lt;/li&gt;
&lt;li&gt;The global bus has rules to route all events to every local bus except the local bus of the event sender. This could be classified as a &lt;em&gt;fan-out pattern&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; Every account comes with a &lt;code&gt;default&lt;/code&gt; EventBridge bus, so it's not mandatory to create custom buses. We do so so we can control permissions at a bus level, and to separate these custom events completely from the AWS service events that are sent to the default bus.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why do we need a global bus &lt;em&gt;and&lt;/em&gt; local buses?
&lt;/h2&gt;

&lt;p&gt;You might ask why services can't send events to their local bus instead of the global bus. Since each service receives events from their local bus, should it not publish there too? Apart from adding an additional layer, it's simply not possible with EventBridge.  You cannot have events transitively routed to a third bus (&lt;code&gt;local -&amp;gt; global -&amp;gt; local&lt;/code&gt;). Only one cross-account target is allowed in the chain (&lt;code&gt;global -&amp;gt; local&lt;/code&gt;). This is covered in the &lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-cross-account.html" rel="noopener noreferrer"&gt;EventBridge documentation&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“If a receiver account sets up a rule that sends events received from a sender account on to a third account, these events are not sent to the third account.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You might also wonder why we can't get rid of the local buses altogether and just have the global bus, letting all services send and receive events to and from it. There are two main reasons against this approach. To receive messages from a bus in another account, you would have to create rules in another account's bus for every pattern you want to match. This is not a clean separation of concerns. Secondly, even if you did create rules in the global bus, you cannot invoke any cross-account target with an EventBridge rule, say, a Lambda function, you can only target another EventBridge bus in another account.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cross-account EventBridge backbone example
&lt;/h2&gt;

&lt;p&gt;Let's return to our eCommerce use case. Our application has two services - the Order Service and the Delivery Service. In a real world scenario, these systems have sufficient features and logic, so it's warranted to separate them in different account. There is a logical link between orders and the delivery of products being fulfilled, but these are regarded as separate services that should not be tightly coupled.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When orders are created, we want the delivery service to be notified.&lt;/li&gt;
&lt;li&gt;When deliveries are sent, we want to update orders accordingly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We have two services and three accounts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Order Service account, which has the order service logic and its own "local" EventBridge bus&lt;/li&gt;
&lt;li&gt;The Delivery Service account, which has the delivery service logic and also has its own "local" EventBridge bus&lt;/li&gt;
&lt;li&gt;The Global Bus account, which only has a "global" EventBridge bus. This is used to route messages to other accounts.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The flow of events for the order creation use case is as follows.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An HTTP POST API is used to create an order. The backing Lambda function generates an order ID and sends an &lt;code&gt;Order.Created&lt;/code&gt; event to the global bus.&lt;/li&gt;
&lt;li&gt;The delivery service picks up the &lt;code&gt;Order.Created&lt;/code&gt; event from its local bus, processes the order , and sends a &lt;code&gt;Delivery.Updated&lt;/code&gt; event including all the important delivery details to the global bus.&lt;/li&gt;
&lt;li&gt;The order service picks up the &lt;code&gt;Delivery.Updated&lt;/code&gt; event from its local bus, and finally sends an &lt;code&gt;Order.Updated&lt;/code&gt; event to the global bus.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F08yys99sjzmwe2ij6zn3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F08yys99sjzmwe2ij6zn3.png" alt="Distributing cross-account events using a Global Bus"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Example Source&lt;/strong&gt;&lt;br&gt;
 The full source code with documentation for this is available on &lt;a href="https://github.com/fourTheorem/cross-account-eventbridge/" rel="noopener noreferrer"&gt;github.com/fourTheorem/cross-account-eventbridge&lt;/a&gt;. It include a CDK pipeline for deployment of all resource to the three accounts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Global bus event rules
&lt;/h2&gt;

&lt;p&gt;Events cannot be sent anywhere in EventBridge without a rule. Rules can be based on a schedule or an event pattern. For our backbone, we need to create pattern-based routing rules in the global bus. We create a single rule for each service account:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;eventPattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;account&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;anything-but'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12345789012&lt;/span&gt;  &lt;span class="c1"&gt;# Anything but the source account&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This rule will route all events to every service account except the one that sent the message.&lt;/p&gt;

&lt;h2&gt;
  
  
  Logging events for debugging and auditing
&lt;/h2&gt;

&lt;p&gt;One of the challenges people encounter with EventBridge when using it for the first time relates to observability. It can be difficult to understand which events are flowing through a bus and see their contents so that you can troubleshoot delivery failures. A simple way to address this is to create a rule to capture and log all events to CloudWatch Logs. How do you capture all events? EventBridge rules require you to have at least one condition in your filter, but a prefix match expression with an empty string will capture all events from any source:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="na"&gt;eventPattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;br&gt;
  &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;br&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;prefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Event structure, schemas and validation&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Parts of this section were taken from &lt;a href="https://www.fourtheorem.com/blog/what-can-you-do-with-eventbridge" rel="noopener noreferrer"&gt;What can you do with EventBridge? (fourTheorem blog)&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;With EventBridge, you have no obligation to provide a schema for your events but there is support if you wish to do so. Without a schema, you can be flexible in how you evolve the event structure but it can also lead to confusion for other developers who are trying to consume your events or even publish them in a way that is consistent with the organisation. This is even more important when we are talking about an event backbone, since you can assume that producers and consumers are in different teams or departments.&lt;/p&gt;

&lt;p&gt;Start with a clear set of principles for the structure of these events and how to manage changes in event structure over time. With EventBridge, each event can contain the following properties:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Property&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Purpose&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Source&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;This defines the message origin. For AWS services, this might be something like &lt;code&gt;aws.config&lt;/code&gt; but for your custom events you could specify the application or service name, like &lt;code&gt;order.service&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;DetailType&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;This usually denotes the event type, for example, &lt;code&gt;Order.Created&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;EventBusName&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;The name of your custom bus or default.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Detail&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;This is the JSON-encoded payload of the message and it can contain relevant details about your event (e.g. order ID, customer name, product name, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The event structure can represent a contract between the producer and the consumer. Once a consumer strictly relies on fields being available in the &lt;code&gt;Detail&lt;/code&gt; payload, you have semantic coupling between producer and consumer. There is a balance to be struck between including as much detail as possible in the message and reducing this semantic coupling. Too little data means that consumers will likely have to go and fetch extra data from the originating system.&lt;/p&gt;

&lt;p&gt;Too much data for your events means consumers come to rely on all that data in a certain structure, semantically coupling it to the producer and making it hard to change the structure later. A reasonable approach here is to start with less data and add properties incrementally as the need arises.&lt;/p&gt;

&lt;p&gt;Some basic principles for event structure include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do not rely on the &lt;code&gt;Source&lt;/code&gt; for pattern matching as a consumer. A consumer should not need to be concerned with where the event came from.&lt;/li&gt;
&lt;li&gt;Enforce a consistent structure for &lt;code&gt;DetailType&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Separate the &lt;code&gt;Detail&lt;/code&gt; into two child properties, &lt;code&gt;metadata&lt;/code&gt; and &lt;code&gt;data&lt;/code&gt;. This allows you to add additional metadata without mixing it in with the event payload itself. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While it is optional to &lt;em&gt;enforce&lt;/em&gt; schema validation, it is worthwhile if you are serious about EventBridge adoption at scale. EventBridge allows you to publish schemas in a registry for other teams and developers. This feature supports typed code binding generation too. If you do not want to create and upload the schema, you have the option to let EventBridge discover the schemas for you from events passing through the bus.&lt;/p&gt;

&lt;p&gt;If stricter schema enforcement is something you want to do, I'd recommend looking at the approach taken by PostNL as described by &lt;a href="https://twitter.com/donkersgood/" rel="noopener noreferrer"&gt;Luc van Donkersgoed&lt;/a&gt; in &lt;a href="https://www.youtube.com/watch?v=nyoMF1AEI7g" rel="noopener noreferrer"&gt;this insightful talk&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For great ideas on structuring event payloads, take a read of &lt;a href="https://medium.com/lego-engineering/the-power-of-amazon-eventbridge-is-in-its-detail-92c07ddcaa40" rel="noopener noreferrer"&gt;Sheen Brisals' post on the Lego Engineering blog&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading and viewing
&lt;/h2&gt;

&lt;p&gt;We have covered the fundamental building blocks for a cross-account backbone with EventBridge. There is plenty more you can do with EventBridge, like using archives and event replaying, as well as integrating it into other AWS services. For a small amount of upfront effort and minimal ongoing maintenance, you can achieve a very flexible and scalable event bus for many applications across accounts.&lt;/p&gt;

&lt;p&gt;If you want to read more on EventBridge, &lt;a href="https://twitter.com/loige" rel="noopener noreferrer"&gt;Luciano Mammino&lt;/a&gt; and I have written an article and have a YouTube video and podcast episode to accompany it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://www.fourtheorem.com/blog/what-can-you-do-with-eventbridge" rel="noopener noreferrer"&gt;What can you do with EventBridge? (fourtheorem.com blog)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.fourtheorem.com/blog/what-do-you-need-to-know-about-sns]" rel="noopener noreferrer"&gt;What do you need to know about SNS? (fourtheorem.com blog)&lt;/a&gt; - includes a comparison of SNS and EventBridge&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/UjIE5qp-v8w" rel="noopener noreferrer"&gt;AWS Bites Episode 23: What’s the big deal with EventBridge? - YouTube&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fourTheorem/cross-account-eventbridge" rel="noopener noreferrer"&gt;GitHub source code for this example&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We also have a full series of podcast episodes covering all the main AWS event services, including a deep dive on Kafka, so check out the playlist &lt;a href="https://www.youtube.com/watch?v=CG7uhkKftoY&amp;amp;list=PLAWXFhe0N1vLHkGO1ZIWW_SZpturHBiE_" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I'd like to say a big thank you to &lt;a href="https://github.com/direnakkocdemir" rel="noopener noreferrer"&gt;Diren Akkoc Demir&lt;/a&gt;, &lt;a href="https://twitter.com/micktwomey" rel="noopener noreferrer"&gt;Michael Twomey&lt;/a&gt; and &lt;a href="https://twitter.com/loige" rel="noopener noreferrer"&gt;Luciano Mammino&lt;/a&gt; for their reviews and excellent feedback. 🙏 &lt;/p&gt;




&lt;p&gt;Eoin Shanaghy works at &lt;a href="https://fourTheorem.com" rel="noopener noreferrer"&gt;fourTheorem&lt;/a&gt;, architecting and building systems like this with a great team of colleagues. Please &lt;a href="https://twitter.com/eoins" rel="noopener noreferrer"&gt;get in touch&lt;/a&gt; if you have any feedback or questions!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>eventbridge</category>
      <category>crossaccount</category>
      <category>kafka</category>
    </item>
    <item>
      <title>3 Ways to Read SSM Parameters </title>
      <dc:creator>Eoin Shanaghy</dc:creator>
      <pubDate>Tue, 23 Nov 2021 22:59:30 +0000</pubDate>
      <link>https://dev.to/eoinsha/3-ways-to-read-ssm-parameters-4555</link>
      <guid>https://dev.to/eoinsha/3-ways-to-read-ssm-parameters-4555</guid>
      <description>&lt;p&gt;AWS Systems Manager Parameter Store (or SSM Parameter Store) is a convenient way to store hierarchical parameters in AWS. You can use it for any configuration values, including secure values like passwords or API keys. It integrates well with other AWS services too.&lt;/p&gt;

&lt;p&gt;When it comes to reading parameters from SSM, there are a few available options.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read at runtime&lt;/li&gt;
&lt;li&gt;Read at build time&lt;/li&gt;
&lt;li&gt;Read at deploy time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9o8zb65gkoc50lk90n82.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9o8zb65gkoc50lk90n82.png" alt="Build time, deploy time and run time"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When thinking about these options it's important to consider:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When can you be sure the parameter will be available?&lt;/li&gt;
&lt;li&gt;Can the parameter value change?&lt;/li&gt;
&lt;li&gt;Is the parameter a secret that should be protected from data leaks?&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Reading at Runtime
&lt;/h2&gt;

&lt;p&gt;Reading at runtime is the safest way to both protect a secret parameter and deal with parameters that might not be available at build or deploy time. It also helps to deal with parameters whose values change frequently. Parameters can be read using the AWS SDK for your language of choice(e.g., &lt;a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/SSM.html" rel="noopener noreferrer"&gt;JS&lt;/a&gt;, &lt;a href="https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ssm.html#SSM.Client.get_parameters" rel="noopener noreferrer"&gt;Python&lt;/a&gt;) or the &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_GetParameters.html" rel="noopener noreferrer"&gt;SSM API&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;It's a good idea to think about how often you will make the call to read parameter values. Parameter Store has a low &lt;a href="https://docs.aws.amazon.com/general/latest/gr/ssm.html" rel="noopener noreferrer"&gt;throughput quota&lt;/a&gt; by default (40 per second) and reading the value every time you need it will also add latency. The typical solution here is to load at startup time. For example, in the context of a Lambda function, the parameter read could be outside the handler function, executed when your code is bootstrapped.&lt;/p&gt;

&lt;p&gt;If you are worried about keeping hold of stale values at runtime, you can re-read after a reasonable timeout. &lt;a href="https://pypi.org/project/ssm-cache/" rel="noopener noreferrer"&gt;ssm-cache&lt;/a&gt; in Python and &lt;a href="https://www.npmjs.com/package/@middy/ssm" rel="noopener noreferrer"&gt;Middy SSM&lt;/a&gt; for Node.js Lambda functions are two of the open source libraries that make this easy.&lt;/p&gt;

&lt;p&gt;The runtime reading approach keeps your secret parameters in memory only, so you can be reassured that, as long as you don't write these values anywhere else, the risk of exfiltrating secrets is lower than with using files or environment variables containing plaintext secrets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading at build time
&lt;/h2&gt;

&lt;p&gt;There is a subtle difference between this option and the next one (reading at deploy time). Let's assume that you are using &lt;em&gt;Infrastructure as Code&lt;/em&gt; and you are packaging your code and infrastructure together at build time. This packaging process happens before you deploy to AWS.&lt;/p&gt;

&lt;p&gt;Reading at build time uses the SDK or API to load a parameter value and include it &lt;em&gt;somewhere&lt;/em&gt; in your code. This could be a generated &lt;code&gt;.env&lt;/code&gt; file or a value set in a Lambda function environment variable.&lt;/p&gt;

&lt;p&gt;With the Serverless Framework, this can look like the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="nx"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="nx"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handleGetItem&lt;/span&gt;
    &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;ssm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="sr"&gt;/path/&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is really convenient because the Serverless Framework takes care of retrieving the value with little code (&lt;a href="https://www.serverless.com/framework/docs/providers/aws/guide/variables#reference-variables-using-the-ssm-parameter-store" rel="noopener noreferrer"&gt;docs&lt;/a&gt;). The downside comes with secret parameters. Storing a protected secret value in code or in an environment variable leaves it open to multiple types of attack.&lt;/p&gt;

&lt;p&gt;The CloudFormation template for this example shows how the SSM parameter value is stored in JSON in the clear. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

   &lt;/span&gt;&lt;span class="nl"&gt;"Environment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"Variables"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"SECRET_CODE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shhhhh!s3cr3t"&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The environment variable can also be seen after we deploy in the Lambda function's configuration. Once it's available as an environment variable, it can easily be discovered and exfiltrated by any malicious code running in the function or actor with access to the function's configuration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fese03hhkqqd4h332yf3h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fese03hhkqqd4h332yf3h.png" alt="Parameter value in cleartext in the Lambda console"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Reading at build time also requires you to ensure that your build environment has the right credentials to read the parameter and that it exists, even if you are only building without deploying.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading at Deploy Time
&lt;/h2&gt;

&lt;p&gt;If you are using AWS CloudFormation, or any of the great tools built on top of it, like &lt;a href="https://github.com/aws/aws-cdk" rel="noopener noreferrer"&gt;CDK&lt;/a&gt;, &lt;a href="https://www.serverless.com/framework" rel="noopener noreferrer"&gt;Serverless Framework&lt;/a&gt; or &lt;a href="https://github.com/aws/serverless-application-model" rel="noopener noreferrer"&gt;SAM&lt;/a&gt;, you have two options to load and use SSM Parameters at &lt;em&gt;deploy time&lt;/em&gt;. This means that responsibility for reading the parameters is deferred until after you build and when CloudFormation performs cloud-side deployment of your full stack. The advantages are that you do not need privileges to read the parameters at build time and you don't have to ensure the values exist until it comes to deployment in any given environment.&lt;/p&gt;

&lt;p&gt;The first option is using a special syntax for Cloudformation Parameters. In raw CloudFormation YAML, the declaration looks something like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;UserPoolArnParameter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::SSM::Parameter::Value&amp;lt;String&amp;gt;&lt;/span&gt;
      &lt;span class="na"&gt;Default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/dev/user-service/user-pool-arn&lt;/span&gt;

  &lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;CognitoAuthorizer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::ApiGateway::Authorizer&lt;/span&gt;
      &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s"&gt;...&lt;/span&gt;
        &lt;span class="s"&gt;ProviderARNs&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;UserPoolArnParameter&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;A complete example is available &lt;a href="https://github.com/fourTheorem/slic-starter/blob/fe1975df71e28b623f6ab5945069500833705c8a/checklist-service/serverless.yml#L72" rel="noopener noreferrer"&gt;here&lt;/a&gt;. This syntax does not support &lt;code&gt;SecureString&lt;/code&gt; types. It is documented along with other &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html" rel="noopener noreferrer"&gt;CloudFormation Parameter types&lt;/a&gt; here. &lt;/p&gt;

&lt;p&gt;The second deploy-time option in CloudFormation uses a feature called &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html" rel="noopener noreferrer"&gt;dynamic parameters&lt;/a&gt; and this option &lt;em&gt;does&lt;/em&gt; support &lt;code&gt;SecureString&lt;/code&gt; types for a fixed set of services. CloudFormation dynamic parameters also support specific parameter versions. It looks like the following.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{{resolve:ssm:/path/to/parameter:VERSION}}&lt;/code&gt;&lt;br&gt;
or&lt;br&gt;
&lt;code&gt;{{resolve:ssm:/path/to/secure-parameter:VERSION}}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;:VERSION&lt;/code&gt; suffix is optional in both cases, with the latest version being automatically selected by default.&lt;/p&gt;

&lt;p&gt;While these options may be more verbose than the Serverless Framework's &lt;code&gt;ssm:&lt;/code&gt; variable syntax, CloudFormation syntax tends to be more stable, ensures deploy-time validation, and fits better with the cloud-side deployment model of CloudFormation stacks.&lt;/p&gt;

&lt;p&gt;AWS CDK provides &lt;a href="https://docs.aws.amazon.com/cdk/latest/guide/get_ssm_value.html" rel="noopener noreferrer"&gt;convenient functions&lt;/a&gt; for SSM parameters that dynamically generate either of these CloudFormation options for you. It might not be obvious that these are deploy-time lookups rather than at CDK build time, but you can check the synthesized CDK output to see what is happening under the hood.&lt;/p&gt;

&lt;h2&gt;
  
  
  Secrets Manager
&lt;/h2&gt;

&lt;p&gt;The focus here is on SSM Parameter Parameters as opposed to Secrets Manager. The principles all apply but with Secrets Manager, you are more likely to be dealing with sensitive values that are rotated regularly so the emphasis on secure, late binding of secret values is event more important! CloudFormation dynamic values support Secrets Manager as well but there is no support in CloudFormation Parameters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The rule of thumb for reading SSM parameters is generally, read them as late as possible. This reduces the likelihood of reading stale values and of having stored secrets that can be compromised. If you need to look up the value before running your code, try one of the CloudFormation methods. Reading at build time should be avoided where possible.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>ssm</category>
      <category>secure</category>
    </item>
    <item>
      <title>Container Image Support in AWS Lambda Deep Dive</title>
      <dc:creator>Eoin Shanaghy</dc:creator>
      <pubDate>Tue, 01 Dec 2020 18:28:22 +0000</pubDate>
      <link>https://dev.to/eoinsha/container-image-support-in-aws-lambda-deep-dive-2keh</link>
      <guid>https://dev.to/eoinsha/container-image-support-in-aws-lambda-deep-dive-2keh</guid>
      <description>&lt;p&gt;&lt;em&gt;AWS today announced support for &lt;a href="https://aws.amazon.com/blogs/aws/new-for-aws-lambda-container-image-support/" rel="noopener noreferrer"&gt;packaging Lambda functions as container images&lt;/a&gt;!&lt;/em&gt; This post takes a look under the hood of this new feature from my experience during the beta period.&lt;/p&gt;

&lt;p&gt;Lambda functions started to look a bit more like container images when Lambda Layers and Custom Runtimes were announced in 2018, albeit with a very different developer experience. Today, the arrival of Container Image support for Lambda makes it possible to use actual Docker/OCI container images up to 10GB in size as the code and runtime for a Lambda function. &lt;/p&gt;

&lt;p&gt;But what about Fargate?! Wasn’t that supposed to be the serverless container service in AWS? While it might seem a bit confusing, support for Image Functions in Lambda makes sense and brings huge benefits that were probably never going to happen in the world of Fargate, ECS and EKS. Container Image deployment to Lambda enables Lambda’s incredibly rapid and responsive scaling as well as Lambda’s integrations, error handling, destinations, DLQs, queueing, throttling and metrics.&lt;/p&gt;

&lt;p&gt;Of course, Lambda functions are stateless and short-lived. That means that a lot of container workloads in their current form may still suit the Fargate/ECS/EKS camp better. Having personally spent too much time optimising Fargate task scheduling in the past, I will be glad to use Lambda for bursty batch processing workloads where the cost trade-offs work for the business. (We all want Lambda performance at Fargate Spot pricing!) Fargate will remain useful for more traditional, longer-lived workloads that don’t have a need to scale quickly to 100’s or 1000’s of containers.&lt;/p&gt;

&lt;p&gt;Let’s take a look at the experience of building and deploying Lambda functions based on container images. In this post, we’ll cover development, deployment, versioning and some of the pros and cons of using image functions.&lt;/p&gt;

&lt;h1&gt;
  
  
  Development
&lt;/h1&gt;

&lt;p&gt;Container images are typically designed to run either tasks or servers. Tasks usually take parameters in through the container’s CMD arguments and exit when complete. Servers will listen for requests and stay up until they are explicitly stopped. &lt;/p&gt;

&lt;p&gt;With Lambda functions, neither of these models applies. Instead, functions deployed from container images operate like functions packaged as ZIPs, staying alive for 30 minutes and handling events one at a time. To support this, a runtime fetches events from the Lambda environment and passes them to the handler function. Since this isn’t something that Docker/OCI containers support, images need to include the &lt;em&gt;Lambda Runtime Interface Client&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Images can be built with any tools that support The Open Container Initiative (OCI) Specification v1.0 or later or Docker Image Manifest V2 Schema 2.&lt;/p&gt;

&lt;p&gt;There are two options to pick from in order to build a container image for use with Lambda:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Take an AWS Lambda base image and add your own layers for code, modules and data&lt;/li&gt;
&lt;li&gt;Take an existing base image and add the AWS Lambda Runtime Interface Client.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fk36efs7a0tmiowmuahmg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fk36efs7a0tmiowmuahmg.png" alt="Container Image packaging options for AWS Lambda"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The AWS Lambda Runtime Interface Client is an open source native binary written in C++ with bindings for the supported runtimes (.NET Core, Go, Java, Node.js, Python and Ruby). Containers can use these flavours of the runtime client or implement the Lambda Runtime API to respond to and process events. This is the same API used in Custom Lambda Runtimes.&lt;/p&gt;

&lt;p&gt;Using the AWS-provided base images, the Dockerfile for building your image is relatively straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM public.ecr.aws/lambda/python:3.8
RUN mkdir -p /var/task
WORKDIR /var/task
COPY app/requirements.txt /var/task
RUN pip install -r requirements.txt
COPY app/ /var/task/app/
CMD [app/handler.handle_event]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To see how this works in practice, you can take a look at our example based on an AWS-provided Node.js base image. It uses Firefox, FFmpeg and Xvfb to capture a video of a webpage loading process and is &lt;a href="https://github.com/fourTheorem/lambda-image-page-record" rel="noopener noreferrer"&gt;available on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To use your own base image instead of an AWS-provided image, you will need to add the runtime interface client. This is available for Python (&lt;a href="https://pypi.org/project/awslambdaric" rel="noopener noreferrer"&gt;PyPi&lt;/a&gt;), Node.js (&lt;a href="http://npmjs.com/package/aws-lambda-ric" rel="noopener noreferrer"&gt;NPM&lt;/a&gt;), Ruby (&lt;a href="https://rubygems.org/gems/aws_lambda_ric" rel="noopener noreferrer"&gt;Gem&lt;/a&gt;), Java (&lt;a href="http://search.maven.org/artifact/com.amazonaws/aws-lambda-java-runtime-interface-client" rel="noopener noreferrer"&gt;Maven&lt;/a&gt;), Go (&lt;a href="https://github.com/aws/aws-lambda-go" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;) and .NET (&lt;a href="https://www.nuget.org/packages/Amazon.Lambda.RuntimeSupport/" rel="noopener noreferrer"&gt;NuGet&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;An example of this can be found in our &lt;a href="https://github.com/fourTheorem/lambda-image-cxr-detection" rel="noopener noreferrer"&gt;PyTorch-based machine learning example&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Deployment
&lt;/h1&gt;

&lt;p&gt;Functions deployed using container images must refer to a pre-existing repository + tag in ECR (other image repositories are not yet supported). Deployment of a function is therefore always a three-step process. This isn’t much different from functions packaged as a ZIP, where code is typically uploaded to S3 and referenced when the function is created or updated. It will however require some thought when planning your deployment. &lt;/p&gt;

&lt;p&gt;The three steps may be performed automatically by serverless packaging tools but you may also wish to deploy the ECR repository and push the container images during separate build phases. In the latter case, there is more control but also more complexity since the order is strict - you cannot deploy a function before a tagged image is in place in ECR. This is a consideration for organisations who want to leverage existing container image build and deployment pipelines and handle it separately to infrastructure deployment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F0gaoz97re68pfqekoz4z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F0gaoz97re68pfqekoz4z.png" alt="Lambda function code and resources are deployed in three stages"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is important to note that the image tag is resolved to the image digest during function deployment time so changes to a tag after deployment have no effect.&lt;/p&gt;

&lt;p&gt;When it comes to the AWS SDK, CloudFormation and the CLI, differences between image-packaged and ZIP-packaged functions is small. &lt;/p&gt;

&lt;p&gt;With boto3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lambda_client.create_function(
    FunctionName=name,
    PackageType=’Image’,
    Code={‘ImageUri’: ecr_repo_tag},..
)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that you do not have to specify the handler when creating functions packaged as container images since this can be configured in the image, most likely using the CMD configuration. The entrypoint, cmd and workdir can be specified when the function is created or updated.&lt;/p&gt;

&lt;p&gt;With the Node.js AWS SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await lambda.updateFunctionConfiguration({
  FunctionName: functionName,
  Code: {ImageUri: ecr_repo_ui},
  PackageType: ‘Image’,
  ImageConfig: {Command: [‘index.handleEvent’]}
}).promise();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this time, once you create a function, it’s not possible to migrate to a different package type. This is set to change so you will soon be able to port existing functions packaged as a ZIP to container images.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fu9dpdmn93m12es6ohy63.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fu9dpdmn93m12es6ohy63.png" alt="Lambda Function configuration contains many properties. The code configuration references a ZIP inline or on S3 or a container image defined by a tagged ECR repository"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once a function has been deployed, it may not be available for invocation just yet! When the cold-start behaviour of Lambdas in VPCs was improved last year, you might recall that functions entered a Pending state while the VPC resources were created. You can &lt;a href="https://aws.amazon.com/blogs/compute/tracking-the-state-of-lambda-functions/" rel="noopener noreferrer"&gt;check the status&lt;/a&gt; of a function and wait for it to enter the &lt;em&gt;Active&lt;/em&gt; state. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyjhimlf84mwd2bqdgmrp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyjhimlf84mwd2bqdgmrp.png" alt="A state machine for Lambda Functions as they are deployed, become inactive and fail"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These states also apply to Lambdas using container images. Functions stay in the Pending state for a few seconds while the container image is “optimised” and cached for AWS Lambda.&lt;/p&gt;

&lt;h1&gt;
  
  
  Local Development and Testing
&lt;/h1&gt;

&lt;p&gt;When it comes to testing in development, I have not yet found a better experience than that provided by Docker tooling. Once you build a container image, you have an immutable artifact that you can run in development, test and production environments. You gain confidence that the runtime is consistent across all environments. When you need to make changes, you can iterate quickly, only modifying the layers that change. I would love to have similar speed and confidence in the development workflow for Lambda functions packaged as ZIPs but that has yet to materialise.&lt;/p&gt;

&lt;p&gt;Local function testing is enabled through the AWS Lambda Runtime Interface Emulator (RIE). The emulator is included in the AWS-provided base images. To test locally, you can just run the container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -p 9000:8080 your_image
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your function can then be triggered by posting an event using a HTTP request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d @test-events/event.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I found this development and testing workflow to be simple, efficient and easy to understand.&lt;/p&gt;

&lt;h1&gt;
  
  
  Versioning
&lt;/h1&gt;

&lt;p&gt;Within Lambda, versioning support remains the same. Every time you update the code for a function, a new version is created with a single numeric version that automatically increments. When a version is published, it receives the &lt;code&gt;$LATEST&lt;/code&gt; alias. Developers can create additional aliases too. Aliases or version numbers can form part of a fully-qualified ARN to invoke specific versions as part of a deployment strategy.&lt;/p&gt;

&lt;p&gt;A common complaint with this versioning system is that it is incompatible with semantic versioning widely used today. With container images, we can at least apply semantic version tags to images and use these tags to point to the function’s code in Lambda. Again, bear in mind that if the tag is moved to point to a different image digest, the Lambda function version will still point to the digest referenced at deployment time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fv2b1ads4yi1ikekeqnhh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fv2b1ads4yi1ikekeqnhh.png" alt="Lambda function versions reference images defined by the digest resolve at deployment time"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Creating a Lambda with the Serverless Framework, AWS SAM or other tools sometimes makes it feel like the “infrastructure” (resources) and code are deployed as a single unit. In reality, deployment of the code and the resources are  separate. Using container images with version tags will allow developers experienced with container deployment to employ a familiar versioning scheme.&lt;/p&gt;

&lt;h1&gt;
  
  
  Layers vs. Layers
&lt;/h1&gt;

&lt;p&gt;Let’s take a look at how container image layers differ from AWS Lambda layers. Lambda functions packaged as ZIPs can have up to five layers. The layers themselves are explicitly defined and packaged in a similar way to function code. When layers were introduced, they enabled teams to support sharing pre-packaged libraries and modules or, in more rare cases, custom runtimes.&lt;/p&gt;

&lt;p&gt;Container image layers are very different. They are more implicitly defined and you can have as many as you need (&lt;a href="https://github.com/docker/docker.github.io/issues/8230#issuecomment-468630685" rel="noopener noreferrer"&gt;up to 127, it appears&lt;/a&gt;). Image layers are created as part of the image build and do not need to be individually deployed.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Lambda Layers&lt;/th&gt;
&lt;th&gt;Container Image Layers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Limited to 5&lt;/td&gt;
&lt;td&gt;Up to 127&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explicitly defined&lt;/td&gt;
&lt;td&gt;Implicitly defined as part of the image build&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Single number versioning (though packaging as a SAR application allows semantic versioning)&lt;/td&gt;
&lt;td&gt;One or more layers can be tagged as an image using any versioning scheme&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployed as Lambda Layer resource&lt;/td&gt;
&lt;td&gt;Pushed automatically to the image registry (e.g., ECR) when an image is pushed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The simple yet powerful relationship between a Dockerfile and the layers is one of the benefits that made Docker and containers successful in the early days. It only takes a single line to add a new layer and the layer is automatically rebuilt only if that line or any of the previous layers change. Layer caching can make the development feedback loop super fast.&lt;/p&gt;

&lt;h1&gt;
  
  
  Runtimes
&lt;/h1&gt;

&lt;p&gt;Lambdas use AWS-provided runtimes by specifying one of &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html" rel="noopener noreferrer"&gt;the supported&lt;/a&gt; Node.js, Go, Java, Python, Ruby or .NET versions in the Runtime property. Custom runtimes, packaged as layers, are also possible by specifying the runtime property value “provided”.&lt;/p&gt;

&lt;p&gt;For Container Image Lambdas, the runtime is always essentially provided by the user. AWS does however provide container base images with runtimes for Java, Python, Node.js, .NET and Go. In addition, Amazon Linux base layers for custom runtimes are available.&lt;/p&gt;

&lt;p&gt;To add Lambda support to existing container images, developers are required to include the Lambda Runtime Interface Client for the language of choice (Java, Python, Node.js, .NET, Go and Ruby). The runtime interface clients are open source implementations of the AWS Lambda Runtime API. Lambda functions of all types use this API to get events and provide results. The &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html" rel="noopener noreferrer"&gt;Runtime API&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/runtimes-context.html" rel="noopener noreferrer"&gt;AWS Lambda execution environment&lt;/a&gt; are nicely documented and worth reading to understand the context in which your function is invoked.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Flasgibb9t575bsywp7zg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Flasgibb9t575bsywp7zg.png" alt="The Runtime Interface Client talks to the Runtime API to pass events and responses to and from the handler"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  More Heavy Lifting
&lt;/h1&gt;

&lt;p&gt;You might notice that using container images gives you more control over the execution environment for a Lambda function. While there are clear benefits, something smells a bit unserverless about this! It is always worth &lt;a href="https://www.fourtheorem.com/blog/aws-compute" rel="noopener noreferrer"&gt;choosing the simplest option&lt;/a&gt;, the one that hands control and responsibility for maintenance and patches to the cloud provider. &lt;/p&gt;

&lt;p&gt;When you deploy a Lambda using a container image, you define the full code stack including OS, standard libraries, dependencies, runtime and application code. Even if you use an AWS-provided base image, you need a process to update the full image when that base image is patched. Make no mistake, this is extra heavy lifting that you should strive to avoid if possible. &lt;/p&gt;

&lt;p&gt;The premise of Lambda and serverless computing in general is to let you focus on the minimal amount of code needed to deploy features that are unique to you. The responsibility of managing and maintaining all these base layers is not something that comes for free. Container Image support may be a bridge to Lambda for many applications but it doesn’t mean it’s the final destination. All applications should aim to eliminate any of this maintenance burden over time. That means creating small, single-purpose Lambda functions using a supported runtime or, better still, looking for ways to eliminate that function altogether!&lt;/p&gt;

&lt;h1&gt;
  
  
  Issues Encountered
&lt;/h1&gt;

&lt;p&gt;There were only a few problems we encountered over the past few weeks of working with Container Image support. &lt;/p&gt;

&lt;p&gt;Firstly, we noticed that Billed Duration was calculated differently to ZIP-packaged functions. The billed duration reported in each REPORT log seemed to be the sum of Init Duration and Duration.&lt;/p&gt;

&lt;p&gt;Here is a log example for a ZIP-packaged function, where the billed duration was 300ms, even though the init duration was over 750ms.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;REPORT RequestId: 5aa36dcc-db7b-4ce6-9132-eae75a97466f 
Duration: 292.24 ms Billed Duration: 300 ms
...
Init Duration: 758.94 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For one of our Image-packaged functions, we were being billed for 5200ms, the sum of duration (502.81ms) + init duration (4638.39) rounded up to the nearest 100ms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;REPORT RequestId: 679c6323-7dff-434d-9b63-d9bdb054a4ba
Duration: 502.81 ms Billed Duration: 5200 ms
...
Init Duration: 4638.39 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I spoke to AWS and they clarified that this billing behaviour is because we are using a &lt;em&gt;custom runtime&lt;/em&gt;, not because we are using a function packaged as an image. This is the same behaviour as with custom runtimes packaged as a ZIP.&lt;/p&gt;

&lt;p&gt;The second issue we encountered was for our machine learning case. We ran into an issue with PyTorch DataSet loaders which use Python multiprocessing Queues (and thus &lt;code&gt;/dev/shm&lt;/code&gt;) to allow parallel data fetching during model execution. Lambda does not provide &lt;code&gt;/dev/shm&lt;/code&gt;. This is a known issue with all types of Lambda functions (see &lt;a href="https://aws.amazon.com/blogs/compute/parallel-processing-in-python-with-aws-lambda/" rel="noopener noreferrer"&gt;this article from AWS&lt;/a&gt; and StackOverflow &lt;a href="https://stackoverflow.com/questions/34005930/multiprocessing-semlock-is-not-implemented-when-running-on-aws-lambda" rel="noopener noreferrer"&gt;here&lt;/a&gt;). &lt;/p&gt;

&lt;p&gt;We had to work around it by setting the loader to use the main CPU rather than separate processes. With Lambda’s remit expanding to handle larger modelling workloads, particularly with multiple vCPUs, issues like this are going to become more prevalent. The traceback is included here in case it helps anyone who's searching for this problem.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ERROR] OSError: [Errno 38] Function not implemented
Traceback (most recent call last):
File "/usr/local/lib/python3.8/site-packages/aws_lambda_powertools/logging/logger.py", line 247, in decorate
return lambda_handler(event, context)
File "/var/task/handler.py", line 12, in handle_event
result = run_test(jobs)
File "/src/aws_test_densenet.py", line 89, in run_test
for data in dataloaders[split_name]:
File "/usr/local/lib/python3.8/site-packages/torch/utils/data/dataloader.py", line 279, in __iter__
return _MultiProcessingDataLoaderIter(self)
File "/usr/local/lib/python3.8/site-packages/torch/utils/data/dataloader.py", line 684, in __init__
self._worker_result_queue = multiprocessing_context.Queue()
File "/usr/local/lib/python3.8/multiprocessing/context.py", line 103, in Queue
return Queue(maxsize, ctx=self.get_context())
File "/usr/local/lib/python3.8/multiprocessing/queues.py", line 42, in __init__
self._rlock = ctx.Lock()
File "/usr/local/lib/python3.8/multiprocessing/context.py", line 68, in Lock
return Lock(ctx=self.get_context())
File "/usr/local/lib/python3.8/multiprocessing/synchronize.py", line 162, in __init__
SemLock.__init__(self, SEMAPHORE, 1, 1, ctx=ctx)
File "/usr/local/lib/python3.8/multiprocessing/synchronize.py", line 57, in __init__
sl = self._semlock = _multiprocessing.SemLock(
[ERROR] OSError: [Errno 38] Function not implemented
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;If you are comfortable with container tooling and deployment, container image support in AWS Lambda will be a big win. If, on the other hand, you are more familiar with ZIP-packaged Lambas and see no need to use container tooling, there is no change required. This feature brings options for new use cases and new types of users with different concerns and perspectives. &lt;/p&gt;

&lt;p&gt;It feels like a lot of thought has gone into providing support for container images in a way that doesn’t disrupt the AWS Lambda experience for existing developers. There’s not a lot to learn if you are familiar with containers and Lambdas as separate topics already. The addition of the open source Runtime Interface Client and Runtime Interface Emulator are really welcome as it allows you to really get to grips with what’s going on under the hood. Even for a managed service, this kind of context can be really valuable when unexpected problems arise.&lt;/p&gt;

&lt;p&gt;If you haven’t already, check out our high level overview of Container Image support for AWS Lambda &lt;a href="https://fourtheorem.com/blog/container-image-lambda" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>lambda</category>
      <category>reinvent</category>
    </item>
    <item>
      <title>How to Create Secure Internal APIs on AWS without VPCs</title>
      <dc:creator>Eoin Shanaghy</dc:creator>
      <pubDate>Fri, 05 Jul 2019 11:24:46 +0000</pubDate>
      <link>https://dev.to/eoinsha/how-to-create-secure-internal-apis-on-aws-without-vpcs-5e08</link>
      <guid>https://dev.to/eoinsha/how-to-create-secure-internal-apis-on-aws-without-vpcs-5e08</guid>
      <description>&lt;p&gt;Let's say you have a serverless deployment in AWS with external, public-facing APIs and some Lambda functions behind those APIs. As your deployment grows, you are likely to need internal communication between isolated parts of your system (microservices). This can be divided into three categories:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;em&gt;Pub/Sub-style event-driven communication.&lt;/em&gt; This is where one service publishes events about what has occurred. Other, subscribing services react to events as required according to their responsibility.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Point-to-point event-driven communication.&lt;/em&gt; This is where you have a queue as part of a service so it can receive messages to be proceeds. For example, an Email Service might receive a message containing &lt;code&gt;to&lt;/code&gt;, &lt;code&gt;from&lt;/code&gt;, &lt;code&gt;subject&lt;/code&gt; and &lt;code&gt;from&lt;/code&gt; fields and use these fields to construct and send an Email using &lt;a href="https://aws.amazon.com/ses/" rel="noopener noreferrer"&gt;SES&lt;/a&gt; or &lt;a href="https://sendgrid.com" rel="noopener noreferrer"&gt;SendGrid&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Point-to-point Synchronous communication&lt;/em&gt;. This is when a calling service needs to make a request to another service (which may be internal or external) and blocks waiting on the response. An example of this could be a request made to the User Service to find a user's email address based on a user ID found in an authorization header.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frstpbk5m6u4o7flh3agj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frstpbk5m6u4o7flh3agj.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Synchronous Calls Between Services
&lt;/h1&gt;

&lt;p&gt;This post is about the third case; point-to-point synchronous communication. In a serverless context, you could do this with a function-to-function invocation. In AWS, this would use &lt;a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Lambda.html#invoke-property" rel="noopener noreferrer"&gt;Lambda.invoke&lt;/a&gt;. The calling service could grant itself the right IAM permissions to invoke the target Lambda defined by its ARN. However, this can be seen as a &lt;a href="https://en.wikipedia.org/wiki/Code_smell" rel="noopener noreferrer"&gt;bad smell&lt;/a&gt;! It &lt;em&gt;leaks&lt;/em&gt; implementation details about the service you are calling. If you want to replace that Lambda service with something else, like an external web-service, a container-based implementation or even a managed AWS service with no Lambda code required, you have a problem. You would then have to replace the implementation and change the code in each individual calling service.&lt;/p&gt;

&lt;p&gt;Instead, a simple abstraction can be achieved by putting the implementation behind a HTTP interface. HTTP is ubiquitous, well understood and can be maintained while you change the underlying implementation without any significant drama. For our serverless, Lambda-backed implementation, this means putting them behind an &lt;a href="https://aws.amazon.com/api-gateway/" rel="noopener noreferrer"&gt;API Gateway&lt;/a&gt;, just like our external APIs.&lt;/p&gt;

&lt;h1&gt;
  
  
  Options for Securing Internal APIs
&lt;/h1&gt;

&lt;p&gt;Then comes the crucial question: how do we secure them? We only want our internal APIs to be accessed internally. The way I see it, these are your options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use VPCs&lt;/li&gt;
&lt;li&gt;Use an API Key to secure the API Gateway&lt;/li&gt;
&lt;li&gt;Use AWS IAM authorization on the API Gateway&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A VPC approach would require putting your invoking Lambdas in a VPC and defining the API Gateway as a private API with a VPC endpoint. &lt;strong&gt;Avoid VPCs for your Lambdas if at all possible!&lt;/strong&gt;. This will give you restrictions in your Lambda scalability and an additional (8-10 second) cold start time. &lt;a href="https://twitter.com/theburningmonk" rel="noopener noreferrer"&gt;Yan Cui&lt;/a&gt; covers this topic really well in the &lt;a href="https://www.manning.com/livevideo/production-ready-serverless" rel="noopener noreferrer"&gt;Production-Ready Serverless&lt;/a&gt; video course. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UPDATE 2019/10/17&lt;/strong&gt;: &lt;em&gt;Since this post was first written, AWS have reworked the ENI allocation method for VPCs so the cold start penalty is starting to go away for Lambdas in VPCs. This is yet to be rolled out to all major regions but it will change the picture significantly. I would still avoid VPCs in many cases unless necessary because of the additional complexity of managing VPCs&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;An API Key approach seem reasonable. You can generate an API Gateway API Key for the internal API service and share it to the invoking service. The key is added to the authorization header when making the request. I have done this and it works. The challenge here was sharing the API Key. In order to do this, I had to create a custom CloudFormation resource to store the API Key in SSM Parameter Store so it could be discovered by other, internal services with permissions to access the key. API Keys are intended for controlling access to external APIs with quotas so internal APIs are not really their intended purpose. If you are interested in understanding how to do this, take a look at my solution &lt;a href="https://github.com/fourTheorem/slic-starter/blob/8e29ec7e688061f094cddd2357867a63ae470416/user-service/sls-resources.yml" rel="noopener noreferrer"&gt;here&lt;/a&gt;. For any questions, comment below or &lt;a href="https://twitter.com/eoins" rel="noopener noreferrer"&gt;tweet me&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Internal API Gateway Security with IAM
&lt;/h1&gt;

&lt;p&gt;The best practice recommendation is to use IAM authorization on the APIs. If you are using the Serverless Framework, it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;services/users/get.main&lt;/span&gt;
  &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;user/{id}&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;get&lt;/span&gt;
        &lt;span class="na"&gt;cors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;authorizer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws_iam&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This will result in the &lt;code&gt;AuthorizationType&lt;/code&gt; being set to &lt;code&gt;AWS_IAM&lt;/code&gt; in the associated &lt;code&gt;ApiGateway::Method&lt;/code&gt; &lt;a href="https://docs.aws.amazon.com/apigateway/api-reference/resource/method/#authorizationType" rel="noopener noreferrer"&gt;CloudFormation resource&lt;/a&gt;. If you now try and invoke your API externally, you should get a &lt;code&gt;403 Forbidden&lt;/code&gt; response. You're API is now secured! So, how do we grant permissions to other internal services who need to call it?&lt;/p&gt;

&lt;p&gt;Now that we are using IAM authorization for the API Gateway, invoking services need to be granted IAM permissions to invoke it. For an AWS Lambda function, this means granting access to invoke the target API and to make requests with the relevant HTTP verbs (GET, POST, PUT, PATCH, etc.)&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
  &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;execute-api:Invoke&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;execute-api:GET&lt;/span&gt;
  &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;arn:aws:execute-api:#{AWS::Region}:#{AWS::AccountId}:*/${self:provider.stage}/*/user/*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This IAM Policy snippet grants access to a Lambda function to invoke the GET methods for any API Gateway in the same account with &lt;code&gt;/user/&lt;/code&gt; in its path. The first wildcard (&lt;code&gt;*&lt;/code&gt;) is for the API Gateway Resource ID. This is dynamically generated, so we don't want to explicitly write it here. The second wildcard is for the HTTP verb and the last is for the specific resource path.&lt;/p&gt;
&lt;h1&gt;
  
  
  Providing IAM Credentials in HTTP Requests
&lt;/h1&gt;

&lt;p&gt;The final piece of the puzzle is in making the invocation with the correct credentials. When we use any HTTP request library (like &lt;a href="https://2.python-requests.org/en/master/" rel="noopener noreferrer"&gt;requests&lt;/a&gt; in Python or &lt;a href="https://github.com/axios/axios" rel="noopener noreferrer"&gt;axios&lt;/a&gt; in JavaScript), our AWS Lambda function role credentials will not be passed by default. To add our credentials, we need to &lt;a href="https://docs.aws.amazon.com/general/latest/gr/signing_aws_api_requests.html" rel="noopener noreferrer"&gt;sign the HTTP request&lt;/a&gt;. There is a bit of figuring out required here so I created an NPM module that wraps axios and automatically signs the request using the Lambda's role. The module is called &lt;code&gt;aws-signed-axios&lt;/code&gt; and is available &lt;a href="https://www.npmjs.com/package/aws-signed-axios" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To make the HTTP request with the credentials associated with the API Gateway permissions, just invoke with the wrapper library like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const signedAxios = require('aws-signed-axios')
...

async function getUser(userId) {
  ...

  const { data: result } = await signedAxios({
    method: 'GET',
    url: userUrl
  })

  return result
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's it. In summary, we now have a clear, repeatable approach for internal serverless APIs with:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A way to secure an API Gateway without VPCs&lt;/li&gt;
&lt;li&gt;IAM permission we can use to grant to any services we wish to allow&lt;/li&gt;
&lt;li&gt;A simple way to sign HTTP requests with the necessary IAM credentials&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you want to explore more, take a look at our open-source serverless starter project, &lt;em&gt;SLIC Starter&lt;/em&gt;:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/fourTheorem" rel="noopener noreferrer"&gt;
        fourTheorem
      &lt;/a&gt; / &lt;a href="https://github.com/fourTheorem/slic-starter" rel="noopener noreferrer"&gt;
        slic-starter
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A complete, serverless starter project
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;SLIC Starter&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;ℹ️ Note&lt;/strong&gt;
SLIC Starter is an archived project. It's a useful reference for ideas on building serverless applications with AWS but is not actively maintained.  If you have any questions or need any help building modern applications on AWS, reach out to us on &lt;a href="https://github.com/fourTheorem/slic-starterhello@fourtheorem.com" rel="noopener noreferrer"&gt;by email&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://www.serverless.com" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/5a0da73da7ea9cc38d18980d734be39327c534574d7038e15387ed966b7eeac5/687474703a2f2f7075626c69632e7365727665726c6573732e636f6d2f6261646765732f76332e737667" alt="serverless"&gt;&lt;/a&gt;
&lt;a href="https://standardjs.com" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/5338a68a0f130dc684279ff3e42e45c9c74006018a1bdeaac76905979b3ccd49/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f636f64655f7374796c652d7374616e646172642d627269676874677265656e2e737667" alt="JavaScript Style Guide"&gt;&lt;/a&gt;
&lt;a href="http://commitizen.github.io/cz-cli/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/1d01d79de532fa2a8b9d3e1c29e2b5d6f700b6d36f108c8416faca472cb35b6f/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f636f6d6d6974697a656e2d667269656e646c792d627269676874677265656e2e737667" alt="Commitizen friendly"&gt;&lt;/a&gt;
&lt;a href="https://github.com/fourTheorem/slic-starter./LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/e4b233858678a0999e3aa70172aca241cc4a786d9fa1aac09c8d916c6f98a5be/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f6c2f736c69632d737461727465722e737667" alt="license"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Jump to:&lt;/strong&gt; &lt;a href="https://github.com/fourTheorem/slic-starter#5-getting-started" rel="noopener noreferrer"&gt;Getting Started&lt;/a&gt; | &lt;a href="https://github.com/fourTheorem/slic-starterdocs/QUICK_START.md" rel="noopener noreferrer"&gt;Quick Start&lt;/a&gt; | &lt;a href="https://github.com/fourTheorem/slic-starter#37-cicd" rel="noopener noreferrer"&gt;CI/CD&lt;/a&gt; | &lt;a href="https://github.com/fourTheorem/slic-starter#2-application-architecture" rel="noopener noreferrer"&gt;Architecture&lt;/a&gt; | &lt;a href="https://github.com/fourTheorem/slic-starterdocs/CONTRIBUTING.md" rel="noopener noreferrer"&gt;Contributing&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SLIC Starter&lt;/strong&gt; is a complete starter project for production-grade &lt;strong&gt;serverless&lt;/strong&gt; applications on AWS. SLIC Starter uses an opinionated, pragmatic approach to structuring, developing and deploying a modern, serverless application with one simple, overarching goal:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Get your serverless application into production fast&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/fourTheorem/slic-starter#slic-starter" rel="noopener noreferrer"&gt;SLIC Starter&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/fourTheorem/slic-starter#1-how-does-slic-starter-help-you" rel="noopener noreferrer"&gt;1. How does SLIC starter help you?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fourTheorem/slic-starter#2-application-architecture" rel="noopener noreferrer"&gt;2. Application Architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/fourTheorem/slic-starter#3-what-does-it-provide" rel="noopener noreferrer"&gt;3. What does it provide?&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/fourTheorem/slic-starter#31-structure" rel="noopener noreferrer"&gt;3.1. Structure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fourTheorem/slic-starter#32-tooling-choice" rel="noopener noreferrer"&gt;3.2. Tooling Choice&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fourTheorem/slic-starter#33-authentication" rel="noopener noreferrer"&gt;3.3. Authentication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fourTheorem/slic-starter#34-data-access-with-a-restful-api" rel="noopener noreferrer"&gt;3.4. Data Access with a RESTful API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fourTheorem/slic-starter#35-messaging" rel="noopener noreferrer"&gt;3.5. Messaging&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fourTheorem/slic-starter#36-front-end" rel="noopener noreferrer"&gt;3.6. Front End&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fourTheorem/slic-starter#37-cicd" rel="noopener noreferrer"&gt;3.7. CI/CD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fourTheorem/slic-starter#38-testing" rel="noopener noreferrer"&gt;3.8. Testing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fourTheorem/slic-starter#39-observability" rel="noopener noreferrer"&gt;3.9. Observability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fourTheorem/slic-starter#310-secret-management" rel="noopener noreferrer"&gt;3.10. Secret Management&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/fourTheorem/slic-starter#4-before-you-begin" rel="noopener noreferrer"&gt;4. Before&lt;/a&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/fourTheorem/slic-starter" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;





&lt;p&gt;I work as the CTO of &lt;a href="https://fourtheorem.com" rel="noopener noreferrer"&gt;fourTheorem&lt;/a&gt; and am the author of &lt;a href="http://www.aiasaservicebook.com/" rel="noopener noreferrer"&gt;AI as a Service&lt;/a&gt;. I'm on twitter as &lt;a href="https://twitter.com/eoins" rel="noopener noreferrer"&gt;@eoins&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>api</category>
      <category>gateway</category>
    </item>
    <item>
      <title>Find Changes Between Two Git Commits Without Cloning</title>
      <dc:creator>Eoin Shanaghy</dc:creator>
      <pubDate>Sat, 02 Mar 2019 21:34:18 +0000</pubDate>
      <link>https://dev.to/eoinsha/find-changes-between-two-git-commits-without-cloning-4kkp</link>
      <guid>https://dev.to/eoinsha/find-changes-between-two-git-commits-without-cloning-4kkp</guid>
      <description>&lt;p&gt;There are some cases where you want to find out information about changes in your Git repository without having to clone the full repository. This will usually be in your automated build environment. When I used Jenkins, Travis or Circle CI, I had access to the cloned Git repository and could use &lt;code&gt;git log&lt;/code&gt;, &lt;code&gt;git ls-remote&lt;/code&gt; and &lt;code&gt;git diff&lt;/code&gt; without any problem.&lt;/p&gt;

&lt;p&gt;Other tools, and I am talking specifically about AWS CodeDeploy, take a different approach. Instead of giving you access to a cloned repo, AWS CodeDeploy gives you a snapshot of your code without the &lt;code&gt;.git&lt;/code&gt; folder. This makes it impossible to run checks on what has changed since a previous build or even to determine what has changed in the commit that triggered your build. Some CI environments will give you a "shallow clone" without the full Git history, leaving you with a similar challenge.&lt;/p&gt;

&lt;p&gt;I wanted to run these kind of checks to determine which microservices in our monorepo had changed so I knew which ones to build and redeploy. This is a technique described well in this &lt;a href="https://blog.shippable.com/ci/cd-of-microservices-using-mono-repos" rel="noopener noreferrer"&gt;Shippable blog post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I looked at two options to find out folders which had seen changes since the last successful deployment:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clone the full repository manually in a CodeBuild step&lt;/li&gt;
&lt;li&gt;Use the GitHub API to retrieve information about the commits&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first option was one I wanted to avoid. It meant cloning a potentially large and growing repository at the start of the build. A shallow clone would not be sufficient as it would not capture the history of changes back to the previous release.&lt;/p&gt;

&lt;p&gt;The GitHub REST API includes a &lt;a href="https://developer.github.com/v3/repos/commits/#compare-two-commits" rel="noopener noreferrer"&gt;&lt;em&gt;compare&lt;/em&gt; API&lt;/a&gt; and a &lt;a href="https://developer.github.com/v3/repos/commits/#list-commits-on-a-repository" rel="noopener noreferrer"&gt;&lt;em&gt;list-commits&lt;/em&gt; API&lt;/a&gt;. The compare API is limited to 250 commits so that couldn't be relied on. The get-commits API could work but it means making multiple paged requests for a large amount of data just to get the changed paths. After a bit of trial and error, I ultimately abandoned the GitHub API approach.&lt;/p&gt;

&lt;p&gt;After some further digging, I came across a &lt;a href="https://stackoverflow.com/questions/3489173/how-to-clone-git-repository-with-specific-revision-changeset" rel="noopener noreferrer"&gt;StackOverflow post&lt;/a&gt; that gave me a third option. It allows me to fetch the two individual commits using the &lt;code&gt;git&lt;/code&gt; command and compare then to determine changed filenames. In this example, I'm using the public &lt;code&gt;lodash/lodash&lt;/code&gt; repository. Assume we want to compare the changes between the tag &lt;code&gt;4.0.0&lt;/code&gt; and the &lt;code&gt;HEAD&lt;/code&gt; of the &lt;code&gt;master&lt;/code&gt; branch, the sequence of commands looks like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

git init &lt;span class="nb"&gt;.&lt;/span&gt;                                               &lt;span class="c"&gt;# Create an empty repository&lt;/span&gt;
git remote add origin git@github.com:lodash/lodash.git   &lt;span class="c"&gt;# Specify the remote repository&lt;/span&gt;

git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; base                                     &lt;span class="c"&gt;# Create a branch for our base state&lt;/span&gt;

git fetch origin &lt;span class="nt"&gt;--depth&lt;/span&gt; 1 4.0.0                         &lt;span class="c"&gt;# Fetch the single commit for the base of our comparison&lt;/span&gt;
git reset &lt;span class="nt"&gt;--hard&lt;/span&gt; FETCH_HEAD                              &lt;span class="c"&gt;# Point the local master to the commit we just fetched&lt;/span&gt;

git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; target                                   &lt;span class="c"&gt;# Create a branch for our target state&lt;/span&gt;

git fetch origin &lt;span class="nt"&gt;--depth&lt;/span&gt; 1 master                        &lt;span class="c"&gt;# Fetch the single commit for the target of our comparison&lt;/span&gt;
git reset &lt;span class="nt"&gt;--hard&lt;/span&gt; FETCH_HEAD                              &lt;span class="c"&gt;# Point the local target to the commit we just fetched&lt;/span&gt;

git diff &lt;span class="nt"&gt;--name-only&lt;/span&gt; base target                         &lt;span class="c"&gt;# Print a list of all files changed between the two commits&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The directory size with this minimal fetching approach is 4.6M compared to 49M for the full &lt;code&gt;lodash&lt;/code&gt; repository.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frwvwcjjh63shookwazrz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frwvwcjjh63shookwazrz.png" alt="Comparing two git commits"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;I'm the CTO at &lt;a href="https://fourtheorem.com" rel="noopener noreferrer"&gt;fourTheorem&lt;/a&gt;. Follow me on twitter: &lt;a href="https://twitter.com/eoins" rel="noopener noreferrer"&gt;@eoins&lt;/a&gt;&lt;/p&gt;

</description>
      <category>git</category>
      <category>microservices</category>
      <category>ci</category>
      <category>cd</category>
    </item>
  </channel>
</rss>
